Skip to content

Commit 7367d16

Browse files
authored
Merge pull request #29 from adurrr/fix/demo-gif-v1.2
feat: recreate demo GIF, bump version to 1.2.0 (fixes #11)
2 parents 0a3f88a + d743c4f commit 7367d16

4 files changed

Lines changed: 95 additions & 38 deletions

File tree

CHANGELOG.md

Lines changed: 25 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -6,30 +6,43 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
66

77
## [Unreleased]
88

9+
## [1.2.0] - 2026-06-02
10+
911
### Added
1012

11-
- Demo GIF to README showing persistent player across page navigation
12-
- Scripts for capturing and generating demo GIF (`scripts/capture-demo.mjs`, `scripts/make-gif.py`)
13-
- Favicon and browser assets (SVG, ICO, PNG sizes, Apple touch icon, Android Chrome icons, webmanifest)
14-
- Theme-color meta tags for light and dark color schemes
15-
- `CHANGELOG.md` with Keep a Changelog format
13+
- Bidirectional real-time sync of mute, volume, playback rate, and chapter seek between inline player and footer via new `podcast-state-change` event (#25, #28)
14+
- `podcast-seek` event dispatched from skip (rewind/forward) and chapter-click buttons so footer tracks all position changes
15+
- Updated demo GIF showing new sync features (8 frames, SVG icons, volume/speed sync)
1616

1717
### Changed
1818

19-
- Replaced emoji with Feather/Lucide SVG icons in inline player skip and mute buttons
19+
- Updated demo capture script (`scripts/capture-demo.mjs`) for new site structure, port 1311, and `/wavecast/` subpath
20+
- Updated GIF generation script (`scripts/make-gif.py`) with new captions matching updated demo flow
2021

21-
## [1.1.0] - 2026-05-31
22+
### Fixed
2223

23-
### Changed
24+
- Speed change on inline player no longer accidentally mutes footer audio (volume/mute defaults now read from UI rather than silenced `<audio>` when footer is active)
25+
- Double-audio prevention: inline `_setVolume`, `_toggleMute`, `_cycleRate`, and keyboard volume handlers now skip modifying the inline `<audio>` when a footer is producing audible output
26+
- Event echo loop prevented with `_suppressSync` guard flag on both inline player and footer
2427

25-
- Updated theme.toml tags for Hugo theme gallery: `podcast`, `radio`, `music`, `responsive`, `dark`, `dark mode`
28+
## [1.1.0] - 2026-05-31
2629

2730
### Added
2831

32+
- Demo GIF to README showing persistent player across page navigation
33+
- Scripts for capturing and generating demo GIF (`scripts/capture-demo.mjs`, `scripts/make-gif.py`)
34+
- Favicon and browser assets (SVG, ICO, PNG sizes, Apple touch icon, Android Chrome icons, webmanifest)
35+
- Theme-color meta tags for light and dark color schemes
36+
- `CHANGELOG.md` with Keep a Changelog format
2937
- Hugo theme gallery screenshots (`images/screenshot.png`, `images/tn.png`)
3038
- README badges (release, license, tests, GitHub stars)
3139
- `demosite` field in theme.toml
3240

41+
### Changed
42+
43+
- Replaced emoji with Feather/Lucide SVG icons in inline player skip and mute buttons
44+
- Updated theme.toml tags for Hugo theme gallery: `podcast`, `radio`, `music`, `responsive`, `dark`, `dark mode`
45+
3346
### Fixed
3447

3548
- Em dashes replaced with hyphens in theme.toml
@@ -80,6 +93,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
8093

8194
---
8295

83-
[Unreleased]: https://github.com/adurrr/wavecast/compare/v1.1.0...HEAD
84-
[1.1.0]: https://github.com/adurrr/wavecast/releases/tag/v1.1.0
96+
[Unreleased]: https://github.com/adurrr/wavecast/compare/v1.2.0...HEAD
97+
[1.2.0]: https://github.com/adurrr/wavecast/compare/v1.1.0...v1.2.0
98+
[1.1.0]: https://github.com/adurrr/wavecast/compare/v1.0.0...v1.1.0
8599
[1.0.0]: https://github.com/adurrr/wavecast/releases/tag/v1.0.0

images/demo.gif

9.64 KB
Loading

scripts/capture-demo.mjs

Lines changed: 63 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { chromium } from 'playwright';
22
import { mkdirSync } from 'fs';
33

4-
const BASE = 'http://localhost:1313';
4+
const BASE = 'http://localhost:1311/wavecast';
55
const OUT = '/tmp/demo-frames';
66

77
mkdirSync(OUT, { recursive: true });
@@ -16,7 +16,7 @@ const context = await browser.newContext({
1616
const page = await context.newPage();
1717

1818
let frame = 0;
19-
const shot = async (label, delay = 600) => {
19+
const shot = async (label, delay = 800) => {
2020
await page.waitForTimeout(delay);
2121
const n = String(frame++).padStart(2, '0');
2222
await page.screenshot({ path: `${OUT}/frame-${n}-${label}.png` });
@@ -40,14 +40,40 @@ async function clickClose() {
4040
});
4141
}
4242

43+
async function clickRateBtn() {
44+
await page.evaluate(() => {
45+
const pp = document.querySelector('podcast-player');
46+
if (pp && pp.shadowRoot) {
47+
pp.shadowRoot.querySelector('[part="rate-btn"]')?.click();
48+
}
49+
});
50+
}
51+
52+
async function setVolume(value) {
53+
await page.evaluate((v) => {
54+
const pp = document.querySelector('podcast-player');
55+
if (pp && pp.shadowRoot) {
56+
const slider = pp.shadowRoot.querySelector('[part="volume"]');
57+
if (slider) {
58+
// Set value and dispatch input event
59+
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
60+
window.HTMLInputElement.prototype, 'value'
61+
).set;
62+
nativeInputValueSetter.call(slider, String(v));
63+
slider.dispatchEvent(new Event('input', { bubbles: true }));
64+
}
65+
}
66+
}, value);
67+
}
68+
4369
async function footerIsActive() {
4470
return page.evaluate(() => {
4571
const f = document.querySelector('podcast-footer');
4672
return f && f.hasAttribute('active');
4773
});
4874
}
4975

50-
async function waitFooterActive(timeout = 5000) {
76+
async function waitFooterActive(timeout = 8000) {
5177
const start = Date.now();
5278
while (Date.now() - start < timeout) {
5379
if (await footerIsActive()) return;
@@ -65,37 +91,52 @@ async function waitFooterInactive(timeout = 5000) {
6591
throw new Error('Footer did not become inactive');
6692
}
6793

68-
// === 1. Homepage — show inline player ===
94+
// === 1. Homepage — inline player loaded ===
6995
await page.goto(`${BASE}/`);
7096
await page.waitForSelector('podcast-player');
71-
await shot('01-homepage-inline-player', 1000);
97+
await shot('01-homepage-inline-player', 1200);
7298

73-
// === 2. Click play on first player → footer appears ===
99+
// === 2. Click play → footer appears with SVG icons and poster ===
74100
await clickPlay();
75101
await waitFooterActive();
76-
await shot('02-footer-appeared');
77-
78-
// === 3. Navigate to a post page → footer persists ===
79-
await page.locator('a[href="/posts/test-episode/"]').first().click();
80-
await page.waitForTimeout(1500);
102+
await shot('02-footer-appears-with-icons', 400);
103+
104+
// === 3. Change volume on inline → footer syncs ===
105+
// Drag inline volume slider to 0.3 and capture footer following
106+
await setVolume(0.3);
107+
await page.waitForTimeout(600);
108+
await shot('03-volume-sync-footer-follows', 200);
109+
110+
// === 4. Change playback speed on inline → footer syncs ===
111+
await clickRateBtn(); // 1× → 1.25×
112+
await page.waitForTimeout(400);
113+
await shot('04-speed-sync-footer-follows', 200);
114+
115+
// === 5. Navigate to episode page → footer persists ===
116+
await page.locator('a[href*="/episodes/"]').first().click();
117+
await page.waitForTimeout(1800);
81118
await waitFooterActive();
82-
await shot('03-navigate-footer-persists');
119+
await shot('05-navigate-footer-persists');
83120

84-
// === 4. Navigate back to home — footer still there ===
85-
await page.locator('a[href="/"]').first().click();
86-
await page.waitForTimeout(1500);
87-
await waitFooterActive();
88-
await shot('04-navigate-back-footer-still-there');
121+
// === 6. Interact with footer controls (skip forward) ===
122+
await page.evaluate(() => {
123+
const f = document.querySelector('podcast-footer');
124+
if (f && f.shadowRoot) {
125+
f.shadowRoot.querySelector('[part="skip-fwd-btn"]')?.click();
126+
}
127+
});
128+
await page.waitForTimeout(600);
129+
await shot('06-footer-skip-forward');
89130

90-
// === 5. Close footer ===
131+
// === 7. Close footer ===
91132
await clickClose();
92133
await waitFooterInactive();
93-
await shot('05-footer-closed');
134+
await shot('07-footer-closed');
94135

95-
// === 6. Navigate again — footer should NOT reappear ===
96-
await page.locator('a[href="/posts/test-episode/"]').first().click();
136+
// === 8. Navigate to another page → footer stays closed ===
137+
await page.locator('a[href*="/programs/"]').first().click();
97138
await page.waitForTimeout(1500);
98-
await shot('06-navigate-no-footer');
139+
await shot('08-navigate-footer-stays-closed');
99140

100141
await browser.close();
101142
console.log(`Captured ${frame} frames to ${OUT}`);

scripts/make-gif.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77
WIDTH = 800
88

99
captions = [
10-
"Page loads with inline podcast player",
11-
"Click play -> sticky footer player appears at bottom",
12-
"Navigate to another page -> footer persists",
13-
"Navigate back -> footer still there, still playing",
10+
"Page loads with SVG-icon podcast player and chapters",
11+
"Click play -> sticky footer player appears with poster",
12+
"Change volume on inline -> footer slider syncs in real time",
13+
"Change playback speed on inline -> footer syncs speed",
14+
"Navigate to another page -> footer persists, never stops",
15+
"Interact with footer (skip forward) while navigating",
1416
"Close the footer player",
15-
"Navigate again -> footer stays closed",
17+
"Navigate again -> footer stays closed, playback stops",
1618
]
1719

1820
frame_files = sorted(

0 commit comments

Comments
 (0)