/* ==========================================================================
   BridgeBI — the analyst panel
   --------------------------------------------------------------------------
   Slide-in side panel + a launcher. Uses the app's own custom properties
   (--navy, --off-white, …) so it inherits any future palette change instead of
   hard-coding a second one. Numbers inside answers render in JetBrains Mono
   with tabular figures, same as every other figure in the app — a CPL that
   changes width as it updates reads as a different kind of thing.
   ========================================================================== */

/* ------------------------------- launcher -------------------------------- */

#bbAgentFab {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 9000;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 11px 18px 11px 15px;
  border: none;
  border-radius: 999px;
  background: var(--navy, #0D2A5E);
  color: #fff;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 14px;
  font-weight: 600;
  letter-spacing: 0.01em;
  cursor: pointer;
  box-shadow: 0 6px 24px rgba(13, 42, 94, 0.28);
  transition: transform var(--duration-normal, 200ms) var(--ease-out-quint, cubic-bezier(0.22, 1, 0.36, 1)),
              box-shadow var(--duration-normal, 200ms) ease,
              opacity var(--duration-fast, 120ms) ease;
}
#bbAgentFab:hover { transform: translateY(-2px); box-shadow: 0 10px 30px rgba(13, 42, 94, 0.34); }
#bbAgentFab:active { transform: translateY(0); }
#bbAgentFab.hidden { opacity: 0; pointer-events: none; transform: translateY(8px); }

.bb-agent-fab-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #DEB83C;
  box-shadow: 0 0 0 0 rgba(222, 184, 60, 0.7);
  /* Deliberately NOT animated at rest. An auto-pulsing chat launcher is the
     most-complained-about pattern in the literature — motion nobody asked for,
     forever, in the corner of the eye. It pulses only while the agent is
     actually working, which makes the motion mean something. */
}
#bbAgentFab.is-thinking .bb-agent-fab-dot { animation: bb-agent-pulse 2.4s ease-out infinite; }
@keyframes bb-agent-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(222, 184, 60, 0.6); }
  70%  { box-shadow: 0 0 0 7px rgba(222, 184, 60, 0); }
  100% { box-shadow: 0 0 0 0 rgba(222, 184, 60, 0); }
}

/* -------------------------------- panel ---------------------------------- */
/* A floating card, not a slab welded to the edge of the window. Insetting it and
   rounding all four corners is most of the difference between "a drawer opened"
   and "something arrived" — the dashboard stays visible around it, so the panel
   reads as layered over your work rather than as having replaced part of it. */

#bbAgentPanel {
  position: fixed;
  top: 14px;
  right: 14px;
  bottom: 14px;
  z-index: 9001;
  display: flex;
  flex-direction: column;
  width: clamp(340px, 38vw, 560px);
  max-width: calc(100vw - 28px);
  border-radius: 22px;
  font-family: 'Outfit', system-ui, sans-serif;
  color: var(--text, #1a1a1a);
  /* Frosted, so the dashboard behind stays legible as texture. */
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.92), rgba(255, 255, 255, 0.78));
  backdrop-filter: blur(26px) saturate(1.7);
  -webkit-backdrop-filter: blur(26px) saturate(1.7);
  border: 1px solid rgba(0, 38, 58, 0.10);
  box-shadow:
    0 30px 60px -24px rgba(0, 38, 58, 0.42),
    0 2px 8px -2px rgba(0, 38, 58, 0.14),
    inset 0 1px 1.5px 0 rgba(255, 255, 255, 0.95);
  /* Arrives with a slight scale as well as a slide: pure translation reads
     mechanical, a touch of scale reads like it came forward. */
  /* cubic-bezier(0.32, 0.72, 0, 1) is the iOS sheet curve — the most-copied
     panel easing in modern web UI. 460ms sounds long for a small element but is
     right for a large surface: travel distance and duration scale together.
     No scale: slide+scale suits a CENTRED dialog, an edge-anchored drawer
     should just slide, or it reads as zooming out of the wall. */
  transform: translateX(calc(100% + 22px));
  opacity: 0;
  visibility: hidden;
  transition:
    transform 460ms cubic-bezier(0.32, 0.72, 0, 1),
    opacity 220ms ease-out,
    visibility 0s linear 460ms;
}
#bbAgentPanel.open {
  transform: translateX(0);
  opacity: 1;
  visibility: visible;
  transition-delay: 0s, 0s, 0s;
}

/* The same gold comet as the page, on the same rule: still at rest, running
   while the agent works. Masked to the 1.5px edge — unmasked it sits behind the
   glass and the backdrop-filter smears it across the whole panel. */
#bbAgentPanel::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.5px;
  background: transparent;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
  opacity: 0;
  transition: opacity 400ms ease;
}
#bbAgentPanel.is-thinking::before {
  opacity: 1;
  background: conic-gradient(from var(--ai-angle),
      rgba(222, 184, 60, 0)    0deg,
      rgba(222, 184, 60, 0)    52deg,
      rgba(246, 226, 155, 0.95) 96deg,
      rgba(222, 184, 60, 1)     122deg,
      rgba(168, 134, 42, 0.55)  158deg,
      rgba(222, 184, 60, 0)     205deg,
      rgba(222, 184, 60, 0)     360deg);
  animation: ai-ring-spin 2.8s linear infinite;
}
/* Halo, outside the card, so working is visible even at the edge of vision. */
#bbAgentPanel::after {
  content: '';
  position: absolute;
  inset: -10px;
  z-index: -1;
  border-radius: 30px;
  background: radial-gradient(60% 50% at 50% 100%, rgba(222, 184, 60, 0.55), transparent 70%);
  filter: blur(24px);
  opacity: 0;
  pointer-events: none;
  transition: opacity 500ms ease;
}
#bbAgentPanel.is-thinking::after { opacity: 0.85; }

/* --------------------------------- head ---------------------------------- */
/* Transparent, not a navy bar. A solid header on a frosted card cuts it in two;
   letting the glass run behind the title keeps it one object. */

