/**
 * =========================================================
 * SHELL.CSS — Shared layout + component styles
 * =========================================================
 *
 * This file contains ALL shared styles for the Ethan app.
 * Page-specific styles go inside each page's <style> block.
 *
 * SECTIONS:
 *   1. Reset + Base — body, typography, colors
 *   2. Layout — app-shell, sidebar, main content area
 *   3. Sidebar — nav links, logo, active states, icons
 *   4. Login Gate — password overlay
 *   5. Mobile — hamburger menu, responsive breakpoints
 *   6. Components — panels, cards, badges, buttons
 *   7. Dashboard — billing hero, card grid, pulse strip
 *   8. Forms — inputs, selects, settings controls
 *   9. Lists — task items, pipeline items, inbox accordion
 *  10. Social — platform icons, badges
 *  11. Chat — SMS bubbles
 *
 * BRAND COLORS (canonical, do not drift):
 *   Dark Base:   #0E1319
 *   Dark Gray:   #8C8C8C
 *   Medium Gray: #B5B5B5
 *   Light Gray:  #D9D9D9
 *   Off-White:   #EDEDEE
 *   Ecto Green:  #A8F06A  (primary accent)
 *   Orange:      #F9A03F  (secondary accent)
 *   Purple:      #A155F0  (secondary accent)
 *   Cyan:        #00E5FF  (secondary accent)
 *
 * ========================================================= */


/* ---------------------------------------------------------
   1. RESET + BASE
   --------------------------------------------------------- */

@import url('https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&display=swap');

* { margin: 0; padding: 0; box-sizing: border-box; }

html, body {
  background: var(--surface-0);
  color: var(--text);
  font-family: 'PP Neue Montreal', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 15px;
  line-height: 1.5;
  min-height: 100vh;
}


/* ---------------------------------------------------------
   2. LAYOUT
   --------------------------------------------------------- */

.app-shell {
  display: flex;
  height: 100vh;
}

.main {
  flex: 1;
  overflow-y: auto;
  /* Extra bottom padding so the floating chat bubble in the lower-right
     never obscures the end of a page's live content (metrics, tables, etc). */
  padding: 32px 40px 120px;
}

.page-content { }


/* ---------------------------------------------------------
   3. SIDEBAR
   --------------------------------------------------------- */

.sidebar {
  width: 170px;
  /* Rail sits VISIBLY LIGHTER than --surface-0 page bg (#000000) so the
     3-tier scale reads cleanly (page darkest → rail middle → tiles
     brightest). Matches OHFA portal dark-mode pattern.
     Right edge gets a 1px border matching the task-card border opacity so
     the rail terminates against the page bg with a soft separator. */
  background: var(--surface-1, #1A1E25);
  border-right: 1px solid var(--surface-border, rgba(255,255,255,0.06));
  padding: 10px 16px 24px;
  display: flex;
  flex-direction: column;
  gap: 6px;
}

/* ALL client portals: 20px rail inset on every side (Brian 2026-06-01). The
   nav full-bleed margins + content padding match the 20px so highlights reach
   the rail edge and icons/logo align to a single 20px column. */
body[data-portal-code] .sidebar { padding: 20px; }
body[data-portal-code] .sidebar-nav a { margin-left: -20px; margin-right: -20px; padding-left: 20px; }
body[data-portal-code] .sidebar-logo { margin-left: 0; }

.sidebar-logo {
  display: block;
  width: 100px;
  height: 37px;
  margin-top: 14px;
  margin-bottom: 12px;
  margin-left: 12px;
  background-color: var(--text);
  -webkit-mask-image: url(branding/ghost_logo.svg);
  mask-image: url(branding/ghost_logo.svg);
  font-size: 0;
  overflow: hidden;
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: left center;
  mask-position: left center;
}

.sidebar-nav a {
  display: flex;
  align-items: center;
  gap: 10px;
  /* Edge-to-edge hover/active fill. Negative horizontal margins cancel
     the sidebar's 16px padding so the highlight spans the full rail
     width; matching extra padding keeps the content inset where it was.
     Square corners — full-bleed, no rounded pill. Matches OHFA pattern. */
  margin-left: -16px;
  margin-right: -16px;
  padding: 4px 16px 4px 28px;
  border-radius: 0;
  color: var(--text);
  text-decoration: none;
  font-size: 13px;
  font-weight: 700;
  transition: background 0.15s;
}

.sidebar-nav a:hover { background: var(--surface-border-strong); color: var(--text); }
.sidebar-nav a.active { background: var(--surface-border-strong); color: var(--text); }

/* Nav icon masks. Each link uses an SVG mask for its icon.
   Slot size is uniform (22px) so every text label starts at the SAME
   left edge regardless of the visual glyph inside. The mask-size knob
   below controls how big the glyph renders WITHIN the uniform slot. */
.sidebar-nav .nav-icon {
  width: 22px;
  height: 22px;
  background-color: var(--text-muted);
  -webkit-mask-size: 19px 19px;
  mask-size: 19px 19px;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  flex-shrink: 0;
  opacity: 0.85;
}

.sidebar-nav a[data-page="social"] .nav-icon { position: relative; top: -1px; }
/* OHSP (house) source SVG has heavy internal whitespace and would
   render visually small at the default 19px mask. Bump its glyph
   only — slot stays at the uniform 22px. */
.sidebar-nav a[data-page="ohsp"] .nav-icon {
  -webkit-mask-size: 22px 22px;
  mask-size: 22px 22px;
}
/* Clients (star). Star glyph reads heavier than other icons at the
   default size, so render it ~15% smaller. Slight upward nudge so it
   sits visually centered next to the label. (Brian 2026-05-25,
   tweaked from -3 to -1.) */
.sidebar-nav a[data-page="clientportal"] .nav-icon {
  -webkit-mask-size: 16px 16px;
  mask-size: 16px 16px;
  position: relative;
  top: -1px;
}
.sidebar-nav a.active .nav-icon,
.sidebar-nav a:hover .nav-icon { opacity: 1; }

/* Per-tab icon colors. shared by :hover and .active so hovering any nav
   item previews its tab accent before commit. Cycle: green → blue →
   purple → orange, repeating in sidebar order (Dashboard → Settings). */
.sidebar-nav a.active[data-page="dashboard"] .nav-icon,
.sidebar-nav a:hover[data-page="dashboard"] .nav-icon      { background-color: var(--accent-lime);   } /* 1  green  */
.sidebar-nav a.active[data-page="chat"] .nav-icon,
.sidebar-nav a:hover[data-page="chat"] .nav-icon           { background-color: var(--accent-cyan);   } /* 2  blue   */
.sidebar-nav a.active[data-page="inbox"] .nav-icon,
.sidebar-nav a:hover[data-page="inbox"] .nav-icon          { background-color: var(--accent-purple); } /* 3  purple */
.sidebar-nav a.active[data-page="pipeline"] .nav-icon,
.sidebar-nav a:hover[data-page="pipeline"] .nav-icon       { background-color: var(--accent-orange); } /* 4  orange */
.sidebar-nav a.active[data-page="tasks"] .nav-icon,
.sidebar-nav a:hover[data-page="tasks"] .nav-icon          { background-color: var(--accent-lime);   } /* 5  green  */
.sidebar-nav a.active[data-page="clientportal"] .nav-icon,
.sidebar-nav a:hover[data-page="clientportal"] .nav-icon   { background-color: var(--accent-cyan);   } /* 6  blue   */
.sidebar-nav a.active[data-page="calendar"] .nav-icon,
.sidebar-nav a:hover[data-page="calendar"] .nav-icon       { background-color: var(--accent-purple); } /* 7  purple */
.sidebar-nav a.active[data-page="insights"] .nav-icon,
.sidebar-nav a:hover[data-page="insights"] .nav-icon       { background-color: var(--accent-orange); } /* 8  orange */
.sidebar-nav a.active[data-page="social"] .nav-icon,
.sidebar-nav a:hover[data-page="social"] .nav-icon         { background-color: var(--accent-lime);   } /* 9  green  */
.sidebar-nav a.active[data-page="adobe"] .nav-icon,
.sidebar-nav a:hover[data-page="adobe"] .nav-icon          { background-color: var(--accent-cyan);   } /* 10 blue   */
.sidebar-nav a.active[data-page="traffic"] .nav-icon,
.sidebar-nav a:hover[data-page="traffic"] .nav-icon        { background-color: var(--accent-purple); } /* 11 purple */
.sidebar-nav a.active[data-page="googleads"] .nav-icon,
.sidebar-nav a:hover[data-page="googleads"] .nav-icon      { background-color: var(--accent-orange); } /* 12 orange */
.sidebar-nav a.active[data-page="sms"] .nav-icon,
.sidebar-nav a:hover[data-page="sms"] .nav-icon            { background-color: var(--accent-lime);   } /* 13 green  */
.sidebar-nav a.active[data-page="guide"] .nav-icon,
.sidebar-nav a:hover[data-page="guide"] .nav-icon          { background-color: var(--accent-cyan);   } /* 14 blue   */
.sidebar-nav a.active[data-page="settings"] .nav-icon,
.sidebar-nav a:hover[data-page="settings"] .nav-icon       { background-color: var(--accent-purple); } /* 15 purple */
.sidebar-nav a.active[data-page="clientprojects"] .nav-icon,
.sidebar-nav a:hover[data-page="clientprojects"] .nav-icon { background-color: var(--accent-cyan);   } /* legacy alias of Portal */


/* ---------------------------------------------------------
   4. LOGIN GATE
   --------------------------------------------------------- */

.login-gate {
  position: fixed;
  inset: 0;
  background: var(--popover-bg);
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  z-index: 9999;
}

.login-gate.hidden { display: none; }

.login-gate h2 {
  font-size: 24px;
  font-weight: 300;
  color: var(--text);
  margin-bottom: 24px;
  letter-spacing: 1px;
}

.login-gate input {
  width: 260px;
  padding: 12px 16px;
  border-radius: 8px;
  border: 1px solid var(--surface-border-strong);
  background: var(--surface-1-bright);
  color: var(--text);
  font-family: inherit;
  font-size: 14px;
  outline: none;
}

.login-gate input:focus {
  border-color: rgba(168,240,106,0.3);
}

.login-error {
  color: #f87171;
  font-size: 13px;
  margin-top: 12px;
  min-height: 20px;
}


/* ---------------------------------------------------------
   5. MOBILE
   --------------------------------------------------------- */

.hamburger {
  display: none;
  position: fixed;
  top: 16px;
  left: 16px;
  z-index: 1100;                            /* sits above mobile-menu (1000) and all page content */
  background: var(--surface-0);             /* solid page fill, no translucency */
  border: 1px solid var(--surface-border-strong);
  border-radius: 8px;
  color: var(--text);
  font-size: 20px;
  padding: 8px 12px;
  cursor: pointer;
}

.mobile-menu {
  display: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 260px;
  height: 100vh;
  background: var(--popover-bg);
  border-right: 1px solid var(--surface-border);
  padding: 70px 20px 20px;
  z-index: 1000;
  flex-direction: column;
  gap: 4px;
  transform: translateX(-100%);
  transition: transform 0.25s ease;
}

.mobile-menu.open {
  transform: translateX(0);
}

.mobile-menu a {
  display: block;
  padding: 10px 12px;
  border-radius: 8px;
  color: var(--text);
  text-decoration: none;
  font-size: 14px;
}

.mobile-menu a.active { background: var(--surface-border-strong); color: var(--text); }

.menu-logo {
  font-size: 17px;
  font-weight: 300;
  color: var(--text);
  margin-bottom: 20px;
  letter-spacing: 1px;
}

/* Mobile breakpoints live at the END of the file. See bottom. */


/* ---------------------------------------------------------
   6. COMPONENTS — Panels, cards, badges, buttons
   --------------------------------------------------------- */

.panel {
  /* Canonical tile. Reads from --surface-1 (Phase 1, 2026-05-08) so
     every other tile in the app can converge on the same fill in
     Phase 2 by referencing the token instead of the literal hex. */
  background: var(--surface-1);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  padding: 20px 24px;
}

/* ============================================================
   CANONICAL SURFACE + CHIP PRIMITIVES (Brian 2026-05-25).

   .panel        canonical tile (above). 20-24px padding, 12px radius.
   .tile         smaller sibling of .panel. 12-14px padding, 10px radius.
                 Use for sub-tiles inside a .panel (e.g. analytics card
                 inside a section, time-log row card, etc.)
   .recessed     inset variant — same border/radius as .tile but the
                 fill is --surface-recessed (subtle rgba 0.02 white).
                 Used for areas that should read as "wells" inside a
                 parent surface (formerly Reports panel default).

   .pill-tab     outlined chip / tab button. transparent fill, muted
                 border + text at rest. Hover + .active flip border +
                 text to accent-lime by default. Override the accent
                 via --pill-accent on the parent (e.g. on .page-tabs)
                 to swap per-surface (cyan / orange / purple).
                 Used for: Reports range filters, Cards/List view toggle,
                 Pipeline kanban tabs, Insights filter chips, etc.

   .pill-fill    filled chip variant. accent-fill background + accent
                 text + accent-border. Use for ACTIVE one-per-row
                 selectors (the active "All" platform tab on Social).

   .divider      horizontal hairline at --table-rule (rgba 0.40).
                 Use as a section separator that matches the rule
                 weight under assignee/user-name rows.

   PER-SURFACE ACCENT OVERRIDE PATTERN:
     <div class="page-tabs" style="--pill-accent: var(--accent-cyan);">
       <button class="pill-tab active">Foo</button>
     </div>
   Drops the accent var on the container; every .pill-tab inside
   inherits it.
   ============================================================ */

.tile {
  background: var(--surface-1);
  border: 1px solid var(--surface-border);
  border-radius: 10px;
  padding: 12px 14px;
}
.recessed {
  background: var(--surface-recessed);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  padding: 16px 20px;
}

.pill-tab {
  /* Base reset for a button rendered as a chip / outlined tab. */
  appearance: none; -webkit-appearance: none;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: 6px;
  background: transparent;
  border: 1px solid var(--surface-border-strong);
  color: var(--text-muted);
  cursor: pointer;
  transition: border-color 0.15s, color 0.15s, background 0.15s;
  user-select: none;
}
.pill-tab:focus { outline: none; }
.pill-tab:focus-visible {
  outline: 2px solid var(--pill-accent, var(--accent-lime));
  outline-offset: 2px;
}
.pill-tab:hover:not(:disabled),
.pill-tab.active {
  border-color: var(--pill-accent, var(--accent-lime));
  color: var(--pill-accent, var(--accent-lime));
}

.pill-fill {
  appearance: none; -webkit-appearance: none;
  font-family: var(--font-sans);
  font-size: 12px;
  font-weight: 600;
  padding: 6px 12px;
  border-radius: 6px;
  /* Default reads from --pill-accent (set per-surface) → accent-lime
     fallback. The -fill / -border tokens give consistent contrast on
     both dark and light themes. */
  background: color-mix(in srgb, var(--pill-accent, var(--accent-lime)) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--pill-accent, var(--accent-lime)) 30%, transparent);
  color: var(--pill-accent, var(--accent-lime));
  cursor: pointer;
  transition: background 0.15s;
}
.pill-fill:hover:not(:disabled) {
  background: color-mix(in srgb, var(--pill-accent, var(--accent-lime)) 22%, transparent);
}

.divider {
  height: 0;
  border: none;
  border-bottom: 1px solid var(--table-rule);
  margin: 0;
}
.divider-strong {
  height: 0;
  border: none;
  border-bottom: 2px solid var(--table-rule);
  margin: 0;
}

/* =============================================================================
 * Take tile (Ethan's / Agent's Take)
 * -----------------------------------------------------------------------------
 * Used on Traffic, Google Ads, Social (per-account), and Tasks. The tile is:
 *   1. Visually emphasized with a 3px border in the host tab's accent color.
 *      The accent comes from --take-accent set inline on the element so each
 *      surface picks its own color (orange / cyan / purple).
 *   2. Collapsible. The header toggles is-collapsed on the wrapper. The body
 *      hides via display:none, the tile keeps its rendered take in the DOM
 *      so the API is NEVER re-hit on collapse/expand. Per-surface localStorage
 *      key persists the user's choice across sessions.
 * Added 2026-04-28 (Brian asked for both treatments). Border thickness was
 * 6px on first pass, dialed to 3px same evening per Brian.
 * =============================================================================*/
.take-tile {
  border: 3px solid var(--take-accent, var(--surface-border)) !important;
}
.take-tile {
  /* Whole tile is the click target for collapse/expand (Brian 2026-05-10).
     Interactive children inside the take body keep working — the JS
     handler bails out when the click originated on a link/button/input.
     user-select: none prevents text selection on the toggle area. */
  cursor: pointer;
  user-select: none;
}
.take-tile-header {
  cursor: pointer;
  user-select: none;
}
.take-tile-toggle {
  /* Caret picks up the tile's accent (--take-accent set inline per
     surface — orange on Pipeline, lime on Tasks, cyan on Traffic,
     etc.). Equilateral CSS triangle (no Unicode glyph). Reduced 30%
     2026-05-09 per Brian: 14×12 → 10×8.5 keeps the same √3/2 ratio. */
  font-size: 0;            /* hide the legacy ▾ text node */
  line-height: 1;
  width: 10px;
  height: 9px;
  display: inline-block;
  position: relative;
  transition: transform 0.2s ease;
  /* Rotate around the caret's geometric center, not its baseline edge,
     so the glyph spins in place instead of swinging across the row. */
  transform-origin: center center;
}
.take-tile-toggle::before {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  /* The CSS triangle's centroid sits 1.5px ABOVE its bbox center
     (centroid is 1/3 the height from the base; for a 9px-tall triangle
     with base at top, centroid = 3px from top = 1.5px above bbox
     center). To pin the centroid AT the wrapper's transform-origin
     (so rotation spins in place AND the caret reads vertically
     centered with the eyebrow text), shift the bbox DOWN by exactly
     1.5px. Both math problems solved with the same constant.
     Brian 2026-05-12 (third time, locking it in). Global rule —
     covers every take tile in the app (Traffic, Pipeline, Tasks,
     Google Ads, OHFA, etc). */
  transform: translate(-50%, calc(-50% + 1.5px));
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 9px solid var(--take-accent, var(--text-muted));
}
/* Hint label sits to the LEFT of the caret. Rendered as a flex
   sibling via ::after on the header (not on the toggle) so the
   caret can rotate without flipping the hint upside-down. The
   header's flexbox already includes pseudo-elements, so margin-left
   here pushes both the hint AND the caret to the right edge. */
.take-tile-header::after {
  content: "Click this row to show or hide content.";
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  font-size: 11px;
  letter-spacing: 0;
  text-transform: none;
  color: var(--text-dim);
  font-weight: 400;
  margin-left: auto;
  margin-right: 10px;
  white-space: nowrap;
  order: 99;       /* sits after every header child */
}
.take-tile-toggle {
  order: 100;      /* and the caret sits after the hint */
}
/* Expanded → caret points down (▾, default glyph at 0deg).
   Collapsed → caret points up (rotate 180deg). Rotates from center.
   Hint is unaffected because it lives on the header, not the toggle. */
.take-tile.is-collapsed .take-tile-toggle {
  transform: rotate(180deg);
}
.take-tile.is-collapsed .take-body {
  display: none;
}
/* When collapsed: zero the header's bottom margin (every page sets
   margin-bottom:12px inline on the header for spacing-from-body) so
   the tile is symmetric — header sits with equal padding above AND
   below. Without this, the 12px margin lives on as dead space below
   the header when the body is hidden, making the tile look top-heavy.
   The header's distance FROM THE TILE TOP doesn't change between
   states, so this preserves the no-shift behavior. !important needed
   to beat the inline style. (Brian, 2026-05-10) */
.take-tile.is-collapsed .take-tile-header {
  margin-bottom: 0 !important;
}

/* ─────────────────────────────────────────────────────────────────
   .pill-toggle — canonical inline button-group pattern
   Reusable across the app for binary / segmented choices: brief
   inline-vs-upload, gallery filters, future tabs, etc.
   Rules (Brian 2026-05-27):
     Inactive (dk): light-grey border + text         (var(--text-faint))
     Inactive (lt): medium-black border + text       (var(--text-faint) flips)
     Active   (any theme): accent-color border + text
     Hover (inactive): foreground text + foreground border so it reads as actionable
   No fill, no shadow. Border 1px so it doesn't compete with the
   real form chrome. Active state via .active OR .is-active OR
   aria-pressed="true" — matches every existing toggle convention so
   markup already in the codebase doesn't need to be touched to
   inherit the look.
   ───────────────────────────────────────────────────────────────── */
.pill-toggle {
  display: inline-flex;
  gap: 8px;
  flex-wrap: wrap;
}
.pill-toggle > button,
.pill-toggle > [role="button"] {
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  border: 1px solid var(--text-faint);
  color: var(--text-faint);
  border-radius: 999px;
  padding: 6px 14px;
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.3px;
  line-height: 1.2;
  cursor: pointer;
  transition: color 120ms, border-color 120ms;
}
.pill-toggle > button:hover,
.pill-toggle > [role="button"]:hover {
  color: var(--text);
  border-color: var(--text);
}
.pill-toggle > button.active,
.pill-toggle > button.is-active,
.pill-toggle > button[aria-pressed="true"],
.pill-toggle > [role="button"].active,
.pill-toggle > [role="button"].is-active,
.pill-toggle > [role="button"][aria-pressed="true"] {
  color: var(--accent-cyan);
  border-color: var(--accent-cyan);
}
.pill-toggle > button:focus-visible,
.pill-toggle > [role="button"]:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--accent-cyan) 35%, transparent);
  outline-offset: 2px;
}
/* Don't override the tile's vertical padding when collapsed — doing so
   shrinks the header row's distance from the tile edge by 8px, which
   visually shifts the header line up on collapse and back down on
   expand. The body's display:none already collapses the tile to just
   the header; keeping the original padding preserves the header's
   vertical position across both states. (Brian, 2026-05-10) */

