0.2.59 (unreleased) ------------------- - Added an optional custom audio player: a dependency-free vanilla-TypeScript web component (transport, interactive transcript, and chapter navigation) that can replace the Podlove Web Player on episode detail pages. Enable it with the new ``CAST_AUDIO_PLAYER = "custom"`` setting (default ``"podlove"`` keeps the existing behaviour). The detail page inlines only small player metadata and chapters as JSON; the transcript is **loaded lazily** — fetched once, the first time the reader opens the Transcript panel, from a public, sanitized endpoint (``cast:api:audio_player_transcript`` at ``/api/audios//player-transcript/``) that returns the normalized, sanitized ``{"cues": [...]}`` shape — never the raw Podlove file. Detail pages therefore never ship the transcript, and the server does not build or sanitize it on every render. There is no hover/click-to-load facade. The player follows the host site's colors via CSS custom properties (light/dark/forced-colors) and ships with zero runtime dependencies (~10 KB gzip total, versus the ~138 KB Podlove embed). It renders only on the episode detail path; list/overview cards render no player in custom mode, feeds are unchanged, and the cast-vue SPA ``podlove_players`` API path is untouched. The ``CAST_AUDIO_PLAYER`` value is validated by the cast system checks. - Custom audio player transcript/share polish: diarized (speaker-labelled) transcripts read as dialogue — every speaker run starts with a heading row (an initial chip, the speaker name, and the run's timestamp; a speakerless run such as intro music gets a time-only anchor heading) and the per-cue timestamp gutter collapses (the timestamps stay in the DOM so click-to-seek works per cue); plain transcripts keep a per-cue timestamp. Speaker names are compared trimmed, so whitespace-padded variants never split a run, and each labelled cue button carries a visually-hidden speaker prefix so screen-reader focus lands with context. The lazy transcript fetch shows a spinner and sets ``aria-busy`` on the scroll region while in flight. The Transcript panel reveals/collapses with a restrained spring animation that is disabled under ``prefers-reduced-motion``. The keyboard-navigable-cues control is demoted from a primary ``Tab cues`` text pill to an icon-only secondary toggle (its accessible name ``Keyboard-navigable cues`` and the ``cast-transcript-tabbable`` preference are unchanged). The in-transport share button can be opted out with a ``data-share="none"`` attribute (``{% cast_custom_player audio post transport_share=False %}``) for hosts that own a page-level share UI; the read-only ``getShareState()`` API is unaffected so that UI can still read the player time. - Custom audio player: make the transport keyboard shortcuts reachable. The shortcuts (Space/K play-pause, arrow seek, Home/End) were scoped to a keydown listener on the ```` element, which had no ``tabindex`` and so could never hold focus — leaving them effectively dead. The player is now focusable (``tabindex=0``, ``role="group"``, ``aria-keyshortcuts``) with a focus-visible ring, and clicking the play button moves focus to the player so the shortcuts work immediately after starting playback. - Custom audio player follow-ups: (1) the transport keyboard shortcuts are now **page-global** — a single document-level listener routes Space/K, arrow seek, and Home/End to the active (or the only) player from anywhere on the episode page, so the reader can pause or scrub while reading the transcript without first focusing the player; keypresses are ignored while focus is on a form field, button, link, or the transcript search, so typing and native activation are never hijacked. (2) Clicking a transcript line now **seeks and starts playback** immediately, instead of only seeking and waiting for a separate play press. (3) The current-cue highlight is a single quiet idiom — an accent left border plus a translucent accent tint — replacing the per-line underline band, which painted across the full column width (past the end of each line) and read as a rendering glitch on wrapped cues. (4) Follow-along keeps look-ahead: cues carry ``scroll-margin-block`` so the current line stays well clear of the panel's bottom edge (a bare ``block: nearest`` pinned it there, hiding all upcoming text), and far jumps (a search hit or chapter seek several panel heights away) land instantly instead of smooth-scrolling for seconds. (5) Searching suspends follow-along so the highlight never scrolls away from the match being read — the Follow toggle dims while suspended, clearing the query re-anchors to the line being spoken, and ``Escape`` in the search field clears the query deterministically. - Custom audio player: an advertised transcript that resolves to **zero cues** (for example a stored transcript file that is missing from media storage) now shows "No transcript available for this episode." in the opened panel and hides the search/follow toolbar, instead of rendering a dead, empty panel. A failed transcript fetch hides the toolbar the same way while keeping the existing retry-on-reopen behaviour. - Update bundled htmx from 2.0.8 to 2.0.10. - Added ``just js-bundles`` to report the raw and gzip sizes of the frontend bundles currently built and shipped with django-cast. The report renders as colored ``rich`` tables with friendly entry names and a gzip-size comparison bar; ``--plain`` (or a missing ``rich``) falls back to plain aligned text.