feat(web): charmbracelet-aesthetic tapir spinner with charm palette
CI / Lint / Test / Vet (push) Successful in 10s
CI / Build & Import (push) Successful in 10s
CI / Mirror to GitHub (push) Has been skipped

Replace the plain ASCII spinner with a Charmbracelet-style TUI panel rendered in
the browser: a dark terminal card holding a rounded purple box (╭─╮╰─╯│) around a
pink block-char tapir (▄▓▀█) with mint eyes (◕ ◕) and a snout that wiggles ∩→∪→~
across three cross-faded frames, plus a lipgloss-style progress bar whose mint
fill grows over a dim track via an 8s CSS clip animation.

Colours come from named palette consts (CharmPurple/Pink/Mint/Cream/Dim) applied
as inline span styles, so the box and CSS share one source of truth. Frames are
built by a small run/line helper that pads each row to a fixed interior width —
an internal test asserts every box row is the same cell width so the border stays
flush. Honours prefers-reduced-motion (static frame + partial bar).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-03 20:11:17 +02:00
co-authored by Claude Opus 4.8
parent 8c6c7ca947
commit a4aeb5efcd
5 changed files with 435 additions and 321 deletions
+10 -7
View File
@@ -140,14 +140,17 @@ templ VideoCard(r store.SummaryRow) {
</li>
}
// TapirSpinner is the summarizing animation: three ASCII tapir frames stacked
// and cross-faded by CSS keyframes (no JS), with a muted "Summarizing…" label.
// The art is aria-hidden — it is decorative; the label carries the meaning.
// TapirSpinner is the summarizing animation: a Charmbracelet-style TUI panel —
// three richly coloured ASCII tapir frames (inline span colours, snout wiggling
// ∩→∪→~) cross-faded by CSS, plus a lipgloss-style progress bar whose mint fill
// grows over the dim track. The panel is aria-hidden (decorative); the
// "Summarizing…" label below carries the meaning for assistive tech.
templ TapirSpinner() {
<div class="tapir-spin" aria-hidden="true">
<pre>{ tapirFrame1 }</pre>
<pre>{ tapirFrame2 }</pre>
<pre>{ tapirFrame3 }</pre>
<div class="tapir-charm" aria-hidden="true">
<pre class="tapir-f1">@templ.Raw(tapirFrameHTML1)</pre>
<pre class="tapir-f2">@templ.Raw(tapirFrameHTML2)</pre>
<pre class="tapir-f3">@templ.Raw(tapirFrameHTML3)</pre>
<div class="tapir-bar"><span class="tapir-bar-fill" style={ "color:" + CharmMint }>{ tapirBarFill }</span></div>
</div>
<p class="tapir-label" role="status" aria-live="polite"><em>Summarizing…</em></p>
}