.bb-agent-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  flex: none;
  padding: 14px 14px 12px 16px;
  border-bottom: 1px solid rgba(0, 38, 58, 0.07);
}
.bb-agent-brand { display: flex; align-items: center; gap: 10px; min-width: 0; }
.bb-agent-mark {
  flex: none;
  width: 20px;
  height: 20px;
  display: grid;
  place-items: center;
}
.bb-agent-title { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.bb-agent-title strong {
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--navy, #0D2A5E);
}

.bb-agent-status {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: 0.02em;
  color: #8a8a8a;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.bb-agent-status.ok  { color: #6f8f5f; }
.bb-agent-status.bad { color: var(--red, #B23B3B); }

.bb-agent-actions { display: flex; align-items: center; gap: 4px; flex: none; }
/* A control you can only find by hovering is a control that is not there. These
   three (new / expand / close) carry a RESTING surface and a hairline, so the
   header reads as three buttons before the pointer arrives — the icon alone at
   28px on frosted glass disappeared into it. 32px is also the smallest square
   that is comfortable to hit. */
.bb-agent-act {
  width: 32px;
  height: 32px;
  padding: 0;   /* index.html's bare `button` rule pads 10/16 — see .ai-session-del */
  display: grid;
  place-items: center;
  border: none;
  border-radius: 9px;
  background: rgba(13, 42, 94, 0.05);
  box-shadow: inset 0 0 0 1px rgba(13, 42, 94, 0.09);
  color: #55607a;
  cursor: pointer;
  transition: background 120ms ease-out, color 120ms ease-out, box-shadow 120ms ease-out,
              transform 120ms cubic-bezier(0.16, 1, 0.3, 1);
}
/* `max-width: none` is load-bearing, not decoration: responsive.css caps svg at
   100%, which resolves to 0 inside this grid button and rendered all three as
   empty squares (measured 0×15, Aug 2026). The global exemption in
   responsive.css covers it; this repeats it so restyling the button can never
   silently re-hide the icon. */
.bb-agent-act svg { width: 16px; height: 16px; max-width: none; flex: none; }
.bb-agent-act:hover {
  background: rgba(13, 42, 94, 0.11);
  box-shadow: inset 0 0 0 1px rgba(13, 42, 94, 0.16);
  color: var(--navy, #0D2A5E);
  transform: translateY(-1px);
}
/* Close is the destructive-ish one of the three, so it warms on hover instead
   of going navy — the difference is what stops a mis-click. */
#bbAgentClose:hover {
  background: rgba(178, 59, 59, 0.12);
  box-shadow: inset 0 0 0 1px rgba(178, 59, 59, 0.22);
  color: var(--red, #B23B3B);
}
.bb-agent-act:active { transform: scale(0.94); transition-duration: 80ms; }
.bb-agent-card:active { transform: scale(0.985); transition-duration: 80ms; }
.ai-new-chat:active { transform: scale(0.98); transition-duration: 80ms; }

/* The chat's container inside the panel. The chat element itself is re-parented
   between here and the AI page, so both hosts have to give it a flex column with
   a real height — otherwise the log's `flex: 1; overflow-y: auto` has nothing to
   size against and the whole conversation grows the page instead of scrolling. */
.bb-agent-host {
  position: relative;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  /* Clip to the card's radius so the aurora and the log's scroll never square
     off the bottom corners the panel just spent a border-radius earning. */
  overflow: hidden;
  border-radius: 0 0 21px 21px;
}
#bbAgentChat {
  position: relative;   /* positioning context for the jump-to-latest pill */
  z-index: 1;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

/* The same bottom-anchored wash as the page, scaled for a narrower column.
   Sits on .bb-agent-host so it is UNDER the chat rather than painted over it —
   a pseudo-element on #bbAgentChat would tint every word in the panel. */
.bb-agent-host::before {
  content: '';
  position: absolute;
  left: -25%;
  right: -25%;
  bottom: -12%;
  height: 42%;
  z-index: 0;
  pointer-events: none;
  filter: blur(34px);
  opacity: 0.8;
  background:
    radial-gradient(56% 70% at 20% 76%, rgba(0, 38, 58, 0.34), transparent 72%),
    radial-gradient(52% 64% at 60% 96%, rgba(222, 184, 60, 0.40), transparent 72%),
    radial-gradient(46% 58% at 92% 70%, rgba(13, 42, 94, 0.28), transparent 74%);
  animation: ai-aurora-drift 26s ease-in-out infinite alternate;
}

/* ========================== BridgeBI Agent page =========================== */
/* Every selector here is class-based and `ai-` prefixed. The markup uses plain
   divs rather than <header>/<section>/<aside> because index.html styles the bare
   `header` tag as the app's sticky navy chrome (92px tall, z-index 100) — a
   semantic <header> inside this page inherited all of it and painted over the
   conversation. Bare tags are not safe here; classes are. */

.ai-page {
  display: flex;
  flex-direction: column;
  gap: 16px;
  padding-top: 22px;
  padding-bottom: 26px;
}

.ai-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 18px;
  flex-wrap: wrap;
}
.ai-top-main { min-width: 0; flex: 1 1 340px; }

.ai-title {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 24px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--navy, #0D2A5E);
}
/* .ai-sub is deliberately gone — the title carries the page on its own. */

.ai-status {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 7px;
  padding: 6px 12px;
  border: 1px solid var(--border, #e0ddd6);
  border-radius: 999px;
  background: #fff;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10.5px;
  white-space: nowrap;
  color: var(--text-muted, #5a5a5a);
}
.ai-status-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--text-muted, #5a5a5a);
}
.ai-status.ok  { border-color: #B6DCC6; color: var(--green, #2E7D52); }
.ai-status.ok  .ai-status-dot { background: var(--green, #2E7D52); }
.ai-status.bad { border-color: #E4BDBD; color: var(--red, #B23B3B); }
.ai-status.bad .ai-status-dot { background: var(--red, #B23B3B); }

/* ---- the ticker --------------------------------------------------------- */
/* A stock tape for divisions and programs. Dark, because that is the visual
   language of a ticker and the contrast is what makes the green/red readable at
   a glance while scrolling. */
.ai-ticker {
  position: relative;
  overflow: hidden;
  border-radius: 12px;
  background: linear-gradient(180deg, #10306c, var(--navy, #0D2A5E));
  box-shadow:
    0 10px 24px -16px rgba(13, 42, 94, 0.8),
    inset 0 1px 0 0 rgba(255, 255, 255, 0.10);
}
/* Fade the ends so items enter and leave instead of being sliced off by a hard
   edge — the single detail that separates a tape from a clipped div. */
.ai-ticker.is-live::before,
.ai-ticker.is-live::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  width: 56px;
  z-index: 2;
  pointer-events: none;
}
.ai-ticker.is-live::before { left: 0;  background: linear-gradient(90deg, #10306c, transparent); }
.ai-ticker.is-live::after  { right: 0; background: linear-gradient(270deg, var(--navy, #0D2A5E), transparent); }

.ai-ticker-track {
  display: flex;
  align-items: center;
  width: max-content;
  padding: 9px 0;
  animation: ai-ticker-roll 60s linear infinite;
}
/* Exactly -50%: the track holds two identical runs, so the second is arriving
   as the first leaves and the loop has no seam. */
@keyframes ai-ticker-roll {
  from { transform: translate3d(0, 0, 0); }
  to   { transform: translate3d(-50%, 0, 0); }
}
.ai-ticker:hover .ai-ticker-track { animation-play-state: paused; }

.ai-tick {
  display: inline-flex;
  align-items: baseline;
  gap: 7px;
  padding: 0 16px;
  border-right: 1px solid rgba(255, 255, 255, 0.10);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  white-space: nowrap;
}
/* Names are words now, not codes, so they get the body face. Monospace with
   letter-spacing is right for a 4-letter symbol and wrong for "AD Nursing". The
   figures stay mono — those still need to line up. */
.ai-tick-sym {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0;
  color: #fff;
}
.ai-tick.is-div .ai-tick-sym { color: #DEB83C; }   /* divisions read as indices */
.ai-tick-val { color: rgba(255, 255, 255, 0.72); }
.ai-tick-move.up   { color: #6FD69B; }
.ai-tick-move.down { color: #F08C8C; }
.ai-tick-move.flat { color: rgba(255, 255, 255, 0.45); }

.ai-ticker-empty {
  padding: 10px 16px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 11.5px;
  color: rgba(255, 255, 255, 0.6);
}

/* ---- context line -------------------------------------------------------- */
/* Demoted to one quiet row. Still the trust anchor — when an answer looks wrong
   the cause is almost always the window — but it no longer competes with the
   tape for the top of the page. */
.ai-context {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 22px;
  padding: 0 2px;
}
.ai-ctx-item { display: inline-flex; align-items: baseline; gap: 7px; min-width: 0; }
.ai-ctx-k {
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #8a8a8a;
}
.ai-ctx-v {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  color: var(--navy, #0D2A5E);
  white-space: nowrap;
}
.ai-ctx-empty { font-size: 12px; color: var(--text-muted, #5a5a5a); }

/* Conversations left, chat right — where every chat product puts them, which is
   the only reason that ordering is right: it is where people look. */
.ai-layout {
  display: grid;
  grid-template-columns: 236px minmax(0, 1fr);
  gap: 16px;
  align-items: stretch;
}

/* Both columns share one height so the rail lines up with the chat instead of
   floating beside it. The log scrolls inside; the page never grows with the
   conversation. */
.ai-chat-col,
.ai-sessions { height: clamp(440px, calc(100vh - 320px), 720px); min-width: 0; }

/* ---- the living border -------------------------------------------------- */
/* A conic gradient can't be animated directly — `from 0deg` is not an
   interpolatable value to the engine. Registering the angle with @property
   gives it a type, which makes it animatable, and the whole thing then runs on
   the compositor instead of repainting. `inherits: true` on purpose: the glow
   pseudo-element reads the SAME animated angle, so ring and halo stay in phase.
   Without @property support the ring simply renders static at 0deg — it
   degrades to a fixed gradient border rather than disappearing. */
/* inherits:false is a performance decision, not a style one. An inherited
   custom property that changes every frame forces a style recalculation on
   EVERY descendant — measured at ~8ms/frame across a large subtree, and the
   chat column has the whole conversation under it. The ring and its halo each
   animate their own copy instead; they stay in phase because they share one
   animation name and start together. */
@property --ai-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

/* Northbridge gold (#DEB83C) over the deep brand navy (#00263A). The earlier
   teal was not a brand colour at all — it came from the reference site.

   The ring is STILL at rest and travels only while the agent is working. A
   border that spins permanently is wallpaper; one that starts moving when you
   ask a question is the answer arriving. It is also the calmer default for a
   page someone leaves open all day. */
.ai-chat-col {
  position: relative;
  display: flex;
  padding: 1.5px;               /* this padding IS the ring's thickness */
  border-radius: 20px;
  background: none;
  /* One travelling comet of gold, not a rainbow: it reads as a single polished
     edge catching light rather than a colour wheel. */
  --ai-ring: conic-gradient(from var(--ai-angle),
      rgba(222, 184, 60, 0)    0deg,
      rgba(222, 184, 60, 0)    52deg,
      rgba(246, 226, 155, 0.95) 96deg,
      rgba(222, 184, 60, 1)     122deg,
      rgba(168, 134, 42, 0.55)  158deg,
      rgba(222, 184, 60, 0)     205deg,
      rgba(222, 184, 60, 0)     360deg);
}
@keyframes ai-ring-spin { to { --ai-angle: 360deg; } }

/* The ring, masked down to its 1.5px edge. The mask is not cosmetic: an
   unmasked gradient filling inset:0 sits BEHIND the glass panel, and the
   panel's backdrop-filter happily samples it — which tinted the entire column
   with the conic instead of showing it only at the border. XOR of border-box
   against content-box leaves exactly the padding band. */
.ai-chat-col::before {
  content: '';
  position: absolute;
  inset: 0;
  border-radius: inherit;
  /* At rest: a plain navy hairline, so the panel still has a defined edge. */
  background: rgba(0, 38, 58, 0.13);
  padding: 1.5px;
  box-sizing: border-box;
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  pointer-events: none;
}
/* Working: the gold comet runs the edge. */
.ai-chat-col.is-thinking::before {
  background: var(--ai-ring);
  animation: ai-ring-spin 2.8s linear infinite;
}
/* Its halo. Pushed further out and blurred wider than instinct suggests,
   because the panel is translucent — anything sitting directly behind the glass
   gets sampled by its backdrop-filter and shows up as a stain on the inside. */
.ai-chat-col::after {
  content: '';
  position: absolute;
  inset: -9px;
  z-index: -1;
  border-radius: 28px;
  background: var(--ai-ring);
  filter: blur(22px);
  opacity: 0;                     /* no halo at rest — the edge alone is enough */
  pointer-events: none;
  transition: opacity 500ms ease;
}
/* The border is a status light, not decoration: gold appears when the agent
   starts working and goes when it stops. That is the honest "system is working"
   cue, and it costs no layout. */
.ai-chat-col:focus-within::after { opacity: 0.16; }
.ai-chat-col.is-thinking::after {
  opacity: 0.75;
  animation: ai-ring-spin 2.8s linear infinite;
}

/* ---- the glass panel inside the ring ------------------------------------ */
#bbAgentChat.in-page {
  position: relative;
  z-index: 1;                   /* above ::before, so the ring reads as a border */
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  border-radius: 18.5px;
  overflow: hidden;
  background: linear-gradient(160deg, rgba(255, 255, 255, 0.88), rgba(255, 255, 255, 0.68));
  backdrop-filter: blur(22px) saturate(1.75);
  -webkit-backdrop-filter: blur(22px) saturate(1.75);
  box-shadow: inset 0 1px 1.5px 0 rgba(255, 255, 255, 0.9);
}

/* ---- aurora ------------------------------------------------------------- */
/* Bottom-anchored so the top of the column stays paper-white and text never
   fights the colour. CSS radial gradients rather than the reference's <canvas>:
   same read, no rAF loop to leak on a page left open all day. One composited
   layer animating transform only. */
/* A pseudo-element paints ON TOP of its own element's background, so this wash
   is over the glass, not under it. That is fine — as long as it is confined to
   the bottom third. Spread across the full height it stopped being depth and
   became a tint on every word in the panel.
   Occupies 68%–114% of the column; blur bleeds it to about 64%. Everything
   above that stays paper. */
#bbAgentChat.in-page::before {
  content: '';
  position: absolute;
  left: -18%;
  right: -18%;
  bottom: -14%;
  height: 46%;
  z-index: 0;
  pointer-events: none;
  filter: blur(34px);
  opacity: 0.85;
  /* Brand only: deep navy and Northbridge gold. */
  background:
    radial-gradient(52% 66% at 18% 74%, rgba(0, 38, 58, 0.42), transparent 72%),
    radial-gradient(48% 60% at 56% 96%, rgba(222, 184, 60, 0.44), transparent 72%),
    radial-gradient(44% 56% at 88% 70%, rgba(13, 42, 94, 0.34), transparent 74%);
  animation: ai-aurora-drift 26s ease-in-out infinite alternate;
}
/* A last touch of white across the very top, so the newest exchange always
   starts against paper no matter how the wash drifts. */
#bbAgentChat.in-page::after {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 22%;
  z-index: 0;
  pointer-events: none;
  background: linear-gradient(rgba(255, 255, 255, 0.5) 0%, rgba(255, 255, 255, 0) 100%);
}

@keyframes ai-aurora-drift {
  0%   { transform: translate3d(0, 0, 0) scale(1); }
  50%  { transform: translate3d(-4%, -3%, 0) scale(1.10); }
  100% { transform: translate3d(5%, 2%, 0) scale(1.04); }
}

/* Everything the user reads sits above the wash. */
.bb-agent-log,
.bb-agent-suggest,
.bb-agent-form { position: relative; z-index: 1; }

/* ---- conversations rail -------------------------------------------------- */

.ai-sessions {
  display: flex;
  flex-direction: column;
  gap: 10px;
  min-width: 0;
  overflow: hidden;
}

.ai-new-chat {
  flex: none;
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px 13px;
  border: 1px solid rgba(13, 42, 94, 0.12);
  border-radius: 12px;
  background: rgba(255, 255, 255, 0.8);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-family: inherit;
  font-size: 12.5px;
  font-weight: 500;
  color: var(--navy, #0D2A5E);
  cursor: pointer;
  transition: border-color 180ms ease, transform 180ms cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 180ms ease;
}
.ai-new-chat svg { width: 15px; height: 15px; flex: none; }
.ai-new-chat span { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-new-chat:hover {
  border-color: rgba(13, 42, 94, 0.3);
  transform: translateY(-1px);
  box-shadow: 0 8px 16px -10px rgba(13, 42, 94, 0.6);
}

/* min-width:0 at EVERY level of the chain. A flex/grid item defaults to
   min-width:auto, which means "at least as wide as my content" — and a nowrap
   title is content that never shrinks. Miss one link and the rail widens past
   its column instead of ellipsising, which is what clipped titles mid-word. */
.ai-session-list {
  flex: 1;
  min-width: 0;
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden;
  display: flex;
  flex-direction: column;
  gap: 2px;
  scrollbar-width: thin;
}

.ai-session {
  display: flex;
  align-items: stretch;
  min-width: 0;
  overflow: hidden;
  border-radius: 9px;
  transition: background 160ms ease;
}
.ai-session:hover { background: rgba(13, 42, 94, 0.05); }
.ai-session.is-active { background: rgba(13, 42, 94, 0.09); }

.ai-session-open {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  /* The bare `button` rule in index.html sets `align-items: center`, and in a
     COLUMN flex that centres the cross axis — i.e. horizontally. Every
     conversation title in the rail was centre-aligned, which reads as a
     misrendered list rather than a history. `text-align: left` alone does not
     fix it: the items themselves were shrink-wrapped and centred.
     Same trap as .bb-agent-card and .bb-viz-card. */
  align-items: stretch;
  gap: 2px;
  padding: 8px 4px 8px 10px;
  border: none;
  background: none;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
}
.ai-session-title {
  max-width: 100%;
  font-size: 12.5px;
  color: var(--text, #1a1a1a);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ai-session.is-active .ai-session-title { color: var(--navy, #0D2A5E); font-weight: 600; }
.ai-session-time { font-size: 10px; color: #8a8a8a; }

/* Quiet at rest, full on hover. It used to be opacity 0, which is worse than it
   sounds: an opacity-0 button still takes the click, so on a touch screen —
   where the hover that reveals it never fires — this was an invisible control
   that deleted a conversation. A faint glyph keeps the list from reading as a
   wall of X's without lying about what is there. */
/* `padding: 0` and `display: grid` are load-bearing, not tidying.
   index.html styles the BARE `button` tag as `display: inline-flex; padding:
   10px 16px`. With `box-sizing: border-box`, a 26px-wide icon button therefore
   has a content box of 26 - 32 = ZERO px, and the icon — a shrinkable flex item
   — collapsed to 0 wide. Measured 0×13px: the trash on every conversation row
   was invisible, hover or no hover. A grid item does not shrink like that, and
   `flex: none` on the icon covers the case where someone switches it back.
   Any icon button in this app narrower than 32px needs this reset. */
.ai-session-del {
  flex: none;
  width: 28px;
  padding: 0;
  display: grid;
  place-items: center;
  border: none;
  background: none;
  color: #9a9a9a;
  cursor: pointer;
  opacity: 0.32;
  transition: opacity 160ms ease, color 160ms ease;
}
.ai-session-del svg { width: 14px; height: 14px; max-width: none; flex: none; }
.ai-session:hover .ai-session-del { opacity: 1; }
.ai-session-del:hover { color: var(--red, #B23B3B); opacity: 1; }
.ai-session-del:focus-visible { opacity: 1; }
/* Touch has no hover, so the resting state is the only state. */
@media (hover: none) { .ai-session-del { opacity: 0.55; } }

.ai-session-empty { padding: 10px; font-size: 11.5px; color: #8a8a8a; }

/* ---- what it has learned ------------------------------------------------- */

.ai-memory {
  flex: none;
  margin-top: 10px;
  padding-top: 11px;
  border-top: 1px solid rgba(13, 42, 94, 0.09);
}
.ai-memory-head {
  display: flex;
  align-items: center;
  gap: 7px;
  width: 100%;
  padding: 4px 2px;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 10.5px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--text-muted, #5a5a5a);
  cursor: pointer;
}
.ai-memory-label { flex: 1; min-width: 0; text-align: left; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ai-memory-head:hover { color: var(--navy, #0D2A5E); }
.ai-memory-caret {
  flex: none;
  width: 12px;
  height: 12px;
  color: #a8a29a;
  transition: transform 200ms cubic-bezier(0.22, 1, 0.36, 1), color 160ms ease;
}
.ai-memory.is-open .ai-memory-caret { transform: rotate(90deg); color: var(--navy, #0D2A5E); }
.ai-memory-count {
  flex: none;
  min-width: 18px;
  padding: 1px 6px;
  border-radius: 999px;
  background: rgba(222, 184, 60, 0.22);
  color: #7a6118;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0;
  text-align: center;
}
.ai-memory-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 9px;
  padding-right: 2px;
  max-height: 260px;
  overflow-y: auto;
  scrollbar-width: thin;
}
/* A card sliced flat by the scroll edge reads as a rendering bug. The class is
   set only while there is actually more below, so a list that fits — or one
   scrolled to the end — keeps its last card at full opacity. */
.ai-memory-list.has-more {
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
          mask-image: linear-gradient(to bottom, #000 calc(100% - 22px), transparent);
}

/* Each memory is a card with a coloured rail, not a tinted block. The rail
   carries the kind, so the body text stays one calm colour at one weight —
   the old version shouted the LEAST useful kind (`avoid`, auto-written) in
   red above a wall of raw JSON, which is exactly backwards. */
.ai-mem {
  --mem-accent: #9aa3b0;
  --mem-ink: #5f6b7d;
  position: relative;
  /* flex:none is load-bearing. The list is a height-capped flex column, so a
     default flex-shrink:1 squeezes every card down instead of scrolling —
     which silently cut each memory off mid-sentence at one visible line. */
  flex: none;
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 8px 9px 8px 13px;
  border: 1px solid rgba(13, 42, 94, 0.08);
  border-radius: 9px;
  background: rgba(255, 255, 255, 0.72);
  overflow: hidden;
  transition: border-color 160ms ease, box-shadow 160ms ease, background 160ms ease;
}
/* The rail is a pseudo-element so it can't be knocked out of the flex flow. */
.ai-mem::before {
  content: '';
  position: absolute;
  inset: 0 auto 0 0;
  width: 3px;
  background: var(--mem-accent);
}
.ai-mem:hover {
  border-color: rgba(13, 42, 94, 0.16);
  background: #fff;
  box-shadow: 0 6px 14px -10px rgba(13, 42, 94, 0.55);
}

.ai-mem--correction { --mem-accent: #B23B3B; --mem-ink: #9A3232; }
.ai-mem--preference { --mem-accent: #DEB83C; --mem-ink: #8A7226; }
.ai-mem--vocabulary { --mem-accent: #0D2A5E; --mem-ink: #274372; }
.ai-mem--fact       { --mem-accent: #4A7C8C; --mem-ink: #3D6874; }
/* Machine-written, least useful, so the quietest thing in the list. */
.ai-mem--avoid      { --mem-accent: #C7C1B5; --mem-ink: #857D70; }
.ai-mem--avoid .ai-mem-text { color: #4f4b45; }

.ai-mem-head {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}
.ai-mem-kind {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  min-width: 0;
  font-size: 9px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--mem-ink);
  white-space: nowrap;
}
/* responsive.css sets `svg { max-width: 100% }`, which resolves to 0 for an
   icon inside a flex row — an explicit width AND flex:none, both required. */
.ai-mem-kind svg { flex: none; width: 11px; height: 11px; }
.ai-mem-when {
  flex: 1;
  min-width: 0;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9px;
  color: #a09a92;
  text-align: right;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ai-mem-text {
  margin: 0;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--text, #1a1a1a);
  min-width: 0;
  overflow-wrap: anywhere;
}
/* Three lines, then "Ver todo". A long memory used to run to ten lines and
   push the privacy note off the rail. */
.ai-mem-text.is-clamped {
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow: hidden;
}
.ai-mem-more {
  align-self: flex-start;
  padding: 0;
  border: none;
  background: none;
  font-family: inherit;
  font-size: 10px;
  font-weight: 600;
  color: var(--navy, #0D2A5E);
  cursor: pointer;
  opacity: 0.7;
}
.ai-mem-more:hover { opacity: 1; text-decoration: underline; }

.ai-mem-del {
  flex: none;
  width: 18px;
  height: 18px;
  /* auto so it stays pinned right even when there is no timestamp to push it. */
  margin: 0 -3px 0 auto;
  /* Zero, against index.html's bare `button { padding: 10px 16px }` — see
     .ai-session-del. At 18px wide that padding leaves NO content box. */
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  border-radius: 5px;
  background: none;
  color: #a8a29a;
  cursor: pointer;
  /* Same reasoning as .ai-session-del: never fully invisible, because the button
     is clickable whether or not you can see it. */
  opacity: 0.32;
  transition: opacity 160ms ease, color 160ms ease, background 160ms ease;
}
.ai-mem-del svg { flex: none; width: 11px; height: 11px; max-width: none; }
.ai-mem:hover .ai-mem-del,
.ai-mem-del:focus-visible { opacity: 1; }
.ai-mem-del:hover { color: var(--red, #B23B3B); background: rgba(178, 59, 59, 0.1); }

/* Touch has no hover, so the affordance has to be permanent there. */
@media (hover: none) {
  .ai-mem-del { opacity: 0.55; }
}

.ai-privacy {
  flex: none;
  padding-top: 11px;
  border-top: 1px solid rgba(13, 42, 94, 0.09);
  font-size: 10px;
  line-height: 1.5;
  color: #8a8a8a;
}

@media (max-width: 1000px) {
  .ai-layout { grid-template-columns: minmax(0, 1fr); }
  .ai-sessions { height: auto; max-height: 210px; order: 2; }
  .ai-chat-col { height: clamp(420px, calc(100vh - 300px), 680px); }
}

/* ---------------------------------- log ---------------------------------- */

.bb-agent-log {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 18px;
  display: flex;
  flex-direction: column;
  gap: 12px;
  /* Bottom-anchored WITHOUT justify-content — see below. A short conversation
     stranded at the top of a tall column with a void beneath it is the clearest
     tell of a chat UI that was laid out rather than designed; messages should
     sit next to the thing you type into. */
}

/* THE bottom-anchor, and the reason `justify-content: flex-end` is not here.
   An auto margin only absorbs POSITIVE free space, so it pushes the
   conversation down while the column has room and resolves to 0 the moment the
   content is taller. justify-content keeps aligning at negative free space too
   — it shoves the start of the log above the top edge, where the scrollable
   overflow region does not reach. Measured in Chromium at 40 messages:
   scrollHeight === clientHeight (531px) with the first message 1524px above the
   top, i.e. scrollTop physically cannot get to it. The conversation was still
   there; it was simply unreachable, which is what "I can't scroll up" was.
   Having BOTH does not help: justify-content wins once the margin collapses. */
.bb-agent-log > *:first-child { margin-top: auto; }

/* The blank slate is the exception: it centres. `safe` so that a greeting taller
   than the column falls back to start-alignment instead of re-creating the same
   unreachable-overflow trap on a short viewport. */
.bb-agent-log.is-empty { justify-content: safe center; }
.bb-agent-log.is-empty > *:first-child { margin-top: 0; }

.bb-agent-msg {
  max-width: 78%;
  padding: 11px 14px;
  border-radius: 14px;
  font-size: 13.5px;
  line-height: 1.55;
  animation: bb-agent-in 200ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* 8px over 200ms on a strong ease-out. Three numbers, each load-bearing:
   travel over ~16px reads as the message FLYING in (a marketing-hero value, not
   a chat one); anything past 300ms makes a conversation feel laggy; and an
   ease-out front-loads the movement so it reads as already-arrived by frame 3.
   `ease` was wrong here — it eases IN first, so the row appears to hesitate. */
@keyframes bb-agent-in {
  from { opacity: 0; transform: translateY(8px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* Empty state — centred in the column instead of one lone bubble pinned to the
   top of 500px of nothing. Removed the moment the first message lands. */
.bb-agent-log.is-empty {
  justify-content: safe center;
  align-items: center;
  text-align: center;
}
/* width:100% as well as max-width — the log centres its children with
   `align-items: center`, which sizes a flex item to its content, so max-width
   alone never engages and the card grid collapses to a single column. */
.bb-agent-empty {
  width: 100%;
  max-width: 560px;
  padding: 0 8px;
  animation: bb-agent-in 360ms ease both;
}

/* Capability cards. Each one IS its prompt — clicking sends that question — so
   the examples double as the fastest way in, and nobody has to be told what the
   thing can do in the abstract. */
.bb-agent-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
  gap: 8px;
  margin-top: 20px;
  text-align: left;
}
.bb-agent-card {
  display: flex;
  flex-direction: column;
  align-items: stretch;   /* see .bb-viz-card — the bare `button` rule again */
  gap: 3px;
  padding: 11px 13px;
  border: 1px solid rgba(13, 42, 94, 0.11);
  border-radius: 13px;
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  animation: bb-agent-in 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
  transition: border-color 200ms ease,
              transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 200ms ease;
}
.bb-agent-card:nth-child(1) { animation-delay: 40ms; }
.bb-agent-card:nth-child(2) { animation-delay: 80ms; }
.bb-agent-card:nth-child(3) { animation-delay: 120ms; }
.bb-agent-card:nth-child(4) { animation-delay: 160ms; }
.bb-agent-card:nth-child(5) { animation-delay: 200ms; }
.bb-agent-card:nth-child(6) { animation-delay: 240ms; }
.bb-agent-card:hover {
  border-color: rgba(13, 42, 94, 0.28);
  transform: translateY(-2px);
  box-shadow: 0 10px 20px -12px rgba(13, 42, 94, 0.55);
}
/* A <button> does not inherit `white-space: normal` from the page — it comes out
   of the UA sheet as nowrap, so both of these lines ran past the card and the
   card's own `overflow: hidden` cut them mid-word ("…por divisi", "…leads del
   CR"). Declared on the card so any label added later wraps too. */
.bb-agent-card { white-space: normal; }
.bb-agent-card-title {
  font-size: 12.5px;
  font-weight: 600;
  color: var(--navy, #0D2A5E);
  line-height: 1.3;
  white-space: normal;
  overflow-wrap: break-word;
}
.bb-agent-card-sub {
  font-size: 11px;
  line-height: 1.4;
  color: var(--text-muted, #5a5a5a);
  white-space: normal;
  overflow-wrap: break-word;
}
/* Holds the 64px idle orb. Sized so the slot does not collapse and reflow the
   whole empty state if the orb module is slow or fails to load. */
.bb-agent-empty-mark {
  width: 64px;
  height: 64px;
  margin: 0 auto 16px;
  display: grid;
  place-items: center;
}
.bb-orb { display: block; }
.bb-agent-empty-lead {
  font-size: 19px;
  font-weight: 600;
  letter-spacing: -0.015em;
  color: var(--navy, #0D2A5E);
}

.bb-agent-user {
  align-self: flex-end;
  max-width: 78%;
  background: var(--navy, #0D2A5E);
  color: #fff;
  border-bottom-right-radius: 4px;
}
/* No bubble. Every AI product still standing converged on this: the user gets a
   bubble, the assistant gets plain text at full width. The asymmetry is the
   point — one is a message, the other is a document. Bubbling both sides halves
   the reading width for the longest content on screen, and in a 400px panel
   that is the difference between prose and a column of fragments. */
.bb-agent-assistant {
  align-self: stretch;
  max-width: 100%;
  padding: 2px 2px 6px;
  background: none;
  border: none;
  box-shadow: none;
  border-radius: 0;
  font-size: 14px;
  line-height: 1.62;
}
/* Errors keep a container even though answers lost theirs — a failure must not
   look like a reply. This has to come after .bb-agent-assistant to win. */
.bb-agent-msg.bb-agent-error {
  align-self: flex-start;
  max-width: 92%;
  padding: 11px 14px;
  border: 1px solid #E4BDBD;
  border-radius: 14px;
  border-bottom-left-radius: 5px;
  background: #FDF4F4;
  font-size: 13.5px;
  line-height: 1.55;
}

/* ---- the row under an answer -------------------------------------------- */
/* Copy lives OUTSIDE the bubble (see UI.msgActions — reveal() drains the text
   nodes inside it). Quiet by default so a conversation is not a wall of buttons,
   but NEVER at zero: an opacity-0 button still takes the click, which makes it an
   invisible control that does something. Faint at rest, full on the newest
   answer, on hover, and on keyboard focus; on touch, where `hover` never fires,
   the resting state is the only state there is. */
.bb-agent-acts {
  align-self: flex-start;
  display: flex;
  align-items: center;
  gap: 4px;
  margin: -6px 0 2px;
  opacity: 0.38;
  transition: opacity 140ms ease-out;
}
.bb-agent-msg:hover + .bb-agent-acts,
.bb-agent-acts:hover,
.bb-agent-acts:focus-within,
.bb-agent-log > .bb-agent-acts:last-child { opacity: 1; }
@media (hover: none) { .bb-agent-acts { opacity: 0.75; } }
/* A failure is not an answer worth pasting anywhere. */
.bb-agent-msg.bb-agent-error + .bb-agent-acts { display: none; }

.bb-agent-msgact {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 28px;
  padding: 4px 10px 4px 8px;
  border: none;
  border-radius: 8px;
  background: var(--bb-field, rgba(13, 42, 94, 0.05));
  color: var(--bb-ink-3, #6b7280);
  font-family: inherit;
  font-size: 11px;
  font-weight: 500;
  cursor: pointer;
  transition: background 140ms ease-out, color 140ms ease-out;
}
.bb-agent-msgact svg { width: 13px; height: 13px; max-width: none; flex: none; }
.bb-agent-msgact:hover { background: var(--bb-hover, rgba(13, 42, 94, 0.1)); color: var(--bb-ink, #0D2A5E); }
.bb-agent-msgact.is-ok  { color: var(--bb-green, #4C7A3F); }

/* Jump-to-latest. Floats over the log rather than sitting in flow, so it never
   reflows the conversation when it appears. */
.bb-agent-latest {
  position: absolute;
  left: 50%;
  /* Sits above the composer, whatever height the composer currently is —
     `--bb-form-h` is published by a ResizeObserver in build(). The 76px literal
     is the desktop measurement, kept only as the no-JS fallback; on a phone the
     form is 107px and the old fixed offset put this pill on the input. */
  bottom: calc(var(--bb-form-h, 76px) + 12px);
  z-index: 3;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 6px 13px 6px 10px;
  transform: translateX(-50%);
  border: 1px solid rgba(0, 38, 58, 0.12);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.94);
  backdrop-filter: blur(12px) saturate(1.4);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  box-shadow: 0 8px 20px -10px rgba(0, 38, 58, 0.55);
  font-family: inherit;
  font-size: 11.5px;
  font-weight: 500;
  color: var(--navy, #0D2A5E);
  cursor: pointer;
  animation: bb-agent-in 200ms cubic-bezier(0.16, 1, 0.3, 1) both;
  transition: transform 120ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 120ms ease;
}
.bb-agent-latest svg { width: 13px; height: 13px; }
.bb-agent-latest:hover {
  transform: translateX(-50%) translateY(-1px);
  box-shadow: 0 12px 24px -10px rgba(0, 38, 58, 0.6);
}
.bb-agent-latest:active { transform: translateX(-50%) scale(0.97); transition-duration: 80ms; }
.bb-agent-latest[hidden] { display: none; }

.bb-agent-msg p  { margin: 0 0 8px; }
.bb-agent-msg p:last-child { margin-bottom: 0; }
.bb-agent-msg ul { margin: 6px 0 8px; padding-left: 18px; }
.bb-agent-msg li { margin-bottom: 4px; }
.bb-agent-msg strong { font-weight: 600; color: var(--navy, #0D2A5E); }
.bb-agent-user strong { color: #fff; }

/* Figures and the evidence citations the answers carry — monospace, tabular,
   so a column of numbers lines up and doesn't jitter between answers. */
.bb-agent-msg code {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 12px;
  padding: 1px 5px;
  border-radius: 4px;
  background: var(--off-white, #F5F3EE);
  color: var(--navy, #0D2A5E);
}
.bb-agent-user code { background: rgba(255, 255, 255, 0.18); color: #fff; }

.bb-agent-msg h4 {
  margin: 12px 0 6px;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--navy, #0D2A5E);
}
.bb-agent-msg h4:first-child { margin-top: 0; }

/* Markdown tables the model writes. They used to render as raw pipes, which is
   the ugliest thing a chat can do with data it got right. Wrapped in its own
   scroller so a wide table never widens the conversation column. */
.bb-agent-tablewrap {
  margin: 8px 0 10px;
  overflow-x: auto;
  border: 1px solid rgba(13, 42, 94, 0.1);
  border-radius: 10px;
  background: rgba(255, 255, 255, 0.72);
}
.bb-agent-table { width: 100%; border-collapse: collapse; font-size: 12px; }
.bb-agent-table th {
  padding: 7px 10px;
  text-align: left;
  white-space: nowrap;
  background: var(--off-white, #F5F3EE);
  border-bottom: 1px solid rgba(13, 42, 94, 0.12);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--navy, #0D2A5E);
}
.bb-agent-table td {
  padding: 7px 10px;
  border-bottom: 1px solid rgba(13, 42, 94, 0.06);
  color: #33383f;
  vertical-align: top;
}
.bb-agent-table tr:last-child td { border-bottom: none; }
.bb-agent-table th.is-num { text-align: right; }
.bb-agent-table td.is-num {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
  color: var(--navy, #0D2A5E);
}

/* -------------------------------- beta mark ------------------------------- */
/* Next to the name in every surface, never as a dismissible banner: a strip you
   can close is read once and then the warning is gone for good. */
.bb-beta {
  display: inline-block;
  margin-left: 7px;
  padding: 2px 6px 1px;
  border: 1px solid rgba(222, 184, 60, 0.55);
  border-radius: 5px;
  background: rgba(222, 184, 60, 0.16);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.1em;
  line-height: 1.4;
  text-transform: uppercase;
  vertical-align: middle;
  color: #8a6a12;
  cursor: help;
}
.ai-title .bb-beta { margin-left: 10px; transform: translateY(-3px); }

.bb-agent-empty-note {
  max-width: 430px;
  margin: 9px auto 0;
  font-size: 11.5px;
  line-height: 1.5;
  color: var(--text-muted, #5a5a5a);
}

/* ------------------------------ tool chips -------------------------------- */
/* Shown live as the agent gathers evidence. The point is that the user can see
   WHICH filters it chose — a wrong filter is the most likely way to get a
   plausible but wrong answer, and this makes it visible instead of buried. */

.bb-agent-tool {
  align-self: flex-start;
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 4px 8px;
  max-width: 92%;
  padding: 5px 10px;
  border: 1px solid var(--border, #e0ddd6);
  border-left: 3px solid var(--navy, #0D2A5E);
  border-radius: 6px;
  background: var(--off-white, #F5F3EE);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 10.5px;
  color: var(--text-muted, #5a5a5a);
  animation: bb-agent-in var(--duration-normal, 200ms) ease both;
}
.bb-agent-tool-tick { width: 11px; height: 11px; flex: none; color: var(--green, #2E7D52); }
/* Plain language, not a function name — sentence case and the app's body face,
   because this is a progress note to a person, not a log line. */
.bb-agent-tool-name {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 11.5px;
  color: var(--navy, #0D2A5E);
  font-weight: 600;
}
.bb-agent-tool-args {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  opacity: 0.85;
  word-break: break-word;
}

/* ------------------------------- thinking --------------------------------- */

/* A shimmering word beats three bouncing dots: it says WHICH phase the agent is
   in, and an honest status cue is what makes the wait feel like work happening
   rather than a hang. The shimmer is a gradient swept through clipped text, so
   it animates background-position only. */
.bb-agent-thinking {
  align-self: flex-start;
  display: inline-flex;
  align-items: center;
  gap: 9px;
  padding: 8px 15px;
  border-radius: 999px;
  border: 1px solid rgba(13, 42, 94, 0.08);
  background: rgba(255, 255, 255, 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  font-size: 12.5px;
  font-weight: 500;
  animation: bb-agent-in 300ms ease both;
}
/* Placeholder shown until the 20px orb canvas swaps in, and the permanent
   indicator if the orb module ever fails to load. */
.bb-agent-thinking-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: linear-gradient(150deg, #00263A, #DEB83C);
  animation: bb-agent-breathe 3.6s ease-in-out infinite;
}
.bb-agent-thinking .bb-orb { margin: -4px 0; }   /* keep the pill its own height */
.bb-agent-thinking-label {
  background: linear-gradient(100deg, #9aa3b8 26%, #B8942A 44%, var(--navy, #0D2A5E) 52%, #9aa3b8 70%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  animation: ai-shimmer 1.7s linear infinite;
}
@keyframes ai-shimmer { to { background-position: -220% 0; } }
@keyframes bb-agent-breathe {
  0%, 100% { transform: scale(1);    opacity: 0.55; }
  50%      { transform: scale(1.25); opacity: 1; }
}

/* ------------------------------ suggestions ------------------------------- */

.bb-agent-suggest {
  flex: none;
  display: flex;
  gap: 6px;
  padding: 0 18px 10px;
  overflow-x: auto;
  scrollbar-width: none;
}
.bb-agent-suggest::-webkit-scrollbar { display: none; }

/* Suggestions belong to the blank slate. Once there is a conversation they are
   noise sitting between the answer and the input, so they retire. The rule is
   inverted (hide only when NOT empty) so a browser without :has keeps them
   visible rather than losing them. */
#bbAgentChat:has(.bb-agent-log:not(.is-empty)) .bb-agent-suggest { display: none; }

.bb-agent-seed {
  flex: none;
  padding: 7px 13px;
  border: 1px solid rgba(13, 42, 94, 0.12);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.72);
  color: var(--text-muted, #5a5a5a);
  font-family: inherit;
  font-size: 11.5px;
  white-space: nowrap;
  cursor: pointer;
  animation: bb-agent-in 420ms cubic-bezier(0.22, 1, 0.36, 1) both;
  transition: border-color 200ms ease, color 200ms ease,
              transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 200ms ease;
}
/* Staggered so the row assembles rather than snapping in as one block. */
.bb-agent-seed:nth-child(1) { animation-delay: 60ms; }
.bb-agent-seed:nth-child(2) { animation-delay: 120ms; }
.bb-agent-seed:nth-child(3) { animation-delay: 180ms; }
.bb-agent-seed:nth-child(4) { animation-delay: 240ms; }
.bb-agent-seed:hover {
  border-color: rgba(13, 42, 94, 0.30);
  color: var(--navy, #0D2A5E);
  transform: translateY(-1px);
  box-shadow: 0 6px 14px -8px rgba(13, 42, 94, 0.5);
}

/* --------------------------------- form ----------------------------------- */

/* The composer floats over the aurora as its own frosted pill rather than a bar
   welded to the bottom edge — the wash is strongest down here, and a hard
   full-width rule across it kills the depth the blur just bought. */
.bb-agent-form {
  flex: none;
  display: flex;
  align-items: flex-end;
  gap: 8px;
  padding: 10px 18px calc(16px + env(safe-area-inset-bottom, 0px));
  background: none;
  border-top: none;
}

#bbAgentInput {
  flex: 1;
  min-width: 0;
  resize: none;
  min-height: 44px;      /* matches the send button, so the row never jitters */
  max-height: 140px;
  padding: 12px 14px;
  border: 1px solid rgba(13, 42, 94, 0.12);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.86);
  backdrop-filter: blur(12px) saturate(1.4);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  box-shadow:
    0 8px 18px -12px rgba(13, 42, 94, 0.35),
    inset 0 1px 1px 0 rgba(255, 255, 255, 0.9);
  font-family: inherit;
  font-size: 13.5px;
  line-height: 1.45;
  color: var(--text, #1a1a1a);
  transition: border-color 200ms ease, box-shadow 200ms ease;
}
#bbAgentInput:focus {
  outline: none;
  border-color: rgba(13, 42, 94, 0.38);
  box-shadow:
    0 10px 22px -12px rgba(13, 42, 94, 0.45),
    inset 0 1px 1px 0 rgba(255, 255, 255, 0.9);
}
#bbAgentInput::placeholder { color: #8a8a8a; }

/* ---- the send / stop button --------------------------------------------- */
/* While a question is in flight this becomes a loader AND a stop control. The
   motion is the honest kind: it is running because a request is running, and
   pressing it actually cancels. A spinner that cannot be stopped is decoration;
   this one is the escape hatch for a question that turned out to be too broad. */
@property --send-angle {
  syntax: '<angle>';
  initial-value: 0deg;
  inherits: false;
}

#bbAgentSend {
  position: relative;
  flex: none;
  width: 44px;              /* 44px: the thumb-reach floor on touch */
  height: 44px;
  border: none;
  border-radius: 14px;
  background: linear-gradient(150deg, #17407f, var(--navy, #0D2A5E));
  color: #fff;
  font-size: 17px;
  cursor: pointer;
  overflow: hidden;         /* clips the sheen to the button */
  display: grid;
  place-items: center;
  box-shadow: 0 8px 16px -8px rgba(13, 42, 94, 0.6);
  transition: opacity 200ms ease,
              transform 200ms cubic-bezier(0.22, 1, 0.36, 1),
              box-shadow 200ms ease;
}
#bbAgentSend:active:not(:disabled) { transform: scale(0.97); transition-duration: 80ms; }
#bbAgentSend:hover:not(:disabled) {
  transform: translateY(-1px);
  box-shadow: 0 12px 22px -8px rgba(13, 42, 94, 0.7);
}
#bbAgentSend:disabled { opacity: 0.35; cursor: default; box-shadow: none; }

#bbAgentSend .bb-send-glyph { position: relative; z-index: 2; line-height: 1; }
/* A stop glyph has to read as a SQUARE. At 11px a 3px radius is 27% of the
   side, which rounds it into a blob; 13px with a 2.5px radius keeps the corners
   soft without losing the shape. */
#bbAgentSend .bb-send-stop {
  position: relative;
  z-index: 2;
  width: 13px;
  height: 13px;
  border-radius: 2.5px;
  background: #fff;
  animation: bb-send-stop-in 220ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
@keyframes bb-send-stop-in {
  from { opacity: 0; transform: scale(0.5); }
  to   { opacity: 1; transform: scale(1); }
}

/* Rim: a conic sweep masked to the button's 1.5px edge. Same trick as the chat
   column — unmasked it would wash the whole face. */
#bbAgentSend::before {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  border-radius: inherit;
  padding: 2px;
  /* Gold ALL the way round, with a bright highlight chasing it. The first
     version left transparent gaps, and on a 44px button you never see a
     rotating ring — you see a gold fragment stuck on one edge, which reads as a
     rendering glitch rather than a deliberate glow. A continuous ring keeps the
     button whole at every frame of the rotation. */
  background: conic-gradient(from var(--send-angle),
      rgba(222, 184, 60, 0.58) 0deg,
      rgba(246, 226, 155, 1)   65deg,
      rgba(222, 184, 60, 0.82) 130deg,
      rgba(222, 184, 60, 0.56) 210deg,
      rgba(222, 184, 60, 0.78) 295deg,
      rgba(222, 184, 60, 0.58) 360deg);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask-composite: exclude;
  opacity: 0;
  transition: opacity 220ms ease;
}
/* Sheen: a soft band crossing the face, offset from the rim so the two read as
   one object catching light rather than two separate animations. */
#bbAgentSend::after {
  content: '';
  position: absolute;
  top: -50%;
  bottom: -50%;
  width: 40%;
  left: -60%;
  z-index: 1;
  background: linear-gradient(90deg, transparent, rgba(246, 226, 155, 0.42), transparent);
  transform: skewX(-18deg);
  opacity: 0;
}

/* Slightly deeper navy while stopped-able, so the gold ring has something to sit
   against and the button reads as a different state, not just a lit-up send. */
#bbAgentSend.is-loading {
  cursor: pointer;
  opacity: 1;
  background: linear-gradient(150deg, #123566, #071d3f);
  box-shadow: 0 8px 18px -8px rgba(13, 42, 94, 0.7);
}
#bbAgentSend.is-loading::before {
  opacity: 1;
  animation: bb-send-rim 2s linear infinite;
}
#bbAgentSend.is-loading::after {
  opacity: 1;
  animation: bb-send-sheen 1.9s cubic-bezier(0.4, 0, 0.2, 1) infinite;
}
@keyframes bb-send-rim   { to { --send-angle: 360deg; } }
@keyframes bb-send-sheen { 0% { left: -60%; } 60%, 100% { left: 120%; } }

/* ------------------------------- visuals ---------------------------------- */
/* What the tools returned, drawn. These are built from the tool result objects
   (see agent.js → Viz), so a card and the sentence under it are the same number
   by construction rather than by agreement.

   Full width on purpose. The assistant's text is a document, not a bubble, and
   a chart squeezed to 78% next to nothing reads as an ornament rather than the
   evidence. Numbers use JetBrains Mono + tabular-nums exactly like every other
   figure in BridgeBI, so a chart in the chat and a card on the dashboard weigh
   the same to the eye. */

/* A CONTAINER query context, not a viewport one. The same chat renders in a
   ~400px floating panel and in a ~900px page column on the same screen, so a
   `@media (max-width: 520px)` would be right for neither: it reads the window
   and the window is wide in both cases. The wrapper is the narrowest element
   that owns the chart's real width, and putting containment on a div we own
   keeps it away from the panel's aurora and the jump-to-latest pill. */
.bb-agent-vizwrap {
  align-self: stretch;
  width: 100%;
  container-type: inline-size;
}

.bb-viz {
  border: 1px solid rgba(13, 42, 94, 0.11);
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.82);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 10px 22px -18px rgba(0, 38, 58, 0.7);
  padding: 12px 13px 13px;
  animation: bb-agent-in 260ms cubic-bezier(0.16, 1, 0.3, 1) both;
}

.bb-viz-head {
  display: flex;
  align-items: baseline;
  gap: 9px;
  flex-wrap: wrap;
  margin-bottom: 11px;
}
.bb-viz-title {
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 12.5px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--navy, #0D2A5E);
}
.bb-viz-sub {
  min-width: 0;
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--text-muted, #5a5a5a);
}

/* ---- cards --------------------------------------------------------------- */

.bb-viz-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: 8px;
}
.bb-viz-card {
  display: flex;
  flex-direction: column;
  /* index.html styles the BARE `button` tag as the app's navy pill, and among
     its declarations is `align-items: center`. An askable card IS a button, so
     it inherited that — which stops the card's children from filling its width,
     sizes each one to max-content and centres it. A long program name then
     spilled out BOTH sides of the card and got sliced by the button rule's
     `overflow: hidden`, which is what cut "D Electricity w Renewable Energy"
     mid-word. It also broke the two-line clamp: a name that never wraps has no
     second line to clamp.
     Same trap the panel documents for <header>. Stated explicitly here because
     the fix is invisible — nothing in this rule looks like it is fighting
     anything, and removing it silently returns the bug. */
  align-items: stretch;
  gap: 5px;
  min-width: 0;
  padding: 10px 11px 11px;
  border: 1px solid rgba(13, 42, 94, 0.1);
  border-radius: 11px;
  background: #fff;
  font-family: inherit;
  text-align: left;
  color: inherit;
  overflow: hidden;             /* clip the thumbnail to the card's radius */
}
/* A card is where the obvious follow-up question lives, so the askable ones are
   real buttons. The lift is the affordance — without it nobody discovers it. */
.bb-viz-card.is-askable { cursor: pointer; transition: border-color 180ms ease, transform 180ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 180ms ease; }
.bb-viz-card.is-askable:hover {
  border-color: rgba(13, 42, 94, 0.3);
  transform: translateY(-2px);
  box-shadow: 0 12px 22px -14px rgba(13, 42, 94, 0.65);
}
.bb-viz-card.is-askable:active { transform: translateY(0) scale(0.99); transition-duration: 80ms; }

/* ---- the creative ------------------------------------------------------- */
/* Two layers of the same picture: a blurred COVER fill behind and the sharp
   CONTAINED creative on top. Cover alone crops a 1:1 or portrait Meta creative
   — most of what Northbridge runs — and the first thing it slices off is the
   headline, which is the part an analyst is actually reading. Same treatment
   the Ineligible analyzer's tiles use, so a creative looks the same wherever it
   appears in the app. */
.bb-viz-thumb {
  position: relative;
  display: block;
  aspect-ratio: 16 / 10;
  /* A single cross-referenced campaign gets a full-width card, and 16:10 of a
     900px column is a poster. The creative is context for the numbers, not the
     subject of the card — cap it. */
  max-height: 190px;
  margin: -10px -11px 8px;      /* bleeds to the card's edges, above its padding */
  background: #eceaf3 center / cover no-repeat;
  overflow: hidden;
}
.bb-viz-thumb::before {
  content: '';
  position: absolute;
  inset: -12%;
  background: inherit;
  filter: blur(14px) saturate(1.15);
  transform: scale(1.06);
}
.bb-viz-thumb img {
  position: relative;
  z-index: 1;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}
/* Waiting on /campaign-creative. A shimmer says "coming", a static grey box
   says "broken" — and the difference matters because that read is rate-limited
   and genuinely can take a moment. */
.bb-viz-thumb.is-loading::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 2;
  background: linear-gradient(100deg, transparent 20%, rgba(255, 255, 255, 0.55) 50%, transparent 80%);
  background-size: 220% 100%;
  animation: bb-viz-shimmer 1.25s ease-in-out infinite;
}
@keyframes bb-viz-shimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }
/* Resolved to nothing, or the URL 404'd. Neither is an error worth shouting
   about — the numbers on the card are the answer. */
.bb-viz-thumb.is-empty,
.bb-viz-thumb.is-broken { background-image: none !important; background: #f1efe9; }
.bb-viz-thumb.is-empty::before,
.bb-viz-thumb.is-broken::before { display: none; }
.bb-viz-thumb.is-broken img { display: none; }

/* A Search ad's creative is its RSA, so it gets drawn as the SERP line it is —
   blue headline, muted description — the same shape the Ineligible analyzer
   uses. Not a fallback for a missing image: for Search there is no image to
   miss, and a grey box would have said "failed" about a creative that is right
   there. */
.bb-viz-thumb.is-serp {
  aspect-ratio: auto;
  min-height: 66px;
  display: flex;
  align-items: center;
  padding: 9px 11px;
  background: linear-gradient(160deg, #fbfaf7, #f2efe8);
  border-bottom: 1px solid rgba(13, 42, 94, 0.07);
}
.bb-viz-thumb.is-serp::before { display: none; }
.bb-viz-serp { display: flex; flex-direction: column; gap: 3px; min-width: 0; text-align: left; }
.bb-viz-serp b {
  font-size: 11px;
  font-weight: 600;
  line-height: 1.3;
  color: #1a0dab;                /* the SERP blue, so it reads as a search ad */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.bb-viz-serp i {
  font-style: normal;
  font-size: 9.5px;
  line-height: 1.35;
  color: var(--text-muted, #5a5a5a);
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

/* The creative variant gives the picture room; everything else stays compact. */
.bb-viz--creative .bb-viz-cards { grid-template-columns: repeat(auto-fit, minmax(186px, 1fr)); }

.bb-viz-live {
  align-self: flex-start;
  margin-top: 2px;
  padding: 3px 7px;
  border: 1px solid rgba(13, 42, 94, 0.16);
  border-radius: 5px;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: var(--navy, #0D2A5E);
  cursor: pointer;
  transition: background 140ms ease, border-color 140ms ease;
}
.bb-viz-live:hover { background: rgba(13, 42, 94, 0.07); border-color: rgba(13, 42, 94, 0.32); }

.bb-viz-card-top { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }

.bb-viz-badge,
.bb-viz-state {
  padding: 2px 6px;
  border-radius: 5px;                /* 6px-ish, never a pill — house rule */
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  white-space: nowrap;
}
.bb-viz-badge.is-meta   { background: rgba(24, 119, 242, 0.12); color: #1058b8; }
.bb-viz-badge.is-google { background: rgba(222, 184, 60, 0.2);  color: #7d5f10; }
.bb-viz-badge.is-div    { background: rgba(13, 42, 94, 0.09);   color: var(--navy, #0D2A5E); }
.bb-viz-badge.is-plain  { background: rgba(13, 42, 94, 0.07);   color: #5a5a5a; }
.bb-viz-state.is-live   { background: rgba(46, 125, 82, 0.13);  color: var(--green, #2E7D52); }
.bb-viz-state.is-off    { background: rgba(90, 90, 90, 0.12);   color: #6d6d6d; }

.bb-viz-card-name {
  font-size: 11.5px;
  font-weight: 600;
  line-height: 1.32;
  color: var(--navy, #0D2A5E);
  /* Campaign names are long and structured (DT_Meta_PR_…). Two lines then clip:
     the full string is in the title attribute, and a name that wraps to five
     lines makes every card in the row that tall. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  overflow-wrap: anywhere;
}
.bb-viz-card-sub {
  font-size: 10px;
  line-height: 1.35;
  color: var(--text-muted, #5a5a5a);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bb-viz-metrics {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-top: 2px;
}
.bb-viz-metric { display: flex; flex-direction: column; min-width: 0; }
.bb-viz-metric b {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 12.5px;
  font-weight: 600;
  line-height: 1.2;
  color: var(--navy, #0D2A5E);
}
.bb-viz-metric span {
  font-size: 9px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #8a8a8a;
}

/* The tile variant leads with ONE number, because that is the whole point of a
   tile — a CPL you can read across the room. */
.bb-viz--tile .bb-viz-cards { grid-template-columns: repeat(auto-fit, minmax(132px, 1fr)); }
.bb-viz-hero { display: flex; flex-direction: column; gap: 1px; }
.bb-viz-hero b {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--navy, #0D2A5E);
}
.bb-viz-hero span {
  font-size: 9px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #8a8a8a;
}
.bb-viz-hero.is-up b   { color: var(--green, #2E7D52); }
.bb-viz-hero.is-down b { color: var(--red, #B23B3B); }
.bb-viz--tile .bb-viz-card-name { font-size: 10px; font-weight: 500; letter-spacing: 0.05em; text-transform: uppercase; color: #8a8a8a; order: -1; }

.bb-viz-warn,
.bb-viz-thin {
  font-size: 9.5px;
  line-height: 1.35;
  padding: 3px 6px;
  border-radius: 5px;
}
.bb-viz-warn { background: rgba(178, 59, 59, 0.09); color: var(--red, #B23B3B); }
.bb-viz-thin { background: rgba(13, 42, 94, 0.06); color: #6d7480; }

/* A share-of-the-leader bar under each card, so the grid ranks visually and not
   only by reading order. */
.bb-viz-cardbar {
  height: 3px;
  margin-top: 2px;
  border-radius: 999px;
  background: rgba(13, 42, 94, 0.08);
  overflow: hidden;
}
.bb-viz-cardbar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--navy, #0D2A5E), #DEB83C);
}

/* ---- ranked bars --------------------------------------------------------- */

.bb-viz-bars { display: flex; flex-direction: column; gap: 6px; }
.bb-viz-row {
  display: grid;
  grid-template-columns: minmax(0, 130px) minmax(30px, 1fr) auto minmax(0, auto);
  align-items: center;
  gap: 9px;
}
.bb-viz-row-label {
  min-width: 0;
  font-size: 11px;
  line-height: 1.3;
  color: #33383f;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bb-viz-track {
  height: 8px;
  border-radius: 999px;
  background: rgba(13, 42, 94, 0.07);
  overflow: hidden;
}
.bb-viz-track i {
  display: block;
  height: 100%;
  min-width: 3px;
  border-radius: inherit;
  background: linear-gradient(90deg, rgba(13, 42, 94, 0.9), rgba(13, 42, 94, 0.62));
  animation: bb-viz-grow 460ms cubic-bezier(0.22, 1, 0.36, 1) both;
}
.bb-viz-track i.is-warn { background: linear-gradient(90deg, #B23B3B, rgba(178, 59, 59, 0.6)); }
@keyframes bb-viz-grow { from { transform: scaleX(0); transform-origin: left; } to { transform: scaleX(1); } }

.bb-viz-row-val {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 11.5px;
  font-weight: 600;
  text-align: right;
  white-space: nowrap;
  color: var(--navy, #0D2A5E);
}
.bb-viz-row-end {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 10px;
  white-space: nowrap;
  text-align: right;
  color: #8a8a8a;
}

.bb-viz-delta {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 9.5px;
  font-weight: 600;
  white-space: nowrap;
  padding: 1px 5px;
  border-radius: 5px;
}
.bb-viz-delta.up   { background: rgba(46, 125, 82, 0.12); color: var(--green, #2E7D52); }
.bb-viz-delta.down { background: rgba(178, 59, 59, 0.11); color: var(--red, #B23B3B); }
.bb-viz-delta.flat { background: rgba(13, 42, 94, 0.07);  color: #6d7480; }

/* ---- week trend ---------------------------------------------------------- */

.bb-viz-plot { position: relative; height: 132px; }
.bb-viz-plot svg { display: block; width: 100%; height: 100%; }
/* Point markers as HTML, not <circle>: the viewBox is stretched, so an SVG
   circle comes out an ellipse. Percentages of the same box are exact. */
.bb-viz-dot {
  position: absolute;
  width: 7px;
  height: 7px;
  margin: -3.5px 0 0 -3.5px;
  border-radius: 50%;
  background: #DEB83C;
  box-shadow: 0 0 0 1.4px var(--navy, #0D2A5E);
  cursor: help;
}
.bb-viz-trend-x {
  display: flex;
  justify-content: space-between;
  gap: 4px;
  margin-top: 4px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 9px;
  color: #8a8a8a;
}
.bb-viz-trend-x span { flex: 1 1 0; text-align: center; min-width: 0; }
.bb-viz-trend-x span:first-child { text-align: left; }
.bb-viz-trend-x span:last-child  { text-align: right; }

/* The floating panel is ~400px wide. Two card columns in there is one column of
   fragments, so below 400px of ACTUAL chart width the cards stack and the bars
   drop their label column to a full-width line above the track. */
@container (max-width: 400px) {
  .bb-viz-cards,
  .bb-viz--tile .bb-viz-cards { grid-template-columns: minmax(0, 1fr); }
  .bb-viz-row {
    grid-template-columns: minmax(0, 1fr) auto;
    grid-template-areas: 'label val' 'track end';
    row-gap: 3px;
  }
  .bb-viz-row-label { grid-area: label; }
  .bb-viz-row-val   { grid-area: val; }
  .bb-viz-track     { grid-area: track; }
  .bb-viz-row-end   { grid-area: end; }
}

@media (prefers-reduced-motion: reduce) {
  .bb-viz, .bb-viz-track i { animation: none; }
}

/* ---------------------------- in-page variant ----------------------------- */
/* Same nodes, more room. The overlay is a sidebar; the page is a workspace, so
   answers get wider measure and the form loses the panel's border treatment. */

#bbAgentChat.in-page .bb-agent-log { padding: 22px 24px; gap: 14px; }
#bbAgentChat.in-page .bb-agent-msg { max-width: 78%; font-size: 14px; }
#bbAgentChat.in-page .bb-agent-tool { max-width: 78%; }
#bbAgentChat.in-page .bb-agent-suggest {
  padding: 0 22px 10px;
  flex-wrap: wrap;
  justify-content: center;   /* the blank slate is centred, so these are too */
  overflow-x: visible;
}
#bbAgentChat.in-page .bb-agent-form { padding: 10px 22px 18px; }

/* ------------------------------ guide cursor ------------------------------ */
/* The agent's on-screen hand. Fixed-position, pointer-events:none so it can
   never intercept a real click — it is a picture of a cursor travelling, while
   the actual navigation happens through the app's own click handlers. */

#bbGuideCursor {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 12000;
  width: 26px;
  height: 26px;
  pointer-events: none;
  opacity: 0;
  transform: translate3d(0, 0, 0);
  transition: opacity 260ms ease;
}
#bbGuideCursor.is-visible { opacity: 1; }

#bbGuideCursor svg {
  width: 26px;
  height: 26px;
  fill: var(--navy, #0D2A5E);
  stroke: #fff;
  stroke-width: 1.1;
  filter: drop-shadow(0 3px 6px rgba(13, 42, 94, 0.45));
}

/* The click: a gold ring expanding from the cursor tip. */
.bb-guide-ring {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  border: 2px solid rgba(222, 184, 60, 0.95);
  opacity: 0;
  transform: scale(0.4);
}
#bbGuideCursor.is-clicking .bb-guide-ring { animation: bb-guide-click 420ms ease-out; }
@keyframes bb-guide-click {
  0%   { opacity: 1; transform: scale(0.4); }
  100% { opacity: 0; transform: scale(3.4); }
}

/* The click burst: a gold flash, an expanding ring, and eight sparks thrown
   outward. Centred on the cursor via translate(-50%,-50%) so it radiates from
   the point rather than starting at it. pointer-events:none throughout — this
   fires over real controls and must never eat the click it is celebrating. */
.bb-guide-burst {
  position: fixed;
  z-index: 11999;
  width: 0;
  height: 0;
  pointer-events: none;
}
.bb-guide-burst::before,
.bb-guide-burst::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  border-radius: 50%;
  transform: translate(-50%, -50%);
}
/* the flash */
.bb-guide-burst::before {
  width: 74px;
  height: 74px;
  background: radial-gradient(circle, rgba(246, 226, 155, 0.85) 0%, rgba(222, 184, 60, 0.35) 42%, rgba(222, 184, 60, 0) 70%);
  animation: bb-burst-flash 620ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
/* the ring */
.bb-guide-burst::after {
  width: 22px;
  height: 22px;
  border: 2px solid rgba(222, 184, 60, 0.95);
  animation: bb-burst-ring 640ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.bb-guide-burst i {
  position: absolute;
  left: 0;
  top: 0;
  width: 3px;
  height: 11px;
  margin: -5px 0 0 -1.5px;
  border-radius: 2px;
  background: linear-gradient(rgba(246, 226, 155, 0.95), rgba(222, 184, 60, 0));
  transform: rotate(var(--a)) translateY(-8px);
  animation: bb-burst-spark 620ms cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
@keyframes bb-burst-flash {
  0%   { opacity: 0;    transform: translate(-50%, -50%) scale(0.25); }
  22%  { opacity: 1; }
  100% { opacity: 0;    transform: translate(-50%, -50%) scale(1.5); }
}
@keyframes bb-burst-ring {
  0%   { opacity: 0.95; transform: translate(-50%, -50%) scale(0.3); border-width: 3px; }
  100% { opacity: 0;    transform: translate(-50%, -50%) scale(3.1); border-width: 1px; }
}
@keyframes bb-burst-spark {
  0%   { opacity: 0;   transform: rotate(var(--a)) translateY(-6px)  scaleY(0.5); }
  25%  { opacity: 1; }
  100% { opacity: 0;   transform: rotate(var(--a)) translateY(-34px) scaleY(1.15); }
}

/* What it is doing, pinned to the top so it never sits under the cursor. */
#bbGuideToast {
  position: fixed;
  top: 18px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 12001;
  padding: 9px 18px;
  border-radius: 999px;
  background: rgba(0, 38, 58, 0.94);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  color: #fff;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 500;
  box-shadow: 0 10px 26px -10px rgba(0, 38, 58, 0.7);
  pointer-events: none;
  animation: bb-agent-in 300ms ease both;
}

/* The destination, announced. Gold ring + a soft halo, on a delay long enough
   to still be there once the scroll settles. */
.bb-guide-target {
  animation: bb-guide-target 2.6s ease-out;
  border-radius: 14px;
}
@keyframes bb-guide-target {
  0%   { box-shadow: 0 0 0 0 rgba(222, 184, 60, 0), 0 0 0 0 rgba(222, 184, 60, 0); }
  18%  { box-shadow: 0 0 0 4px rgba(222, 184, 60, 0.85), 0 0 34px 6px rgba(222, 184, 60, 0.4); }
  70%  { box-shadow: 0 0 0 4px rgba(222, 184, 60, 0.7),  0 0 30px 4px rgba(222, 184, 60, 0.3); }
  100% { box-shadow: 0 0 0 0 rgba(222, 184, 60, 0), 0 0 0 0 rgba(222, 184, 60, 0); }
}

@media (prefers-reduced-motion: reduce) {
  #bbGuideCursor { transition: none; }
  .bb-guide-target { animation-duration: 0.01ms; box-shadow: 0 0 0 3px rgba(222, 184, 60, 0.85); }
}

/* ------------------------------ narrow screens ---------------------------- */
/* The panel is chrome, not layout, so it keys off the viewport rather than the
   app's content-width buckets: on a phone it becomes a full-height sheet. */

@media (max-width: 520px) {
  /* Full-bleed sheet on a phone: a floating card with 14px of dashboard showing
     around it wastes width that a 380px screen does not have. */
  #bbAgentPanel {
    top: 0; right: 0; bottom: 0; left: 0;
    width: 100vw;
    border-radius: 0;
    border: none;
  }
  #bbAgentPanel::before, #bbAgentPanel::after { border-radius: 0; }
  .bb-agent-host { border-radius: 0; }
  #bbAgentFab { right: 14px; bottom: 14px; padding: 10px 15px 10px 13px; font-size: 13px; }
}

@media (prefers-reduced-motion: reduce) {
  #bbAgentPanel, #bbAgentFab, .bb-agent-msg, .bb-agent-tool,
  .bb-agent-seed, .bb-agent-empty { transition: none; animation: none; }
  .bb-agent-fab-dot, .bb-agent-thinking span { animation: none; }
  /* The aurora keeps its colour and loses its drift: the depth was the point,
     the motion was the flourish. */
  .ai-chat-col::before { animation: none; }
}

/* ---- folded step trail --------------------------------------------------- */
/* The chips stay visible while the agent works — watching it gather evidence is
   the point. Once the answer lands they fold into one line, because eight
   stacked steps above an answer in a 400px column is noise, not transparency.
   Still one click from the full trail. */
.bb-agent-steps { align-self: flex-start; max-width: 92%; }
.bb-agent-steps-head {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 4px 10px 4px 6px;
  border: 1px solid rgba(13, 42, 94, 0.10);
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.6);
  font-family: inherit;
  font-size: 11px;
  color: var(--text-muted, #5a5a5a);
  cursor: pointer;
  transition: border-color 120ms ease, color 120ms ease;
}
.bb-agent-steps-head:hover { border-color: rgba(13, 42, 94, 0.26); color: var(--navy, #0D2A5E); }
.bb-agent-steps-caret {
  width: 12px; height: 12px; flex: none;
  transition: transform 160ms cubic-bezier(0.16, 1, 0.3, 1);
}
.bb-agent-steps.is-open .bb-agent-steps-caret { transform: rotate(90deg); }
.bb-agent-steps-body {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-top: 7px;
  animation: bb-agent-in 200ms cubic-bezier(0.16, 1, 0.3, 1) both;
}
.bb-agent-steps-body[hidden] { display: none; }
.bb-agent-tool.is-folded { animation: none; }

/* ==========================================================================
   DETAIL SHEET — what a card opens
   --------------------------------------------------------------------------
   A card click used to send a question. Now it opens the thing: the creative at
   a size you can actually read, and its numbers ordered by whatever was being
   asked (agent.js → Detail).

   Everything here is `--bb-*` tokens from beautiful.css, so the sheet follows
   the app's theme (including dark) with no second palette. Buttons carry explicit
   resets because index.html styles the BARE `button` tag as the app's navy pill —
   the same trap `.bb-viz-card` documents.
   ========================================================================== */

html.bb-det-lock { overflow: hidden; }

.bb-det {
  position: fixed;
  inset: 0;
  /* Above the floating agent panel (9001), which is where most clicks into this
     come from. */
  z-index: 9100;
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: 26px 20px;
}
.bb-det[hidden] { display: none; }

.bb-det-scrim {
  position: absolute;
  inset: 0;
  background: rgba(6, 18, 34, 0.44);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  opacity: 0;
  transition: opacity 180ms ease-out;
}
.bb-det.is-in .bb-det-scrim { opacity: 1; }

.bb-det-sheet {
  position: relative;
  z-index: 1;
  width: min(860px, 100%);
  max-height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  border-radius: 16px;
  background: var(--bb-page, #F5F3EE);
  box-shadow: 0 0 0 1px var(--bb-line, #E6E1D8), 0 30px 70px -30px rgba(0, 38, 58, 0.5);
  /* A CONTAINER context, like the chat visuals: the same sheet renders at 860px
     on a desktop and at ~340px on a phone, and the grids inside it must key off
     their own width rather than the window's. */
  container-type: inline-size;
  transform: translateY(10px) scale(0.985);
  opacity: 0;
  transition: transform 220ms cubic-bezier(0.16, 1, 0.3, 1), opacity 180ms ease-out;
}
.bb-det.is-in .bb-det-sheet { transform: none; opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  .bb-det-scrim, .bb-det-sheet { transition: none; }
  .bb-det-sheet { transform: none; }
}

/* ---- head ---------------------------------------------------------------- */
/* Sticky, because the name of the thing has to survive scrolling through its
   own ads — a long sheet with the title scrolled off reads as a different page. */
.bb-det-head {
  position: sticky;
  top: 0;
  z-index: 3;
  display: flex;
  align-items: flex-start;
  gap: 10px;
  padding: 14px 16px 12px;
  border-bottom: 1px solid var(--bb-line, #E6E1D8);
  background: var(--bb-page, #F5F3EE);
}
.bb-det-headtext { flex: 1 1 auto; min-width: 0; }
.bb-det-kind {
  display: block;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--bb-ink-3, #8a8a8a);
}
.bb-det-name {
  margin: 2px 0 0;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 16.5px;
  font-weight: 600;
  line-height: 1.28;
  letter-spacing: -0.01em;
  color: var(--bb-ink, #1A1A1A);
  overflow-wrap: anywhere;
}

.bb-det-nav {
  flex: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 30px;
  height: 30px;
  padding: 0;
  border: 0;
  border-radius: var(--bb-r-control, 8px);
  background: var(--bb-surface, #fff);
  box-shadow: var(--bb-sh-hairline, 0 0 0 1px #E6E1D8);
  color: var(--bb-ink-2, #33383f);
  cursor: pointer;
  transition: background-color 120ms ease-out, color 120ms ease-out;
}
.bb-det-nav:hover { background: var(--bb-hover, #F4F2EC); color: var(--bb-ink, #1A1A1A); }
.bb-det-nav:active { transform: none; }
/* responsive.css sets `svg { max-width: 100% }`, which resolves to 0 for an icon
   inside a flex container — the same collapse documented for the rail icons. */
.bb-det-nav svg { width: 15px; height: 15px; max-width: none; flex: none; }
.bb-det-nav[hidden] { display: none; }

/* ---- body ---------------------------------------------------------------- */

.bb-det-body { padding: 14px 16px 18px; }

/* Why this sheet is in the order it is in. Said out loud, because a page that
   silently reorders itself is indistinguishable from a page with different data. */
.bb-det-lens {
  margin-bottom: 12px;
  padding: 7px 11px;
  border-radius: var(--bb-r-chip, 6px);
  background: var(--bb-gold-tint, #FBF4E2);
  color: var(--bb-gold-ink, #A8842C);
  font-size: 11.5px;
  line-height: 1.4;
}
.bb-det-lens b { font-weight: 600; }

.bb-det-hero {
  display: grid;
  grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  gap: 14px;
  align-items: start;
  margin-bottom: 16px;
}
@container (max-width: 620px) {
  .bb-det-hero { grid-template-columns: minmax(0, 1fr); }
}

.bb-det-heroart { min-width: 0; }
.bb-det-artof {
  display: block;
  margin-top: 6px;
  font-size: 10.5px;
  line-height: 1.4;
  color: var(--bb-ink-3, #8a8a8a);
}
.bb-det-artof b { font-weight: 600; color: var(--bb-ink-2, #33383f); overflow-wrap: anywhere; }

/* The picture, two layers: a blurred COVER fill behind and the sharp CONTAINED
   creative on top. Cover alone crops the 1:1 and portrait Meta creatives
   Northbridge runs, and what it crops is the headline. */
.bb-det-media {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  aspect-ratio: 4 / 3;
  border-radius: var(--bb-r-control, 8px);
  background: var(--bb-canvas, #EFEDE6);
  background-size: cover;
  background-position: center;
  box-shadow: var(--bb-sh-hairline, 0 0 0 1px #E6E1D8);
  overflow: hidden;
}
.bb-det-media.is-img::before {
  content: '';
  position: absolute;
  inset: -12%;
  background: inherit;
  background-size: cover;
  background-position: center;
  filter: blur(18px) saturate(1.15);
  transform: scale(1.06);
  opacity: 0.72;
}
.bb-det-media img {
  position: relative;
  z-index: 1;
  width: 100%;
  height: 100%;
  max-width: 100%;
  object-fit: contain;
}
.bb-det-media.is-broken img { display: none; }

.bb-det-media.is-loading {
  background-image: linear-gradient(100deg, var(--bb-canvas, #EFEDE6) 20%, var(--bb-hover, #F4F2EC) 42%, var(--bb-canvas, #EFEDE6) 62%);
  background-size: 220% 100%;
  animation: bb-det-shim 1.5s linear infinite;
}
@keyframes bb-det-shim { from { background-position: 120% 0; } to { background-position: -120% 0; } }

.bb-det-media.is-empty {
  flex-direction: column;
  gap: 5px;
  padding: 16px;
  text-align: center;
}
.bb-det-media.is-empty span { font-size: 12px; font-weight: 600; color: var(--bb-ink-2, #33383f); }
.bb-det-media.is-empty i {
  font-style: normal;
  font-size: 10.5px;
  line-height: 1.45;
  color: var(--bb-ink-3, #8a8a8a);
}

/* A Search ad has no image and is not missing one — its creative IS the text, so
   it gets drawn as the SERP line it actually is. */
.bb-det-media.is-serp { background: var(--bb-surface, #fff); padding: 16px; }
.bb-det-serpcard { width: 100%; text-align: left; }
.bb-det-serptag {
  display: inline-block;
  margin-bottom: 7px;
  padding: 2px 6px;
  border-radius: var(--bb-r-chip, 6px);
  background: var(--bb-field, #F1EFE8);
  color: var(--bb-ink-3, #8a8a8a);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.bb-det-serpcard b {
  display: block;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.35;
  color: var(--bb-accent-ink, #11326D);
}
.bb-det-serpcard i {
  display: block;
  margin-top: 5px;
  font-style: normal;
  font-size: 11.5px;
  line-height: 1.45;
  color: var(--bb-ink-2, #33383f);
}

.bb-det-heronum { min-width: 0; display: flex; flex-direction: column; gap: 11px; }
.bb-det-badges { display: flex; align-items: center; gap: 5px; flex-wrap: wrap; }
.bb-det-acct {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: 0.03em;
  color: var(--bb-ink-3, #8a8a8a);
}
/* The campaign an ad belongs to, as a way BACK to it. */
.bb-det-crumb {
  padding: 2px 7px;
  border: 0;
  border-radius: var(--bb-r-chip, 6px);
  background: var(--bb-accent-tint, #EAEFF8);
  color: var(--bb-accent-ink, #11326D);
  font-family: inherit;
  font-size: 10px;
  font-weight: 600;
  cursor: pointer;
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.bb-det-crumb:hover { background: var(--bb-accent, #0D2A5E); color: #fff; }
.bb-det-crumb:active { transform: none; }

/* ---- the numbers --------------------------------------------------------- */
/* Two tiers: the three the question was about, then the rest. Both are drawn —
   nothing is hidden by the reordering, it just stops competing. */
.bb-det-kpis {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(96px, 1fr));
  gap: 9px;
  padding: 11px 12px;
  border-radius: var(--bb-r-card, 10px);
  background: var(--bb-surface, #fff);
  box-shadow: var(--bb-sh-card, 0 0 0 1px #E6E1D8);
}
.bb-det-kpi { display: flex; flex-direction: column; gap: 2px; min-width: 0; }
.bb-det-kpi b {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 15px;
  font-weight: 600;
  line-height: 1.15;
  letter-spacing: -0.01em;
  color: var(--bb-ink, #1A1A1A);
}
.bb-det-kpi span {
  font-size: 9.5px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bb-ink-3, #8a8a8a);
}
/* The lead tier CLUSTERS LEFT instead of spreading. As a grid, three numbers on
   a 1440px sheet sat ~300px apart and stopped reading as one group — which is
   the only reason they are promoted in the first place. */
.bb-det-kpis.is-lead {
  display: flex;
  gap: 10px 30px;
  flex-wrap: wrap;
  background: var(--bb-accent-tint, #EAEFF8);
  box-shadow: var(--bb-sh-hairline, 0 0 0 1px #E6E1D8);
}
.bb-det-kpis.is-lead .bb-det-kpi b { font-size: 26px; letter-spacing: -0.025em; color: var(--bb-accent-ink, #11326D); }
.bb-det-kpis.is-lead .bb-det-kpi span { color: var(--bb-ink-2, #33383f); }
.bb-det-kpi.is-up b   { color: var(--bb-green, #2E7D52) !important; }
.bb-det-kpi.is-down b { color: var(--bb-red, #B23B3B) !important; }

.bb-det-acts { display: flex; align-items: center; gap: 7px; flex-wrap: wrap; }
.bb-det-act {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 7px 12px;
  border: 0;
  border-radius: var(--bb-r-control, 8px);
  background: var(--bb-surface, #fff);
  box-shadow: var(--bb-sh-btn, 0 0 0 1px #D4CFC4);
  color: var(--bb-ink, #1A1A1A);
  font-family: inherit;
  font-size: 12px;
  font-weight: 600;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 120ms ease-out;
}
.bb-det-act:hover { background: var(--bb-hover, #F4F2EC); }
.bb-det-act:active { transform: none; }
.bb-det-act.is-primary { background: var(--bb-accent, #0D2A5E); color: #fff; box-shadow: none; }
.bb-det-act.is-primary:hover { background: var(--bb-accent-ink, #11326D); }

/* ---- sections ------------------------------------------------------------ */

.bb-det-sec {
  padding: 13px 14px 14px;
  margin-bottom: 10px;
  border-radius: var(--bb-r-card, 10px);
  background: var(--bb-surface, #fff);
  box-shadow: var(--bb-sh-card, 0 0 0 1px #E6E1D8);
}
.bb-det-sec h4 {
  margin: 0 0 4px;
  font-family: 'Outfit', system-ui, sans-serif;
  font-size: 13px;
  font-weight: 600;
  letter-spacing: -0.005em;
  color: var(--bb-ink, #1A1A1A);
}
/* The caveat travels WITH the number it qualifies — the counting rule, the
   relead recovery, the PMax limit. Above the data, not in a footnote nobody
   scrolls to. */
.bb-det-note {
  margin: 0 0 10px;
  font-size: 11px;
  line-height: 1.5;
  color: var(--bb-ink-3, #8a8a8a);
}
.bb-det-sub {
  margin: 10px 0 6px;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bb-ink-3, #8a8a8a);
}
.bb-det-sub:first-child { margin-top: 0; }
.bb-det-empty { margin: 0; font-size: 11.5px; line-height: 1.5; color: var(--bb-ink-3, #8a8a8a); }

.bb-det-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: 8px;
}
@container (max-width: 420px) {
  .bb-det-grid { grid-template-columns: minmax(0, 1fr); }
}

.bb-det-copy { margin-bottom: 9px; }
.bb-det-copy:last-child { margin-bottom: 0; }
.bb-det-copy span {
  display: block;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--bb-ink-3, #8a8a8a);
}
.bb-det-copy b {
  display: block;
  margin-top: 3px;
  font-size: 13px;
  font-weight: 600;
  line-height: 1.4;
  color: var(--bb-ink, #1A1A1A);
  overflow-wrap: anywhere;
}
.bb-det-copy p {
  margin: 3px 0 0;
  font-size: 12px;
  line-height: 1.55;
  color: var(--bb-ink-2, #33383f);
  overflow-wrap: anywhere;
}

/* Two numbers side by side when the whole point is that they DISAGREE
   (platform-reported conversions vs the CRM). */
.bb-det-vs {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(120px, 1fr));
  gap: 10px;
}
.bb-det-vs > div {
  padding: 9px 11px;
  border-radius: var(--bb-r-control, 8px);
  background: var(--bb-inset, #FAF9F6);
  box-shadow: var(--bb-sh-hairline, 0 0 0 1px #E6E1D8);
}
.bb-det-vs b {
  display: block;
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 20px;
  font-weight: 600;
  line-height: 1.1;
  color: var(--bb-ink, #1A1A1A);
}
.bb-det-vs span { font-size: 10.5px; line-height: 1.4; color: var(--bb-ink-3, #8a8a8a); }

.bb-det-split { display: flex; flex-direction: column; gap: 8px; }
.bb-det-splitbar {
  display: block;
  height: 10px;
  border-radius: 999px;
  background: var(--bb-gold, #C9A43F);
  overflow: hidden;
}
.bb-det-splitbar i {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--bb-accent, #0D2A5E);
}
.bb-det-splitlegend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 11.5px; color: var(--bb-ink-2, #33383f); }
.bb-det-splitlegend span { display: inline-flex; align-items: center; gap: 6px; }
.bb-det-splitlegend i { width: 8px; height: 8px; border-radius: 2px; flex: none; }
.bb-det-splitlegend i.is-new { background: var(--bb-accent, #0D2A5E); }
.bb-det-splitlegend i.is-re  { background: var(--bb-gold, #C9A43F); }

.bb-det-skel {
  height: 46px;
  border-radius: var(--bb-r-control, 8px);
  background-image: linear-gradient(100deg, var(--bb-canvas, #EFEDE6) 20%, var(--bb-hover, #F4F2EC) 42%, var(--bb-canvas, #EFEDE6) 62%);
  background-size: 220% 100%;
  animation: bb-det-shim 1.5s linear infinite;
}
.bb-det-skel.is-tall { height: 92px; }

.bb-det-notes {
  padding: 10px 12px;
  margin-bottom: 10px;
  border-radius: var(--bb-r-card, 10px);
  background: var(--bb-inset, #FAF9F6);
  box-shadow: var(--bb-sh-hairline, 0 0 0 1px #E6E1D8);
}
.bb-det-notes p { margin: 0 0 6px; font-size: 11px; line-height: 1.5; color: var(--bb-ink-3, #8a8a8a); }
.bb-det-notes p:last-child { margin-bottom: 0; }

/* A read that did not land is stated, never left as a blank that reads as zero. */
.bb-det-fail {
  padding: 10px 12px;
  margin-bottom: 10px;
  border-radius: var(--bb-r-card, 10px);
  background: var(--bb-red-tint, #F9EBEB);
  color: var(--bb-red, #B23B3B);
  font-size: 11.5px;
  line-height: 1.5;
}

.bb-det-foot {
  display: flex;
  gap: 6px 16px;
  flex-wrap: wrap;
  padding-top: 10px;
  border-top: 1px solid var(--bb-line, #E6E1D8);
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-variant-numeric: tabular-nums;
  font-size: 9.5px;
  letter-spacing: 0.02em;
  color: var(--bb-ink-3, #8a8a8a);
}
.bb-det-foot b { font-weight: 600; color: var(--bb-ink-2, #33383f); }

/* A bar row that names a campaign, an ad or a program is a door to it. It is a
   real <button>, so it needs the full reset off index.html's navy pill — the
   same trap `.bb-viz-card` documents. */
.bb-viz-row.is-openable {
  width: 100%;
  padding: 2px 4px;
  margin: -2px -4px;
  border: 0;
  border-radius: var(--bb-r-chip, 6px);
  background: transparent;
  color: inherit;
  font: inherit;
  text-align: left;
  align-items: center;
  cursor: pointer;
  transition: background-color 120ms ease-out;
}
.bb-viz-row.is-openable:hover { background: var(--bb-hover, #F4F2EC); }
.bb-viz-row.is-openable:active { transform: none; }

/* Phone: the sheet takes the screen. A 16px-inset card on a 375px viewport wastes
   the only width the creative has. */
@media (max-width: 640px) {
  .bb-det { padding: 0; align-items: stretch; }
  .bb-det-sheet { width: 100%; max-height: 100%; border-radius: 0; }
}

/* A surface with no possible creative (a program is a lead-side unit, not a
   published thing) drops the art column rather than showing an empty one. */
.bb-det-hero.is-nomedia { grid-template-columns: minmax(0, 1fr); }

/* The sheet takes focus on open so screen readers land inside the dialog; it is
   not an interactive control, so it must not paint a ring. */
.bb-det-sheet:focus { outline: none; }
.bb-det-sheet:focus-visible { outline: none; }

/* An unresolved tile in the CREATIVE variant (campaign cards now default to it)
   was a 190px grey void that read as a broken card. Shrink it and label it: the
   honest statement is "no art came back", not a hole where a picture goes. */
.bb-viz--creative .bb-viz-thumb.is-empty,
.bb-viz--creative .bb-viz-thumb.is-broken,
.bb-det-grid .bb-viz-thumb.is-empty,
.bb-det-grid .bb-viz-thumb.is-broken {
  aspect-ratio: auto;
  min-height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.bb-viz--creative .bb-viz-thumb.is-empty::after,
.bb-viz--creative .bb-viz-thumb.is-broken::after,
.bb-det-grid .bb-viz-thumb.is-empty::after,
.bb-det-grid .bb-viz-thumb.is-broken::after {
  content: 'sin creativo sincronizado';
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--bb-ink-3, #8a8a8a);
}

/* The sheet's hero art: square, because Northbridge runs square and portrait Meta
   creatives and a 4:3 frame spent its height on empty bands. Wider column too —
   the picture is the subject here, not a chip beside the numbers. */
.bb-det-hero { grid-template-columns: minmax(0, 340px) minmax(0, 1fr); }
.bb-det-media { aspect-ratio: 1 / 1; }
.bb-det-media.is-serp { aspect-ratio: auto; min-height: 150px; align-items: flex-start; }
