/* Shared UI components used across beats. */

/* -------------------------------------------------------------- chat UI */
.ruby-header { position:relative; z-index:4; padding:16px 18px 14px; display:flex; align-items:center; gap:10px; border-bottom:1px solid var(--border); }
/* Real glossy sphere: a distinct specular highlight blob layered on top via
   ::after (not just a gradient shift, which reads flat at this size), a
   deeper jewel-tone core, and a dark rim for actual sphere falloff. */
.ruby-avatar {
  position:relative; width:30px; height:30px; border-radius:50%; overflow:hidden;
  background:radial-gradient(circle at 38% 34%, #C9AFFF 0%, #9B6FF0 30%, #7C3AED 62%, #501E9E 100%);
  box-shadow: 0 6px 14px -4px rgba(124,58,237,0.55), inset -4px -4px 8px rgba(20,6,50,0.4), inset 2px 2px 4px rgba(255,255,255,0.3);
  display:flex; align-items:center; justify-content:center; font-weight:900; color:#fff; font-size:12px;
}
.ruby-avatar::after {
  content:""; position:absolute; top:14%; left:20%; width:36%; height:26%;
  background:radial-gradient(ellipse, rgba(255,255,255,0.95), rgba(255,255,255,0) 72%);
  border-radius:50%;
}
.ruby-name { font-size:13px; font-weight:800; color:var(--ink); }
.ruby-status { font-size:10.5px; color:var(--good); font-weight:600; }

.ruby-orb-wrap { position:relative; z-index:4; display:flex; flex-direction:column; align-items:center; padding:18px 0 8px; flex-shrink:0; }
.ruby-orb {
  position:relative; width:70px; height:70px; border-radius:50%; overflow:hidden;
  background: radial-gradient(circle at 36% 32%, #C9AFFF 0%, #9B6FF0 28%, #7C3AED 58%, #4C1D95 100%);
  box-shadow: 0 16px 36px -8px rgba(124,58,237,0.5), inset -9px -9px 18px rgba(20,6,50,0.4), inset 6px 6px 14px rgba(255,255,255,0.25);
  animation: orbBreathe 4s ease-in-out infinite;
}
/* The gradient + ::after highlight above are the static fallback (and the
   brief flash before the video's first frame paints); the generated brand
   orb video fills the same circle once it loads, so its own baked-in
   highlight replaces this one rather than stacking under it. */
.ruby-orb:has(video)::after { display:none; }
.ruby-orb video { width:100%; height:100%; object-fit:cover; display:block; }
.ruby-orb::after {
  content:""; position:absolute; top:15%; left:20%; width:34%; height:24%;
  background:radial-gradient(ellipse, rgba(255,255,255,0.95), rgba(255,255,255,0) 72%);
  border-radius:50%;
}
@keyframes orbBreathe { 0%,100%{transform:scale(1)} 50%{transform:scale(1.06)} }
.ruby-orb-caption { margin-top:8px; font-size:10px; color:var(--ink-faint); letter-spacing:0.05em; font-weight:600; }

/* ---------------------------------------------------------- IG chat chrome */
/* Real Instagram DM chrome: the header shows the OTHER person (the
   prospect), not the account's own name — that's how a real DM thread
   reads from the business account's side. */
.ig-header { position:relative; z-index:4; display:flex; align-items:center; gap:9px; padding:8px 12px; border-bottom:1px solid var(--border); }
.ig-back { color:var(--ink); flex-shrink:0; }
.ig-avatar-wrap { position:relative; flex-shrink:0; }
.ig-avatar-sm {
  width:30px; height:30px; border-radius:50%; position:relative; overflow:hidden;
  background:radial-gradient(circle at 38% 34%, #C9AFFF 0%, #9B6FF0 30%, #7C3AED 62%, #501E9E 100%);
  box-shadow: 0 4px 10px -3px rgba(124,58,237,0.5), inset -3px -3px 6px rgba(20,6,50,0.4), inset 2px 2px 4px rgba(255,255,255,0.3);
  display:flex; align-items:center; justify-content:center; font-weight:900; color:#fff; font-size:11.5px;
}
.ig-avatar-sm::after {
  content:""; position:absolute; top:14%; left:20%; width:36%; height:26%;
  background:radial-gradient(ellipse, rgba(255,255,255,0.95), rgba(255,255,255,0) 72%);
  border-radius:50%;
}
/* The chat header now reads from the CUSTOMER's side of the thread — the
   contact is Coexa itself, not the prospect — so this avatar carries the
   real brand mark (same diamond+eye SVG as coexa-website's nav logo) on a
   flat dark chip, not the glossy sphere used for a person's initial; the
   sphere's highlight sheen above is suppressed here for the same reason. */
.ig-avatar-sm.coexa-mark {
  background:#18161E; box-shadow:0 4px 10px -3px rgba(139,92,246,0.5), inset 0 0 0 1px rgba(255,255,255,0.1);
  display:flex; align-items:center; justify-content:center;
}
.ig-avatar-sm.coexa-mark::after { content:none; }
.ig-online-dot { position:absolute; bottom:-1px; right:-1px; width:8px; height:8px; border-radius:50%; background:var(--good); border:2px solid #fff; }
.ig-user-info { flex:1; min-width:0; }
.ig-username { font-size:12px; font-weight:700; color:var(--ink); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.ig-status { font-size:10px; color:var(--ink-faint); font-weight:500; margin-top:1px; }
.ig-icons { display:flex; align-items:center; gap:13px; color:var(--ink); flex-shrink:0; }
.ig-system-note { position:relative; z-index:4; text-align:center; font-size:10px; color:var(--ink-faint); font-weight:600; padding:10px 24px 2px; }
.ig-input-bar { position:relative; z-index:4; display:flex; align-items:center; gap:9px; padding:8px 14px; border-top:1px solid var(--border); flex-shrink:0; }
.ig-input-bar .ig-icon { color:var(--ink-dim); flex-shrink:0; }
.ig-input-pill { flex:1; height:30px; border-radius:999px; background:var(--bg-soft); display:flex; align-items:center; justify-content:space-between; padding:0 10px; min-width:0; }
.ig-input-pill span { font-size:11px; color:var(--ink-faint); }
.ig-input-pill svg { color:var(--ink-faint); flex-shrink:0; }

.chat-scroll {
  flex:1; position:relative; z-index:4; padding:8px 14px 4px;
  display:flex; flex-direction:column; gap:8px;
  overflow-y:auto; scrollbar-width:none;
}
.chat-scroll::-webkit-scrollbar { display:none; }
.chat-bubble {
  max-width:84%; padding:10px 13px; border-radius:16px; font-size:11.5px; line-height:1.45; font-weight:500;
  opacity:0; transform:translateY(6px); animation: bubbleIn 320ms ease forwards;
}
@keyframes bubbleIn { to { opacity:1; transform:translateY(0); } }
/* Prospect = incoming, plain, left. Coexa/Ruby = outgoing, glossy brand
   gradient, right — highlighting what RUBY says, not the random prospect. */
.chat-bubble.prospect { align-self:flex-start; background:#EFEFF2; color:var(--ink); }
.chat-bubble.coexa { align-self:flex-end; background:linear-gradient(120deg, var(--grad-a), var(--grad-b)); color:#fff; box-shadow:0 8px 20px -8px rgba(139,92,246,0.4); }
.chat-typing { align-self:flex-end; display:flex; gap:3px; padding:10px 13px; border-radius:16px; background:linear-gradient(120deg, var(--grad-a), var(--grad-b)); opacity:0; }
.chat-typing.show { opacity:1; }
.chat-typing span { width:5px; height:5px; border-radius:50%; background:#fff; opacity:0.8; animation:typingDot 1s ease-in-out infinite; }
.chat-typing span:nth-child(2){animation-delay:.15s} .chat-typing span:nth-child(3){animation-delay:.3s}
@keyframes typingDot { 0%,60%,100%{transform:translateY(0);opacity:.5} 30%{transform:translateY(-3px);opacity:1} }

/* ---------------------------------------------------------- glossy orbs */
.gloss-orb { border-radius:50%; flex-shrink:0; overflow:hidden; }
.gloss-orb.trust { background: radial-gradient(circle at 32% 28%, #F0FBFF, var(--trust-a) 25%, var(--trust-b) 60%, var(--trust-c) 100%); box-shadow: 0 14px 30px -10px rgba(62,111,224,0.4), inset -5px -5px 14px rgba(20,60,120,0.15), inset 4px 4px 10px rgba(255,255,255,0.5); }
.gloss-orb.memory { background: radial-gradient(circle at 32% 28%, #FFF6E8, var(--memory-a) 25%, var(--memory-b) 60%, var(--memory-c) 100%); box-shadow: 0 14px 30px -10px rgba(255,184,107,0.4), inset -5px -5px 14px rgba(120,70,20,0.12), inset 4px 4px 10px rgba(255,255,255,0.55); }
/* The generated liquid-glass orb video sits inside the same sized/shadowed
   circle as the CSS-gradient variants above, so swapping one for the other
   never touches sizing or elevation — only what fills the circle changes. */
.gloss-orb video { width:100%; height:100%; object-fit:cover; display:block; }

/* -------------------------------------------------------------- glass card */
.glass-card {
  background: var(--glass-bg); backdrop-filter: var(--glass-blur); -webkit-backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-border); border-radius: var(--radius-lg); box-shadow: var(--shadow-glass);
}

/* ------------------------------------------------------------ tab bar (iOS-style segmented control) */
/* Dark smoked glass, not tinted white — this is the actual Liquid Glass
   material per every reference photo Joe supplied: a dark, translucent
   control that reads the same whether it sits on a light band or a dark
   one, exactly like iOS Control Center over a light wallpaper. */
.tab-bar {
  display:inline-flex; align-items:center; gap:2px; padding:4px;
  background:rgba(24,22,30,0.42); backdrop-filter:blur(28px) saturate(180%); -webkit-backdrop-filter:blur(28px) saturate(180%);
  border:1px solid rgba(255,255,255,0.14); border-radius:999px;
  box-shadow:0 1px 0 rgba(255,255,255,0.12) inset, 0 8px 22px rgba(0,0,0,0.18);
}
.tab-pill { padding:8px 18px; border-radius:999px; font-size:12.5px; font-weight:700; color:rgba(255,255,255,0.6); white-space:nowrap; transition:color .2s; }
.tab-pill.active { background:linear-gradient(180deg,rgba(255,255,255,0.96),rgba(255,255,255,0.82)); color:#18161E; box-shadow:0 1px 0 rgba(255,255,255,0.9) inset, 0 4px 14px rgba(0,0,0,0.22); }

/* ---------------------------------------------------------- glass button */
.btn-glass {
  display:inline-flex; align-items:center; gap:7px; padding:9px 17px; border-radius:12px;
  font-size:12.5px; font-weight:700; color:rgba(255,255,255,0.92); white-space:nowrap;
  background:rgba(24,22,30,0.42);
  backdrop-filter:blur(22px) saturate(180%); -webkit-backdrop-filter:blur(22px) saturate(180%);
  border:1px solid rgba(255,255,255,0.16);
  box-shadow:0 1px 0 rgba(255,255,255,0.14) inset, 0 8px 20px rgba(0,0,0,0.18);
}
.btn-glass.primary {
  color:#fff; background:linear-gradient(135deg, var(--grad-a), var(--grad-b));
  border:1px solid rgba(255,255,255,0.35);
  box-shadow:0 1px 0 rgba(255,255,255,0.4) inset, 0 12px 26px -8px rgba(139,92,246,0.55);
}

/* --------------------------------------------------------- app nav chrome */
.app-nav {
  display:flex; align-items:center; justify-content:space-between; gap:16px;
  padding:16px 24px; border-bottom:1px solid var(--border);
  background: linear-gradient(120deg, rgba(139,92,246,0.18), rgba(255,111,168,0.15) 55%, rgba(91,141,239,0.14)), rgba(255,255,255,0.3);
  backdrop-filter:blur(24px) saturate(200%); -webkit-backdrop-filter:blur(24px) saturate(200%);
}

/* ---------------------------------------------------------------- stats */
.stat-row { display:grid; grid-template-columns:repeat(auto-fit,minmax(150px,1fr)); gap:14px; }
/* Every tile is real glass now, not just the accented ones: a crisp
   diagonal sweep + a bright top rim, same recipe proven in the glass demo
   (low fill, real blur, hard-edged highlight over a sharp backdrop). */
.stat-tile {
  position:relative; overflow:hidden;
  background:var(--bg-card-solid); backdrop-filter:blur(16px) saturate(200%); -webkit-backdrop-filter:blur(16px) saturate(200%);
  border:1.5px solid var(--border); border-radius:var(--radius-md); padding:18px 20px;
  box-shadow:0 2px 8px rgba(0,0,0,0.16), 0 16px 30px -18px rgba(0,0,0,0.35);
}
.stat-tile::before {
  content:""; position:absolute; top:-60%; left:-30%; width:45%; height:220%; z-index:0;
  background:linear-gradient(75deg, transparent 38%, rgba(255,255,255,0.3) 50%, rgba(255,255,255,0.3) 54%, transparent 66%);
  pointer-events:none;
}
.stat-tile::after {
  content:""; position:absolute; inset:0; border-radius:inherit; z-index:0; pointer-events:none;
  box-shadow: inset 0 1.5px 0 rgba(255,255,255,0.35);
}
.stat-num { position:relative; z-index:1; font-family:var(--font-body); font-size:30px; font-weight:800; letter-spacing:-0.02em; color:var(--ink); font-variant-numeric: tabular-nums; line-height:1; }
.stat-num.hot { color:var(--hot); }
.stat-num.good { color:var(--good); }
.stat-label { position:relative; z-index:1; font-size:12px; color:var(--ink-dim); margin-top:6px; font-weight:600; }
.stat-sub { position:relative; z-index:1; font-size:11px; color:var(--ink-faint); margin-top:2px; }

/* Bento variant: mixed tile sizes instead of a uniform grid — one hero
   number, one wide progress strip, rather than 8 identical boxes. */
.stat-row.bento { display:grid; grid-template-columns:repeat(4,1fr); gap:14px; }
.stat-tile.span-2 { grid-column:span 2; }
.stat-tile.span-4 { grid-column:span 4; }
.stat-tile.wide { display:flex; align-items:center; justify-content:space-between; gap:28px; }
.stat-tile.wide .stat-progress-track { position:relative; z-index:1; flex:1; max-width:240px; }
.stat-progress { height:6px; border-radius:3px; background:rgba(255,255,255,0.16); overflow:hidden; }
.stat-progress-fill { height:100%; border-radius:3px; background:linear-gradient(90deg, var(--grad-a), var(--grad-b)); }

/* Color-blocked tiles, per the Taste Library's own Coexa glassmorphism set
   (tst-b1989d61a5a8 "Conversion rate 3.0x" stat card, tst-3513d1236e9f
   GLASSMORTISIN): a genuinely translucent DARK card, real blur, color
   carried by a glowing metric + a glowing outline, not by opacity. Recipe
   sharpened per Joe's demo feedback — less blur so detail stays legible,
   a crisp bright border, a harder contact shadow, real glow layers. The
   anchor tile stays neutral (the same dark glass as the marketing-canvas
   cards, no glow); the two named tiles get the violet glow treatment. */
.stat-tile.hero {
  background:var(--glass-bg); backdrop-filter:blur(16px) saturate(200%); -webkit-backdrop-filter:blur(16px) saturate(200%);
  border:1.5px solid var(--glass-border);
  box-shadow:0 2px 8px rgba(0,0,0,0.2), 0 18px 32px -16px rgba(0,0,0,0.4);
}
.stat-tile.hero .stat-num { font-size:40px; color:var(--canvas-ink); }
.stat-tile.hero .stat-label { color:var(--canvas-ink-dim); }

/* Glow dialed back to one tight layer per tile — the first pass (two wide
   layers, 36px+64px blur) read as a blast, not a premium finish. */
.stat-tile.glass-purple {
  background:rgba(124,58,237,0.22);
  backdrop-filter:blur(15px) saturate(220%); -webkit-backdrop-filter:blur(15px) saturate(220%);
  border:1.5px solid rgba(196,171,255,0.55);
  box-shadow:
    0 0 0 1px rgba(139,92,246,0.28),
    0 2px 10px rgba(0,0,0,0.2),
    0 18px 32px -16px rgba(0,0,0,0.4),
    0 0 16px -6px rgba(139,92,246,0.5);
}
.stat-tile.glass-purple .stat-num { color:#fff; text-shadow:0 0 8px rgba(196,171,255,0.5); }
.stat-tile.glass-purple .stat-label { color:rgba(255,255,255,0.85); }
.stat-tile.glass-purple .stat-sub { color:rgba(255,255,255,0.62); }

/* Yellow glow — reserved for the top-of-funnel volume metric (leads),
   same recipe family as glass-purple with the hue swapped. */
.stat-tile.glass-yellow {
  background:rgba(234,179,8,0.2);
  backdrop-filter:blur(15px) saturate(220%); -webkit-backdrop-filter:blur(15px) saturate(220%);
  border:1.5px solid rgba(253,224,71,0.5);
  box-shadow:
    0 0 0 1px rgba(234,179,8,0.3),
    0 2px 10px rgba(0,0,0,0.2),
    0 18px 32px -16px rgba(0,0,0,0.4),
    0 0 16px -6px rgba(234,179,8,0.5);
}
.stat-tile.glass-yellow .stat-num { color:#fff; text-shadow:0 0 8px rgba(253,224,71,0.5); }
.stat-tile.glass-yellow .stat-label { color:rgba(255,255,255,0.85); }
.stat-tile.glass-yellow .stat-sub { color:rgba(255,255,255,0.62); }

/* Green glow — reserved for closed-money metrics (sales closed, revenue),
   using the same --good hue as every other "success" accent on the page. */
.stat-tile.glass-green {
  background:rgba(27,166,114,0.22);
  backdrop-filter:blur(15px) saturate(220%); -webkit-backdrop-filter:blur(15px) saturate(220%);
  border:1.5px solid rgba(110,231,183,0.5);
  box-shadow:
    0 0 0 1px rgba(27,166,114,0.3),
    0 2px 10px rgba(0,0,0,0.2),
    0 18px 32px -16px rgba(0,0,0,0.4),
    0 0 16px -6px rgba(27,166,114,0.5);
}
.stat-tile.glass-green .stat-num { color:#fff; text-shadow:0 0 8px rgba(110,231,183,0.5); }
.stat-tile.glass-green .stat-label { color:rgba(255,255,255,0.85); }
.stat-tile.glass-green .stat-sub { color:rgba(255,255,255,0.62); }

/* ------------------------------------------------------------ pill filter */
.filter-pills { display:flex; gap:8px; flex-wrap:wrap; margin-bottom:18px; }
.filter-pill {
  padding:7px 15px; border-radius:999px; font-size:12px; font-weight:700; color:var(--ink-dim);
  background:rgba(255,255,255,0.08); backdrop-filter:blur(12px) saturate(180%); -webkit-backdrop-filter:blur(12px) saturate(180%);
  border:1px solid rgba(255,255,255,0.2); box-shadow:0 1px 0 rgba(255,255,255,0.15) inset;
}
.filter-pill.active { color:#fff; background:linear-gradient(135deg, var(--grad-a), var(--grad-b)); border-color:transparent; }

/* Range pills only (beat 7) — a "you are here" glow dot, not a click-lure
   pulse, so it stays still: the source-filter pills (beat 5) reuse .filter-pill
   without this marker since "all sources" isn't a meaningful current-position cue. */
#rangePills .filter-pill { padding-left:22px; position:relative; }
#rangePills .filter-pill::before {
  content:""; position:absolute; left:11px; top:50%;
  width:5px; height:5px; border-radius:50%; background:#fff;
  box-shadow:0 0 7px 2px rgba(255,255,255,0.8);
  transform:translateY(-50%) scale(0); opacity:0;
  transition: transform .15s ease, opacity .15s ease;
}
#rangePills .filter-pill.active::before { transform:translateY(-50%) scale(1); opacity:1; }

/* ------------------------------------------------------------ progress */
.ring-wrap { position:relative; display:inline-flex; align-items:center; justify-content:center; }
.ring-value { position:absolute; font-size:20px; font-weight:800; color:var(--ink); font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------------- badges */
/* True glass, not a gradient fill: translucent + blurred + a bright edge,
   letting whatever sits behind it show through, rather than a solid
   colored pebble. */
.chip {
  display:inline-flex; align-items:center; gap:6px; padding:6px 13px; border-radius:999px;
  font-size:11.5px; font-weight:700; backdrop-filter:blur(16px) saturate(180%); -webkit-backdrop-filter:blur(16px) saturate(180%);
  box-shadow: 0 1px 0 rgba(255,255,255,0.6) inset, 0 4px 10px -6px rgba(20,20,40,0.22);
}
.chip.soft-violet { background:rgba(139,92,246,0.14); color:#6D28D9; border:1px solid rgba(139,92,246,0.32); }
.chip.soft-hot { background:rgba(255,111,168,0.16); color:#C22770; border:1px solid rgba(255,111,168,0.35); }
.chip.soft-good { background:rgba(27,166,114,0.14); color:#12805A; border:1px solid rgba(27,166,114,0.32); }
/* Inside any dark surface (the metric-strip glass-card on the marketing
   canvas, or now the whole dashboard's profile/campaign cards) the light
   chip recipe above has dark-on-dark text — brighten and soften instead.
   Grey glass fill with the color carried only by the border + a tight glow,
   same "ring reserved for meaning" logic as the stat tiles, not a tinted
   fill — and a much smaller glow radius than the tiles get, since a chip
   this size reads as a blob rather than a ring if the glow is too wide. */
.glass-card .chip.soft-good, .browser-content .chip.soft-good, .ipad-screen .chip.soft-good,
.browser-content .chip.soft-violet, .ipad-screen .chip.soft-violet,
.browser-content .chip.soft-hot, .ipad-screen .chip.soft-hot {
  background: rgba(255,255,255,0.07);
}
.glass-card .chip.soft-good, .browser-content .chip.soft-good, .ipad-screen .chip.soft-good {
  color:#6EE7B7; border-color:rgba(110,231,183,0.5);
  box-shadow: 0 1px 0 rgba(255,255,255,0.15) inset, 0 0 8px -2px rgba(27,166,114,0.5);
}
.browser-content .chip.soft-violet, .ipad-screen .chip.soft-violet {
  color:#D8C6FF; border-color:rgba(196,171,255,0.5);
  box-shadow: 0 1px 0 rgba(255,255,255,0.15) inset, 0 0 8px -2px rgba(139,92,246,0.5);
}
.browser-content .chip.soft-hot, .ipad-screen .chip.soft-hot {
  color:#FFC2DD; border-color:rgba(255,182,213,0.5);
  box-shadow: 0 1px 0 rgba(255,255,255,0.15) inset, 0 0 8px -2px rgba(255,111,168,0.5);
}

/* --------------------------------------------------------- ad preview */
/* Same pattern as the real Coexa/Don ad dashboard's "Exactly As Prospects
   See It" panel (founder-dashboard, Ads tab): a pill tab strip switching
   placements, above a dark recessed frame holding the preview. That real
   panel embeds a live Meta-API iframe; this mock has no backend to source
   one from, so the preview itself is a hand-built, accurate mockup of the
   creative instead. Lives inside the hero campaign card as a collapsible
   drawer rather than its own section — the ad only means something in the
   context of the campaign that ran it. */
.ad-preview-toggle {
  display:inline-flex; align-items:center; gap:6px; align-self:flex-start;
  font-size:12px; font-weight:700; color:var(--ink-dim);
  background:none; border:none; padding:0; cursor:pointer; font-family:inherit;
}
.ad-preview-toggle:hover { color:var(--ink); }
.ad-preview-toggle .chev { transition:transform .2s ease; }
.ad-preview-toggle.open .chev { transform:rotate(180deg); }
/* Tier 4 of ORB_DESIGN_GUIDE.md — a small glowing accent, not a full orb,
   marking "there's rich content behind this control." Pulses to draw the
   eye before it's opened; goes still once the content is already visible. */
.ad-preview-toggle .accent-dot {
  width:7px; height:7px; border-radius:50%; flex-shrink:0;
  background:linear-gradient(135deg, var(--grad-a), var(--grad-b));
  animation: accentPulse 2.2s ease-in-out infinite;
}
.ad-preview-toggle.open .accent-dot { animation:none; opacity:0.5; box-shadow:none; }
@keyframes accentPulse {
  0%, 100% { box-shadow: 0 0 0 0 rgba(139,92,246,0.55); }
  50% { box-shadow: 0 0 7px 3px rgba(139,92,246,0.5); }
}
.ad-preview-drawer { margin-top:-4px; }
.ad-preview-label { font-size:10.5px; font-weight:700; color:var(--ink-faint); letter-spacing:0.02em; text-transform:uppercase; margin-bottom:10px; }
.ad-preview-tab-strip { display:flex; gap:8px; margin-bottom:14px; }
.ad-preview-tab {
  padding:7px 16px; border-radius:999px; font-size:12px; font-weight:700; color:var(--ink-dim);
  background:rgba(255,255,255,0.06); border:1px solid rgba(255,255,255,0.16);
  font-family:inherit; cursor:pointer; transition:background .15s,border-color .15s,color .15s;
}
.ad-preview-tab:hover { border-color:rgba(139,92,246,0.5); color:var(--ink); }
.ad-preview-tab.on { color:#fff; background:linear-gradient(135deg, var(--grad-a), var(--grad-b)); border-color:transparent; }
.ad-preview-frame {
  border:1px solid var(--border); border-radius:16px; background:rgba(0,0,0,0.22);
  display:flex; justify-content:center; padding:18px;
}
.ad-mock { width:100%; max-width:300px; background:#0E0C14; border-radius:14px; overflow:hidden; border:1px solid rgba(255,255,255,0.1); }
.ad-avatar { width:32px; height:32px; border-radius:50%; flex-shrink:0; background:radial-gradient(circle at 35% 30%, #E4D9FF, var(--grad-a) 45%, var(--grad-b) 100%); }
.ad-page-name { font-size:12.5px; font-weight:700; color:#fff; }
.ad-sponsored { font-size:10.5px; color:rgba(255,255,255,0.5); display:flex; align-items:center; gap:4px; margin-top:1px; }
.ad-media { position:relative; display:flex; align-items:center; justify-content:center; background:linear-gradient(150deg, #7C3AED, #FF6FA8 60%, #FFB86B); }
.ad-play {
  width:50px; height:50px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  background:rgba(255,255,255,0.2); backdrop-filter:blur(6px); border:1.5px solid rgba(255,255,255,0.55);
  color:#fff; font-size:17px; padding-left:3px;
}
.ad-mock-feed .ad-feed-head { display:flex; align-items:center; gap:10px; padding:12px 14px; }
.ad-caption { padding:0 14px 10px; font-size:12px; line-height:1.4; color:rgba(255,255,255,0.88); }
.ad-media-feed { aspect-ratio:1/1; }
.ad-cta-bar { display:flex; align-items:center; justify-content:space-between; padding:10px 14px; background:rgba(255,255,255,0.04); border-top:1px solid rgba(255,255,255,0.08); }
.ad-cta-domain { font-size:10.5px; color:rgba(255,255,255,0.5); text-transform:uppercase; letter-spacing:0.03em; font-weight:700; }
.ad-cta-btn { font-size:12px; font-weight:700; color:#18161E; background:#fff; border:none; border-radius:8px; padding:7px 14px; font-family:inherit; }
.ad-engage-row { display:flex; gap:16px; padding:10px 14px 12px; font-size:11.5px; color:rgba(255,255,255,0.65); }
.ad-mock-story { max-width:190px; aspect-ratio:9/16; display:flex; flex-direction:column; position:relative; }
.ad-story-bars { display:flex; gap:4px; padding:8px 10px 0; }
.ad-story-bars span { flex:1; height:2.5px; border-radius:2px; background:rgba(255,255,255,0.28); }
.ad-story-bars span.on { background:#fff; }
.ad-story-head { display:flex; align-items:center; gap:8px; padding:8px 10px; }
.ad-story-head .ad-avatar { width:24px; height:24px; }
.ad-story-head .ad-page-name { font-size:11.5px; }
.ad-story-head .ad-sponsored { font-size:10px; margin-top:0; }
.ad-media-story { flex:1; }
.ad-story-cta {
  position:absolute; left:50%; bottom:16px; transform:translateX(-50%); white-space:nowrap;
  font-size:12px; font-weight:700; color:#18161E; background:#fff; border-radius:999px; padding:9px 18px;
}

/* ------------------------------------------------------------ campaigns */
/* An asymmetric card grid — one hero card for the top performer spanning
   the full column height, three compact cards stacked beside it — instead
   of a table where every row carries equal visual weight regardless of
   performance. */
.campaign-grid { display:grid; grid-template-columns:1.5fr 1fr; grid-auto-rows:min-content; gap:14px; }
.campaign-card {
  position:relative; overflow:hidden;
  background:var(--bg-card-solid); backdrop-filter:blur(16px) saturate(200%); -webkit-backdrop-filter:blur(16px) saturate(200%);
  border:1.5px solid var(--border); border-radius:var(--radius-md); padding:18px 20px;
  box-shadow:0 2px 8px rgba(0,0,0,0.16), 0 16px 30px -18px rgba(0,0,0,0.35);
}
.campaign-card::before {
  content:""; position:absolute; top:-60%; left:-30%; width:45%; height:220%; z-index:0;
  background:linear-gradient(75deg, transparent 38%, rgba(255,255,255,0.28) 50%, rgba(255,255,255,0.28) 54%, transparent 66%);
  pointer-events:none;
}
.campaign-card::after {
  content:""; position:absolute; inset:0; border-radius:inherit; z-index:0; pointer-events:none;
  box-shadow: inset 0 1.5px 0 rgba(255,255,255,0.3);
}
.campaign-card > * { position:relative; z-index:1; }
/* No justify-content:space-between — the hero card now holds a collapsible
   ad-preview drawer, so its content height varies and shouldn't be forced
   to distribute across fixed slots; a flat gap flows naturally instead. */
.campaign-card.hero { grid-row:span 3; display:flex; flex-direction:column; gap:16px; padding:26px; }
.campaign-card.hero .campaign-name { font-size:16px; }
.campaign-name { font-weight:800; font-size:13.5px; color:var(--ink); margin-bottom:12px; }
.campaign-stats { display:flex; gap:18px; flex-wrap:wrap; }
.campaign-stats > div { display:flex; flex-direction:column; }
.cs-val { font-weight:800; font-size:15px; color:var(--ink); font-variant-numeric:tabular-nums; }
.cs-lbl { font-size:10.5px; color:var(--ink-faint); font-weight:600; margin-top:2px; }
.mini-bar { height:4px; border-radius:2px; background:rgba(255,255,255,0.14); margin-top:14px; overflow:hidden; }
.mini-bar-fill { height:100%; border-radius:2px; background:linear-gradient(90deg, var(--source-ad), var(--source-engaged)); }
.campaign-chips { display:flex; gap:8px; flex-wrap:wrap; }
@media (max-width:900px) {
  .campaign-grid { grid-template-columns:1fr; }
  .campaign-card.hero { grid-row:auto; }
}

/* ------------------------------------------------------------- kanban */
/* A connected node-flow line (see renderFlowConnector in charts.js) sits
   directly above this row, its node x-positions assuming these tiles
   divide the width into even flex:1 segments — keep this row's gap small
   relative to tile width so the nodes stay visually aligned with them. */
.flow-wrap { display:flex; flex-direction:column; }
.kanban-row { display:flex; gap:8px; overflow-x:auto; padding-bottom:4px; }
.kanban-col {
  position:relative; overflow:hidden;
  flex:1; min-width:110px; padding:14px 12px; text-align:center; border-radius:var(--radius-sm);
  background:var(--bg-card-solid);
  backdrop-filter:blur(15px) saturate(200%); -webkit-backdrop-filter:blur(15px) saturate(200%);
  border:1.5px solid var(--border);
  box-shadow:0 2px 8px rgba(0,0,0,0.14), 0 14px 26px -16px rgba(0,0,0,0.35);
}
.kanban-col .k-num, .kanban-col .k-label { position:relative; z-index:1; }
.kanban-col .k-num { font-size:22px; font-weight:800; color:var(--ink); font-variant-numeric: tabular-nums; }
.kanban-col .k-label { font-size:10.5px; color:var(--ink-dim); margin-top:4px; font-weight:600; }

/* This file loads after sections.css, so a same-specificity mobile override
   placed there would lose the cascade tie regardless of its media query —
   it has to live here to actually win. */
@media (max-width: 900px) {
  .stat-row.bento { grid-template-columns: repeat(2, 1fr); }
  .stat-tile.span-2, .stat-tile.span-4 { grid-column: span 2; }
}
