beecast-player 0.6.0

The first-party beecast player — a clean-room, dependency-free asciicast (v1/v2/v3) player and VT emulator, exposed as inlinable JS/CSS string constants, with live-follow append for recordings that are still growing.
Documentation
/* beecast-player chrome + terminal palette (see the crate README).
   Clean-room, MIT like the rest of beecast. Semantic chrome tokens (--beecast-*) are the
   stable theming surface; --sp-* covers the terminal palette and remains independently
   themeable. Internal .sp-* classes are not a public API. */

/* Semantic theme tokens (Phase 8). Defaults map to the classic dark chrome. */
.beecast-player {
  --beecast-color-surface: #121417;
  --beecast-color-surface-raised: rgba(255, 255, 255, 0.04);
  --beecast-color-text: #cdd3dc;
  --beecast-color-text-muted: rgba(205, 211, 220, 0.8);
  --beecast-color-accent: #6a9ded;
  --beecast-color-focus: rgba(88, 166, 255, 0.5);
  --beecast-color-marker: #d5b45e;
  --beecast-color-error: #e05f65;
  --beecast-control-height: 44px;
  --beecast-radius: 4px;
  --beecast-font-ui: 13px/1.2 ui-sans-serif, system-ui, sans-serif;
  --beecast-font-terminal: 13px/1.25 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;

  /* Terminal palette */
  --sp-bg: var(--beecast-color-surface);
  --sp-fg: var(--beecast-color-text);
  --sp-c0: #1c1f24; --sp-c1: #e05f65; --sp-c2: #78bd65; --sp-c3: #d5b45e;
  --sp-c4: #6a9ded; --sp-c5: #bd7bd8; --sp-c6: #55b8c2; --sp-c7: #b8bfc9;
  --sp-c8: #596273; --sp-c9: #ff8188; --sp-c10: #98d989; --sp-c11: #f0d182;
  --sp-c12: #8fb7ff; --sp-c13: #d99df0; --sp-c14: #7fd6df; --sp-c15: #eef1f5;

  display: block;
  background: var(--sp-bg);
  color: var(--sp-fg);
  border-radius: var(--beecast-radius);
  outline: none;
  overflow: hidden;
}

/* Built-in themes */
.beecast-player[data-theme="light"] {
  --beecast-color-surface: #f4f5f7;
  --beecast-color-surface-raised: rgba(0, 0, 0, 0.04);
  --beecast-color-text: #1c1f24;
  --beecast-color-text-muted: rgba(28, 31, 36, 0.75);
  --beecast-color-accent: #3b6fd4;
  --beecast-color-focus: rgba(59, 111, 212, 0.45);
  --beecast-color-marker: #b8860b;
  --sp-bg: #f4f5f7;
  --sp-fg: #1c1f24;
}

@media (prefers-color-scheme: light) {
  .beecast-player[data-theme="system"] {
    --beecast-color-surface: #f4f5f7;
    --beecast-color-surface-raised: rgba(0, 0, 0, 0.04);
    --beecast-color-text: #1c1f24;
    --beecast-color-text-muted: rgba(28, 31, 36, 0.75);
    --beecast-color-accent: #3b6fd4;
    --beecast-color-focus: rgba(59, 111, 212, 0.45);
    --beecast-color-marker: #b8860b;
    --sp-bg: #f4f5f7;
    --sp-fg: #1c1f24;
  }
}

.beecast-player:focus-visible { box-shadow: 0 0 0 2px var(--beecast-color-focus); }
/* Fullscreen: control bar pinned at the bottom (unshrinkable); terminal block
   centers in the free space above via auto margins. Correct scale+height in
   layout() keeps the bar on-screen — previously a too-tall screen box pushed it
   off under overflow:hidden, taking the speed control with it. */
.beecast-player:fullscreen {
  width: 100%; height: 100%; border-radius: 0;
  display: flex; flex-direction: column;
  box-sizing: border-box;
}
.beecast-player:fullscreen .sp-screen-box {
  flex: 0 0 auto;
  width: 100%;
  margin-top: auto;
  margin-bottom: auto;
}
.beecast-player:fullscreen .sp-bar {
  flex: 0 0 auto;
  width: 100%;
  margin-top: 0;
  position: relative;
  z-index: 20;
}
.beecast-player [hidden] { display: none !important; }

.sp-screen-box { overflow: hidden; position: relative; }

/* Center play overlay: soft dim + refined block-character glyph */
.sp-overlay {
  position: absolute; inset: 0; z-index: 3;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, 0.42);
  cursor: pointer;
}
.sp-overlay:focus-visible {
  outline: 2px solid var(--beecast-color-accent);
  outline-offset: -4px;
}
.sp-bigplay {
  margin: 0;
  font: 14px/1.05 ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  letter-spacing: 0.02em;
  color: #fff;
  opacity: 0.92;
  text-shadow:
    0 0 1px rgba(0, 0, 0, 0.9),
    0 4px 18px rgba(0, 0, 0, 0.65);
  transition: opacity 0.15s, transform 0.15s;
  user-select: none;
}
.sp-overlay:hover .sp-bigplay,
.sp-overlay:focus-visible .sp-bigplay {
  opacity: 1;
  transform: scale(1.06);
}
@media (prefers-reduced-motion: reduce) {
  .sp-bigplay { transition: none; }
  .sp-overlay:hover .sp-bigplay,
  .sp-overlay:focus-visible .sp-bigplay { transform: none; }
}