.panel-title {
  font-family: 'Space Mono', monospace;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 2.24px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 16px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.badge {
  font-size: 11px;
  padding: 3px 10px;
  border-radius: 10px;
  /* Sans bold, mixed case. Was 'Space Mono' uppercase via the
     .panel-title cascade — small pills with mono + letter-spacing
     read poorly. text-transform:none here resets the parent's
     uppercase so handles render the way the platform actually
     shows them ("@ghostagency" not "@GHOSTAGENCY"). */
  font-family: 'PP Neue Montreal', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}

.panel-title .badge {
  background: rgba(248,113,113,0.15);
  color: #f87171;
}

.card {
  background: var(--surface-1);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  padding: 16px 20px;
}

.card-label {
  font-family: 'Space Mono', monospace;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 2.24px;
  text-transform: uppercase;
  color: var(--text-muted);
  margin-bottom: 12px;
}

/* Canonical eyebrow primitive. The single source of truth for every
   mono kicker / label / section header in the app. Surfaces that
   render small mono labels (WHY MISSING, GAPS + FIXES, SECTIONS,
   SCHEMA, OUTREACH TARGETS, etc.) apply this class instead of
   recreating the rule inline.

   Size variants:
     .eyebrow            10px (default - in-content labels)
     .eyebrow.eyebrow-sm  9px (compact - inside chips, badges)
     .eyebrow.eyebrow-md 11px (panel section headers)

   Brian 2026-05-29. */
.eyebrow {
  font-family: 'Space Mono', monospace;
  font-size: var(--label-mono-size);
  font-weight: 700;
  letter-spacing: 2.24px;
  text-transform: uppercase;
  color: var(--text-eyebrow);
  display: block;
}
.eyebrow.eyebrow-sm { font-size: 9px;  letter-spacing: 1.6px; }
.eyebrow.eyebrow-md { font-size: 11px; letter-spacing: 1.8px; }

.card-value {
  font-size: 36px;
  font-weight: 700;
  letter-spacing: -0.5px;
  color: var(--text);
}

.card-sub {
  font-size: 12px;
  color: var(--text-muted);
  margin-top: 4px;
}

.card-value.green { color: var(--accent-green); }
.card-value.amber { color: #fbbf24; }
.card-value.red   { color: #f87171; }

/* Shared delta indicators (used by Traffic + Google Ads + future tabs) */
/* Delta indicators are SEMANTIC, not brand-themed: green = positive,
   red = negative, on every portal. Do not bind these to --accent-green
   (it remaps per portal: OCAC->cyan, CRM->orange, etc.). Brian 2026-06-05. */
.delta-up   { color: #4ade80; }
.delta-down { color: #f87171; }
.delta-flat { color: var(--text-muted); }

/* Canonical send button — used by Chat tab and the chat bubble modal.
   Border + glyph color + tinted fill all driven by a single CSS var so
   the bubble can flex it per-tab while the Chat tab uses the cyan default. */
.send-btn {
  width: 32px;
  height: 32px;
  padding: 0;
  border-radius: 8px;
  border: 1px solid var(--bubble-accent, #00E5FF);
  background: rgba(var(--bubble-accent-rgb, 0,229,255), 0.18);
  color: var(--bubble-accent, #00E5FF);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  font-family: inherit;
  transition: background 0.15s, transform 0.15s, opacity 0.15s;
}
.send-btn:hover:not(:disabled) {
  background: rgba(var(--bubble-accent-rgb, 0,229,255), 0.12);
  transform: translateY(-1px);
}
.send-btn:disabled {
  opacity: 0.70;
  cursor: default;
}
.send-btn .send-icon {
  display: block;
  width: 18px;
  height: 18px;
  background-color: currentColor;
  -webkit-mask-image: url(icons/send-brand.svg);
  mask-image: url(icons/send-brand.svg);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  position: relative;
  z-index: 1;
}

.two-col {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px;
  margin-bottom: 24px;
  /* default grid behavior stretches both tiles to row height — paired
     tiles always match (e.g. top pages + traffic sources, donut + line
     chart). The shorter tile fills its panel; render functions are
     responsible for not leaving awkward gaps inside. */
}
.two-col > * { min-width: 0; overflow: hidden; }
.two-col .panel-title,
.two-col .panel { min-width: 0; }
.two-col .panel-title > span:not(.badge) { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }


/* ---------------------------------------------------------
   7. DASHBOARD — Billing hero, pulse strip, card grid
   --------------------------------------------------------- */

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

.header h1 {
  font-size: 36px;
  font-weight: 300;
  color: var(--text);
  letter-spacing: -0.3px;
}

.header-affirmation {
  font-size: 33px;
  font-weight: 200;
  color: var(--text);
  margin-top: 4px;
  letter-spacing: -0.3px;
  line-height: 1.2;
}

.header-meta {
  font-family: 'Space Mono', monospace;
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 2.24px;
  text-transform: uppercase;
  color: var(--text-muted);
  padding-top: 4px;
  white-space: nowrap;
  flex-shrink: 0;
}

/* Pulse strip — system health bar at top of dashboard */
@keyframes pulse-glow {
  0%, 100% { box-shadow: 0 0 0 0 rgba(168,240,106,0.5); }
  50% { box-shadow: 0 0 0 6px rgba(168,240,106,0); }
}

.pulse-dot {
  width: 10px; height: 10px; border-radius: 50%; background: var(--accent-lime);
  animation: pulse-glow 2s ease-in-out infinite; flex-shrink: 0;
}
.pulse-dot.warn  { background: var(--accent-orange); animation-name: none; }
.pulse-dot.error { background: #E74C3C; animation-name: none; }

.pulse-strip {
  display: flex; align-items: center; gap: 16px;
  padding: 20px 24px; border-radius: 12px;
  background: var(--surface-1); border: 1px solid var(--surface-border);
  margin-bottom: 24px; flex-wrap: wrap;
}

.pulse-label {
  font-family: 'Space Mono', monospace; font-size: 10px; font-weight: 700;
  letter-spacing: 2.24px; text-transform: uppercase; color: var(--accent-lime);
}

.pulse-systems { display: flex; gap: 12px; flex: 1; justify-content: flex-end; flex-wrap: wrap; }
.pulse-sys { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--text-muted); }
.pulse-sys-dot { width: 6px; height: 6px; border-radius: 50%; background: var(--accent-lime); }
.pulse-sys-dot.off  { background: #666; }
.pulse-sys-dot.warn { background: var(--accent-orange); }

.pulse-activity { width: 100%; font-size: 12px; color: var(--text-muted); }
.pulse-activity-stats { display: flex; gap: 16px; flex-wrap: wrap; margin-top: 6px; }
.pulse-activity-item { display: flex; align-items: center; gap: 6px; }
.pulse-activity-item::before { content: ''; width: 4px; height: 4px; border-radius: 50%; background: var(--text-muted); flex-shrink: 0; }

/* Billing hero card */
.billing-hero {
  background: var(--surface-1);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  padding: 28px 32px;
  margin-bottom: 24px;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.billing-hero .amount { font-size: 36px; font-weight: 600; letter-spacing: -1px; }
.billing-hero .target { color: var(--text-muted); font-size: 14px; margin-top: 4px; }
.billing-hero .target span { color: var(--text); }

.billing-bar-container { width: 280px; }
.billing-bar-label { display: flex; justify-content: space-between; font-size: 12px; color: var(--text-muted); margin-bottom: 8px; }
.billing-bar { height: 10px; background: var(--surface-border-strong); border-radius: 5px; overflow: hidden; }
.billing-bar-fill { height: 100%; border-radius: 5px; background: linear-gradient(90deg, var(--gradient-bar-from), var(--gradient-bar-to)); width: 68%; transition: width 0.6s ease; }
.billing-bar-gap { font-size: 13px; color: var(--text); margin-top: 8px; text-align: right; }

/* Card grid — dashboard top section */
.card-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 16px; margin-bottom: 24px; align-items: stretch; }
.card-grid .card-recap { grid-column: span 2; }
.stats-left { display: contents; }

.card-recap {
  background: var(--surface-1);
  border: 1px solid var(--surface-border);
  border-radius: 12px;
  padding: 16px 20px;
  max-height: 160px;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.card-recap .recap-list { flex: 1; overflow-y: auto; font-size: 12px; line-height: 1.6; color: var(--text-dim); scrollbar-width: thin; scrollbar-color: var(--surface-border-strong) transparent; }
.card-recap .recap-item { padding: 3px 0; border-bottom: 2px solid var(--table-rule); }
.card-recap .recap-item:last-child { border-bottom: none; }
.recap-status-ok   { color: var(--accent-green); }
.recap-status-fail { color: #f87171; }

/* Page header — the canonical row containing the .page-label on the
   left and any page-level meta/actions on the right (e.g. Total Value,
   header badges, refresh button). Locks the spacing contract: 24px
   below the header, before whatever follows (.page-tabs, panels). */
.page-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 16px;
  margin-bottom: 24px;
  /* Lock to a consistent row height across pages so the tabs row below
     sits at the same Y on every page, regardless of whether the right
     side carries badges (Tasks), a big Total Value (Pipeline), buttons
     (Adobe), or nothing. Tracks the natural height of the tallest known
     header (Pipeline's 28px Total Value). */
  min-height: 44px;
}
/* When .page-label sits inside .page-header, the row owns the bottom
   margin. zero out .page-label's own 24px to avoid double spacing. */
.page-header .page-label { margin-bottom: 0; }

/* Page label — icon + title at top of each page */
.page-label { display: flex; align-items: center; gap: 14px; margin-bottom: 24px; }
.page-label-icon {
  width: 28px; height: 28px;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  flex-shrink: 0;
}
.page-label h2 {
  font-family: 'PP Neue Montreal', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  font-size: 28px;
  font-weight: 700;
  letter-spacing: -0.4px;
  line-height: 1;
  margin: 0;
}

/* ---------------------------------------------------------
   7b. PAGE TABS — canonical underline-tab primitive
   ---------------------------------------------------------
   Single source of truth for the underlined tab pattern used on
   Pipeline, Tasks, Social, Traffic, and any future tabbed page.
   Replaces .pipe-tab / .tasks-filter / .social-view-tab /
   .traffic-view-tab one-offs.

   Usage:
     <div class="page-tabs" style="--page-accent: var(--accent-lime);">
       <div class="page-tabs-left">
         <button class="page-tab is-active">Label</button>
         <button class="page-tab">Label <span class="page-tab-badge">17</span></button>
       </div>
       <div class="page-tabs-right">...action buttons...</div>
     </div>

   Spacing contract: 16px above the row (gap from .page-label),
   24px below the row (gap to the first content panel). This is
   the precedent. Don't override with inline margins per page.
   --------------------------------------------------------- */
.page-tabs {
  --page-accent: var(--accent-lime); /* default; pages override */
  display: flex;
  justify-content: space-between;
  align-items: center;
  border-bottom: 1px solid var(--surface-border);
  min-height: 42px;
  /* Spacing contract: .page-header owns the 24px above; .page-tabs
     owns the 24px below. Don't add margin-top here or it stacks on
     top of the header's margin-bottom. */
  margin-top: 0;
  margin-bottom: 24px;
  gap: 16px;
  flex-wrap: wrap;
  /* Sticky on scroll inside .main (the page-level scroll container).
     Bar pins to the top while the title + chrome scrolls away, so the
     user can always switch sub-tabs without scrolling back up. Negative
     horizontal margins + matching padding cover .main's 40px gutter so
     the bar background masks content scrolling underneath. Brian
     2026-05-24: applies to every tabbed page (Tasks, Pipeline, Inbox,
     Calendar, Social, Traffic, Google Ads, SMS, Settings). */
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--surface-0);
  margin-left: -40px;
  margin-right: -40px;
  padding-left: 40px;
  padding-right: 40px;
}
/* Sticky tab flush-to-top: combine negative margin-top + padding-top with
   sticky top: -32 so when pinned, the bar's TOP sits 32px ABOVE the
   visible scroll-container edge (covering .main's padding-top zone) and
   the visible content row pins flush at y=0. At rest the negative margin
   neutralizes the padding so the bar appears in its natural position.
   Brian 2026-05-25, revised. */
.page-tabs { margin-top: -32px; padding-top: 32px; top: -40px; }
@media (max-width: 1024px) { .page-tabs { margin-top: -24px; padding-top: 24px; top: -32px; } }
@media (max-width: 640px)  { .page-tabs { margin-top: -60px; padding-top: 60px; top: -68px; } }
@media (max-width: 1024px) {
  .page-tabs { margin-left: -20px; margin-right: -20px; padding-left: 20px; padding-right: 20px; }
}
@media (max-width: 640px) {
  .page-tabs { margin-left: -12px; margin-right: -12px; padding-left: 12px; padding-right: 12px; }
}
.page-tabs-left  { display: flex; align-items: center; gap: 4px; }
.page-tabs-right { display: flex; align-items: center; gap: 8px; }

.page-tab {
  /* Full UA reset — Chrome's default <button> bg can leak through a
     transparent declaration until interacted-with. Belt + suspenders. */
  appearance: none;
  -webkit-appearance: none;
  background: transparent;
  background-color: transparent;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px; /* overlap the row's 1px underline cleanly */
  padding: 12px 20px;
  color: var(--text-muted);
  font-family: inherit;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  border-radius: 0;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  transition: color .15s, border-color .15s;
}
.page-tab:hover:not(.is-active):not(.active) { color: var(--text-dim); }
.page-tab.is-active,
.page-tab.active {
  color: var(--page-accent);
  border-bottom-color: var(--page-accent);
}
.page-tab:focus { outline: none; }
.page-tab:focus-visible {
  outline: 2px solid color-mix(in srgb, var(--page-accent) 45%, transparent);
  outline-offset: -2px;
}

/* Count / status badge that sits inside a tab (Pipeline's "17",
   Tasks' future "overdue" count, etc.). Inactive: muted. Active:
   inherits the page accent at 14% tint. */
.page-tab-badge {
  font-family: inherit;
  font-weight: 600;
  font-size: 11px;
  padding: 2px 8px;
  border-radius: 10px;
  background: var(--surface-border);
  color: var(--text-muted);
  line-height: 1;
}
.page-tab.is-active .page-tab-badge,
.page-tab.active .page-tab-badge {
  background: color-mix(in srgb, var(--page-accent) 14%, transparent);
  color: var(--page-accent);
}


/* ---------------------------------------------------------
   8. FORMS
   --------------------------------------------------------- */

.settings-select {
  background: var(--surface-1-bright);
  border: 1px solid var(--surface-border-strong);
  border-radius: 6px;
  color: #A155F0;
  font-size: 13px;
  font-family: inherit;
  padding: 4px 10px;
  cursor: pointer;
  appearance: auto;
}
.settings-select:focus { outline: none; border-color: #A155F0; }

.settings-input {
  background: var(--surface-1-bright);
  border: 1px solid var(--surface-border-strong);
  border-radius: 6px;
  color: #A155F0;
  font-size: 13px;
  font-family: inherit;
  padding: 4px 10px;
}
.settings-input:focus { outline: none; border-color: #A155F0; }


/* ---------------------------------------------------------
   9. LISTS — Tasks, pipeline, inbox
   --------------------------------------------------------- */

.task-list { list-style: none; }

.task-item {
  display: flex; align-items: center; gap: 10px;
  padding: 8px 0; font-size: 14px;
}
.task-item:last-child { border-bottom: none; }
.task-item[draggable="true"] { cursor: grab; }
.task-item.dragging {
  opacity: 0.7; background: rgba(0,229,255,0.04) !important;
  border: 1px solid rgba(0,229,255,0.5); border-radius: 6px;
}
.task-item.drag-over-top { box-shadow: inset 0 2px 0 0 var(--accent-cyan); }
.task-item.drag-over-bottom { box-shadow: inset 0 -2px 0 0 var(--accent-cyan); }
.drag-handle {
  width: 16px; height: 16px; opacity: 0; cursor: grab;
  transition: opacity 0.15s;
  background-image: radial-gradient(circle, var(--table-rule) 1.5px, transparent 1.5px);
  background-size: 6px 5px;
  background-position: 1px 1px;
  flex-shrink: 0;
}
.task-item:hover .drag-handle { opacity: 0.6; }
.task-item.dragging .drag-handle { opacity: 1; cursor: grabbing; }

.task-check {
  width: 16px; height: 16px; border: 1.5px solid var(--surface-border-strong);
  border-radius: 4px; flex-shrink: 0; margin-top: 2px; cursor: pointer;
  transition: border-color 0.15s, background 0.15s;
}
.task-check:hover { border-color: var(--accent-cyan); }
.task-check.done  { background: var(--accent-cyan); border-color: var(--accent-cyan); }

.task-meta { font-size: 12px; color: var(--text-muted); margin-top: 2px; }
.task-meta .overdue { color: #f87171; }
.task-meta .job-id  { color: #A155F0; font-weight: 500; }

.task-date {
  font-size: 17px; font-weight: 400; color: var(--text-muted);
  white-space: nowrap; flex-shrink: 0; align-self: center;
}
.task-date.overdue { color: #f87171; }

.pipeline-item {
  display: flex; justify-content: space-between; align-items: center;
  padding: 8px 0; font-size: 14px;
}
.pipeline-item:last-child { border-bottom: none; }

.pipeline-stage {
  font-size: 11px; padding: 3px 10px; border-radius: 10px;
  font-family: 'Space Mono', monospace; letter-spacing: 0.5px;
}

/* Inbox accordion */
.inbox-accordion-item.open .inbox-accordion-body { display: block !important; }
.inbox-accordion-item.open .inbox-accordion-header > div:last-child { transform: rotate(180deg); }


/* ---------------------------------------------------------
   10. SOCIAL — Platform icons + badges
   --------------------------------------------------------- */

/* Platform icon masks — colored via background-color */
.plat-icon {
  display: inline-block; width: 14px; height: 14px;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
}
.plat-icon-fb { background-color: var(--plat-fb); -webkit-mask-image: url(icons/facebook.svg); mask-image: url(icons/facebook.svg); }
.plat-icon-ig { background-color: var(--plat-ig); -webkit-mask-image: url(icons/instagram.svg); mask-image: url(icons/instagram.svg); }
.plat-icon-li { background-color: var(--plat-li); -webkit-mask-image: url(icons/linkedin.svg); mask-image: url(icons/linkedin.svg); }
.plat-icon-yt { background-color: var(--plat-yt); -webkit-mask-image: url(icons/youtube.svg); mask-image: url(icons/youtube.svg); }

.plat-badge {
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; border-radius: 6px;
}
.plat-badge-fb { background: rgba(0,229,255,0.15); }
.plat-badge-ig { background: rgba(249,160,63,0.15); }
.plat-badge-li { background: rgba(161,85,240,0.15); }
.plat-badge-yt { background: rgba(168,240,106,0.15); }

/* Post-type icons — mask-based, colored via background-color */
.post-type-icon {
  display: inline-block; width: 12px; height: 12px;
  -webkit-mask-size: contain; mask-size: contain;
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-position: center; mask-position: center;
  vertical-align: middle;
}
.post-type-image { -webkit-mask-image: url(icons/post-image.svg); mask-image: url(icons/post-image.svg); }
.post-type-story { -webkit-mask-image: url(icons/post-story.svg); mask-image: url(icons/post-story.svg); }
.post-type-reel  { -webkit-mask-image: url(icons/post-reel.svg);  mask-image: url(icons/post-reel.svg); }
.post-type-feed  { -webkit-mask-image: url(icons/post-feed.svg);  mask-image: url(icons/post-feed.svg); }


/* ---------------------------------------------------------
   11. CHAT — SMS bubbles
   --------------------------------------------------------- */

.sms-bubble { max-width: 75%; padding: 10px 14px; border-radius: 16px; font-size: 14px; line-height: 1.5; }
.sms-bubble.outgoing { background: rgba(168,240,106,0.06); color: var(--text); border-bottom-right-radius: 4px; margin-left: auto; }
.sms-bubble.incoming { background: var(--surface-border-strong); color: var(--text); border-bottom-left-radius: 4px; }

/* ============================================================
   PER-CLIENT PORTAL PALETTE — CSS variables
   ============================================================
   Brand colors are referenced via var(--accent-*) throughout
   shell.css. :root carries Ghost defaults; per-portal blocks
   below override the same var names so every accent swaps in
   one place.

   Adding a new client palette = add one body[data-portal-code]
   block. No selector hunting required.

   Semantic delta colors (#f87171 red for down/overdue, lime
   for up) stay HARDCODED — they're up/down indicators, not
   brand. Don't pull them through the variable system.

   Priority order per Brian: blue, orange, green, red.
   ============================================================ */
:root {
  /* ============================================================
     SURFACE PALETTE (Phase 1, 2026-05-08).
     Three levels for everything that draws a dark surface, so we
     stop sprinkling #0E1319 / #111114 / #14141a / #1a1d24 / #1A1A1E
     / #2a2a2e through pages.

       --surface-0  page background, the deepest fill
       --surface-1  primary tile sitting on the page bg (this is the
                    .panel fill — translucent white over surface-0,
                    resolves to ~#0F1419)
       --surface-2  elevated chip/input/secondary tile that lives
                    inside a --surface-1 tile

     New components author against these tokens. Do NOT introduce
     new dark-grey hex literals in pages/*.html. The existing literals
     get migrated incrementally in Phase 2 — see the cleanup plan.
     ============================================================ */
  --surface-0:               #000000;                /* page bg — pure black, matches dark task-row interior. bumped 2026-05-25 alongside surface-1 lift */
  --surface-recessed:        rgba(255,255,255,0.02); /* inset/recessed (timesheet, ready-to-bill etc) */
  /* Canonical tile fill. Pinned to the sidebar-rail value (#14171C) so
     every tile reads as a solid surface that contrasts against the
     darker #05080C page bg — a translucent 3%-white fill was nearly
     invisible on the near-black page. Page (darkest) → tile = rail
     (#14171C) → nested chip (--surface-2, lighter still). */
  --surface-1:               #1A1E25;                /* tier 2: tiles + sidebar rail. bumped lighter 2026-05-25 to create more contrast against #05080C page bg */
  --surface-1-bright:        #20242C;                /* slightly brighter variant + sidebar hover (also bumped) */
  --surface-2:               rgba(255,255,255,0.06); /* nested chip/input on surface-1 tile */
  --surface-border:          rgba(255,255,255,0.06);
  --surface-border-strong:   rgba(255,255,255,0.12);
  /* Generic --border alias for legacy inline styles + classes that
     reference var(--border) without the surface- prefix. Was missing,
     so every "border: 1px solid var(--border)" rendered effectively
     borderless (the width collapsed because the color was undefined).
     Aliasing to --surface-border-strong so existing code resolves
     without needing a sweep across every page. */
  --border:                  var(--surface-border-strong);

  /* Text scale — use these instead of one-off hex codes so dim/muted
     reads consistently across the app. Brightness ladder:
       --text         primary body (white)
       --text-dim     secondary labels, hints (was scattered var(--text-muted))
       --text-muted   tertiary, sub-labels (was scattered #6B6B6E)
       --text-faint   placeholders, the dimmest legible level */
  --text:        #F2F2F2;
  --text-dim:    #B8B8BA;
  --text-muted:  #9F9FA0;
  --text-faint:  #6B6B6E;
  /* Mono eyebrows / kickers. Defined once, used everywhere. The old
     practice of pinning eyebrow color to --text-faint made them too
     dim against recessed surfaces. Standardized at a slightly higher
     middle grey so labels read as labels, not as deprecated text.
     Brian 2026-05-29.

     Canonical use via the .eyebrow primitive class (below) — apply
     the class instead of recreating the rule per-page. */
  --text-eyebrow: #9F9FA0;

  /* Font families. Mono is reserved for accent kickers / status
     indicators / major tile labels in accent color — never body. */
  --font-sans:  'PP Neue Montreal', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono:  'Space Mono', monospace;

  /* Canonical mono tile/eyebrow label size — ONE value across every portal.
     The .eyebrow primitive and any hand-rolled mono kicker should read from
     this so labels never drift out of size again (Brian 2026-07-10). */
  --label-mono-size: 10px;

  --accent-lime:    #A8F06A;  /* Ecto green, Ghost primary */
  --accent-cyan:    #00E5FF;  /* Ghost UI cyan */
  --accent-cyan-2:  #00E5FF;  /* alias of --accent-cyan; Ghost blue is the only cyan */
  --accent-orange:  #F9A03F;
  --accent-purple:  #A155F0;
  --nvr-new:        var(--accent-purple);  /* "New" slice in New-vs-Returning; per-portal overridable (BEDA=blue) */
  /* Ghost team-member "person" colors — pinned to true Ghost hues so the
     project team line renders identically on EVERY client portal. Portals
     remap --accent-* (which would BSB-ify / brand-ify the team); no portal
     overrides these --gp-* tokens. Theme-aware via the light block. The team
     colors are Ghost's people, so they stay Ghost colors everywhere. Brian
     2026-07-24. */
  --gp-blue:   #00E5FF;
  --gp-lime:   #A8F06A;
  --gp-orange: #F9A03F;
  --gp-purple: #A155F0;
  /* Status green: drives delta-up, .card-value.green, .recap-status-ok,
     etc. Tokenized so the light theme block can flip it to #00E32B. */
  --accent-green:   #4ade80;
  /* Text-only accent variants. Equal to the bright accent in dark
     mode; the light theme block darkens them for contrast on white.
     Solid accent fills keep the bright --accent-lime / --accent-cyan. */
  --accent-lime-text: var(--accent-lime);
  --accent-cyan-text: var(--accent-cyan);
  --accent-orange-text: var(--accent-orange);
  --accent-purple-soft: rgba(161,85,240,0.45);  /* translucent purple — used for off-state secondary bars (e.g. weekend bars in day-of-week chart) */
  --accent-pink:    #FF6EC7;
  --footer-fg:      rgba(255,255,255,0.40);

  /* Bar gradient endpoints — used by every horizontal % bar in the app
     (pctBar in core.js, .os-bar, .analytics-bar-fill, social pct bar).
     Override per-portal to flip every bar gradient at once. */
  --gradient-bar-from: #A8F06A;  /* Ghost: lime */
  --gradient-bar-to:   #00E5FF;  /* Ghost: cyan */

  /* Per-platform icon colors. Both the small .plat-icon-* set and the
     dashboard social tiles read these. Each portal cycles its own
     palette across the four platforms. */
  /* Meta (FB + IG combined) chrome in the composer. Tokenized 2026-07-24 so
     client portals can remap it — it was hardcoded #EC4899 and bled Ghost pink
     onto every portal. Ghost keeps the original pink. */
  --plat-meta: #EC4899;
  /* Sessions-by-device series. Tokenized 2026-07-24 — these were reading raw
     --accent-* vars, and on a portal that remaps several accents to the same
     brand hue two devices collapsed to an identical color. */
  --device-desktop: var(--accent-purple);
  --device-mobile:  var(--accent-lime);
  --device-tablet:  var(--accent-orange);
  --device-tv:      var(--accent-cyan);
  /* Primary trend-line series (Users over time, Engagement rate, day-of-week
     bars, peak dots + endpoint labels). Tokenized 2026-07-24 so a portal can
     move it off the page accent. */
  --chart-line: var(--accent-lime);
  /* Area-fill gradient beneath the trend lines. Defaults to a single-color
     fade (both ends the same) so Ghost is pixel-identical; a portal can set a
     two-color ramp. The 55% mid-stop matches a linear fade exactly. */
  --chart-grad-from: var(--chart-line);
  --chart-grad-to:   var(--chart-line);
  /* Popover / chart-tooltip accent — border, bullet dot, label AND value, so
     everything tinted inside a popover is one consistent color. Defaults to the
     purple the popover content has always used on Ghost. Brian 2026-07-24. */
  --popover-accent: var(--accent-purple);
  --plat-fb: #00E5FF;            /* Ghost: cyan */
  --plat-ig: #F9A03F;            /* Ghost: orange */
  --plat-li: #A155F0;            /* Ghost: purple */
  --plat-yt: #A8F06A;            /* Ghost: lime */

  /* Translucent tint behind active sub-tabs (Queue/Published/Metrics
     on Social, etc.). Uses the same hue as --accent-purple but at a
     low opacity that doesn't translate via the color var. */
  --tab-active-bg: rgba(161,85,240,0.15);  /* Ghost: translucent purple */

  /* ============================================================
     TRANSLUCENT ACCENT TOKEN SYSTEM (Brian 2026-05-25).

     Every accent color carries the same family of translucent
     variants so page CSS can compose without writing color-mix()
     by hand. Audit before this expansion: ~300 unique color-mix()
     callsites across pages, with the same 6 opacities recurring
     for each accent (8% / 12% / 18% / 22% / 30% / 50%). Tokens
     below pin the canonical opacities so the same chip / tile /
     border treatment reads identically across surfaces.

     Naming:
       --accent-{X}-fill-soft   ~8% opacity. background tint at rest
       --accent-{X}-fill        ~12-15%.    background tint on active
       --accent-{X}-fill-hover  ~18-22%.    background tint on hover/strong
       --accent-{X}-border      ~30%.       outlined chip border
       --accent-{X}-border-strong ~50%.     outlined chip border on hover

     Per-portal palette overrides further down remap --accent-cyan
     etc. The tokens here are defined in terms of color-mix() against
     those vars, so portal skins auto-flip every fill/border with no
     extra work. */
  --accent-cyan-fill-soft:    color-mix(in srgb, var(--accent-cyan) 8%,  transparent);
  --accent-cyan-fill:         color-mix(in srgb, var(--accent-cyan) 15%, transparent);
  --accent-cyan-fill-hover:   color-mix(in srgb, var(--accent-cyan) 22%, transparent);
  /* Pill/button borders ride the accent color at 100% opacity so the
     translucent-fill + accent-text + accent-border family reads as a
     crisp chip rather than a faded one. Brian 2026-05-27. Every per-
     portal override below mirrors this — solid border, not rgba(...
     0.45). The border-strong alias keeps a softer 50% variant for
     places (focus rings, dashed inputs) that want a hint, not a frame. */
  --accent-cyan-border:       var(--accent-cyan);
  --accent-cyan-border-strong:color-mix(in srgb, var(--accent-cyan) 50%, transparent);

  --accent-lime-fill-soft:    color-mix(in srgb, var(--accent-lime) 8%,  transparent);
  --accent-lime-fill:         color-mix(in srgb, var(--accent-lime) 12%, transparent);
  --accent-lime-fill-hover:   color-mix(in srgb, var(--accent-lime) 22%, transparent);
  --accent-lime-border:       color-mix(in srgb, var(--accent-lime) 30%, transparent);
  --accent-lime-border-strong:color-mix(in srgb, var(--accent-lime) 50%, transparent);

  --accent-orange-fill-soft:    color-mix(in srgb, var(--accent-orange) 8%,  transparent);
  --accent-orange-fill:         color-mix(in srgb, var(--accent-orange) 14%, transparent);
  --accent-orange-fill-hover:   color-mix(in srgb, var(--accent-orange) 22%, transparent);
  --accent-orange-border:       color-mix(in srgb, var(--accent-orange) 30%, transparent);
  --accent-orange-border-strong:color-mix(in srgb, var(--accent-orange) 50%, transparent);

  --accent-purple-fill-soft:    color-mix(in srgb, var(--accent-purple) 8%,  transparent);
  --accent-purple-fill:         color-mix(in srgb, var(--accent-purple) 14%, transparent);
  --accent-purple-fill-hover:   color-mix(in srgb, var(--accent-purple) 22%, transparent);
  --accent-purple-border:       color-mix(in srgb, var(--accent-purple) 30%, transparent);
  --accent-purple-border-strong:color-mix(in srgb, var(--accent-purple) 50%, transparent);

  --accent-pink-fill-soft:    color-mix(in srgb, var(--accent-pink) 8%,  transparent);
  --accent-pink-fill:         color-mix(in srgb, var(--accent-pink) 14%, transparent);
  --accent-pink-fill-hover:   color-mix(in srgb, var(--accent-pink) 22%, transparent);
  --accent-pink-border:       color-mix(in srgb, var(--accent-pink) 30%, transparent);
  --accent-pink-border-strong:color-mix(in srgb, var(--accent-pink) 50%, transparent);

  /* ============================================================
     SEMANTIC STATE COLORS — universal across every tenant + portal.
     These represent EVALUATION states (positive / negative / warning)
     and are intentionally distinct from any brand cycle color so a
     "connected" pill never collides with a tab accent.
     RULE: portal skin blocks (body[data-portal-code="..."]) MUST NOT
     override these. Every agent and subdomain reads the same green
     for "good" and the same red for "bad".
     ============================================================ */
  --state-positive:        #22C55E;
  --state-positive-fill:   rgba(34,197,94,0.15);
  --state-positive-border: rgba(34,197,94,0.30);
  --state-negative:        #f87171;
  --state-negative-fill:   rgba(248,113,113,0.15);
  --state-negative-border: rgba(248,113,113,0.30);
  --state-warning:         #F5943B;
  --state-warning-fill:    rgba(245,148,59,0.15);
  --state-warning-border:  rgba(245,148,59,0.30);

  /* Data-table row/column dividers. Stronger than --surface-border-strong
     so column dividers stay clearly readable in both modes. Theme-aware
     (per-portal light blocks override). Use this anywhere a table needs
     a visible rule. */
  --table-rule:            rgba(255,255,255,0.40);

  /* JS-injected hover popovers + chart tooltips. Background + text adapt
     per theme so the same render code works in dark and light. */
  --popover-bg:            #0A0E14;
  --popover-text:          #F2F2F2;
  --popover-border:        rgba(255,255,255,0.10);
}

/* ============================================================
   TYPOGRAPHY UTILITY CLASSES (2026-05-16).
   The text-style system. Two families: sans (PP Neue Montreal)
   for everything readable, mono (Space Mono) reserved for accent
   kickers + status indicators + major tile labels in accent color.

   Three sans weights, each with a defined purpose:
     400 Regular  — anything you READ (body, descriptions, hints)
     500 Medium   — anything you INTERACT with or that's META
                    (buttons, captions, timestamps, metric values)
     700 Bold     — anything you LABEL with (titles, eyebrows,
                    tile headers)

   Use these classes instead of inline font-size/font-weight rules.
   Page-local class definitions can compose with utility classes
   (e.g. <span class="ts-eyebrow adobe-upload-label">) but should
   stop redefining font properties from scratch.
   ============================================================ */
.ts-display     { font-family: var(--font-sans); font-weight: 700; font-size: 24px; line-height: 1.2; }
.ts-title       { font-family: var(--font-sans); font-weight: 700; font-size: 16px; line-height: 1.3; }
.ts-tile-label  { font-family: var(--font-sans); font-weight: 700; font-size: 13px; line-height: 1.3; }
.ts-eyebrow     { font-family: var(--font-sans); font-weight: 700; font-size: 11px; line-height: 1.3; letter-spacing: 0.4px; text-transform: uppercase; }
.ts-body        { font-family: var(--font-sans); font-weight: 400; font-size: 14px; line-height: 1.5; }
.ts-hint        { font-family: var(--font-sans); font-weight: 400; font-size: 12px; line-height: 1.4; }
.ts-button      { font-family: var(--font-sans); font-weight: 500; font-size: 12px; line-height: 1.3; }
.ts-meta        { font-family: var(--font-sans); font-weight: 500; font-size: 11px; line-height: 1.3; }
.ts-mono-kicker { font-family: var(--font-mono); font-weight: 700; font-size: 10px; line-height: 1.4; letter-spacing: 1.5px; text-transform: uppercase; }

/* Safety net (Brian, 2026-05-23). All Space Mono uppercase subheads
   render bold for readability. Catches every inline-styled label
   across pages that hasn't yet migrated to .ts-mono-kicker. Keeps
   "AE / DAYS ACTIVE / DUE DATE" style labels readable everywhere. */
[style*="Space Mono"][style*="uppercase"] { font-weight: 700 !important; }

/* ============================================================
   ELEMENT-LEVEL TYPOGRAPHY (Brian, 2026-05-25).

   Why this block exists:
   ----------------------
   Before today, semantic <h1>/<h2>/<h3> tags either inherited the UA
   default sizing (which looks wrong against our 15px body) or got
   overridden by inline `style="font-size: 22px; font-weight: 600;..."`
   spew on a per-page basis. Same heading rendered at different sizes
   on different pages. Same paragraph rendered at different colors.

   These element rules give every page a single visual baseline. Drop
   the inline styles and just write semantic HTML:
       <h2>Clients</h2>            (no style attr)
       <h3>Status calls</h3>
       <p>Body copy goes here.</p>
       <small>Caption.</small>
   ...and the result matches what you'd get from the canonical
   .ts-* utility classes (.ts-display / .ts-title / .ts-body etc.).

   When to override:
   -----------------
   You almost never need to. A page-local class can compose with a
   semantic heading when a specific surface needs a non-default
   treatment (e.g., a dashboard tile's metric value needs a 40px h3).
   Compose, don't replace. Example:
       <h3 class="metric-value">$42K</h3>
       .metric-value { font-size: 40px; line-height: 1.1; }

   DO NOT:
   -------
   - Sprinkle inline font-size / font-weight / color on heading tags.
     If a heading should be bigger or smaller than the canonical scale,
     consider whether the markup level is wrong (h2 vs h3) before
     overriding the size.
   - Skip a level (don't go h1 -> h3 just to get the size). Use the
     correct semantic level and override with a class if needed.
   - Use <div style="font-size: 28px; font-weight: 700;"> when a
     real <h2> is what the document outline wants.

   Canonical sizes match the .ts-* utility scale so semantic markup
   and utility-class markup produce visually identical output:
       h1 (32px) ≈ .ts-display + 8px
       h2 (28px)  = .page-label h2 canonical
       h3 (20px)  = section header
       h4 (16px)  = .ts-title
       h5 (13px)  = .ts-tile-label
       h6 (11px)  = .ts-eyebrow / .ts-mono-kicker (Space Mono uppercase)
   ============================================================ */
h1, h2, h3, h4, h5, h6 {
  font-family: var(--font-sans);
  color: var(--text);
  font-weight: 700;
  line-height: 1.2;
  /* Surrounding containers own vertical rhythm. No UA defaults: we
     don't want a stray <h2> pushing the page-label row down because
     the browser added 0.83em margin-top. */
  margin: 0;
}
h1 { font-size: 32px; line-height: 1.15; letter-spacing: -0.4px; }   /* page-hero — big, rare. dashboard greeting, splash. */
h2 { font-size: 28px; line-height: 1.2;  letter-spacing: -0.3px; }   /* canonical page header inside .page-label */
h3 { font-size: 20px; line-height: 1.3; }                            /* section titles inside a page */
h4 { font-size: 16px; line-height: 1.3; }                            /* sub-section + tile headers */
h5 { font-size: 13px; line-height: 1.3; }                            /* small structural labels */
h6 {
  /* Eyebrow / kicker text. Default is Space Mono uppercase. If a
     page needs a non-uppercase h6 (rare), wrap in a child class. */
  font-size: 11px; line-height: 1.3;
  font-family: var(--font-mono);
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--text-muted);
}

/* Body copy. The HTML body itself is 15px (see §1 RESET) but element
   <p> defaults to 14px because most page-content prose reads better
   slightly tighter than chrome text. If you want body-sized text in a
   non-paragraph element, use .ts-body. */
p { font-size: 14px; line-height: 1.5; color: var(--text); margin: 0; }
/* Stacked paragraphs get spacing automatically; surrounding containers
   never need to add a margin-bottom hack to <p>. */
p + p { margin-top: 10px; }

/* Inline / phrasing defaults. Small is one notch dimmer than body so
   captions, hints, and timestamps read as secondary at a glance. */
small      { font-size: 12px; color: var(--text-muted); }
strong, b  { font-weight: 700; }    /* no color shift — readable in both themes */
em, i      { font-style: italic; }

/* Links. Cyan default → lime on hover. Both are accent vars so portal
   skin overrides flip them automatically (BlueJay link reads as
   BJ orange because --accent-cyan resolves to BJ orange there). */
a        { color: var(--accent-cyan); text-decoration: none; transition: color 0.12s; }
a:hover  { color: var(--accent-lime); }

/* ============================================================
   CANONICAL BUTTON PRIMITIVES (Brian, 2026-05-25).

   Why this block exists:
   ----------------------
   Before today, every page built its buttons inline:
       style="padding: 8px 16px; border-radius: 8px;
              background: color-mix(in srgb, var(--accent-cyan) 18%, transparent);
              color: var(--accent-cyan); border: 1px solid ...
              onmouseenter=... onmouseleave=..."
   Forty lines of style spew per button, repeated across the app, with
   subtle drift everywhere (12px padding here, 14px there; cyan 18%
   here, 15% there). Hover handled via inline JS in some places, via
   pseudo-class in others.

   Use these classes instead. Hover, focus-visible, and disabled
   states are all handled in CSS. Portal palette overrides cascade
   through automatically because every variant references --accent-cyan
   (which the portal skin remaps to its own brand color).

   The classes:
   ------------
     .btn            base reset. ALWAYS pair with a variant class.
                     Provides UA reset, sizing, transitions, focus
                     ring, disabled state. Never use .btn alone.

     .btn-primary    solid accent fill — primary action on a panel.
                     Use sparingly; one per surface. Examples:
                     "Save", "Send Request", "Submit", "Apply".

     .btn-outline    transparent fill + accent border + accent text.
                     Secondary action. Examples: "Edit", "Cancel" (when
                     paired with a primary), "Add Item", "Refresh".

     .btn-ghost      transparent fill + muted text + subtle border.
                     Tertiary action. Examples: dismiss, close, "Not
                     now", filter chips at rest, view-toggle inactive.

     .btn-danger     red-state. Destructive actions only. Examples:
                     "Delete", "Archive", "Cancel Project".

   Size modifiers (compose):
   -------------------------
     .btn-sm         compact (5px y / 12px x padding, 12px font)
                     Use in toolbar / inline / list rows.

     .btn-lg         roomy (11px y / 20px x padding, 14px font)
                     Use for hero CTAs / modal primary actions.

   Example:
   --------
     <button class="btn btn-primary">Send Request</button>
     <button class="btn btn-outline btn-sm">Edit</button>
     <button class="btn btn-ghost" onclick="closeModal()">Cancel</button>
     <button class="btn btn-danger">Delete</button>

   DO NOT:
   -------
   - Recreate any of these treatments inline. If you find yourself
     writing `color-mix(in srgb, var(--accent-cyan) X%, transparent)`
     on a button, you're recreating .btn-outline. Stop.
   - Add color-specific button variants per page (no .social-orange-btn,
     no .pipeline-cyan-btn). Page accent comes from the portal palette;
     buttons already inherit it.
   - Set onmouseenter / onmouseleave to swap colors. The :hover state
     here covers it.
   - Override font-family or border-radius on a per-button basis. If
     your design needs a different shape, propose it for the system,
     don't fork.

   When to add a new variant:
   --------------------------
   Three uses of the same non-standard treatment is the threshold.
   Two is coincidence; three is a pattern. Bring it back here, name
   it, document it, then page-level CSS can compose.
   ============================================================ */
.btn {
  /* UA reset + base. appearance:none zeroes Chrome's default button
     paint (the faint grey lift on focus). Inline-flex so a leading
     SVG icon + text label share the baseline without a wrapper. */
  appearance: none; -webkit-appearance: none;
  font-family: var(--font-sans);
  font-weight: 500;
  font-size: 13px;
  line-height: 1.3;
  padding: 8px 16px;
  border-radius: 8px;
  border: 1px solid transparent;
  background: transparent;
  color: var(--text);
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  user-select: none;
  transition: background 0.15s, border-color 0.15s, color 0.15s, opacity 0.12s;
  /* .btn also works on <a> tags. Remove the default underline so
     button-styled links read as buttons, not links. */
  text-decoration: none;
}
.btn:focus { outline: none; }
.btn:focus-visible {
  /* Keyboard-only focus ring. Not visible on mouse click. */
  outline: 2px solid color-mix(in srgb, var(--accent-cyan) 45%, transparent);
  outline-offset: 2px;
}
.btn:disabled,
.btn[aria-disabled="true"] {
  /* Both forms supported. aria-disabled lets non-button elements
     (a, div with role) participate in the same disabled visual. */
  opacity: 0.45; cursor: not-allowed;
}

.btn-primary {
  /* Solid accent fill. Used once per surface. Text color is dark
     because the lime/cyan fills are bright. */
  background: var(--accent-cyan);
  color: #0E0E10;
  font-weight: 700;
  letter-spacing: 0.3px;
}
.btn-primary:hover:not(:disabled) {
  /* Slight lift toward white. Stays on-brand because the base is
     the portal accent. */
  background: color-mix(in srgb, var(--accent-cyan) 88%, white);
}

.btn-outline {
  /* Translucent border, accent text. The 45% border keeps it
     visible against both --surface-0 (black) and --surface-1
     (the tile fill) without going loud. */
  background: transparent;
  color: var(--accent-cyan);
  border-color: color-mix(in srgb, var(--accent-cyan) 45%, transparent);
}
.btn-outline:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent-cyan) 12%, transparent);
  border-color: var(--accent-cyan);
}

.btn-ghost {
  /* Muted neutral. Used for actions that exist but shouldn't fight
     for attention (Cancel next to a Save primary, close icons, etc.). */
  background: transparent;
  color: var(--text-muted);
  border-color: var(--surface-border-strong);
}
.btn-ghost:hover:not(:disabled) {
  background: var(--surface-border-strong);
  color: var(--text);
}

/* LIGHT MODE: transparent-fill accent buttons (.btn-outline) read
   washed-out on a light ground, so they flip to a SOLID accent fill
   with WHITE text. Standing rule (Brian 2026-06-01). Fill rides the
   deepened accent-text var so white stays legible even on the pale
   portal palettes (e.g. GLD's pistachio/periwinkle/rose). The 0,2,0
   selector beats the base .btn-outline (0,1,0). Social queue-card
   action buttons use their own locked treatment (separate classes)
   and are intentionally unaffected. .btn-ghost (neutral/quiet) is
   left transparent. */
body[data-theme="light"] .btn-outline {
  background: var(--accent-cyan-text, var(--accent-cyan));
  color: #FFFFFF;
  border-color: var(--accent-cyan-text, var(--accent-cyan));
}
body[data-theme="light"] .btn-outline:hover:not(:disabled) {
  background: color-mix(in srgb, var(--accent-cyan-text, var(--accent-cyan)) 88%, black);
  border-color: color-mix(in srgb, var(--accent-cyan-text, var(--accent-cyan)) 88%, black);
  color: #FFFFFF;
}

.btn-danger {
  /* Red-state. Universal across all tenants (state-negative is NOT
     overridden by portal palettes — semantic colors stay constant).
     Outline-style by default; if a destructive action needs the
     full red fill, compose with .btn-primary-on-danger (TBD). */
  background: transparent;
  color: var(--state-negative);
  border-color: var(--state-negative-border);
}
.btn-danger:hover:not(:disabled) {
  background: var(--state-negative-fill);
  border-color: var(--state-negative);
}

/* Size modifiers. Compose with any variant. */
.btn-sm { padding: 5px 12px; font-size: 12px; }
.btn-lg { padding: 11px 20px; font-size: 14px; }

body[data-portal-code="BLU"] {
  /* Raw palette — declared once, referenced by both slot mappings
     below and explicit tab/platform overrides further down.
     Brian 2026-05-28: BlueJay portal is blue/green/orange only.
     Red (#EF4136) was retired; --bj-red stays declared as an alias
     of --bj-blue so any legacy reference resolves to a brand color
     instead of an undefined var, but no rule writes red anymore. */
  --bj-blue:   #00AEEF;
  --bj-orange: #F7941D;
  --bj-green:  #8DC63F;
  --bj-red:    var(--bj-blue);

  /* Slot mapping for non-tab elements that resolve through the
     Ghost --accent-* vars (pulse-dots, status text, etc.). Every
     slot lands on one of the three BlueJay brand colors. */
  --accent-lime:    var(--bj-blue);
  --accent-cyan:    var(--bj-orange);
  --accent-cyan-2:  var(--bj-blue);
  --accent-orange:  var(--bj-green);
  --accent-purple:  var(--bj-blue);
  --accent-purple-soft: rgba(0,174,239,0.45);  /* translucent BJ blue */
  --accent-pink:    var(--bj-blue);

  /* Bar gradient on BlueJay: dark blue → light blue (#2B3990 → BJ blue).
     Overrides Ghost's purple → lime default for every bar in the app. */
  --gradient-bar-from: #2B3990;
  --gradient-bar-to:   var(--bj-blue);

  /* Per-platform cycle. Social is hidden on BlueJay so YouTube/etc.
     never render here; values kept brand-safe in case staff inspect-
     mode shows the tab. */
  --plat-fb: var(--bj-blue);
  --plat-ig: var(--bj-orange);
  --plat-li: var(--bj-green);
  --plat-yt: var(--bj-blue);

  /* Active sub-tab tint = blue translucent (matches primary accent). */
  --tab-active-bg: rgba(0,174,239,0.15);

  /* BJ orange translucent — drives every fill/border that hangs off
     --accent-cyan (Cards/List, Current/Archive, FINAL pill, detail
     modal tabs / CTA). Hex F7941D = rgb(247,148,29). */
  --accent-cyan-fill:        rgba(247,148,29,0.15);
  --accent-cyan-fill-hover:  rgba(247,148,29,0.10);
  --accent-cyan-border:      rgb(247,148,29);
}

/* Client portals never see the staff billing hero (monthly $ target,
   Billed / Billed+Projected bars) or the staff Pipeline tile. Per Brian
   2026-05-04: "BlueJay will NOT get billing tile." + "no pipeline tile,
   site traffic could go next to This Week On Deck." When the pipeline
   panel is hidden, the .two-col layout collapses so the analytics
   panel takes the full width. */
body[data-portal-code] .billing-hero        { display: none !important; }
body[data-portal-code] #dash-pipeline-panel { display: none !important; }
body[data-portal-code] #dash-closing-panel  { display: none !important; }
/* The dashboard's first .two-col (week-on-deck + closing) keeps its 2-col
   grid; pageInit_dashboard moves the analytics panel into the empty
   right slot when in portal mode and hides the second .two-col. */

/* BlueJay: explicit per-tab nav-icon palette. Visible tabs (Projects,
   Calendar, Traffic, Guide) are pinned by Brian to the four assignments
   below; both active AND hover render in that tab's primary accent so
   the icon previews its tab color on hover and stays in it when active.
   Hidden tabs (Dashboard, Social, Google Ads, Settings, ClientProjects)
   are left covered in case staff inspect-mode shows them. */
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="clientportal"] .nav-icon   { background-color: var(--bj-blue);   } /* Projects */
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="calendar"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="calendar"] .nav-icon       { background-color: var(--bj-green);  } /* Calendar */
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="traffic"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="traffic"] .nav-icon        { background-color: var(--bj-orange); } /* Traffic */
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="social"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="social"] .nav-icon         { background-color: var(--bj-blue);   } /* Social */
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="guide"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="guide"] .nav-icon          { background-color: var(--bj-green);  } /* Guide */
/* Hidden tabs (in case staff inspect-mode reveals them). */
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="dashboard"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="dashboard"] .nav-icon      { background-color: var(--bj-blue);   }
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="googleads"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="googleads"] .nav-icon      { background-color: var(--bj-orange); }
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="settings"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="settings"] .nav-icon       { background-color: var(--bj-blue);   }
body[data-portal-code="BLU"] .sidebar-nav a.active[data-page="clientprojects"] .nav-icon,
body[data-portal-code="BLU"] .sidebar-nav a:hover[data-page="clientprojects"] .nav-icon { background-color: var(--bj-blue);   } /* legacy alias of Portal */

/* ─── Blue Sky Bank (SKY) ────────────────────────────────────────────
   Five-stop brand palette matched to bluesky.bank (Brian 2026-07-23):
   navy #171348 · indigo #221f72 · blue #026cb6 · light #1DAEEB · pale #CBDFEE.
   Primary interactive = blue; secondary/take-tiles = indigo; dark-mode lift =
   light; gradients start on navy. Light-first portal. */
/* Blue Sky Bank skin. Collapses Ghost's multi-accent palette onto the two
   brand colors: primary interactive = blue (#026cb6), secondary/take-tiles =
   navy (#221f72). Green/red stay semantic (success/error).
   CRITICAL: the light-mode accent block (~line 3167, body[data-theme="light"])
   re-declares --accent-cyan/orange/lime at EQUAL specificity but LATER in the
   file, so a single-attribute SKY selector loses in light mode (SKY defaults to
   light). The [data-theme="light"] and [data-theme="dark"] selectors below add
   a second attribute -> higher specificity -> the SKY palette always wins,
   regardless of source order. This is the same fix GLD/HS/PATRONS use. Do NOT
   drop the theme-qualified selectors or the portal reverts to Ghost orange. */
body[data-portal-code="SKY"],
body[data-portal-code="SKY"][data-theme="light"],
body[data-portal-code="SKY"][data-theme="dark"] {
  /* Blue Sky Bank official brand palette (Brian 2026-07-23) */
  --sky-navy:     #171348;  /* Deep Navy Blue */
  --sky-midnight: #221f72;  /* Midnight Blue */
  --sky-steel:    #026cb6;  /* Steel Blue */
  --sky-sky:      #1DAEEB;  /* Sky Blue */
  --sky-alice:    #CBDFEE;  /* Alice Blue */
  /* legacy aliases so downstream fill/border rules still resolve */
  --sky-indigo: var(--sky-midnight);
  --sky-blue:   var(--sky-steel);
  --sky-light:  var(--sky-sky);
  --sky-pale:   var(--sky-alice);

  /* Icon + accent cycle: Steel (c1), Sky (c2), c3. Light-first portal, so c3
     defaults to Deep Navy here (Alice is invisible on white); the dark block
     below swaps c3 to Alice Blue. */
  /* Icon + accent cycle: Steel (c1), Sky (c2), c3. Light-first portal, so c3
     defaults to Deep Navy here (Alice is invisible on white); the dark block
     below swaps c3 to Alice Blue. NOTE: the Social tab (+ its modals) overrides
     the accent to Sky Blue — see the scoped block after this theme. Every other
     tab keeps Steel. */
  --sky-c1: var(--sky-steel);
  --sky-c2: var(--sky-sky);
  --sky-c3: var(--sky-navy);

  --accent-lime:    var(--sky-c1);
  --accent-cyan:    var(--sky-c1);
  --accent-cyan-2:  var(--sky-c2);
  --accent-orange:  var(--sky-c2);
  --accent-purple:  var(--sky-c1);
  --accent-pink:    var(--sky-c3);

  --accent-lime-text:   var(--sky-c1);
  --accent-cyan-text:   var(--sky-c1);
  --accent-orange-text: var(--sky-c2);
  --accent-purple-soft: rgba(2,108,182,0.42);

  /* New-vs-Returning "New" slice = Steel Blue, not Ghost purple. Set explicitly
     (like BEDA) so the doughnut/legend never fall back to --accent-purple. (Brian 2026-08-10) */
  --nvr-new: var(--sky-steel);

  /* Ranked-table bar fill runs Steel into c3 — Alice in dark, Deep Navy in
     light. Was Navy -> Steel, which read as a flat blue. Brian 2026-07-24. */
  --gradient-bar-from: var(--sky-steel);
  --gradient-bar-to:   var(--sky-c3);
  --tab-active-bg:     color-mix(in srgb, var(--sky-c1) 14%, transparent);

  /* cyan family (primary interactive) -> Steel (c1) */
  --accent-cyan-fill-soft:     color-mix(in srgb, var(--sky-c1) 8%,  transparent);
  --accent-cyan-fill:          color-mix(in srgb, var(--sky-c1) 14%, transparent);
  --accent-cyan-fill-hover:    color-mix(in srgb, var(--sky-c1) 22%, transparent);
  --accent-cyan-border:        var(--sky-c1);
  --accent-cyan-border-strong: color-mix(in srgb, var(--sky-c1) 50%, transparent);
  /* orange family (Take tiles, Google Ads) -> Sky (c2) */
  --accent-orange-fill-soft:     color-mix(in srgb, var(--sky-c2) 8%,  transparent);
  --accent-orange-fill:          color-mix(in srgb, var(--sky-c2) 14%, transparent);
  --accent-orange-fill-hover:    color-mix(in srgb, var(--sky-c2) 22%, transparent);
  --accent-orange-border:        color-mix(in srgb, var(--sky-c2) 30%, transparent);
  --accent-orange-border-strong: color-mix(in srgb, var(--sky-c2) 50%, transparent);
  /* purple family (Traffic, Social) -> Steel (c1) */
  --accent-purple-fill-soft:     color-mix(in srgb, var(--sky-c1) 8%,  transparent);
  --accent-purple-fill:          color-mix(in srgb, var(--sky-c1) 14%, transparent);
  --accent-purple-fill-hover:    color-mix(in srgb, var(--sky-c1) 22%, transparent);
  --accent-purple-border:        color-mix(in srgb, var(--sky-c1) 30%, transparent);
  --accent-purple-border-strong: color-mix(in srgb, var(--sky-c1) 50%, transparent);
  /* lime family -> Steel (c1) */
  --accent-lime-fill-soft:     color-mix(in srgb, var(--sky-c1) 8%,  transparent);
  --accent-lime-fill:          color-mix(in srgb, var(--sky-c1) 14%, transparent);
  --accent-lime-fill-hover:    color-mix(in srgb, var(--sky-c1) 22%, transparent);
  --accent-lime-border:        color-mix(in srgb, var(--sky-c1) 30%, transparent);
  --accent-lime-border-strong: color-mix(in srgb, var(--sky-c1) 50%, transparent);
  /* pink family -> c3 (Alice dark / Deep Navy light) */
  --accent-pink-fill-soft:     color-mix(in srgb, var(--sky-c3) 8%,  transparent);
  --accent-pink-fill:          color-mix(in srgb, var(--sky-c3) 14%, transparent);
  --accent-pink-fill-hover:    color-mix(in srgb, var(--sky-c3) 22%, transparent);
  --accent-pink-border:        color-mix(in srgb, var(--sky-c3) 30%, transparent);
  --accent-pink-border-strong: color-mix(in srgb, var(--sky-c3) 50%, transparent);
  /* Social channel icon colors on the BSB palette (Brian 2026-07-24):
     Facebook = BSB cyan Sky Blue, Instagram = Construction & land gold,
     LinkedIn = Farmland purple. YouTube unused by Blue Sky, left on base. */
  --plat-fb: var(--sky-sky);
  --plat-ig: #F2B90C;
  --plat-li: #8B2FE8;
  /* Meta is the parent pill covering FB + IG, so it takes the BSB primary
     (Steel) rather than either child's color. */
  --plat-meta: var(--sky-steel);
  /* Sessions-by-device rides the brand cycle: Steel (royal), Sky, then c3 —
     Alice in dark, Deep Navy in light. Midnight backs the rare Smart TV slice. */
  /* Analytics series order: Steel, then c3 (Alice dark / Navy light), then Sky. */
  --device-desktop: var(--sky-c1);
  --device-mobile:  var(--sky-c3);
  --device-tablet:  var(--sky-c2);
  --device-tv:      var(--sky-midnight);
  /* Users over time, Engagement rate, day-of-week bars and their endpoint
     labels ride c3: Alice in dark, Deep Navy in light. Brian 2026-07-24. */
  --chart-line: var(--sky-c3);
  /* Area gradients run Steel into c3 (Alice dark / Navy light). */
  --chart-grad-from: var(--sky-c1);
  --chart-grad-to:   var(--sky-c3);
  /* Popovers + map hover cards: border and accent text are Sky Blue. */
  --popover-accent: var(--sky-sky);
}

/* BSB SOCIAL TAB ONLY — accent is Sky Blue instead of Steel (Brian 2026-08-01).
   Scoped to the Social page container (which holds the edit/preview/builder
   modals inline) plus the Boost modal (appended to document.body). Every OTHER
   tab keeps the portal's Steel accent. Re-declaring the accent families here (not
   just --sky-c1) is required: custom properties substitute var() where declared,
   so the families must be re-declared in this scope to pick up Sky. */
body[data-portal-code="SKY"] #page-wrap-social,
body[data-portal-code="SKY"] #boost-modal-overlay {
  --accent-cyan:        var(--sky-sky);
  --accent-lime:        var(--sky-sky);
  --accent-purple:      var(--sky-sky);
  --accent-cyan-text:   var(--sky-sky);
  --accent-lime-text:   var(--sky-sky);
  --accent-purple-soft: color-mix(in srgb, var(--sky-sky) 42%, transparent);
  --tab-active-bg:      color-mix(in srgb, var(--sky-sky) 14%, transparent);
  --accent-cyan-fill-soft:     color-mix(in srgb, var(--sky-sky) 8%,  transparent);
  --accent-cyan-fill:          color-mix(in srgb, var(--sky-sky) 14%, transparent);
  --accent-cyan-fill-hover:    color-mix(in srgb, var(--sky-sky) 22%, transparent);
  --accent-cyan-border:        var(--sky-sky);
  --accent-cyan-border-strong: color-mix(in srgb, var(--sky-sky) 50%, transparent);
  --accent-lime-fill-soft:     color-mix(in srgb, var(--sky-sky) 8%,  transparent);
  --accent-lime-fill:          color-mix(in srgb, var(--sky-sky) 14%, transparent);
  --accent-lime-fill-hover:    color-mix(in srgb, var(--sky-sky) 22%, transparent);
  --accent-lime-border:        color-mix(in srgb, var(--sky-sky) 30%, transparent);
  --accent-lime-border-strong: color-mix(in srgb, var(--sky-sky) 50%, transparent);
  --accent-purple-fill-soft:     color-mix(in srgb, var(--sky-sky) 8%,  transparent);
  --accent-purple-fill:          color-mix(in srgb, var(--sky-sky) 14%, transparent);
  --accent-purple-fill-hover:    color-mix(in srgb, var(--sky-sky) 22%, transparent);
  --accent-purple-border:        color-mix(in srgb, var(--sky-sky) 30%, transparent);
  --accent-purple-border-strong: color-mix(in srgb, var(--sky-sky) 50%, transparent);
}

