/* ============================================================
   BUBBLE — an animated, scroll-triggered note bubble
   dictionary · component 01
   ------------------------------------------------------------
   A description that pops out of the page instead of sitting
   flat inside it. Authored as real DOM prose, so it stays fully
   legible with no JS or under reduced motion; the pop-in/out and
   dismiss behaviours are layered on only when motion is allowed.

   Themeable: set any --bubble-* hook on the bubble OR any ancestor
   (a host skin, a section, :root) to re-skin it. The hooks fall
   back to the shared design tokens (--bs-*), which fall back to
   literal defaults — so the component works even with no tokens.
   The public --bubble-* hooks are resolved into private --_* vars
   here (never declared on .bubble itself) precisely so an ancestor
   override is inherited rather than shadowed.
   ============================================================ */

.bubble {
  /* resolve theme hooks (inherited from any ancestor) → private vars */
  --_bg:      var(--bubble-bg, var(--bs-paper-2, #fbf8f1));
  --_fg:      var(--bubble-fg, var(--bs-ink-2, #6f6657));
  --_fg-em:   var(--bubble-fg-em, var(--bs-ink, #1b1712));
  --_accent:  var(--bubble-accent, var(--hue, var(--bs-glow, #3a4ec9)));
  --_border:  var(--bubble-border, var(--bs-hair, #ddd6c6));
  --_radius:  var(--bubble-radius, 8px);
  --_font:    var(--bubble-font, var(--bs-font-editorial, Georgia, "Times New Roman", serif));
  --_font-chrome: var(--bubble-font-chrome, var(--bs-font-chrome, system-ui, sans-serif));
  --_size:    var(--bubble-size, var(--bs-t-body, 17px));
  --_mono:    var(--bs-font-mono, ui-monospace, monospace);
  --_ease:    var(--bubble-ease, var(--bs-ease, cubic-bezier(.2, .7, .2, 1)));
  --_spring:  var(--bubble-spring, var(--bs-ease-spring, cubic-bezier(.34, 1.56, .64, 1)));
  --_dur:     var(--bubble-dur, var(--bs-rise, .55s));
  --_pad:     var(--bubble-pad, clamp(14px, 1.4vw, 20px));
  --_max:     var(--bubble-max, 58ch);

  position: relative;
  max-width: var(--_max);
  padding: var(--_pad) calc(var(--_pad) + 6px) var(--_pad) calc(var(--_pad) + 4px);
  background: var(--_bg);
  color: var(--_fg);
  font-family: var(--_font);
  font-size: var(--_size);
  line-height: 1.55;
  border: 1px solid var(--_border);
  border-left: 3px solid var(--_accent);
  border-radius: var(--_radius);
  box-shadow: 0 18px 40px -30px rgba(27, 23, 18, .5);
}
.bubble__body > :first-child { margin-top: 0; }
.bubble__body > :last-child { margin-bottom: 0; }
.bubble__body p + p { margin-top: .85rem; }
.bubble__body em { font-style: italic; color: var(--_fg-em); }
.bubble__body strong { color: var(--_fg-em); }
.bubble__body code { font-family: var(--_mono); font-size: .86em; color: var(--_fg-em); }
.bubble__body .muted { color: var(--bs-ink-3, #978d79); }

/* the little corner tail that makes it read as a "bubble" popping out */
.bubble::before {
  content: ""; position: absolute; left: 20px; top: -7px;
  width: 12px; height: 12px; background: var(--_bg);
  border-left: 1px solid var(--_border);
  border-top: 1px solid var(--_border);
  transform: rotate(45deg); border-top-left-radius: 3px;
}
/* the tail only makes sense for the in-flow (pops from the title above) case */
.bubble[data-bubble-place="beside"]::before,
.bubble[data-bubble-place="corner"]::before { display: none; }

/* ---- dismiss (×) ---- */
.bubble__close {
  position: absolute; top: 6px; right: 8px;
  appearance: none; border: 0; background: none; cursor: pointer;
  font-family: var(--_font-chrome); font-size: 15px; line-height: 1;
  padding: 4px 6px; color: var(--bs-ink-3, #978d79);
  border-radius: 4px; transition: color .2s var(--_ease), background .2s var(--_ease);
}
.bubble__close:hover { color: var(--_accent); background: color-mix(in oklab, var(--_fg) 12%, transparent); }
.bubble__close:focus-visible { outline: 2px solid var(--_accent); outline-offset: 2px; }

/* ---- recall pill (shown when collapsed; hidden otherwise) ---- */
.bubble__recall {
  display: none; align-items: center; gap: 8px;
  appearance: none; cursor: pointer;
  font-family: var(--_font-chrome); font-size: 11px; font-weight: 600;
  letter-spacing: .1em; text-transform: uppercase;
  color: var(--_fg);
  padding: 5px 12px 5px 10px;
  background: var(--_bg);
  border: 1px solid var(--_border); border-radius: 999px;
  transition: color .2s var(--_ease), border-color .2s var(--_ease);
}
.bubble__recall:hover { color: var(--_fg-em); border-color: var(--_accent); }
.bubble__recall:focus-visible { outline: 2px solid var(--_accent); outline-offset: 2px; }
.bubble__recall-dot { width: 8px; height: 8px; border-radius: 999px; background: var(--_accent); flex: none; }

/* ---- collapsed: morph the whole bubble down to just the recall pill ---- */
.bubble.is-collapsed { max-width: none; padding: 0; background: none; border: 0; box-shadow: none; }
.bubble.is-collapsed::before { display: none; }
.bubble.is-collapsed .bubble__body,
.bubble.is-collapsed .bubble__close { display: none; }
.bubble.is-collapsed .bubble__recall { display: inline-flex; }

/* ---- gone: once-and-done ---- */
.bubble.is-gone { display: none; }

/* =====================================================================
   MOTION — armed only when the module opts in (html[data-bubbles="on"]),
   which it does only when motion is allowed. Without that attribute
   (no JS / reduced motion / non-participating host) bubbles are just
   visible, static prose. Force-visible under a host's live .is-reduced.
   ===================================================================== */
[data-bubbles="on"] .bubble {
  opacity: 0; transform: translateY(14px) scale(.975);
  transition: opacity var(--_dur) var(--_ease), transform var(--_dur) var(--_spring);
  will-change: opacity, transform;
}
[data-bubbles="on"] .bubble.is-in { opacity: 1; transform: none; }
/* a collapsed pill should not sit hidden waiting for scroll — show it */
[data-bubbles="on"] .bubble.is-collapsed { opacity: 1; transform: none; }

/* reduced motion always wins on legibility (covers a live host toggle too) */
.is-reduced .bubble,
[data-bubbles="on"] .is-reduced .bubble {
  opacity: 1 !important; transform: none !important; transition: none !important;
}
@media (prefers-reduced-motion: reduce) {
  [data-bubbles="on"] .bubble { opacity: 1; transform: none; transition: none; }
}

/* ---- beside / corner placement ----
   The module wraps the anchor in .bubble-anchor-host (tight to the element)
   and moves the bubble inside it, so these offsets are relative to the anchor. */
.bubble-anchor-host { position: relative; display: inline-block; vertical-align: top; max-width: 100%; }
.bubble[data-bubble-place="beside"],
.bubble[data-bubble-place="corner"] { position: absolute; z-index: 6; width: min(34ch, 40vw); max-width: none; }
.bubble[data-bubble-place="beside"] { top: 0; left: calc(100% + 18px); }
.bubble[data-bubble-place="corner"] { right: 12px; bottom: 12px; }

/* narrow screens: don't shove the bubble off-canvas — stack it under the anchor */
@media (max-width: 900px) {
  .bubble[data-bubble-place="beside"],
  .bubble[data-bubble-place="corner"] { position: static; width: auto; margin-top: 14px; }
}