/* Off-screen snapshot for accessibility="snapshot" — readable, not a live region. */
.sp-a11y {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: pre;
  border: 0;
}

.sp-chapters {
  position: absolute; top: 0; right: 0; bottom: 0; z-index: 4;
  width: min(320px, 60%);
  overflow-y: auto;
  background: rgba(9, 11, 15, 0.92);
  border-left: 1px solid rgba(255, 255, 255, 0.14);
  font: 12px/1.45 ui-sans-serif, system-ui, sans-serif;
  padding: 4px 0;
}
.sp-chap {
  display: block; width: 100%; text-align: left;
  font: inherit; color: var(--sp-fg);
  background: transparent; border: none; cursor: pointer;
  padding: 5px 12px;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sp-chap:hover { background: rgba(106, 157, 237, 0.18); color: #fff; }
.sp-chap:focus-visible {
  outline: 2px solid var(--beecast-color-accent);
  outline-offset: -2px;
}
.sp-chap-on {
  background: rgba(106, 157, 237, 0.22);
  color: #fff;
  box-shadow: inset 3px 0 0 var(--beecast-color-accent);
}
.sp-chap-ann { font-style: italic; opacity: 0.95; }
.sp-chap-t { color: var(--sp-c4); margin-right: 8px; font-variant-numeric: tabular-nums; }

.sp-screen {
  margin: 0;
  padding: 4px 6px;
  font: var(--beecast-font-terminal);
  white-space: pre;
  transform-origin: top left;
  width: max-content;
}
/* Styled runs as inline-blocks so a run's background paints the full 1.25 line box. */
.sp-screen span { display: inline-block; }
.sp-probe { visibility: hidden; position: absolute; }
.sp-b { font-weight: 700; }
.sp-d { opacity: 0.6; }
.sp-i { font-style: italic; }
.sp-u { text-decoration: underline; }
.sp-s { text-decoration: line-through; }
.sp-u.sp-s { text-decoration: underline line-through; }
.sp-cur { outline: 1px solid var(--sp-fg); outline-offset: -1px; }

.sp-bar {
  display: flex;
  align-items: center;
  flex-wrap: nowrap;
  gap: 10px;
  min-height: var(--beecast-control-height);
  padding: 10px 12px;
  background: var(--beecast-color-surface-raised);
  border-top: 1px solid rgba(255, 255, 255, 0.08);
  font: var(--beecast-font-ui);
  user-select: none;
  box-sizing: border-box;
  /* Menus grow upward out of the bar; keep them paintable over the terminal. */
  position: relative;
  z-index: 6;
  overflow: visible;
}
.sp-bar button {
  font: inherit;
  color: var(--sp-fg);
  background: transparent;
  border: none;
  cursor: pointer;
  padding: 8px 12px;
  border-radius: 4px;
  min-height: 36px;
  min-width: 36px;
  line-height: 1.2;
  flex: 0 0 auto;
}
.sp-bar button:hover { color: #fff; background: rgba(255, 255, 255, 0.06); }
.sp-bar button:focus-visible {
  outline: 2px solid var(--beecast-color-accent);
  outline-offset: 1px;
}
.sp-play { font-size: 18px; padding: 8px 14px; min-width: 40px; }

.sp-speedwrap {
  position: relative;
  display: inline-flex;
  flex: 0 0 auto;
  align-items: center;
}
.sp-speed {
  min-width: 3.25em;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}
.sp-speedmenu {
  position: absolute; bottom: calc(100% + 8px); right: 0; z-index: 30;
  display: flex; flex-direction: column;
  min-width: 4.5em; padding: 4px;
  background: #161a20;
  border: 1px solid rgba(255, 255, 255, 0.16);
  border-radius: var(--beecast-radius);
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.5);
}
.sp-speedopt {
  text-align: center;
  border-radius: 3px;
  min-height: 32px;
  padding: 6px 10px;
}
.sp-speedopt.sp-on {
  color: var(--sp-c12);
  font-weight: 700;
  box-shadow: inset 0 0 0 1px var(--beecast-color-accent);
}
.sp-speedopt:hover { background: rgba(106, 157, 237, 0.18); }
.sp-speedopt:focus-visible {
  outline: 2px solid var(--beecast-color-accent);
  outline-offset: 0;
}

.sp-time, .sp-dur {
  color: var(--beecast-color-text-muted);
  min-width: 3.6em;
  text-align: center;
  font-variant-numeric: tabular-nums;
  flex: 0 0 auto;
  font-size: 13px;
}
.sp-seek {
  position: relative;
  flex: 1 1 auto;
  min-width: 64px;
  height: 10px;
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.12);
  cursor: pointer;
}
.sp-seek:focus-visible {
  outline: 2px solid var(--beecast-color-accent);
  outline-offset: 3px;
}
.sp-fill {
  position: absolute; left: 0; top: 0; bottom: 0;
  width: 0;
  border-radius: 3px;
  background: var(--beecast-color-accent);
  pointer-events: none;
}
.sp-markers { position: absolute; inset: 0; pointer-events: none; }
.sp-marker {
  position: absolute; top: -2px; bottom: -2px; width: 2px;
  background: var(--beecast-color-marker);
  pointer-events: auto;
}
.sp-marker-ann {
  width: 0;
  border-left: 2px dashed var(--beecast-color-marker);
  background: transparent;
}

/* Custom element host fills its mount when used directly. */
beecast-player {
  display: block;
  width: 100%;
  height: 100%;
}