/* Calendar campaign-category colors. Default to the Ghost accent quartet;
   Blue Sky remaps to its blue/cyan/teal/green data-viz palette (only 3 brand
   hues exist, so the 4th reuses the deposit-engine teal/green). All four read
   on both themes, so no dark override is needed for these. */
:root {
  --cal-cat-landing:    var(--accent-cyan);
  --cal-cat-email:      var(--accent-orange);
  --cal-cat-collateral: var(--accent-purple);
  --cal-cat-social:     var(--accent-lime);
  --cal-cat-meetings:   var(--accent-cyan-2);
}
body[data-portal-code="SKY"] {
  --cal-cat-landing:    var(--sky-steel);  /* Steel Blue */
  --cal-cat-email:      var(--sky-sky);    /* Sky Blue */
  --cal-cat-collateral: #1E9D6C;           /* green (kept) */
  --cal-cat-social:     #F0B429;           /* yellow (kept) */
  --cal-cat-meetings:   var(--sky-c3);     /* Alice Blue (dark) / Deep Navy (light) */
}

/* Dark mode: swap the c3 slot from Deep Navy to Alice Blue. Everything that
   references c3 (pink accents, the Calendar + Google Ads nav icons, the
   Meetings category) follows automatically. SKY is light-first, so light is the
   explicit attribute and dark = attribute absent. Brian 2026-07-23. */
