/* Shared "explainer player" component - a small looping SVG animation with a play/replay
   control, embedded via partials/explainer-*.html on the homepage and on the child-feed/margin
   blog posts. One CSS file + one JS file (js/explainer-player.js) serves every instance; the
   per-animation SVG markup and timeline live in the partial itself.

   The player's own scene is deliberately a fixed dark surface (#0e1a15, the same color already
   used site-wide for header.site / footer / .diagram-hub / --code-bg) rather than following
   --surface/--bg, so it reads the same in light or dark mode - consistent with how those other
   fixed-dark chrome pieces already behave (see base.css's comment on header.site). */

.xp-shell {
  background: #0e1a15;
  border-radius: 20px;
  padding: 14px 14px 0;
  box-shadow: var(--shadow);
}
.xp-player {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: radial-gradient(120% 140% at 18% 14%, #16281f 0%, #0e1a15 58%);
  border-radius: 10px 10px 0 0;
  overflow: hidden;
}
.xp-player svg.xp-diagram { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }

/* every timed piece uses this one transition, toggled by a JS-driven timeline - no
   animation-delay cascade trickery, so restarting the timeline can never desync a piece from
   the moment it's actually supposed to appear. */
.xp-reveal { opacity: 0; transform: scale(.94); transition: opacity .45s ease, transform .45s cubic-bezier(.2,.8,.3,1.15); }
.xp-reveal.xp-visible { opacity: 1; transform: scale(1); }
.xp-pulse.xp-visible { animation: xp-pulse 2.4s ease-in-out .4s infinite; }
@keyframes xp-pulse { 0%, 100% { opacity: .55; } 50% { opacity: 1; } }

/* a quick scale-emphasis for a value that just changed in place (a stock number, a status pill) -
   distinct from .xp-reveal's own fade-in, since these elements are already visible and are being
   re-emphasized, not revealed for the first time. */
.xp-bump.xp-visible { animation: xp-bump .5s cubic-bezier(.2,.8,.3,1.4); }
@keyframes xp-bump { 0% { transform: scale(.7); } 60% { transform: scale(1.12); } 100% { transform: scale(1); } }

.xp-character.xp-visible .xp-bob-inner { animation: xp-bob 1.1s ease-in-out .3s infinite; transform-origin: 50% 100%; }
@keyframes xp-bob { 0%, 100% { transform: translateY(0) rotate(0deg); } 50% { transform: translateY(-5px) rotate(-2deg); } }

/* a slow marching-dash flow along a connecting line - a continuous cue that this is a live feed,
   not a one-time drawing. runs only once that piece's .xp-visible class lands. */
.xp-flow-line { stroke-dasharray: 7 6; }
.xp-reveal.xp-visible .xp-flow-line { animation: xp-flowdash .9s linear infinite; }
@keyframes xp-flowdash { to { stroke-dashoffset: -26; } }

@media (prefers-reduced-motion: reduce) {
  .xp-reveal { transition: none; }
  .xp-character.xp-visible .xp-bob-inner { animation: none; }
  .xp-reveal.xp-visible .xp-flow-line { animation: none; }
  .xp-bump.xp-visible { animation: none; }
}

.xp-caption-zone {
  position: absolute; left: 0; right: 0; bottom: 9%;
  display: flex; justify-content: center; padding: 0 5%;
  pointer-events: none;
}
.xp-caption {
  /* explicit bottom (not "auto") so a caption long enough to wrap to two lines grows upward from
     a fixed anchor instead of falling back to the ambiguous flex-item static-position algorithm -
     which is what was silently clipping the second line at narrower widths (e.g. two players
     side by side) before this was added. */
  position: absolute; left: 0; right: 0; bottom: 0;
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(.85rem, 2.3vw, 1.14rem);
  color: #f5f2ea; text-align: center; text-wrap: balance;
}
.xp-brandcard {
  position: absolute; inset: 0; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: .6rem;
}
.xp-brandcard .xp-mark { position: static; inset: auto; width: 15%; height: auto; flex: none; }
.xp-brandcard .xp-word { font-family: var(--font-display); font-weight: 800; color: #f5f2ea; font-size: clamp(1.3rem, 4.4vw, 2rem); letter-spacing: -.01em; }
.xp-brandcard .xp-url { font-family: var(--font-mono); color: #4fb8ae; font-size: clamp(.75rem, 2vw, .95rem); letter-spacing: .02em; }

.xp-bar-track { height: 4px; background: rgba(255,255,255,.1); border-radius: 999px; overflow: hidden; margin: 12px 2px; }
.xp-bar-fill { height: 100%; width: 0%; background: linear-gradient(90deg, #4fb8ae, #ef5a3a); border-radius: inherit; }

.xp-controls { display: flex; align-items: center; justify-content: space-between; padding: 2px 2px 14px; }
.xp-runtime { font-family: var(--font-mono); font-size: .72rem; color: #9fb0a8; }
.xp-replay-btn {
  font-family: var(--font-body); font-weight: 600; font-size: .82rem;
  display: inline-flex; align-items: center; gap: .4em;
  background: rgba(255,255,255,.08); color: #f5f2ea; border: 1px solid rgba(255,255,255,.16);
  border-radius: 999px; padding: .4rem .9rem; cursor: pointer;
}
.xp-replay-btn:hover:not(:disabled) { background: rgba(255,255,255,.14); }
.xp-replay-btn:disabled { opacity: .5; cursor: default; }
.xp-replay-btn svg { width: 13px; height: 13px; }
.xp-replay-btn:focus-visible { outline: 2px solid #4fb8ae; outline-offset: 2px; }

.xp-legend { display: flex; flex-wrap: wrap; gap: .6rem 1.1rem; margin: 1rem 2px 0; }

/* the margins player's .xp-shell used to carry this spacing as an inline style, which was fine
   standalone in blog prose but added an unwanted top offset once the same partial sat in the
   homepage grid next to the channels player - misaligning the two boxes. Scoping it to
   .art-content keeps the blog spacing without affecting the grid. */
.art-content .xp-shell { margin: 2rem 0; }
.xp-legend-item { display: inline-flex; align-items: center; gap: .5em; font-size: .82rem; color: var(--ink-soft); }
.xp-legend-swatch { width: 10px; height: 10px; border-radius: 3px; flex: none; }

/* two players side by side (homepage). Children are NOT wrapped in a per-column div - they're
   direct grid items in the order title1, sub1, shell1, legend1, title2, sub2, shell2, legend2 -
   because grid-auto-flow:column fills column 1 with the first 4 items then column 2 with the
   next 4, and grid sizes each of the 4 explicit rows to the tallest cell IN THAT ROW across both
   columns. That's what keeps the two titles, the two subs, and (critically) the two players
   themselves aligned even when one blurb wraps to a different number of lines than the other -
   wrapping each column in its own div would size columns independently and the players would
   drift out of alignment exactly as they did before this. */
.xp-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  grid-template-rows: auto auto auto auto;
  grid-auto-flow: column;
  column-gap: 2.2rem;
  align-items: start;
}
.xp-col-title { font-size: 1.05rem; margin: 0 0 .35rem; }
.xp-col-sub { font-size: .87rem; color: var(--muted); margin: 0 0 1rem; }
@media (max-width: 860px) {
  .xp-grid {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    grid-auto-flow: row;
    row-gap: .35rem;
  }
  /* extra breathing room between one stacked video block and the next title, not needed on the
     very last legend but harmless there either way. */
  .xp-grid .xp-legend { margin-bottom: 2.6rem; }
}
