files: the player's −5 s and +5 s buttons, as Ham Share's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Mitchell-Johnson
2026-10-23 23:48:37 -07:00
co-authored by Claude Opus 5.5
parent f2997a04c8
commit 033343793f
2 changed files with 26 additions and 1 deletions
+13
View File
@@ -67,6 +67,19 @@ if (await page.locator('canvas.vp-sphere').count()) {
} }
await page.screenshot({ path: `${SHOTS}/h1-auto.png` }) await page.screenshot({ path: `${SHOTS}/h1-auto.png` })
// The 5-second buttons: back and on, the ball with them.
{
await controls()
const t0 = (await vstate()).t
await page.getByRole('button', { name: 'On 5 seconds' }).click()
const t1 = (await vstate()).t
await page.getByRole('button', { name: 'Back 5 seconds' }).click()
await page.getByRole('button', { name: 'Back 5 seconds' }).click()
const t2 = (await vstate()).t
const ball = await page.evaluate(() => Number(document.querySelector('.vp input[type=range]').value))
check('+5 and −5 buttons', t1 - t0 > 4 && t1 - t0 < 6.5 && t1 - t2 > 9 && t1 - t2 < 11 && Math.abs(ball - t2) < 1, `${t0.toFixed(1)} → ${t1.toFixed(1)} → ${t2.toFixed(1)}, ball ${ball.toFixed(1)}`)
}
const mid = Math.floor(s.d / 2) const mid = Math.floor(s.d / 2)
mark('seek') mark('seek')
await page.evaluate((m) => { document.querySelector('.vp video').currentTime = m }, mid) await page.evaluate((m) => { document.querySelector('.vp video').currentTime = m }, mid)
+13 -1
View File
@@ -68,6 +68,14 @@ const I = ({ d, size: sz = 20, fill }: { d: string; size?: number; fill?: boolea
const PLAY = 'M7 4.5v15l12.5-7.5z', PAUSE = 'M8 5v14M16 5v14', CHECK = 'M5 12.5l4.5 4.5L19 7.5', TUNE = 'M4 7h10M18 7h2M4 17h4M12 17h8M16 5v4M10 15v4' const PLAY = 'M7 4.5v15l12.5-7.5z', PAUSE = 'M8 5v14M16 5v14', CHECK = 'M5 12.5l4.5 4.5L19 7.5', TUNE = 'M4 7h10M18 7h2M4 17h4M12 17h8M16 5v4M10 15v4'
const FULL = 'M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5', SMALL = 'M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5', PHONE = 'M8 3h8a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zM3 9a9 9 0 0 0 0 6M21 9a9 9 0 0 1 0 6' const FULL = 'M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5', SMALL = 'M9 4v5H4M15 4v5h5M9 20v-5H4M15 20v-5h5', PHONE = 'M8 3h8a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H8a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1zM3 9a9 9 0 0 0 0 6M21 9a9 9 0 0 1 0 6'
// Five seconds back or on: a circling arrow with the 5 inside.
const Skip = ({ back }: { back?: boolean }) => (
<svg width={20} height={20} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d={back ? 'M12 5a7 7 0 1 1-7 7M12 5l2.5-2.5M12 5l2.5 2.5' : 'M12 5a7 7 0 1 0 7 7M12 5L9.5 2.5M12 5l-2.5 2.5'} />
<text x="12" y="15" textAnchor="middle" fontSize="7.5" fontWeight="700" fill="currentColor" stroke="none" fontFamily="system-ui, sans-serif">5</text>
</svg>
)
export default function Player({ path, sudo }: { path: string; sudo: boolean; name: string }) { export default function Player({ path, sudo }: { path: string; sudo: boolean; name: string }) {
const [open, setOpen] = useState<PlayOpen | null>(null) const [open, setOpen] = useState<PlayOpen | null>(null)
const [err, setErr] = useState('') const [err, setErr] = useState('')
@@ -234,7 +242,9 @@ export default function Player({ path, sudo }: { path: string; sudo: boolean; na
const seekBy = useCallback((d: number) => { const seekBy = useCallback((d: number) => {
const v = video.current const v = video.current
if (!v) return if (!v) return
v.currentTime = Math.max(0, Math.min(info?.duration ?? v.duration, v.currentTime + d)) const to = Math.max(0, Math.min(info?.duration ?? v.duration, v.currentTime + d))
v.currentTime = to
setTime(to) // the ball goes there at once, made yet or not
poke() poke()
}, [info, poke]) }, [info, poke])
// Full screen: the browser's where it has one for an element, else (an iPhone) the window. // Full screen: the browser's where it has one for an element, else (an iPhone) the window.
@@ -336,6 +346,8 @@ export default function Player({ path, sudo }: { path: string; sudo: boolean; na
</div> </div>
<div className="vp-row"> <div className="vp-row">
<button className="vp-icon" onClick={toggle} aria-label={paused ? 'Play' : 'Pause'}>{paused ? <I d={PLAY} fill /> : <I d={PAUSE} />}</button> <button className="vp-icon" onClick={toggle} aria-label={paused ? 'Play' : 'Pause'}>{paused ? <I d={PLAY} fill /> : <I d={PAUSE} />}</button>
<button className="vp-icon" onClick={() => seekBy(-5)} aria-label="Back 5 seconds" title="Back 5 seconds"><Skip back /></button>
<button className="vp-icon" onClick={() => seekBy(5)} aria-label="On 5 seconds" title="On 5 seconds"><Skip /></button>
<span className="vp-time">{clock(shown)} / {clock(dur)}</span> <span className="vp-time">{clock(shown)} / {clock(dur)}</span>
<span className="spacer" /> <span className="spacer" />
{vr && canMotion() && ( {vr && canMotion() && (