body[data-portal-code="SKY"]:not([data-theme="light"]) {
  --sky-c3: var(--sky-alice);
  /* Traffic by Location ramp, BSB blue family (dark -> light), replacing the
     Ghost purple default. Brian 2026-07-24. */
  /* Ends pulled in for dark mode (Brian 2026-07-24): pure Navy read as almost
     the card background, and Alice as almost white. Bottom lifts to a visible
     indigo, top darkens toward a bluer Alice, so all four tiers separate. */
  --traffic-tier-1: #3A34A6;  /* brighter indigo — lowest (lifted for contrast vs the dark map, Brian 2026-07-26) */
  --traffic-tier-2: #026cb6;  /* Steel */
  --traffic-tier-3: #1DAEEB;  /* Sky */
  --traffic-tier-4: #A8CEE8;  /* deepened Alice — highest */
}
/* Light mode inverts the ramp so the top tier is the strongest value on white
   (Navy), mirroring the OHFA precedent. Alice for dark, Navy for white. */
body[data-portal-code="SKY"][data-theme="light"] {
  --traffic-tier-1: #CBDFEE;  /* Alice — lowest */
  --traffic-tier-2: #1DAEEB;  /* Sky */
  --traffic-tier-3: #026cb6;  /* Steel */
  --traffic-tier-4: #171348;  /* Deep Navy — highest */
}
/* Nav icons cycle Steel (c1), Sky (c2), c3 down the visible tab order:
   Deposit Engine, Projects, Calendar, Social, Traffic, Google Ads. c3 swaps
   Alice/Navy by theme via the block above, so no separate dark rule is needed. */
/* Inactive icons stay the base light grey; hover + active reveal the section
   color, cycling Steel (c1), Sky (c2), c3 (Alice dark / Navy light) down the
   tab order. Matches the section's own content accent. Brian 2026-07-24. */
body[data-portal-code="SKY"] .sidebar-nav a[data-page="depositengine"].active .nav-icon,
body[data-portal-code="SKY"] .sidebar-nav a[data-page="depositengine"]:hover .nav-icon { background-color: var(--sky-c1); }
body[data-portal-code="SKY"] .sidebar-nav a[data-page="clientportal"].active .nav-icon,
body[data-portal-code="SKY"] .sidebar-nav a[data-page="clientportal"]:hover .nav-icon  { background-color: var(--sky-c2); }
body[data-portal-code="SKY"] .sidebar-nav a[data-page="calendar"].active .nav-icon,
body[data-portal-code="SKY"] .sidebar-nav a[data-page="calendar"]:hover .nav-icon      { background-color: var(--sky-c3); }
body[data-portal-code="SKY"] .sidebar-nav a[data-page="social"].active .nav-icon,
body[data-portal-code="SKY"] .sidebar-nav a[data-page="social"]:hover .nav-icon        { background-color: var(--sky-c1); }
body[data-portal-code="SKY"] .sidebar-nav a[data-page="traffic"].active .nav-icon,
body[data-portal-code="SKY"] .sidebar-nav a[data-page="traffic"]:hover .nav-icon       { background-color: var(--sky-c2); }
/* MailChimp (Brian 2026-07-26): alice blue in dark mode, deep navy in light mode
   — pale mark on the dark nav, dark mark on the light nav. */
body[data-portal-code="SKY"] .sidebar-nav a[data-page="mailchimp"].active .nav-icon,
body[data-portal-code="SKY"] .sidebar-nav a[data-page="mailchimp"]:hover .nav-icon     { background-color: var(--sky-alice); }
body[data-portal-code="SKY"][data-theme="light"] .sidebar-nav a[data-page="mailchimp"].active .nav-icon,
body[data-portal-code="SKY"][data-theme="light"] .sidebar-nav a[data-page="mailchimp"]:hover .nav-icon { background-color: var(--sky-navy); }
body[data-portal-code="SKY"] .sidebar-nav a[data-page="googleads"].active .nav-icon,
body[data-portal-code="SKY"] .sidebar-nav a[data-page="googleads"]:hover .nav-icon     { background-color: var(--sky-c3); }

/* Per-platform colors are now driven by --plat-fb/ig/li/yt vars at
   :root + the BlueJay block above; no per-class overrides needed. */

/* BlueJay: sidebar user-avatar circle + floating chat bubble — both
   hardcode Ghost purple inline (sidebar via inline style on
   #sidebar-avatar; bubble via radial-gradient in chat-bubble.js JS
   constants). Override to BJ blue. !important required to beat the
   inline style attributes. */
body[data-portal-code="BLU"] #sidebar-avatar {
  background: rgba(0,174,239,0.2) !important;
  border: 2px solid rgba(0,174,239,0.3) !important;
  color: var(--bj-blue) !important;
}
body[data-portal-code="BLU"] #ethan-bubble-btn {
  background: radial-gradient(circle at 30% 30%, #00AEEF 0%, #006B8E 100%) !important;
  box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 0 3px rgba(0,174,239,0.18) !important;
}
body[data-portal-code="BLU"] #ethan-bubble-btn:hover {
  box-shadow: 0 14px 36px rgba(0,0,0,0.5), 0 0 0 4px rgba(0,174,239,0.28) !important;
}

/* ---------- OHFA portal skin ---------- */
/* Cycle through OHFA's four brand colors (yellow / blue / green / red)
   so the dashboard reads as OHFA's, not Ghost's. */
body[data-portal-code="OHFA"] {
  --oh-yellow: #F2B62E;
  --oh-blue:   #1AA8C9;
  --oh-green:  #1F9F4E;
  --oh-red:    #E63329;

  --accent-lime:    var(--oh-yellow);
  --accent-cyan:    var(--oh-blue);
  --accent-cyan-2:  var(--oh-yellow);
  --accent-orange:  var(--oh-green);
  /* Weekday bars on Day-of-Week chart use --accent-purple, weekend
     uses --accent-purple-soft. For OHFA we want both in blue per Brian
     2026-05-13 (was red). */
  --accent-purple:  var(--oh-blue);
  --accent-purple-soft: rgba(26,168,201,0.30);
  --accent-pink:    var(--oh-red);

  --gradient-bar-from: var(--oh-blue);
  --gradient-bar-to:   var(--oh-green);

  --plat-fb: var(--oh-blue);
  --plat-ig: var(--oh-yellow);
  --plat-li: var(--oh-green);
  --plat-yt: var(--oh-red);

  --tab-active-bg: rgba(242,182,46,0.18);
}
/* Across every OHFA portal page, metric-tile eyebrow labels render in
   pure white (Brian 2026-05-13: dim grey was too quiet on the dark
   OHFA surfaces). Applies to .card-label everywhere. */
body[data-portal-code="OHFA"] .card-label { color: #FFFFFF; }
body[data-portal-code="OHFA"] {

  --accent-cyan-fill:        rgba(242,182,46,0.16);
  --accent-cyan-fill-hover:  rgba(242,182,46,0.10);
  --accent-cyan-border:      rgb(242,182,46);

  /* Traffic by Location 4-tier ramp — OHFA's official 4-blue palette,
     refreshed from color2.png (Brian 2026-05-12 evening, supersedes
     the original colors.png navy-leaning ramp). Brand triangles are
     bright cyan / medium steel-blue / dark teal; we synthesize a
     deeper shade for tier-1 so the darkest bucket reads distinctly.
     Tier 1 (darkest) = bottom-25%, Tier 4 (lightest) = top-25%. */
  --traffic-tier-1: #07313F;
  --traffic-tier-2: #0F5868;
  --traffic-tier-3: #1568A0;
  --traffic-tier-4: #00BDE8;
}

/* OHFA tab nav-icon cycle: yellow → blue → green → red, mirroring
   BlueJay's pattern (slot-positioned). Order matches the visible OHFA
   tab list: Portal, Housing Stability, Calendar, Social, Traffic. */
/* OHFA per-tab color map (per Brian 2026-05-12):
   Portal=blue, Housing Stability=yellow, Calendar=green, Social=red,
   Traffic=blue, Guide=yellow. */
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="dashboard"] .nav-icon          { background-color: var(--oh-yellow); }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon       { background-color: var(--oh-blue);   }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="ohsp"] .nav-icon  { background-color: var(--oh-yellow); }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="calendar"] .nav-icon           { background-color: var(--oh-green);  }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="social"] .nav-icon             { background-color: var(--oh-red);    }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="traffic"] .nav-icon            { background-color: var(--oh-blue);   }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="googleads"] .nav-icon          { background-color: var(--oh-yellow); }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="guide"] .nav-icon              { background-color: var(--oh-yellow); }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="settings"] .nav-icon           { background-color: var(--oh-red);    }
body[data-portal-code="OHFA"] .sidebar-nav a.active[data-page="clientprojects"] .nav-icon     { background-color: var(--oh-blue);   }

/* OHFA per-page accent swap: each page's body content paints in the
   same color as its sidebar tab. router.js stamps data-active-page on
   body whenever switchToPage fires. */
body[data-portal-code="OHFA"][data-active-page="clientportal"] {
  --accent-cyan:    var(--oh-blue);
  --accent-cyan-2:  var(--oh-blue);
  --accent-cyan-fill:        rgba(26,168,201,0.16);
  --accent-cyan-fill-hover:  rgba(26,168,201,0.10);
  --accent-cyan-border:      rgb(26,168,201);
}
body[data-portal-code="OHFA"][data-active-page="calendar"] {
  --accent-cyan:    var(--oh-green);
  --accent-cyan-2:  var(--oh-green);
  --accent-cyan-fill:        rgba(31,159,78,0.16);
  --accent-cyan-fill-hover:  rgba(31,159,78,0.10);
  --accent-cyan-border:      rgb(31,159,78);
}
body[data-portal-code="OHFA"][data-active-page="social"] {
  --accent-cyan:    var(--oh-red);
  --accent-cyan-2:  var(--oh-red);
  --accent-cyan-fill:        rgba(230,51,41,0.16);
  --accent-cyan-fill-hover:  rgba(230,51,41,0.10);
  --accent-cyan-border:      rgb(230,51,41);
}
body[data-portal-code="OHFA"][data-active-page="traffic"] {
  --accent-cyan:    var(--oh-blue);
  --accent-cyan-2:  var(--oh-blue);
  --accent-cyan-fill:        rgba(26,168,201,0.16);
  --accent-cyan-fill-hover:  rgba(26,168,201,0.10);
  --accent-cyan-border:      rgb(26,168,201);
  /* Traffic metric vis on OHFA: primary yellow, secondary blue,
     tertiary green. accent-lime drives the primary doughnut slice
     (Desktop, New users), accent-purple is the secondary slot used by
     Mobile + Returning, accent-orange is tertiary (Tablet). */
  --accent-lime:    var(--oh-yellow);
  --accent-purple:  var(--oh-blue);
  --accent-orange:  var(--oh-green);
  /* Bar chart gradients on Traffic: blue → yellow. */
  --gradient-bar-from: var(--oh-blue);
  --gradient-bar-to:   var(--oh-yellow);
}
body[data-portal-code="OHFA"][data-active-page="guide"],
body[data-portal-code="OHFA"][data-active-page="ohsp"] {
  --accent-cyan:    var(--oh-yellow);
  --accent-cyan-2:  var(--oh-yellow);
  --accent-cyan-fill:        rgba(242,182,46,0.16);
  --accent-cyan-fill-hover:  rgba(242,182,46,0.10);
  --accent-cyan-border:      rgb(242,182,46);
}

body[data-portal-code="OHFA"] #sidebar-avatar {
  background: rgba(242,182,46,0.18) !important;
  border: 2px solid rgba(242,182,46,0.35) !important;
  color: var(--oh-yellow) !important;
}

/* ============================================================
   SMO PORTAL PALETTE  (smo.myghostagent.com)
   ============================================================
   Science Museum Oklahoma / "smart TALKS". Brand: blue #1b99d6
   (PRIMARY), green #6dbe42, orange #dc721c. Blue fills every Ghost
   accent slot that drives UI chrome (pulse dots, pills, CTAs, links,
   active nav); green + orange ride the nav cycle, platform-icon set,
   and bar gradients. Light + dark both handled — blue reads on either
   surface, and a [data-theme="light"] re-pin (higher specificity than
   the generic light block) keeps the accents on-brand in light mode,
   the same guard SKY uses. (Brian 2026-07-17.)
   ============================================================ */
body[data-portal-code="SMO"] {
  --smo-blue:   #1b99d6;
  --smo-green:  #6dbe42;
  --smo-orange: #dc721c;

  --accent-cyan:    var(--smo-blue);
  --accent-cyan-2:  var(--smo-green);
  --accent-lime:    var(--smo-green);
  --accent-green:   var(--smo-green);
  --accent-orange:  var(--smo-orange);
  /* Day-of-Week chart weekday/weekend bars use purple; keep SMO on blue. */
  --accent-purple:  var(--smo-blue);
  --accent-purple-soft: rgba(27,153,214,0.30);
  --accent-pink:    var(--smo-orange);

  --accent-cyan-fill:        rgba(27,153,214,0.16);
  --accent-cyan-fill-hover:  rgba(27,153,214,0.10);
  --accent-cyan-border:      rgb(27,153,214);

  --gradient-bar-from: var(--smo-blue);
  --gradient-bar-to:   var(--smo-green);

  --plat-fb: var(--smo-blue);
  --plat-ig: var(--smo-orange);
  --plat-li: var(--smo-green);
  --plat-yt: var(--smo-orange);

  --tab-active-bg: rgba(27,153,214,0.18);
}
/* Light re-pin: the generic body[data-theme="light"] block (same
   specificity, later in the file) would otherwise reset these to
   Ghost's light accents. This selector is 0,2,1 and wins. */
body[data-portal-code="SMO"][data-theme="light"] {
  --accent-cyan:        var(--smo-blue);
  --accent-cyan-2:      var(--smo-green);
  --accent-lime:        var(--smo-green);
  --accent-green:       var(--smo-green);
  --accent-orange:      var(--smo-orange);
  --accent-cyan-text:   var(--smo-blue);
  --accent-lime-text:   var(--smo-green);
  --accent-orange-text: var(--smo-orange);
  --state-positive:     var(--smo-green);
}
/* SMO nav-icon active color: blue -> green -> orange in the portal's
   VISIBLE tab order (Projects, Calendar, Social, Traffic, Google Ads,
   Index). Hidden staff tabs are display:none but still consume
   :nth-of-type slots, so the cycle is pinned per data-page instead of
   by raw position. "Projects" is the clientportal tab relabeled
   (core.js) and is the primary -> blue. (Brian 2026-07-18.) */
body[data-portal-code="SMO"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon { background-color: var(--smo-blue);   }
body[data-portal-code="SMO"] .sidebar-nav a.active[data-page="calendar"] .nav-icon     { background-color: var(--smo-green);  }
body[data-portal-code="SMO"] .sidebar-nav a.active[data-page="social"] .nav-icon       { background-color: var(--smo-orange); }
body[data-portal-code="SMO"] .sidebar-nav a.active[data-page="traffic"] .nav-icon      { background-color: var(--smo-blue);   }
body[data-portal-code="SMO"] .sidebar-nav a.active[data-page="googleads"] .nav-icon    { background-color: var(--smo-green);  }
body[data-portal-code="SMO"] .sidebar-nav a.active[data-page="aiindex"] .nav-icon      { background-color: var(--smo-orange); }

body[data-portal-code="SMO"] #sidebar-avatar {
  background: rgba(27,153,214,0.18) !important;
  border: 2px solid rgba(27,153,214,0.35) !important;
  color: var(--smo-blue) !important;
}

/* ============================================================
   HS, INC. PORTAL PALETTE  (hs.myghostagent.com)
   ============================================================
   Brand cycle per Brian: orange → yellow → red → green.
   orange #de4726 is the HS primary — it fills every Ghost accent
   slot that drives UI chrome (pulse dots, status text, pills,
   CTAs). yellow / red / green ride the nav cycle, platform-icon
   set and bar gradients. Light + dark both supported through the
   generic body[data-theme="light"] block; the only light tweak
   needed here is re-pinning the accent-text vars back to HS
   orange (the generic light block darkens them toward Ghost's
   cyan/orange, which would knock HS off-brand).
   ============================================================ */
body[data-portal-code="HS"] {
  --hs-orange: #de4726;
  --hs-yellow: #f4b233;
  --hs-green:  #00b194;
  /* Legacy alias: --hs-red retained as orange so any stale rule still
     paints on-brand instead of regressing. The three-color cycle is
     orange/yellow/green per Brian 2026-05-29. */
  --hs-red:    var(--hs-orange);

  /* HS Traffic by Location heatmap ramp. 4-tier rank-quartile
     buckets, all in the HS-orange family — tier 1 is the darkest
     bucket, tier 4 the lightest. Mirrors the OHFA/CRM convention.
     (Brian 2026-05-29.) */
  --traffic-tier-1: #6B2806;
  --traffic-tier-2: #A14010;
  --traffic-tier-3: #DE4726;
  --traffic-tier-4: #F9C18A;

  /* HS primary (orange) fills every chrome-driving accent slot. */
  --accent-lime:    var(--hs-orange);
  --accent-cyan:    var(--hs-orange);
  --accent-cyan-2:  var(--hs-orange);
  --accent-orange:  var(--hs-yellow);
  --accent-purple:  var(--hs-green);
  --accent-purple-soft: rgba(0,177,148,0.40);
  --accent-pink:    var(--hs-yellow);

  /* Accent-text follows the primary so light-mode contrast logic
     resolves through a single hue. */
  --accent-lime-text:   var(--hs-orange);
  --accent-cyan-text:   var(--hs-orange);
  --accent-orange-text: var(--hs-orange);

  --gradient-bar-from: var(--hs-orange);
  --gradient-bar-to:   var(--hs-yellow);

  /* Platform icon cycle: FB orange, IG yellow, LI green, YT orange. */
  --plat-fb: var(--hs-orange);
  --plat-ig: var(--hs-yellow);
  --plat-li: var(--hs-green);
  --plat-yt: var(--hs-orange);

  /* Active sub-tab tint + the cyan-fill family hang off HS orange
     so pills / CTAs / Cards-List toggles read on-brand. */
  --tab-active-bg:           rgba(222,71,38,0.15);
  --accent-cyan-fill:        rgba(222,71,38,0.15);
  --accent-cyan-fill-hover:  rgba(222,71,38,0.10);
  --accent-cyan-border:      rgb(222,71,38);
}

/* HS nav-icon + per-page accent cycle — Brian 2026-05-29:
     Portal   = orange
     Social   = yellow
     Calendar = green
     Traffic  = orange
     Guide    = yellow */
/* Hover AND active both paint each tab's color (Brian 2026-05-29). */
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon,
body[data-portal-code="HS"] .sidebar-nav a[data-page="clientportal"]:hover .nav-icon,
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="clientprojects"] .nav-icon,
body[data-portal-code="HS"] .sidebar-nav a[data-page="clientprojects"]:hover .nav-icon { background-color: var(--hs-orange); } /* Projects */
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="social"] .nav-icon,
body[data-portal-code="HS"] .sidebar-nav a[data-page="social"]:hover .nav-icon         { background-color: var(--hs-yellow); } /* Social */
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="calendar"] .nav-icon,
body[data-portal-code="HS"] .sidebar-nav a[data-page="calendar"]:hover .nav-icon       { background-color: var(--hs-green);  } /* Calendar */
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="traffic"] .nav-icon,
body[data-portal-code="HS"] .sidebar-nav a[data-page="traffic"]:hover .nav-icon        { background-color: var(--hs-orange); } /* Traffic */
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="guide"] .nav-icon,
body[data-portal-code="HS"] .sidebar-nav a[data-page="guide"]:hover .nav-icon          { background-color: var(--hs-yellow); } /* Guide */
/* Hidden tabs (dashboard / googleads / settings) — kept here for future. */
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="dashboard"] .nav-icon     { background-color: var(--hs-orange); }
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="googleads"] .nav-icon     { background-color: var(--hs-yellow); }
body[data-portal-code="HS"] .sidebar-nav a.active[data-page="settings"] .nav-icon      { background-color: var(--hs-orange); }

/* Per-page chrome accent. Each page's header icon, sub-tabs, pills, and
   accent borders read from a DIFFERENT --accent-* var depending on the
   page (clientportal uses --accent-cyan, social uses --accent-lime,
   calendar + traffic use --accent-purple, guide uses --accent-cyan).
   Override the var that THAT page's chrome actually reads, scoped to
   the active page so the swap doesn't leak. (Brian 2026-05-29.)

   Projects = orange. clientportal.html paints chrome via --accent-cyan,
   which on HS already resolves to --hs-orange via the portal block. No
   per-page override needed; left explicit for clarity. */
body[data-portal-code="HS"][data-active-page="clientportal"] {
  --accent-cyan: var(--hs-orange); --accent-cyan-2: var(--hs-orange);
  --accent-cyan-fill: rgba(222,71,38,0.16);
  --accent-cyan-fill-hover: rgba(222,71,38,0.10);
  --accent-cyan-border: rgb(222,71,38);
}
/* Social = yellow. social.html paints chrome via --accent-lime. */
body[data-portal-code="HS"][data-active-page="social"] {
  --accent-lime: var(--hs-yellow);
  --accent-lime-text: var(--hs-yellow);
  --accent-cyan: var(--hs-yellow); --accent-cyan-2: var(--hs-yellow);
  --accent-cyan-fill: rgba(244,178,51,0.16);
  --accent-cyan-fill-hover: rgba(244,178,51,0.10);
  --accent-cyan-border: rgb(244,178,51);
}
/* Calendar = green. calendar.html paints chrome via --accent-purple,
   which on HS already resolves to --hs-green. No swap needed; explicit
   for clarity. */
body[data-portal-code="HS"][data-active-page="calendar"] {
  --accent-purple: var(--hs-green);
  --accent-cyan: var(--hs-green); --accent-cyan-2: var(--hs-green);
  --accent-cyan-fill: rgba(0,177,148,0.16);
  --accent-cyan-fill-hover: rgba(0,177,148,0.10);
  --accent-cyan-border: rgb(0,177,148);
}
/* Traffic = orange chrome + orange/yellow/green chart palette
   (Brian 2026-05-29). traffic.html paints chrome via --accent-purple
   (header icon, sub-tabs, take tile border, range pills, geo toggle).
   Sessions by Device doughnut uses --accent-purple (desktop, primary),
   --accent-lime (mobile, secondary), --accent-orange (tablet, tertiary).
   Flip all three on this page so:
     desktop = orange
     mobile  = yellow
     tablet  = green
   --accent-lime also drives the line-chart color on this page; on HS
   Traffic that becomes yellow, which trades primary-orange-line for
   the cleaner Sessions-by-Device tier rotation Brian asked for. */
body[data-portal-code="HS"][data-active-page="traffic"] {
  --accent-purple: var(--hs-orange);
  --accent-lime:   var(--hs-yellow);
  --accent-lime-text: var(--hs-yellow);
  --accent-orange: var(--hs-green);
  --accent-cyan: var(--hs-orange); --accent-cyan-2: var(--hs-orange);
  --accent-cyan-fill: rgba(222,71,38,0.16);
  --accent-cyan-fill-hover: rgba(222,71,38,0.10);
  --accent-cyan-border: rgb(222,71,38);
}
/* Guide = yellow. guide.html paints chrome via --accent-cyan. */
body[data-portal-code="HS"][data-active-page="guide"] {
  --accent-cyan: var(--hs-yellow); --accent-cyan-2: var(--hs-yellow);
  --accent-cyan-text: var(--hs-yellow);
  --accent-cyan-fill: rgba(244,178,51,0.16);
  --accent-cyan-fill-hover: rgba(244,178,51,0.10);
  --accent-cyan-border: rgb(244,178,51);
}

/* HS logo straightening. The HS, INC. brand mark has a small clockwise
   tilt baked into the source SVG artwork (the red horizontal bars
   slope down to the left). Counter-rotate the sidebar-logo element so
   the bars read level in the rail. (Brian 2026-05-29.) */
body[data-portal-code="HS"] .sidebar-logo {
  transform: rotate(5deg);
  transform-origin: center center;
}
body[data-portal-code="HS"] #sidebar-avatar {
  background: rgba(222,71,38,0.18) !important;
  border: 2px solid rgba(222,71,38,0.35) !important;
  color: var(--hs-orange) !important;
}

/* Light mode: re-pin accent-text to HS orange. The generic
   body[data-theme="light"] block darkens these toward Ghost's
   cyan/orange; HS orange is already dark enough to read on white,
   so it stays the brand color in both themes. */
body[data-portal-code="HS"][data-theme="light"] {
  --accent-lime-text:   var(--hs-orange);
  --accent-cyan-text:   var(--hs-orange);
  --accent-orange-text: var(--hs-orange);

  /* Light-mode heatmap tier ramp. Lifted to peach/cream so the brightest
     buckets pop above the white-rail bg without going neon. */
  --traffic-tier-4: #F2A86A;
  --traffic-tier-3: #F6C29A;
  --traffic-tier-2: #FBDDC4;
  --traffic-tier-1: #FFF1E5;
}

/* Patrons Bank portal. Brand is a single periwinkle blue (#788cff). The
   Projects + Guide tabs are the only nav visible to the client, so we
   only need to paint those two active states. Slot mapping replaces
   Ghost's --accent-cyan everywhere so the page-label ★, active sub-tabs,
   pills, and badges all carry the brand color without per-element rules. */
body[data-portal-code="PATRONS"] {
  --pat-blue: #788cff;

  --accent-lime:    var(--pat-blue);
  --accent-cyan:    var(--pat-blue);
  --accent-cyan-2:  var(--pat-blue);
  --accent-orange:  var(--pat-blue);
  --accent-purple:  var(--pat-blue);
  --accent-pink:    var(--pat-blue);
  --accent-green:   var(--pat-blue);

  /* Even success/state-positive (the FINAL pill, approved badges) paints
     pat-blue on Patrons: Brian's rule is "no green, no any-other-accent,
     #788cff is the ONLY accent." Semantic meaning ("this is final / good")
     stays — the pill is still styled distinctly via .final shape + label,
     just in brand color. (Brian 2026-05-27.) */
  --state-positive:        var(--pat-blue);
  --state-positive-fill:   rgba(120,140,255,0.15);
  --state-positive-border: rgba(120,140,255,0.30);

  /* rgba(120,140,255, …) ≡ #788cff. Drives every fill/border that hangs
     off --accent-cyan (sub-tab tints, FINAL pill, modal CTA). */
  --accent-cyan-fill:        rgba(120,140,255,0.15);
  --accent-cyan-fill-hover:  rgba(120,140,255,0.10);
  --accent-cyan-border:      rgb(120,140,255);

  --tab-active-bg: rgba(120,140,255,0.15);
}
body[data-portal-code="PATRONS"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon,
body[data-portal-code="PATRONS"] .sidebar-nav a.active[data-page="clientprojects"] .nav-icon,
body[data-portal-code="PATRONS"] .sidebar-nav a.active[data-page="guide"] .nav-icon {
  background-color: var(--pat-blue);
}
/* The generic body[data-theme="light"] block (around line ~2293)
   re-assigns --accent-cyan to Ghost's light-mode #0096FF and ties on
   specificity (both 0,1,0) with the Patrons block above. Source order
   then puts the light block AFTER, so on patrons.myghostagent.com in
   light mode the ★ icon, active tab underline, PAT###, PHOTOGRAPHY
   label, etc. were all painting Ghost-cyan instead of pat-blue. This
   block runs at 0,2,0 (two attribute selectors) so it beats the light
   block regardless of source order, re-asserting every accent slot. */
body[data-portal-code="PATRONS"][data-theme="light"] {
  --accent-lime:    var(--pat-blue);
  --accent-cyan:    var(--pat-blue);
  --accent-cyan-2:  var(--pat-blue);
  --accent-orange:  var(--pat-blue);
  --accent-purple:  var(--pat-blue);
  --accent-pink:    var(--pat-blue);
  --accent-green:   var(--pat-blue);
  --state-positive:        var(--pat-blue);
  --state-positive-fill:   rgba(120,140,255,0.15);
  --state-positive-border: rgba(120,140,255,0.30);
  --accent-cyan-fill:        rgba(120,140,255,0.15);
  --accent-cyan-fill-hover:  rgba(120,140,255,0.10);
  --accent-cyan-border:      rgb(120,140,255);
  --tab-active-bg: rgba(120,140,255,0.15);
}

/* City Rescue Mission portal. Four-color brand cycle: red, orange,
   yellow-orange, yellow (left-to-right in the brand swatch). Red is
   primary chrome. Only Projects tab is visible to the client; the
   cycle is wired anyway so any future tab paints on-brand. Mirrors
   the HS pattern (primary fills all chrome slots, per-page accent
   rides the cycle). */

/* ============================================================
   GLD / HEIRLOOM PROPERTIES PORTAL  (gld.myghostagent.com)
   ============================================================
   Light mode by default (see index.html head DEFAULT_LIGHT).
   Accent cycle, IN ORDER (Brian 2026-06-01):
     Hydrangea -> Pistachio -> Periwinkle -> Rose Quartz.
   Hydrangea is primary chrome. Navy / Linen / Rosemary / Stone
   are the neutral support set, NOT part of the accent cycle.
   The four hues are pastel, so on the light Linen ground the
   text / underline slots ride deepened "-dk" variants (mixed
   toward Navy) for legibility; fills + nav icons keep the true
   hue. Per-page accent swap mirrors OHFA/CRM: each page paints
   in the same color as its sidebar tab. The six GLD tabs cycle
   the four colors in Brian's order:
     Projects=Hydrangea, Calendar=Pistachio, Insights=Periwinkle,
     Social=Rose Quartz, Traffic=Hydrangea, Index=Pistachio.
   ============================================================ */
/* ============================================================
   OCAC / Oklahoma Contemporary  (ocac.myghostagent.com)
   Brand = pure CMY, cycle order YELLOW -> MAGENTA -> CYAN.
   YELLOW is primary: the single Traffic tab + all chrome.
   Data/charts cycle Yellow -> Magenta -> Cyan. Dark-default.
   Logo/icon: WHITE on dark, BLACK on light. (Brian 2026-06-05.)
   ============================================================ */
body[data-portal-code="OCAC"] {
  --ocac-yellow:  #FFFF00;  /* primary - tab + chrome + data series 1 */
  --ocac-magenta: #FF00FF;  /* data series 2 */
  --ocac-cyan:    #00FFFF;  /* data series 3 */

  /* Chrome/tab primary = Yellow. Traffic-page chrome rides --accent-purple;
     the sidebar tab + global chrome ride --accent-cyan. Data series ride
     --accent-lime (2) + --accent-orange (3). */
  --accent-purple:  var(--ocac-yellow);
  --accent-cyan:    var(--ocac-yellow);
  --accent-cyan-2:  var(--ocac-yellow);
  --accent-lime:    var(--ocac-magenta);
  --accent-orange:  var(--ocac-cyan);
  --accent-pink:    var(--ocac-magenta);
  --accent-green:   var(--ocac-cyan);

  --accent-cyan-fill:        rgba(255,255,0,0.16);
  --accent-cyan-fill-hover:  rgba(255,255,0,0.10);
  --accent-cyan-border:      rgb(255,255,0);
  --tab-active-bg: rgba(255,255,0,0.15);

  /* Bar gradient + social cycle: Yellow -> Magenta -> Cyan. */
  --gradient-bar-from: var(--ocac-yellow);
  --gradient-bar-to:   var(--ocac-magenta);
  --plat-fb: var(--ocac-yellow);
  --plat-ig: var(--ocac-magenta);
  --plat-li: var(--ocac-cyan);
  --plat-yt: var(--ocac-yellow);

  /* Traffic-by-Location heatmap, single magenta ramp, dark (low) -> bright (high). */
  --traffic-tier-1: #520052;  /* darkest magenta (low) */
  --traffic-tier-2: #8A008A;  /* deep magenta */
  --traffic-tier-3: #C400C4;  /* mid magenta */
  --traffic-tier-4: #FF00FF;  /* full magenta (high) */
}
/* OCAC nav-icon: only Traffic is visible; active + hover ride yellow. */
body[data-portal-code="OCAC"] .sidebar-nav a.active[data-page="traffic"] .nav-icon,
body[data-portal-code="OCAC"] .sidebar-nav a:hover[data-page="traffic"] .nav-icon { background-color: var(--ocac-yellow); }
/* User avatar + chat bubble tint = yellow (primary). */
body[data-portal-code="OCAC"] #sidebar-avatar {
  background: rgba(255,255,0,0.18) !important;
  border: 2px solid rgba(255,255,0,0.34) !important;
  color: var(--ocac-yellow) !important;
}
body[data-portal-code="OCAC"] #ethan-bubble-btn {
  background: radial-gradient(circle at 30% 30%, #FFFF00 0%, #B8A800 100%) !important;
  box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 0 3px rgba(255,255,0,0.18) !important;
}
/* OCAC Agent's Take tiles ride MAGENTA (data series 2), not the yellow
   primary: 2px solid magenta border at full opacity, and every accent
   inside (dot, label, highlighted numbers, bullets, The One Move, caret)
   turns magenta by re-scoping --accent-purple within the tile. The take
   body's highlightNumbers() and headers all bind var(--accent-purple).
   Brian 2026-06-05. */
body[data-portal-code="OCAC"] .take-tile {
  --accent-purple: var(--ocac-magenta);
  border-width: 2px !important;
  border-color: var(--ocac-magenta) !important;
  border-style: solid !important;
}
/* OCAC chart + map hover popovers: 2px solid magenta border at FULL
   opacity. The inline tooltip styles use 1px solid color-mix(...35%,
   transparent), so override width/style/color here. Brian 2026-06-05. */
body[data-portal-code="OCAC"] #t-hover-tip,
body[data-portal-code="OCAC"] #t-vpu-tip,
body[data-portal-code="OCAC"] #t-dow-tip,
body[data-portal-code="OCAC"] #t-eng-tip,
body[data-portal-code="OCAC"] .loc-hover-card {
  /* Re-scope the accent vars inside the popover so the colored text (dot
     + metric value) goes magenta to match the border. The vpu tooltip and
     map card bind var(--accent-purple) (yellow on OCAC); others bind
     var(--accent-lime) (already magenta). Pin both. */
  --accent-purple: var(--ocac-magenta);
  --accent-lime: var(--ocac-magenta);
  border-width: 2px !important;
  border-style: solid !important;
  border-color: var(--ocac-magenta) !important;
}

body[data-portal-code="GLD"] {
  --gld-hydrangea:  #758CC0;
  --gld-pistachio:  #A7C186;
  --gld-periwinkle: #BECAE9;
  --gld-rose:       #E9C5C7;
  /* Neutrals (support, not cycled). */
  --gld-navy:       #252A36;
  --gld-rosemary:   #5E7461;
  --gld-stone:      #C8C3B3;
  --gld-linen:      #FBF9F4;

  /* Deepened text/underline variants for legibility on light Linen. */
  --gld-hydrangea-dk:  color-mix(in srgb, var(--gld-hydrangea) 80%, var(--gld-navy));
  --gld-pistachio-dk:  color-mix(in srgb, var(--gld-pistachio) 58%, var(--gld-navy));
  --gld-periwinkle-dk: color-mix(in srgb, var(--gld-periwinkle) 46%, var(--gld-navy));
  --gld-rose-dk:       color-mix(in srgb, var(--gld-rose) 48%, var(--gld-navy));

  /* Primary (Hydrangea) fills the chrome-driving accent slots. */
  --accent-lime:    var(--gld-hydrangea);
  --accent-cyan:    var(--gld-hydrangea);
  --accent-cyan-2:  var(--gld-hydrangea);
  --accent-orange:  var(--gld-pistachio);
  --accent-purple:  var(--gld-periwinkle);
  --accent-purple-soft: rgba(190,202,233,0.45);
  --accent-pink:    var(--gld-rose);
  --accent-green:   var(--gld-pistachio);

  /* Text slots ride the deepened hue so labels stay readable on light. */
  --accent-lime-text:   var(--gld-hydrangea-dk);
  --accent-cyan-text:   var(--gld-hydrangea-dk);
  --accent-orange-text: var(--gld-pistachio-dk);

  --accent-cyan-fill:        rgba(117,140,192,0.16);
  --accent-cyan-fill-hover:  rgba(117,140,192,0.10);
  --accent-cyan-border:      rgb(117,140,192);
  --tab-active-bg: rgba(117,140,192,0.15);

  /* Bar gradient + data cycle: Hydrangea -> Pistachio. */
  --gradient-bar-from: var(--gld-hydrangea);
  --gradient-bar-to:   var(--gld-pistachio);

  --plat-fb: var(--gld-hydrangea);
  --plat-ig: var(--gld-pistachio);
  --plat-li: var(--gld-periwinkle);
  --plat-yt: var(--gld-rose);

  /* Traffic by Location 4-tier ramp, Hydrangea family (dark -> light). */
  --traffic-tier-1: #2E3A57;
  --traffic-tier-2: #4F659A;
  --traffic-tier-3: #758CC0;
  --traffic-tier-4: #BECAE9;
}

/* GLD nav-icon cycle (active + hover), Brian's order across the six
   visible tabs. Other tabs fall back to the generic remap above. */
body[data-portal-code="GLD"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon,
body[data-portal-code="GLD"] .sidebar-nav a:hover[data-page="clientportal"] .nav-icon { background-color: var(--gld-hydrangea);  } /* Projects */
body[data-portal-code="GLD"] .sidebar-nav a.active[data-page="calendar"] .nav-icon,
body[data-portal-code="GLD"] .sidebar-nav a:hover[data-page="calendar"] .nav-icon     { background-color: var(--gld-pistachio);  } /* Calendar */
body[data-portal-code="GLD"] .sidebar-nav a.active[data-page="insights"] .nav-icon,
body[data-portal-code="GLD"] .sidebar-nav a:hover[data-page="insights"] .nav-icon     { background-color: var(--gld-periwinkle); } /* Insights */
body[data-portal-code="GLD"] .sidebar-nav a.active[data-page="social"] .nav-icon,
body[data-portal-code="GLD"] .sidebar-nav a:hover[data-page="social"] .nav-icon       { background-color: var(--gld-rose);       } /* Social */
body[data-portal-code="GLD"] .sidebar-nav a.active[data-page="traffic"] .nav-icon,
body[data-portal-code="GLD"] .sidebar-nav a:hover[data-page="traffic"] .nav-icon      { background-color: var(--gld-hydrangea);  } /* Traffic */
body[data-portal-code="GLD"] .sidebar-nav a.active[data-page="aiindex"] .nav-icon,
body[data-portal-code="GLD"] .sidebar-nav a:hover[data-page="aiindex"] .nav-icon      { background-color: var(--gld-pistachio);  } /* Index */

/* Light-mode chrome reassert. (0,2,0) beats the generic light block
   (~line 2680, 0,1,0) so global/default chrome holds Hydrangea in
   GLD's default light mode. Placed BEFORE the per-page swaps so a
   page's own color (also 0,2,0, later in source) wins when active. */
body[data-portal-code="GLD"][data-theme="light"] {
  /* Light-mode richness (Brian 2026-06-01): the soft pastels wash out on the
     Linen ground, so light mode uses deeper, more-saturated, in-family
     versions for real visibility. Overriding the hue vars here flows to the
     nav icons AND every var-based per-page accent automatically; dark mode
     keeps the original soft pastels. */
  --gld-hydrangea:  #4E6AB8;   /* richer Hydrangea */
  --gld-pistachio:  #6F9E45;   /* richer Pistachio */
  --gld-periwinkle: #7E92D4;   /* richer Periwinkle */
  --gld-rose:       #CC757D;   /* richer Rose Quartz */
  --gld-hydrangea-dk:  #3E579C;
  --gld-pistachio-dk:  #557F33;
  --gld-periwinkle-dk: #5468AE;
  --gld-rose-dk:       #A85159;

  --accent-lime:    var(--gld-hydrangea);
  --accent-cyan:    var(--gld-hydrangea);
  --accent-cyan-2:  var(--gld-hydrangea);
  --accent-orange:  var(--gld-pistachio);
  --accent-purple:  var(--gld-periwinkle);
  --accent-pink:    var(--gld-rose);
  --accent-green:   var(--gld-pistachio);
  --accent-lime-text:   var(--gld-hydrangea-dk);
  --accent-cyan-text:   var(--gld-hydrangea-dk);
  --accent-orange-text: var(--gld-pistachio-dk);
  --accent-cyan-fill:        color-mix(in srgb, var(--gld-hydrangea) 16%, transparent);
  --accent-cyan-fill-hover:  color-mix(in srgb, var(--gld-hydrangea) 10%, transparent);
  --accent-cyan-border:      var(--gld-hydrangea);
  --tab-active-bg: color-mix(in srgb, var(--gld-hydrangea) 15%, transparent);
}

/* GLD per-page accent swap. Each page paints in its sidebar tab's hue.
   router.js stamps data-active-page on body. All values are var-based
   (color-mix off the hue var), so they follow the light-mode rich hue
   override automatically; the -dk variant drives text/underline. */
body[data-portal-code="GLD"][data-active-page="clientportal"] {
  --page-accent: var(--gld-hydrangea-dk);
  --accent-cyan: var(--gld-hydrangea); --accent-cyan-2: var(--gld-hydrangea);
  --accent-cyan-text: var(--gld-hydrangea-dk);
  --accent-cyan-fill: color-mix(in srgb, var(--gld-hydrangea) 16%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--gld-hydrangea) 10%, transparent);
  --accent-cyan-border: var(--gld-hydrangea);
  --tab-active-bg: color-mix(in srgb, var(--gld-hydrangea) 15%, transparent);
}
body[data-portal-code="GLD"][data-active-page="calendar"] {
  --page-accent: var(--gld-pistachio-dk);
  --accent-cyan: var(--gld-pistachio); --accent-cyan-2: var(--gld-pistachio);
  --accent-purple: var(--gld-pistachio);
  --accent-cyan-text: var(--gld-pistachio-dk);
  --accent-cyan-fill: color-mix(in srgb, var(--gld-pistachio) 18%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--gld-pistachio) 12%, transparent);
  --accent-cyan-border: var(--gld-pistachio);
  --tab-active-bg: color-mix(in srgb, var(--gld-pistachio) 18%, transparent);
}
body[data-portal-code="GLD"][data-active-page="insights"] {
  --page-accent: var(--gld-periwinkle-dk);
  --accent-cyan: var(--gld-periwinkle); --accent-cyan-2: var(--gld-periwinkle);
  --accent-cyan-text: var(--gld-periwinkle-dk);
  --accent-cyan-fill: color-mix(in srgb, var(--gld-periwinkle) 22%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--gld-periwinkle) 14%, transparent);
  --accent-cyan-border: var(--gld-periwinkle-dk);
  --tab-active-bg: color-mix(in srgb, var(--gld-periwinkle) 22%, transparent);
}
body[data-portal-code="GLD"][data-active-page="social"] {
  --page-accent: var(--gld-rose-dk);
  --accent-cyan: var(--gld-rose); --accent-cyan-2: var(--gld-rose);
  --accent-lime: var(--gld-rose);
  --accent-cyan-text: var(--gld-rose-dk);
  --accent-cyan-fill: color-mix(in srgb, var(--gld-rose) 24%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--gld-rose) 16%, transparent);
  --accent-cyan-border: var(--gld-rose-dk);
  --tab-active-bg: color-mix(in srgb, var(--gld-rose) 24%, transparent);
}
body[data-portal-code="GLD"][data-active-page="traffic"] {
  --page-accent: var(--gld-hydrangea-dk);
  --accent-cyan: var(--gld-hydrangea); --accent-cyan-2: var(--gld-hydrangea);
  --accent-cyan-text: var(--gld-hydrangea-dk);
  --accent-cyan-fill: color-mix(in srgb, var(--gld-hydrangea) 16%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--gld-hydrangea) 10%, transparent);
  --accent-cyan-border: var(--gld-hydrangea);
  --tab-active-bg: color-mix(in srgb, var(--gld-hydrangea) 15%, transparent);
  /* Traffic vis cycle: primary Hydrangea, secondary Pistachio, tertiary Periwinkle. */
  --accent-lime:   var(--gld-hydrangea);
  --accent-purple: var(--gld-pistachio);
  --accent-orange: var(--gld-periwinkle);
  --gradient-bar-from: var(--gld-hydrangea);
  --gradient-bar-to:   var(--gld-pistachio);
}
body[data-portal-code="GLD"][data-active-page="aiindex"] {
  --page-accent: var(--gld-pistachio-dk);
  --accent-cyan: var(--gld-pistachio); --accent-cyan-2: var(--gld-pistachio);
  --accent-cyan-text: var(--gld-pistachio-dk);
  --accent-cyan-fill: color-mix(in srgb, var(--gld-pistachio) 18%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--gld-pistachio) 12%, transparent);
  --accent-cyan-border: var(--gld-pistachio);
  --tab-active-bg: color-mix(in srgb, var(--gld-pistachio) 18%, transparent);
}

body[data-portal-code="GLD"] #sidebar-avatar {
  background: rgba(117,140,192,0.18) !important;
  border: 2px solid rgba(117,140,192,0.35) !important;
  color: var(--gld-hydrangea) !important;
}
body[data-portal-code="GLD"] #ethan-bubble-btn {
  background: radial-gradient(circle at 30% 30%, #758CC0 0%, #44588C 100%) !important;
  box-shadow: 0 10px 30px rgba(0,0,0,0.25), 0 0 0 3px rgba(117,140,192,0.18) !important;
}

/* ============================================================
   BEDA - Bethany Economic Development Authority (beda.myghostagent.com)
   ============================================================
   Brand: blue #055A9B + green #39953F. Accent cycle blue/green,
   repeating, across the five tabs (Brian 2026-06-02):
   Projects=blue, Calendar=green, Social=blue, Traffic=green, Index=blue.
   Both hues are dark enough to read on light or dark, so no -dk text
   variants needed.
   TERTIARY: harvest gold #D29F22 (Brian 2026-06-05) so charts with 3+
   categories (device doughnut, etc.) don't collapse blue/green/green.
   Rides the --accent-orange slot (3rd in the device cycle = Tablet).
   ============================================================ */
body[data-portal-code="BEDA"] {
  --beda-blue:  #055A9B;
  --beda-green: #39953F;
  --beda-gold:  #D29F22;
  --nvr-new:    var(--beda-blue);  /* New-vs-Returning "New" slice = BEDA blue, not green (Brian 2026-08-07) */

  --accent-lime:    var(--beda-blue);
  --accent-cyan:    var(--beda-blue);
  --accent-cyan-2:  var(--beda-blue);
  --accent-orange:  var(--beda-gold);
  --accent-purple:  var(--beda-green);
  --accent-purple-soft: rgba(57,149,63,0.35);
  --accent-pink:    var(--beda-blue);
  --accent-green:   var(--beda-green);
  --accent-lime-text:   var(--beda-blue);
  --accent-cyan-text:   var(--beda-blue);
  --accent-orange-text: var(--beda-gold);
  --accent-cyan-fill:        color-mix(in srgb, var(--beda-blue) 16%, transparent);
  --accent-cyan-fill-hover:  color-mix(in srgb, var(--beda-blue) 10%, transparent);
  --accent-cyan-border:      var(--beda-blue);
  --tab-active-bg: color-mix(in srgb, var(--beda-blue) 15%, transparent);

  --gradient-bar-from: var(--beda-blue);
  --gradient-bar-to:   var(--beda-green);

  --plat-fb: var(--beda-blue);
  --plat-ig: var(--beda-green);
  --plat-li: var(--beda-blue);
  --plat-yt: var(--beda-green);

  /* Traffic by Location ramp, blue family (dark -> light). */
  --traffic-tier-1: #06314F;
  --traffic-tier-2: #055A9B;
  --traffic-tier-3: #2F7FB8;
  --traffic-tier-4: #7FB2D6;
}

/* Light-mode chrome reassert (0,2,0 beats the generic light block). */
body[data-portal-code="BEDA"][data-theme="light"] {
  --accent-lime:    var(--beda-blue);
  --accent-cyan:    var(--beda-blue);
  --accent-cyan-2:  var(--beda-blue);
  --accent-orange:  var(--beda-gold);
  --accent-purple:  var(--beda-green);
  --accent-pink:    var(--beda-blue);
  --accent-green:   var(--beda-green);
  --accent-cyan-text:   var(--beda-blue);
  --accent-lime-text:   var(--beda-blue);
  --accent-orange-text: var(--beda-gold);
  --accent-cyan-border: var(--beda-blue);
  --accent-cyan-fill:        color-mix(in srgb, var(--beda-blue) 16%, transparent);
  --accent-cyan-fill-hover:  color-mix(in srgb, var(--beda-blue) 10%, transparent);
  --tab-active-bg: color-mix(in srgb, var(--beda-blue) 15%, transparent);
}

/* Nav-icon cycle (active + hover): blue / green / blue / green / blue. */
body[data-portal-code="BEDA"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon,
body[data-portal-code="BEDA"] .sidebar-nav a:hover[data-page="clientportal"] .nav-icon { background-color: var(--beda-blue);  } /* Projects */
body[data-portal-code="BEDA"] .sidebar-nav a.active[data-page="calendar"] .nav-icon,
body[data-portal-code="BEDA"] .sidebar-nav a:hover[data-page="calendar"] .nav-icon     { background-color: var(--beda-green); } /* Calendar */
body[data-portal-code="BEDA"] .sidebar-nav a.active[data-page="social"] .nav-icon,
body[data-portal-code="BEDA"] .sidebar-nav a:hover[data-page="social"] .nav-icon       { background-color: var(--beda-blue);  } /* Social */
body[data-portal-code="BEDA"] .sidebar-nav a.active[data-page="traffic"] .nav-icon,
body[data-portal-code="BEDA"] .sidebar-nav a:hover[data-page="traffic"] .nav-icon      { background-color: var(--beda-green); } /* Traffic */
body[data-portal-code="BEDA"] .sidebar-nav a.active[data-page="aiindex"] .nav-icon,
body[data-portal-code="BEDA"] .sidebar-nav a:hover[data-page="aiindex"] .nav-icon      { background-color: var(--beda-blue);  } /* Index */

/* Per-page accent swap, same blue/green cycle. */
body[data-portal-code="BEDA"][data-active-page="clientportal"],
body[data-portal-code="BEDA"][data-active-page="social"],
body[data-portal-code="BEDA"][data-active-page="aiindex"] {
  --page-accent: var(--beda-blue);
  --accent-cyan: var(--beda-blue); --accent-cyan-2: var(--beda-blue);
  --accent-cyan-text: var(--beda-blue);
  --accent-cyan-border: var(--beda-blue);
  --accent-cyan-fill: color-mix(in srgb, var(--beda-blue) 16%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--beda-blue) 10%, transparent);
  --tab-active-bg: color-mix(in srgb, var(--beda-blue) 15%, transparent);
}
body[data-portal-code="BEDA"][data-active-page="calendar"],
body[data-portal-code="BEDA"][data-active-page="traffic"] {
  --page-accent: var(--beda-green);
  --accent-cyan: var(--beda-green); --accent-cyan-2: var(--beda-green);
  --accent-purple: var(--beda-green);
  --accent-cyan-text: var(--beda-green);
  --accent-cyan-border: var(--beda-green);
  --accent-cyan-fill: color-mix(in srgb, var(--beda-green) 16%, transparent);
  --accent-cyan-fill-hover: color-mix(in srgb, var(--beda-green) 10%, transparent);
  --tab-active-bg: color-mix(in srgb, var(--beda-green) 15%, transparent);
}

body[data-portal-code="BEDA"] #sidebar-avatar {
  background: rgba(5,90,155,0.18) !important;
  border: 2px solid rgba(5,90,155,0.35) !important;
  color: var(--beda-blue) !important;
}

body[data-portal-code="CRM"] {
  --crm-red:           #ee2e24;
  --crm-orange:        #ee6327;
  --crm-yellow-orange: #faa629;
  --crm-yellow:        #f9d914;

  /* Chrome slots (cyan family). CRM brand cycle is orange (primary) /
     yellow (secondary) / red (tertiary). Default chrome reads the Projects
     page since that's the landing tab — paints orange. Per-page overrides
     below (Traffic = yellow) shift chrome as the user navigates. Red is
     reserved as the tertiary, used on Guide. (Brian 2026-05-29.) */
  --accent-cyan:    var(--crm-orange);
  --accent-cyan-2:  var(--crm-orange);

  /* Data slots (line chart, donut, bar fills): primary=orange,
     secondary=yellow, tertiary=red per Brian's locked CRM chart palette
     (Brian 2026-05-28). Mapped through accent-lime / accent-purple /
     accent-orange so the existing chart code paints CRM-correct without
     per-page overrides. */
  --accent-lime:    var(--crm-orange);
  --accent-purple:  var(--crm-yellow-orange);
  --accent-orange:  var(--crm-red);
  --accent-pink:    var(--crm-yellow);
  --accent-green:   var(--crm-orange);

  --accent-lime-text:   var(--crm-orange);
  --accent-cyan-text:   var(--crm-red);
  --accent-orange-text: var(--crm-red);

  /* Horizontal bar gradient: orange → yellow (was red → yellow). */
  --gradient-bar-from: var(--crm-orange);
  --gradient-bar-to:   var(--crm-yellow);

  --plat-fb: var(--crm-red);
  --plat-ig: var(--crm-orange);
  --plat-li: var(--crm-yellow-orange);
  --plat-yt: var(--crm-yellow);

  --tab-active-bg:           rgba(238,99,39,0.15);
  --accent-cyan-fill:        rgba(238,99,39,0.15);
  --accent-cyan-fill-hover:  rgba(238,99,39,0.10);
  --accent-cyan-border:      rgb(238,99,39);
}
/* Sidebar nav icon cycle (Brian 2026-05-29):
     Projects (clientportal/clientprojects) = orange (primary)
     Traffic                                = yellow (secondary)
     Guide                                  = red (tertiary)
   Mirrors the page chrome shifts below. */
body[data-portal-code="CRM"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon,
body[data-portal-code="CRM"] .sidebar-nav a.active[data-page="clientprojects"] .nav-icon,
body[data-portal-code="CRM"] .sidebar-nav a[data-page="clientportal"]:hover .nav-icon,
body[data-portal-code="CRM"] .sidebar-nav a[data-page="clientprojects"]:hover .nav-icon {
  background-color: var(--crm-orange);
}
body[data-portal-code="CRM"] .sidebar-nav a.active[data-page="traffic"] .nav-icon,
body[data-portal-code="CRM"] .sidebar-nav a[data-page="traffic"]:hover .nav-icon {
  background-color: var(--crm-yellow);
}
body[data-portal-code="CRM"] .sidebar-nav a.active[data-page="guide"] .nav-icon,
body[data-portal-code="CRM"] .sidebar-nav a[data-page="guide"]:hover .nav-icon {
  background-color: var(--crm-red);
}
/* Per-page chrome: when on the Traffic page, shift --accent-cyan and
   its fill/border family to CRM yellow so the page-label ★, active
   sub-tab underline, pills, and CTAs paint yellow. Charts/graphs are
   driven by --accent-lime / --accent-purple / --accent-orange /
   --accent-pink and are deliberately NOT touched here — the locked CRM
   chart palette (orange/yellow/red) stays put. (Brian 2026-05-29.) */
body[data-portal-code="CRM"][data-active-page="traffic"] {
  --accent-cyan: var(--crm-yellow); --accent-cyan-2: var(--crm-yellow);
  --accent-cyan-text: var(--crm-yellow);
  --accent-cyan-fill: rgba(249,217,20,0.16);
  --accent-cyan-fill-hover: rgba(249,217,20,0.10);
  --accent-cyan-border: rgb(249,217,20);
  --tab-active-bg: rgba(249,217,20,0.15);
  /* Traffic page chrome (page-label icon, sub-tab underline, range
     pills, geo toggle, Agent's Take border + dot, Traffic Metrics
     badge) is driven through --accent-purple in the page markup. Flip
     it to yellow ONLY on this page. Chart fills paint through the same
     var but resolve to the chart palette through downstream chart-color
     overrides; if a series shifts unexpectedly we'll re-isolate per
     chart. (Brian 2026-05-29.) */
  --accent-purple: var(--crm-yellow);
}
/* Guide page chrome — red tertiary. */
body[data-portal-code="CRM"][data-active-page="guide"] {
  --accent-cyan: var(--crm-red); --accent-cyan-2: var(--crm-red);
  --accent-cyan-text: var(--crm-red);
  --accent-cyan-fill: rgba(238,46,36,0.16);
  --accent-cyan-fill-hover: rgba(238,46,36,0.10);
  --accent-cyan-border: rgb(238,46,36);
  --tab-active-bg: rgba(238,46,36,0.15);
}
body[data-portal-code="CRM"] #sidebar-avatar {
  background: rgba(238,99,39,0.18) !important;
  border: 2px solid rgba(238,99,39,0.35) !important;
  color: var(--crm-orange) !important;
}
/* Light mode override at 0,2,0 beats the generic light block
   (which would re-pin --accent-cyan to Ghost cyan). Re-asserts every
   accent slot so chrome stays CRM red on the white rail. */
body[data-portal-code="CRM"][data-theme="light"] {
  --accent-cyan:    var(--crm-orange);
  --accent-cyan-2:  var(--crm-orange);
  --accent-lime:    var(--crm-orange);
  --accent-purple:  var(--crm-yellow-orange);
  --accent-orange:  var(--crm-red);
  --accent-pink:    var(--crm-yellow);
  --accent-green:   var(--crm-orange);
  --accent-lime-text:   var(--crm-orange);
  --accent-cyan-text:   var(--crm-orange);
  --accent-orange-text: var(--crm-red);
  --accent-cyan-fill:        rgba(238,99,39,0.15);
  --accent-cyan-fill-hover:  rgba(238,99,39,0.10);
  --accent-cyan-border:      rgb(238,99,39);
  --tab-active-bg: rgba(238,99,39,0.15);
  --gradient-bar-from: var(--crm-orange);
  --gradient-bar-to:   var(--crm-yellow);
}
body[data-portal-code="CRM"][data-theme="light"][data-active-page="traffic"] {
  --accent-cyan: var(--crm-yellow); --accent-cyan-2: var(--crm-yellow);
  --accent-cyan-text: var(--crm-yellow);
  --accent-cyan-fill: rgba(249,217,20,0.16);
  --accent-cyan-fill-hover: rgba(249,217,20,0.10);
  --accent-cyan-border: rgb(249,217,20);
  --tab-active-bg: rgba(249,217,20,0.15);
  /* Light mode: keep the chrome paths driven by --accent-purple (popover
     border + metric text, range pills, geo toggle) on CRM orange instead
     of yellow. Yellow on white tanks readability. Dark mode keeps yellow
     since it has the contrast to carry it. (Brian 2026-05-29.) */
  --accent-purple: var(--crm-orange);
}
body[data-portal-code="CRM"][data-theme="light"][data-active-page="guide"] {
  --accent-cyan: var(--crm-red); --accent-cyan-2: var(--crm-red);
  --accent-cyan-text: var(--crm-red);
  --accent-cyan-fill: rgba(238,46,36,0.16);
  --accent-cyan-fill-hover: rgba(238,46,36,0.10);
  --accent-cyan-border: rgb(238,46,36);
  --tab-active-bg: rgba(238,46,36,0.15);
}

/* Spectrum portal: the shipped spectrum-logo.svg is a white wordmark
   built for the dark rail. On the light rail (which is white) the
   letters disappear. brightness(0) flips the SVG to pure black so the
   Spectrum Business wordmark reads against the light sidebar.
   (Brian 2026-05-22.) */
body[data-portal-code="SPECTRUM"][data-theme="light"] .sidebar-logo {
  filter: brightness(0);
}

/* ─────────────────────────────────────────────────────────────────
   OHFA sidebar interior alignment. one 20px inset on every side.
   All sidebar elements left-align to the same column so Light text,
   nav items, avatar, and logo share a single vertical guideline.
   ───────────────────────────────────────────────────────────────── */
body[data-portal-code="OHFA"] .sidebar {
  padding: 20px;
  gap: 4px;
  /* Dark mode: lift the rail off the near-black page bg with a middle
     dark grey, matching the 3-tier scale (page var(--surface-0) / section
     #14171C / surface #1A1E25). Light mode override at line ~1248
     keeps the rail white. */
  background: #14171C;
}
body[data-portal-code="OHFA"] .sidebar-logo {
  margin: 0 0 16px 0;
}
body[data-portal-code="OHFA"] .sidebar-nav a {
  /* Edge-to-edge hover/active fill. The anchor extends to the rail's
     actual left/right edges (negative margins cancel the sidebar's
     30px padding), but content stays inset at 30px via matching
     padding. Square corners — full-bleed, no rounded pill. */
  margin-left: -20px;
  margin-right: -20px;
  padding-left: 20px;
  padding-right: 20px;
  border-radius: 0;
}
/* Light-mode hover/active fills. Default dark rule uses
   rgba(255,255,255,X) which is invisible on a white sidebar. */
body[data-theme="light"] .sidebar-nav a:hover  { background: rgba(0,0,0,0.05); }
/* Active state for OHFA: no background fill. The colored icon (yellow/
   blue/green/red per tab, per the per-tab nav cycle) IS the active
   indicator. Override the base .sidebar-nav a.active rule which sets
   var(--surface-border-strong) bg + var(--text) text. */
body[data-portal-code="OHFA"] .sidebar-nav a.active {
  background: transparent;
}
body[data-theme="light"] .sidebar-nav a.active {
  color: var(--text);
}

/* ─────────────────────────────────────────────────────────────────
   OHFA LIGHT THEME — global class flips
   These classes have hardcoded white/near-white text in their base
   definition because the original design was dark-mode only. Audit
   (2026-05-13) found them surfacing white text on light tiles
   across multiple pages. Flip via class selector with the OHFA
   light selector chain — no !important needed since these match
   the same specificity as the base rule and come later.
   ───────────────────────────────────────────────────────────────── */
body[data-theme="light"] .panel-title { color: var(--text-dim); }
body[data-theme="light"] .card-label  { color: var(--text-dim); }
body[data-theme="light"] .card-value  { color: var(--text); }
body[data-theme="light"] .card-sub    { color: var(--text-dim); }
/* Card itself: pure white in light mode (tile L2). The panel it sits
   inside is --surface-1 (#F4F4F4), so the card lifts off the panel
   with a clean three-tier visual: page #E8E8E8 -> panel #F4F4F4 ->
   card #FFFFFF. */
/* Light-mode soft shadow for tile-shaped surfaces that don't pick up
   the .panel / .card shadow on their own. Same canonical shadow used
   below for .card/.panel so every tile reads with consistent lift on
   the light canvas. (Brian 2026-05-22.) */
body[data-theme="light"] .cal-day,
body[data-theme="light"] .social-queue-card,
body[data-theme="light"] .social-post-card {
  background: var(--surface-card-bg, #FFFFFF);
  border: 1px solid rgba(0,0,0,0.06);
  box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 4px 14px rgba(0,0,0,0.07);
}

body[data-theme="light"] .card,
body[data-theme="light"] .card-recap {
  background: #FFFFFF;
  border-color: rgba(0,0,0,0.10);
  box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
}
/* Guide page section + pillar text (hardcoded var(--text) / var(--text)).
   Eight different .guide-* classes carry var(--text) body copy. All flip
   to var(--text) in light mode. */
body[data-theme="light"] .guide-section-title,
body[data-theme="light"] .guide-overview-heading,
body[data-theme="light"] .guide-overview-body,
body[data-theme="light"] .guide-pillar-text,
body[data-theme="light"] .guide-cap-desc,
body[data-theme="light"] .guide-cap-desc strong,
body[data-theme="light"] .guide-list li,
body[data-theme="light"] .guide-steps li,
body[data-theme="light"] .guide-example-text,
body[data-theme="light"] .guide-tagline,
body[data-theme="light"] .guide-footer strong {
  color: var(--text);
}
body[data-theme="light"] .guide-pillar-label {
  color: var(--text-dim);
}
/* Client portal residue. #cpp-panel-projects ID-scoped rules beat
   the generic OHFA light selectors on specificity, so the Archive/
   List buttons, Sort by label, and pillbar text never flipped.
   Match specificity by re-including the ID. */
body[data-theme="light"] #cpp-panel-projects .cp-sort-label,
body[data-theme="light"] #cpp-panel-projects .cp-view-toggle button,
body[data-theme="light"] #cpp-panel-projects .cp-pillbar button {
  color: var(--text-dim);
}
/* LIGHT MODE (Brian 2026-06-01): active Current/Archive + Cards/List pills and
   the sort-direction caret render SOLID accent with white text/icon, matching
   the standing light-mode solid-fill rule. */
body[data-theme="light"] #cpp-panel-projects .cp-pillbar button.active,
body[data-theme="light"] #cpp-panel-projects .cp-view-toggle button.active {
  background: var(--accent-cyan); color: #fff;
}
body[data-theme="light"] #cpp-panel-projects .cp-sort-dir {
  background: var(--accent-cyan); color: #fff; border-color: var(--accent-cyan);
}
body[data-theme="light"] #cpp-panel-projects .cp-sort-select {
  color: var(--text);
}
/* Anything with hardcoded color:var(--text-muted) (inline or class default).
   var(--text-muted) was the dark-mode text-muted token; on light tiles it
   reads at ~2.4 contrast which is barely legible. Flip to
   var(--text-dim) globally in light mode. */
body[data-theme="light"] [style*="color:#9F9FA0"],
body[data-theme="light"] [style*="color: #9F9FA0"] {
  color: var(--text-dim) !important;
}
/* Bar chart empty tracks consistent across the app. Multiple pages
   use var(--surface-2) (which becomes #FFFFFF in light = invisible)
   or inline var(--surface-border) from the core.js pctBar() helper.
   Single rgba(0,0,0,0.08) baseline so all bar tracks read with the
   same mid-grey weight against white tiles. */
body[data-theme="light"] .traffic-bar-bg,
body[data-theme="light"] .os-bar-wrap,
body[data-theme="light"] .analytics-bar-bg,
body[data-theme="light"] .gads-device-bar-wrap,
body[data-theme="light"] .rpt-util-bar-bg {
  background: rgba(0,0,0,0.08) !important;
}
body[data-theme="light"] [style*="background:rgba(255,255,255,0.06)"],
body[data-theme="light"] [style*="background: rgba(255,255,255,0.06)"] {
  background: rgba(0,0,0,0.08) !important;
}
/* Class-level offenders that bind var(--text-muted) in their base rule. */
body[data-theme="light"] .delta-flat,
body[data-theme="light"] .cp-sort-label {
  color: var(--text-dim);
}
/* Yellow text in OHFA light mode: darken to var(--ohfa-yellow-text)
   (#B58518) so brand-color text is actually readable. Solid yellow
   fills (price badges, button bgs) stay #F2B62E via the unchanged
   --oh-yellow token. */
body[data-portal-code="OHFA"][data-theme="light"] .traffic-view-tab.active,
body[data-portal-code="OHFA"][data-theme="light"] .guide-section-tablabel,
body[data-portal-code="OHFA"][data-theme="light"] .traffic-range-btn[style*="color:var(--accent-lime)"],
body[data-portal-code="OHFA"][data-theme="light"] .traffic-geo-btn[style*="color:var(--accent-lime)"] {
  color: var(--ohfa-yellow-text) !important;
}
body[data-portal-code="OHFA"][data-theme="light"] .traffic-view-tab.active {
  border-bottom-color: var(--ohfa-yellow-text);
}
/* Positive delta + .card-value.green need a darker green in light
   mode — the dark-mode #4ade80 reads pale and washed-out on white
   tiles. Bump to a richer mid-green that holds contrast on #FFFFFF. */
body[data-theme="light"] .delta-up,
body[data-theme="light"] .card-value.green {
  color: #16A34A;
}
/* Same treatment for the negative-delta red. #F87171 is the pale
   dark-mode red; in light it reads ~2.3 contrast on #E8E8E8 (barely
   readable). #DC2626 (Tailwind red-600) is a richer red with ~5.0
   contrast. Covers delta-down, .card-value.red, overdue indicators,
   recap fail, and the cpp-empty inline error red. */
body[data-theme="light"] .delta-down,
body[data-theme="light"] .card-value.red,
body[data-theme="light"] .task-meta .overdue,
body[data-theme="light"] .task-date.overdue,
body[data-theme="light"] .recap-status-fail {
  color: #DC2626;
}
body[data-theme="light"] [style*="color:#f87171"],
body[data-theme="light"] [style*="color: #f87171"],
body[data-theme="light"] [style*="color:#F87171"],
body[data-theme="light"] [style*="color: #F87171"] {
  color: #DC2626 !important;
}
/* Accent-as-text darkens for light-mode contrast. ECTO lime and
   Ghost blue are unreadable as text on white; solid accent fills
   (backgrounds, borders, color-mix) keep the bright accent. */
body[data-theme="light"] [style*="color:var(--accent-lime)"],
body[data-theme="light"] [style*="color: var(--accent-lime)"],
body[data-theme="light"] .pulse-label {
  color: var(--accent-lime-text) !important;
}
body[data-theme="light"] [style*="color:var(--accent-cyan)"],
body[data-theme="light"] [style*="color: var(--accent-cyan)"],
body[data-theme="light"] [style*="color:var(--accent-cyan-2)"],
body[data-theme="light"] [style*="color: var(--accent-cyan-2)"] {
  color: var(--accent-cyan-text) !important;
}
body[data-theme="light"] [style*="color:var(--accent-orange)"],
body[data-theme="light"] [style*="color: var(--accent-orange)"] {
  color: var(--accent-orange-text) !important;
}
/* Agent's Take tiles in light mode (Brian 2026-06-01): keep the accent
   border (the .panel light rule above greys border-color) and keep inner
   links as accent-colored TEXT, not a reversed solid chip. Matches dark
   mode. .panel.take-tile (0,1,2) beats .panel; .panel.take-tile a (0,2,2)
   beats the generic accent-text inversion. */
body[data-theme="light"] .panel.take-tile { border-color: var(--take-accent) !important; }
body[data-theme="light"] .panel.take-tile a {
  background: transparent !important;
  color: var(--take-accent) !important;
}
/* Oklahoma county map: county outlines are stroked white via
   setAttribute('stroke','#FFFFFF') in the JS render path. Invisible
   on light mode. CSS stroke overrides the SVG presentation attribute. */
body[data-theme="light"] svg path[stroke="#FFFFFF"] {
  stroke: rgba(0,0,0,0.30);
}
/* Soft elevation for tile L1 surfaces in light mode. .panel is the
   canonical wrapper; #calendar-grid stands alone but should read as
   the same level of tile. Both get var(--surface-1) fill + a soft
   two-stop shadow so they lift off the darker page bg. */
body[data-theme="light"] .panel {
  background: var(--surface-1) !important;
  border-color: rgba(0,0,0,0.10) !important;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
}
/* #calendar-grid's background IS its gridline color (cells sit on it
   with a 1px gap), so it must NOT get the panel surface fill. */
body[data-theme="light"] #calendar-grid {
  border-color: rgba(0,0,0,0.10) !important;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
}
body[data-portal-code="OHFA"] #sidebar-user {
  /* !important to beat the inline padding:16px 20px on the element.
     Sidebar's own 30px padding is the only horizontal margin we want. */
  padding: 16px 0 !important;
  align-items: flex-start !important;
}
body[data-portal-code="OHFA"] #sidebar-avatar,
body[data-portal-code="OHFA"] #sidebar-user > div {
  align-self: flex-start;
  margin-left: 0;
}
.theme-toggle {
  margin: 0 0 16px 0;
  padding: 8px 0;
  /* Left-align the label + pill with every other interior element.
     Was width:100% + space-between which pushed the pill to the
     far right; now natural width + left flex anchor + small gap. */
  width: auto;
  justify-content: flex-start;
  gap: 10px;
}
/* OHFA sidebar already has 20px padding on every side, so footer
   wordmark + copy must drop their own 20px horizontal margin so they
   line up with the nav, avatar, and toggle. Bottom is 0 — the sidebar's
   own 20px bottom padding is the final margin. */
body[data-portal-code="OHFA"] .sidebar-footer-wordmark {
  margin: 0 0 6px 0;
}
body[data-portal-code="OHFA"] .sidebar-footer-copy {
  padding: 0;
}

/* ─────────────────────────────────────────────────────────────────
   THEME TOGGLE (sidebar footer, under the avatar)
   Standard control on the Ghost dashboard and OHFA. Hidden only on
   portals not yet retrofitted for light mode — drop a code from the
   list below once its page files are tokenized.
   ───────────────────────────────────────────────────────────────── */
/* BLU + OWC still pending light-mode retrofit. Spectrum joined the
   retrofitted portals 2026-05-22; toggle shows there. */
body[data-portal-code="BLU"] .theme-toggle,
body[data-portal-code="OWC"] .theme-toggle { display: none; }

/* ============================================================
   State-Agency Portal Palette (LOCKED 2026-07-21)
   The shared four-color ODMHSAS brand cycle for state client portals
   (DMH, OWC, and any future state agency). Fixed order, do not reorder:
   blue -> green -> orange -> gold. Applied as the sidebar nav-icon
   active + hover cycle, mapped to each portal's tabs in order. Hexes are
   the ODMHSAS brand colors. See docs/client-portal-decisions.md. (Brian.)
   ============================================================ */
body[data-portal-code="DMH"],
body[data-portal-code="OWC"] {
  --state-blue:   #0066A8;
  --state-green:  #669B41;
  --state-orange: #D15420;
  --state-gold:   #DE9027;
}
/* DMH tabs in order: Projects=blue, Calendar=green, Guide=orange, Settings=gold */
body[data-portal-code="DMH"] .sidebar-nav a.active[data-page="clientportal"] .nav-icon,
body[data-portal-code="DMH"] .sidebar-nav a:hover[data-page="clientportal"] .nav-icon { background-color: var(--state-blue);   }
body[data-portal-code="DMH"] .sidebar-nav a.active[data-page="calendar"] .nav-icon,
body[data-portal-code="DMH"] .sidebar-nav a:hover[data-page="calendar"] .nav-icon     { background-color: var(--state-green);  }
body[data-portal-code="DMH"] .sidebar-nav a.active[data-page="guide"] .nav-icon,
body[data-portal-code="DMH"] .sidebar-nav a:hover[data-page="guide"] .nav-icon        { background-color: var(--state-orange); }
body[data-portal-code="DMH"] .sidebar-nav a.active[data-page="settings"] .nav-icon,
body[data-portal-code="DMH"] .sidebar-nav a:hover[data-page="settings"] .nav-icon     { background-color: var(--state-gold);   }
.theme-toggle {
  display: flex;
  align-items: center;
  /* Left-anchored: label + pill sit at the sidebar's 30px guideline
     with every other footer item. Was justify-content:center +
     calc(100% - 40px) + 20px side margins — pushed the whole toggle
     into a centered band ~20px in from the rail edges. */
  justify-content: flex-start;
  gap: 10px;
  margin: 0 0 14px 0;
  /* Left padding aligns the toggle label with the footer cluster:
     the avatar, name, and wordmark all sit at 36px (sidebar
     padding-left 16px + 20px). Vertical padding kept tight + even. */
  padding: 4px 0 4px 20px;
  background: transparent;
  border: 0;
  color: rgba(255,255,255,0.6);
  font: inherit; font-size: 12px; font-weight: 600;
  cursor: pointer;
  width: auto;
  user-select: none;
}
.theme-toggle .tt-side {
  color: var(--table-rule);
  letter-spacing: 0.3px;
  transition: color 0.15s;
}
.theme-toggle .tt-side.active { color: rgba(255,255,255,0.92); }
.theme-toggle .tt-pill {
  position: relative;
  width: 42px; height: 22px;
  border-radius: 999px;
  background: transparent;
  border: 1px solid var(--table-rule);
  flex-shrink: 0;
  transition: border-color 0.18s;
}
.theme-toggle .tt-knob {
  position: absolute; top: 2px; left: 2px;
  width: 18px; height: 18px;
  border-radius: 50%;
  background: var(--text);
  transition: transform 0.18s ease;
}
/* Knob position. dark = right, light = left. matches label order. */
body[data-theme="light"] .theme-toggle .tt-knob { transform: translateX(0); }
body:not([data-theme="light"]) .theme-toggle .tt-knob { transform: translateX(20px); }
body[data-theme="light"] .theme-toggle .tt-side.tt-light { color: #18181B; }
body:not([data-theme="light"]) .theme-toggle .tt-side.tt-dark  { color: rgba(255,255,255,0.92); }
/* Show only the active label. Light mode displays "Light" on the
   right, dark mode displays "Dark" on the right. Pill (toggle) stays on
   the LEFT via flex order (Brian 2026-07-28). */
.theme-toggle .tt-pill { order: 1; }
body[data-theme="light"] .theme-toggle .tt-light { order: 2; }
body[data-theme="light"] .theme-toggle .tt-dark  { display: none; }
body:not([data-theme="light"]) .theme-toggle .tt-dark  { order: 2; }
body:not([data-theme="light"]) .theme-toggle .tt-light { display: none; }
.theme-toggle:hover .tt-pill {
  border-color: rgba(255,255,255,0.70);
}

/* ─────────────────────────────────────────────────────────────────
   OHFA LIGHT THEME (dashboard shell + pages)
   Greyscale-only flip on body[data-theme="light"].
   Accents (--oh-yellow, --oh-blue, --oh-green, --oh-red) and the
   4-tier traffic ramp stay identical. shell.css uses hardcoded body
   + sidebar backgrounds, so we override those explicitly here.
   ───────────────────────────────────────────────────────────────── */
body[data-theme="light"] {
  --text:        #18181B;
  --text-dim:    #555558;
  --text-muted:  #7C7C82;
  --text-faint:  #6B6B6E;   /* darker than the dark-mode #8A8A8E so faint text still reads on light tiles */
  --text-eyebrow: #71717A;  /* light-mode eyebrow: darkened version of the dark-mode #9F9FA0 so labels read on white tiles */
  --state-negative: #DC2626;  /* delta red darkened for light mode; ratio 5.0+ on #E8E8E8 vs 2.3 with #F87171 */
  --footer-fg:   rgba(0,0,0,0.45);
  /* Three-tier surface scale, inverted for light mode.
     Page bg (deepest) -> section/tile L1 -> nested tile L2 (lightest).
     Pages that already use these tokens (most panels, tiles, inputs,
     chips) flip automatically. Hardcoded #0E1319 / var(--text) / etc.
     still need per-page attribute-selector overrides — see each
     page file's light-mode block. */
  --surface-0:               #E8E8E8;   /* tier 1: page bg, ~2.5 L* darker than tile L1 — subtle but noticeable */
  --surface-1:               #F4F4F4;   /* tier 2: section / tile L1 */
  --surface-1-bright:        #F4F4F4;   /* alias used in many inline styles */
  --surface-2:               #FFFFFF;   /* tier 3: nested tile L2 / chip */
  --surface-recessed:        #E8E8E8;   /* inset/recessed (timesheet etc) */
  --surface-card-bg:         #FFFFFF;
  --surface-border:          rgba(0,0,0,0.08);
  --surface-border-soft:     rgba(0,0,0,0.04);
  --surface-border-strong:   rgba(0,0,0,0.16);
  /* Light-mode counterparts to dark-mode tokens — see :root for the
     dark variants and the rationale on each. */
  --table-rule:              rgba(0,0,0,0.28);
  --popover-bg:              #FFFFFF;
  --popover-text:            #18181B;
  --popover-border:          rgba(0,0,0,0.10);
  /* Brian 2026-05-22: in light mode the accent IS the darkened
     variant, for fills AND text. Single hue across the board instead
     of the prior "bright fill, darkened -text" split. Any place that
     reads var(--accent-lime) or var(--accent-cyan) in light mode now
     gets the darkened value automatically; the -text aliases below
     resolve through and are kept for backwards compatibility. */
  /* Lime + green collapse to the same vibrant value in light mode
     (Brian 2026-05-22): the older #4A7E16 olive read as muddy on
     white. Single bright #00E32B for any lime/green surface. */
  --accent-lime:             #00E32B;
  --accent-cyan:             #0096FF;
  --accent-orange:           #FF7E00;
  --accent-green:            #00E32B;
  /* Ghost person colors, light-mode variants (see :root defs). */
  --gp-blue:   #0096FF;
  --gp-lime:   #00E32B;
  --gp-orange: #FF7E00;
  --gp-purple: #A155F0;
  /* state-positive drives the FINAL / approved pill background and
     every other "success" surface; in light mode it follows the
     accent-green value so green stays green. */
  --state-positive:          #00E32B;
  /* Accent text aliases — redundant with the swapped tokens above but
     preserved so existing var(--accent-*-text) references keep working
     until a future cleanup. */
  --accent-lime-text:        var(--accent-lime);
  --accent-cyan-text:        var(--accent-cyan);
  --accent-orange-text:      var(--accent-orange);
}

/* Light-mode pill override. Brian 2026-05-22.
   Dark-mode pills use "colored text on 14% accent-tinted fill". On a
   light canvas that tint reads as washed-out, so the rule flips:
   solid 100% accent fill + pure white text. The accent token is
   swapped in light mode (see block above): lime to #4A7E16 (passes
   AA on white text), cyan to vibrant #0096FF (brand-vibrancy call by
   Brian 2026-05-22, runs ~2.8:1 with white text — below strict AA
   but read-able and on-brand).
   Selector list covers every tinted-pill class in use. Add new pill
   classes here as they're introduced. */
body[data-theme="light"] .pill,
body[data-theme="light"] .sq-proof-type {
  background: var(--accent-cyan) !important;
  color: #FFFFFF !important;
  border-color: transparent !important;
}
/* DRAFT vs FINAL on project-card / PDM pills: TEXT ONLY (Brian
   2026-05-28). No pill outline, no fill — just the colored word.
   DRAFT in the page accent; FINAL muted grey. Same in both themes
   so the .cp-status-pill / .cp-tactic-pill / .pdm-status-pill rules
   in clientportal.css are the single source of truth. The light-mode
   catch-all above intentionally omits these three classes now. */

/* Light-mode catch-all for ANY inline-styled element using the
   translucent-accent-fill pattern (color-mix + accent var). Catches
   every pill, badge, tag, chip, button, anchor, span — anything with
   that inline-style signature, regardless of tag. The pattern is the
   signature of dark-mode pills/buttons; tiles/panels usually drive
   their backgrounds from CSS rules (not inline styles) so they're
   not affected. Per-accent rules preserve color identity. */
/* Same flip rule, for the named pill/badge classes whose translucent
   fill is declared in their own CSS rule rather than inline (so the
   [style*="color-mix"] selector below can't see it). Each named class
   gets the solid + white treatment in light mode. */
body[data-theme="light"] .cpp-status-badge.live,
body[data-theme="light"] .cpp-status-badge.provisioning {
  background: var(--accent-cyan) !important;
  color: #FFFFFF !important;
  border-color: transparent !important;
}

body[data-theme="light"] [style*="color-mix"][style*="var(--accent-lime)"] {
  background: var(--accent-lime) !important;
  color: #FFFFFF !important;
  border-color: transparent !important;
}
body[data-theme="light"] [style*="color-mix"][style*="var(--accent-cyan)"] {
  background: var(--accent-cyan) !important;
  color: #FFFFFF !important;
  border-color: transparent !important;
}
body[data-theme="light"] [style*="color-mix"][style*="var(--accent-orange)"] {
  background: var(--accent-orange) !important;
  color: #FFFFFF !important;
  border-color: transparent !important;
}
body[data-theme="light"] [style*="color-mix"][style*="var(--accent-purple)"] {
  background: var(--accent-purple) !important;
  color: #FFFFFF !important;
  border-color: transparent !important;
}
body[data-theme="light"] [style*="color-mix"][style*="var(--accent-green)"] {
  background: var(--accent-green) !important;
  color: #FFFFFF !important;
  border-color: transparent !important;
}

/* Exemption: the Social "build from link" bar is a CONTAINER, not a pill, but
   it carries the color-mix + accent-var inline signature the catch-alls above
   key on — so in light mode it was wrongly flipped to a solid accent block.
   Restore its subtle accent tint (id specificity + !important beats the
   attribute catch-all). Applies to all brands. */
body[data-theme="light"] #social-buildlink-bar {
  background: color-mix(in srgb, var(--accent-lime) 6%, var(--surface-1)) !important;
  border-color: color-mix(in srgb, var(--accent-lime) 22%, transparent) !important;
  color: var(--text) !important;
}
/* Solid-accent "Build post" button: its inline dark text (#0c1207) reads fine
   on Ghost's bright lime/cyan, but is low-contrast on Blue Sky's navy/blue
   fill, so force white text where the accent is dark. */
body[data-portal-code="SKY"] #social-buildlink-btn { color: #FFFFFF !important; }
/* Build Asset (brand studio) is LIVE + client-facing for Blue Sky as of 2026-07-26:
   the BSB template suite launches scoped via ?client=BSB. Button shown on SKY portal. */
/* Link auto-fill is a URL scraper for blog-driven posting — genuinely useful for
   Ghost/OWC, but meaningless on a client portal where we author the copy and it
   has to clear the client's compliance review. Hidden on every client portal
   (page-level bar + the one inside the composer). Brian 2026-07-24. */
body[data-portal-code] #social-buildlink-bar,
body[data-portal-code] #social-modal-buildlink-row { display: none !important; }
/* SEO tab hidden on every client portal (Brian 2026-07-24). Search Console is
   Ghost-only live: core.js only injects a per-client siteUrl when the portal has
   a gscSiteUrl, and with none configured the backend falls through to
   DEFAULT_GSC_SITE_URL ('sc-domain:ghost.agency') — so the tab served GHOST's
   queries and pages on a client's portal. Verified via searchconsole.selfTest:
   the only reachable properties are ghost.agency, myghostagent.com, okimready.org
   and thewellok.org. Re-enable per-portal once that client's property is granted
   and stored. A matching fail-closed guard lives in traffic.js reloadSeoView. */
body[data-portal-code] .traffic-view-tab[data-view="seo"] { display: none !important; }
/* Status label reads orange on Blue Sky (was hardcoded Ghost purple #B47BFF).
   Uses the theme-aware Ghost-orange token so the SKY accent skin never remaps
   it. Brian 2026-07-24. */
body[data-portal-code="SKY"] .pdm-status-pill.in_progress { color: var(--gp-orange); }
/* Traffic page's MAIN accent is Sky Blue, not Steel (Brian 2026-07-24). Page
   chrome — the Analytics/SEO tab underline, active range pills, the region
   toggle, links and focus rings — rides --accent-purple/--accent-cyan, which the
   SKY skin maps to c1 (Steel). Remap just those chrome slots inside the traffic
   wrapper. Data visuals are untouched: the doughnut reads --device-*, the bars
   --gradient-bar-*, the maps --traffic-tier-*, and the trend lines --chart-line,
   all set explicitly elsewhere. Specificity (attr + id) beats traffic.css. */
body[data-portal-code="SKY"] #page-wrap-traffic {
  --accent-purple:   var(--sky-c2);
  --accent-cyan:     var(--sky-c2);
  --accent-cyan-2:   var(--sky-c2);
  --accent-purple-text: var(--sky-c2);
  --accent-cyan-text:   var(--sky-c2);
  --tab-active-bg:   color-mix(in srgb, var(--sky-c2) 14%, transparent);
  --accent-cyan-fill:          color-mix(in srgb, var(--sky-c2) 14%, transparent);
  --accent-cyan-fill-hover:    color-mix(in srgb, var(--sky-c2) 22%, transparent);
  --accent-cyan-border:        var(--sky-c2);
  --accent-cyan-border-strong: color-mix(in srgb, var(--sky-c2) 50%, transparent);
  --accent-purple-fill:          color-mix(in srgb, var(--sky-c2) 14%, transparent);
  --accent-purple-fill-hover:    color-mix(in srgb, var(--sky-c2) 22%, transparent);
  --accent-purple-border:        color-mix(in srgb, var(--sky-c2) 30%, transparent);
  --accent-purple-border-strong: color-mix(in srgb, var(--sky-c2) 50%, transparent);
}
/* Map hover card border rides the shared popover accent so it matches the dot,
   label and value inside it. Ghost resolves to its usual purple; SKY to Sky
   Blue. Beats the base .loc-hover-card rules on specificity. Brian 2026-07-24. */
body .loc-hover-card,
body[data-theme="light"] .loc-hover-card,
body:not([data-theme="light"]) .loc-hover-card {
  border-color: var(--popover-accent);
}

/* OHFA-only light values: traffic ramp inverts to the cyan family,
   brand yellow darkens for text use. Solid yellow fills stay #F2B62E. */
body[data-portal-code="OHFA"][data-theme="light"] {
  --traffic-tier-4: #00BDE8;
  --traffic-tier-3: #5BD0E8;
  --traffic-tier-2: #9DE0EE;
  --traffic-tier-1: #D0EEF5;
  --ohfa-yellow-text: #D49B20;
}

/* Traffic by Location hover card. Brian's locked spec 2026-05-28:
   Light: white fill, accent border, dark title, accent metric text.
   Dark:  dark fill, accent border, white title, accent metric text.
   --accent-purple is the metric-row color (resolves per portal). */
.loc-hover-card {
  position: fixed;
  pointer-events: none;
  background: #FFFFFF;
  border: 1px solid var(--accent-purple);
  border-radius: 6px;
  padding: 10px 14px;
  font-size: 14px;
  color: #18181B;
  line-height: 1.5;
  white-space: nowrap;
  box-shadow: 0 6px 20px rgba(0,0,0,0.15);
  z-index: 9999;
  display: none;
  font-family: inherit;
  letter-spacing: 0;
}
body[data-theme="light"] .loc-hover-card {
  background: #FFFFFF;
  color: #18181B;
  border-color: var(--accent-purple);
  box-shadow: 0 6px 20px rgba(0,0,0,0.15);
}
/* Default (dark mode). Selector beats the unscoped block above on
   source order so the dark theme wins when no [data-theme] is set. */
body:not([data-theme="light"]) .loc-hover-card {
  background: var(--popover-bg);
  color: var(--popover-text);
  border-color: var(--accent-purple);
  box-shadow: 0 6px 20px rgba(0,0,0,0.5);
}

/* CRM Traffic by Location 4-tier ramp. Traffic page on the CRM portal
   paints orange (per-page accent), so the heatmap ramp follows. Tier 1
   (darkest) → tier 4 (lightest), matching the OHFA spec direction.
   Brian 2026-05-28. */
body[data-portal-code="CRM"] {
  --traffic-tier-1: #6B2806;
  --traffic-tier-2: #A14010;
  --traffic-tier-3: #ED7128;
  --traffic-tier-4: #F9C18A;
}
body[data-portal-code="CRM"][data-theme="light"] {
  --traffic-tier-4: #F2A86A;
  --traffic-tier-3: #F6C29A;
  --traffic-tier-2: #FBDDC4;
  --traffic-tier-1: #FFF1E5;
}

/* BlueJay Traffic by Location 4-tier ramp. Traffic page on BLU paints
   BJ orange (#F7941D, per --accent-purple override in #page-wrap-traffic);
   the heatmap ramp follows. Tier 1 (darkest) = bottom-25%, Tier 4
   (lightest) = top-25%. Tier 3 is the brand color; tier 4 is a
   light tint so the brightest bucket reads above brand. Brian 2026-05-28. */
body[data-portal-code="BLU"] {
  --traffic-tier-1: #5B3208;
  --traffic-tier-2: #A05A11;
  --traffic-tier-3: #F7941D;
  --traffic-tier-4: #FAC58C;
}
body[data-portal-code="BLU"][data-theme="light"] {
  --traffic-tier-4: #F2A86A;
  --traffic-tier-3: #F6C29A;
  --traffic-tier-2: #FBDDC4;
  --traffic-tier-1: #FFF1E5;
}
body[data-theme="light"],
body[data-theme="light"] html {
  background: #E8E8E8 !important;
  color: #18181B;
}
body[data-theme="light"] .sidebar {
  background: #FFFFFF;
  border-right-color: transparent;
  /* Soft shadow on the right edge so the rail lifts off the page bg
     instead of butting against it with a hard line. */
  box-shadow: 4px 0 16px rgba(0,0,0,0.06);
}
body[data-theme="light"] .sidebar-nav a {
  color: #7C7C82;  /* mid-grey inactive label — visible but clearly
                     inactive vs the pure-black active state below.
                     Was #3A3A3D, lifted per Brian 2026-05-15 for
                     stronger active/inactive contrast. */
}
body[data-theme="light"] .sidebar-nav a:hover {
  background: rgba(0,0,0,0.04);
  color: #18181B;
}
/* Inactive nav icon: matches inactive label grey. Active state still wins
   on specificity via the per-page color rules in the global
   .sidebar-nav a.active[data-page=...] block. :not(:hover) lets the
   per-page hover rules paint the icon in its accent color on hover
   (was getting trampled by this rule in light mode). */
body[data-theme="light"] .sidebar-nav a:not(.active):not(:hover) .nav-icon {
  background-color: #7C7C82;
}
/* Active label: pure black so the colored icon + dark label combo
   reads as the active state. */
body[data-theme="light"] .sidebar-nav a.active {
  color: #000000;
  /* Lighter grey surround for the active nav item. the inherited
     --surface-border-strong (16% black) read too heavy. */
  background: rgba(0,0,0,0.06);
}
/* Active page tab: knock the loud accent text back to neutral in
   light mode. the accent underline stays as the active indicator. */
body[data-theme="light"] .page-tab.active {
  color: var(--text);
}
/* Ghost light mode is 2-tier: --surface-0 (the page) is the only
   grey; every panel / tile / card sits on the white tier, so the
   dashboard reads uniform instead of mixed. OHFA keeps its own
   3-tier scale (it sets --surface-1 in the main light block). */
body[data-theme="light"]:not([data-portal-code="OHFA"]) {
  --surface-1: #FFFFFF;
  --surface-1-bright: #FFFFFF;
}
/* JOB ID tag labels: neutral dark tier in light mode. lime-as-text
   (and its per-user color variants) read inconsistently on white. */
body[data-theme="light"] .te-job-id,
body[data-theme="light"] .te-combo-jobid,
body[data-theme="light"] .rpt-job-id,
body[data-theme="light"] .job-id,
body[data-theme="light"] .focus-ctx-src.jobid {
  color: var(--text) !important;
}
/* Billing-hero secondary text reads black in light mode. */
body[data-theme="light"] .billing-bar-label,
body[data-theme="light"] .billing-hero .target {
  color: var(--text);
}
/* Inbox: message list + viewer on the white tier; row hover a
   light grey that contrasts against the white. */
body[data-theme="light"] #inbox-list,
body[data-theme="light"] #inbox-detail {
  background: #FFFFFF;
}
body[data-theme="light"] .inbox-row:hover {
  background: rgba(0,0,0,0.04);
}
/* Pipeline: kanban columns are the light-grey container; the
   opportunity cards inside stay white so they lift off. */
body[data-theme="light"] .kanban-col {
  background: #F4F4F4;
}
/* Chat conversation rail: a hair darker than the page so it reads
   as a recessed secondary rail, not a heavy dark panel. */
body[data-theme="light"] #app #sidebar {
  background: #E0E0E0 !important;
}
/* Client portal cards + their draft/final tiles on the white tier
   with soft elevation. */
body[data-theme="light"] .cpp-client {
  background: #FFFFFF;
  box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
}
body[data-theme="light"] .cpp-stat {
  background: #FFFFFF;
  /* Standard light-mode tile-on-tile elevation (Brian 2026-05-22):
     drop shadow bottom-right, 3px/4px offset, 8px blur, 15% alpha.
     Same recipe for any nested-tile surface in light mode. */
  box-shadow: 3px 4px 8px rgba(0,0,0,0.15);
}
/* SVG client logos get a soft drop-shadow for depth on white. */
body[data-theme="light"] .cpp-logo {
  filter: drop-shadow(0 2px 5px rgba(0,0,0,0.22));
}
/* LoRA Studio: dark control buttons + run thumbnails flip light. */
body[data-theme="light"] #studio-add-style,
body[data-theme="light"] #studio-add-url {
  background: #FFFFFF !important;
}
body[data-theme="light"] #studio-runs > div {
  background-color: #FFFFFF !important;
}
/* Social "No image" placeholder reads as a faint visible box. */
body[data-theme="light"] .social-queue-thumb.is-placeholder,
body[data-theme="light"] .sq-cards-thumb.is-placeholder {
  background: rgba(0,0,0,0.04);
}
/* Guide overview panel + pillar tiles get the soft elevation in
   light mode (they aren't .card / .panel, so they were missed). */
body[data-theme="light"] .guide-overview,
body[data-theme="light"] .guide-pillar {
  box-shadow: 0 1px 2px rgba(0,0,0,0.04), 0 8px 24px rgba(0,0,0,0.06);
}
body[data-theme="light"] #sidebar-user {
  border-top-color: rgba(0,0,0,0.06) !important;
}
body[data-theme="light"] #sidebar-user-name { color: #18181B !important; }
body[data-theme="light"] #sidebar-user-email { color: rgba(0,0,0,0.45) !important; }
body[data-theme="light"] .sidebar-footer-copy {
  color: rgba(0,0,0,0.45);
}
body[data-theme="light"] .theme-toggle {
  color: rgba(0,0,0,0.6);
}
body[data-theme="light"] .theme-toggle .tt-side {
  color: rgba(0,0,0,0.40);
}
body[data-theme="light"] .theme-toggle .tt-pill {
  background: transparent;
  border-color: rgba(0,0,0,0.40);
}
body[data-theme="light"] .theme-toggle .tt-knob {
  background: #18181B;
}
body[data-theme="light"] .theme-toggle:hover .tt-pill {
  border-color: rgba(0,0,0,0.70);
}
body[data-portal-code="OHFA"] #ethan-bubble-btn {
  background: radial-gradient(circle at 30% 30%, #F2B62E 0%, #7a5a14 100%) !important;
  box-shadow: 0 10px 30px rgba(0,0,0,0.45), 0 0 0 3px rgba(242,182,46,0.20) !important;
}

/* Client instances never see Ghost-internal counters like "Active Client
   Portals: N" — that aggregate stat is for staff only. */
body[data-portal-code] .cpp-staff-only { display: none !important; }

/* OWC: agent icon for the Ghost Agent chat bubble. The default ghost
   wordmark is single-fill (CSS mask) and white; OWC's State Icon is
   multi-color so we have to swap from mask-image to background-image
   and clear the mask. Used on both the floating bubble button and the
   modal-header avatar. */
body[data-portal-code="OWC"] #ethan-bubble-btn .mark,
body[data-portal-code="OWC"] #ethan-bubble-modal .bh .avatar .m {
  -webkit-mask-image: none !important;
  mask-image: none !important;
  background-color: transparent !important;
  background-image: url(branding/clients/owc-agent-icon.svg) !important;
  background-size: contain !important;
  background-repeat: no-repeat !important;
  background-position: center !important;
}
/* The modal-header avatar slot is 36x36 with an 18x18 .m; bumping the
   .m to fill more of the slot reads as an "agent face" rather than a
   tiny logo when the icon is detailed. */
body[data-portal-code="OWC"] #ethan-bubble-modal .bh .avatar .m {
  width: 28px !important;
  height: 28px !important;
}
body[data-portal-code="OWC"] #ethan-bubble-btn .mark {
  width: 32px !important;
  height: 32px !important;
}
/* DMH (Oklahoma Dept of Mental Health) mirrors OWC exactly — same shared
   State Icon, same treatment. Both are state agencies. (Brian 2026-07-21.) */
body[data-portal-code="DMH"] #ethan-bubble-btn .mark,
body[data-portal-code="DMH"] #ethan-bubble-modal .bh .avatar .m {
  -webkit-mask-image: none !important;
  mask-image: none !important;
  background-color: transparent !important;
  background-image: url(branding/clients/dmh-agent-icon.svg) !important;
  background-size: contain !important;
  background-repeat: no-repeat !important;
  background-position: center !important;
}
body[data-portal-code="DMH"] #ethan-bubble-modal .bh .avatar .m {
  width: 28px !important;
  height: 28px !important;
}
body[data-portal-code="DMH"] #ethan-bubble-btn .mark {
  width: 32px !important;
  height: 32px !important;
}

/* BlueJay: + New Post button on Social tab uses translucent BJ red.
   The button's inline style hardcodes purple rgba(161,85,240,...) which
   doesn't flow through the var system; !important here beats both the
   static inline style and the inline onmouseenter/leave handlers (which
   set element.style without !important). */
body[data-portal-code="BLU"] #social-compose-btn {
  background: rgba(239,65,54,0.18) !important;
  border-color: rgba(239,65,54,0.35) !important;
}
body[data-portal-code="BLU"] #social-compose-btn:hover {
  background: rgba(239,65,54,0.28) !important;
}

/* BlueJay Traffic page: data viz primary = blue, secondary = orange.
   The default BlueJay slot mapping has --accent-purple → BJ red, which
   is correct for tab cycling (Social = red) but wrong for Traffic data
   viz where the "secondary metric" (Returning users, off-peak day,
   line charts) should be orange. Override --accent-purple ONLY inside
   the Traffic page wrapper so other pages keep red. */
body[data-portal-code="BLU"] #page-wrap-traffic {
  --accent-purple: var(--bj-orange);
  --accent-pink:   var(--bj-orange);
  --accent-purple-soft: rgba(247,148,29,0.45);  /* translucent BJ orange — Sat/Sun bars on day-of-week chart */
}

/* BlueJay Projects page: tab and content read BLUE. Override every
   accent var the page uses (--accent-cyan, --accent-lime, --accent-
   purple, --accent-orange, --accent-green) so accent fills/borders
   resolve to BJ blue regardless of which slot the page references. */
body[data-portal-code="BLU"] #page-wrap-clientportal {
  --accent-lime:   var(--bj-blue);
  --accent-cyan:   var(--bj-blue);
  --accent-cyan-2: var(--bj-blue);
  --accent-purple: var(--bj-blue);
  --accent-orange: var(--bj-blue);
  --accent-green:  var(--bj-blue);
  --accent-pink:   var(--bj-blue);
  --accent-cyan-fill:        rgba(0,174,239,0.15);
  --accent-cyan-fill-hover:  rgba(0,174,239,0.10);
  --accent-cyan-border:      var(--bj-blue);
}

/* BlueJay Calendar page reads GREEN. Page primary on Ghost is
   --accent-purple; override every accent var the page may touch so
   event tiles + today highlight render BJ green. */
body[data-portal-code="BLU"] #page-wrap-calendar {
  --accent-purple: var(--bj-green);
  --accent-purple-soft: rgba(141,198,63,0.45);
  --accent-lime:   var(--bj-green);
  --accent-cyan:   var(--bj-green);
  --accent-cyan-2: var(--bj-green);
  --accent-orange: var(--bj-green);
}

/* BlueJay Social page reads BLUE. Override every accent var the
   page may resolve (social page references lime/cyan for active
   tabs, post tiles, and the queue card buttons). */
body[data-portal-code="BLU"] #page-wrap-social {
  --accent-lime:   var(--bj-blue);
  --accent-cyan:   var(--bj-blue);
  --accent-cyan-2: var(--bj-blue);
  --accent-purple: var(--bj-blue);
  --accent-orange: var(--bj-blue);
  --accent-pink:   var(--bj-blue);
  --accent-cyan-fill:        rgba(0,174,239,0.15);
  --accent-cyan-fill-hover:  rgba(0,174,239,0.10);
  --accent-cyan-border:      var(--bj-blue);
}

/* BlueJay Guide page reads GREEN. Override accent-lime + accent-cyan
   (the two vars the page references) plus the rest for safety so
   headings + callouts render BJ green. */
body[data-portal-code="BLU"] #page-wrap-guide {
  --accent-lime:   var(--bj-green);
  --accent-cyan:   var(--bj-green);
  --accent-cyan-2: var(--bj-green);
  --accent-purple: var(--bj-green);
  --accent-orange: var(--bj-green);
}

/* Bar gradients use the gradient-bar vars universally — :root sets the
   Ghost default (purple→lime), BlueJay overrides to dark blue → light
   blue. No per-class override block needed. */
/* Inline-styled NEW/RETURNING bars in Traffic page (rgba inline,
   need !important to beat the inline style attribute). */
body[data-portal-code="BLU"] #traffic-nvr-new {
  background: linear-gradient(to right,
    rgba(0,174,239,0.35), rgba(0,174,239,0.6) 40%,
    rgba(0,174,239,0.85) 70%, rgba(0,174,239,1) 100%) !important;
}
body[data-portal-code="BLU"] #traffic-nvr-ret {
  background: linear-gradient(to right,
    rgba(247,148,29,0.35), rgba(247,148,29,0.6) 40%,
    rgba(247,148,29,0.85) 70%, rgba(247,148,29,1) 100%) !important;
}

/* Sidebar footer: small Ghost wordmark above the © line. Light
   grey matching the copyright text so the attribution reads as
   secondary, not primary brand. */
.sidebar-footer-wordmark {
  display: block;
  width: 78px;
  height: 25px;
  margin: 0 20px 6px;
  background-color: var(--footer-fg);
  -webkit-mask-image: url(branding/ghost_logo.svg);
  mask-image: url(branding/ghost_logo.svg);
  -webkit-mask-size: contain;
  mask-size: contain;
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: left center;
  mask-position: left center;
}
.sidebar-footer-copy {
  padding: 0 20px 16px;
  font-size: 10px;
  color: var(--footer-fg);
  letter-spacing: 0.2px;
  white-space: nowrap;
  overflow: hidden;
}


/* ============================================================
   RESPONSIVE BREAKPOINTS — must live at file end so they win
   over desktop defaults at equal specificity.
   ============================================================
   Brian 2026-05-07:
     >1080  desktop:  sidebar visible, content 2-col
     481-1080 tablet: sidebar visible, content 1-col, stats 2-up
     ≤480   phone:    no sidebar (hamburger only), 1-col, stats 2-up
   ============================================================ */

/* Tablet portrait + large phone landscape (481-1080px) */
@media (max-width: 1080px) {
  .main { padding: 24px 20px 110px; }

  .header { flex-direction: column; gap: 4px; }
  .header h1 { font-size: 24px; }
  .header-meta { white-space: normal; font-size: 10px; }
  .header-affirmation { font-size: 20px !important; }

  .billing-hero { flex-direction: column; gap: 16px; padding: 20px; }
  .billing-hero .amount { font-size: 28px; }
  .billing-bar-container { width: 100%; }

  /* 7-up stat row collapses to 2-up; recap card claims a full row. */
  .card-grid { grid-template-columns: repeat(2, 1fr); }
  .card-grid .card-recap { grid-column: 1 / -1; }
  .stats-left { grid-template-columns: repeat(2, 1fr); }

  .two-col { grid-template-columns: 1fr !important; }
  /* Traffic page paired panels stay side-by-side at narrower widths.
     The pair (Users Over Time / Sessions by Device, Page Views / Day
     of Week, etc.) is information-dense and reads as a comparison —
     stacking buries the second tile below the fold. Hold the 50/50
     split down to ~720px; below that the global 1fr collapse wins. */
  #page-wrap-traffic .two-col { grid-template-columns: minmax(0,1fr) minmax(0,1fr) !important; }

  .card-value { font-size: 24px; }

  .panel { padding: 16px; }

  .pulse-strip { padding: 14px 16px; gap: 10px; }
  .pulse-systems { justify-content: flex-start; }

  .page-label { flex-wrap: wrap; gap: 8px; }
  .page-label h2 { font-size: 17px; }

  .pipeline-item { flex-wrap: wrap; gap: 4px; font-size: 13px; }

  .task-date { font-size: 13px; }
}

/* Filter / sub-tab rows: at phone widths force horizontal-scroll
   (no wrap to multiple lines, no cramming) so the filter bar stays
   one swipeable row. Brian 2026-05-07: too many filter bars wrap
   awkwardly on phone. Pattern matches the scrollable account-tabs
   we already use on Social. Hides scrollbar on webkit for clean
   look. */
@media (max-width: 720px) {
  /* Below ~720px the side-by-side traffic pair gets unreadably cramped.
     stack at this breakpoint, overrides the 1080px-scoped pin above. */
  #page-wrap-traffic .two-col { grid-template-columns: 1fr !important; }
}

@media (max-width: 600px) {
  #tasks-filter-bar,
  #inbox-filter-bar,
  #traffic-filter-bar,
  #social-account-tabs,
  .social-view-tabs-group,
  .social-ctab-row,
  #cpp-panel-projects .cp-pillbar,
  #cpp-panel-projects .cp-view-toggle {
    flex-wrap: nowrap !important;
    overflow-x: auto !important;
    -webkit-overflow-scrolling: touch;
    scrollbar-width: none;
  }
  #tasks-filter-bar::-webkit-scrollbar,
  #inbox-filter-bar::-webkit-scrollbar,
  #traffic-filter-bar::-webkit-scrollbar,
  #social-account-tabs::-webkit-scrollbar,
  .social-view-tabs-group::-webkit-scrollbar,
  .social-ctab-row::-webkit-scrollbar { display: none; }
  #tasks-filter-bar > button,
  #inbox-filter-bar > button,
  #traffic-filter-bar > button,
  .social-view-tabs-group > button {
    flex-shrink: 0; white-space: nowrap;
  }
}

/* Hamburger / mobile menu mode:
     - ≤600px portrait (Brian 2026-05-07: at 600 with a 170px sidebar
       the content area is too tight; give it the full width and use
       the hamburger). Covers all phone portrait widths.
     - Any viewport with ≤500px height covers phone landscape (e.g.
       iPhone Pro Max 932×430). Tablets in landscape are ≥760 tall
       so they don't match. */
@media (max-width: 600px), (max-height: 500px) {
  .sidebar { display: none; }
  .hamburger { display: block; }
  .mobile-menu { display: flex; }
  .main { padding: 60px 12px 100px; }
}

/* Portrait-phone-only rules (these would feel cramped applied to a
   wider landscape-phone viewport, so they live in their own block). */
@media (max-width: 480px) {
  .card-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
  .card-grid .card-recap { grid-column: 1 / -1; }
  .stats-left { grid-template-columns: repeat(2, 1fr); gap: 10px; }

  .header h1 { font-size: 20px; }
  .header-affirmation { font-size: 17px !important; }
  .billing-hero .amount { font-size: 24px; }
  .card-value { font-size: 20px; }
  .panel { padding: 12px 10px; }
  .card { padding: 10px 12px; }
  .page-label h2 { font-size: 15px; }
}
