Commit Graph
5 Commits
Author SHA1 Message Date
terencechoandClaude Sonnet 4.6 dd375e2784 fix(player): clamp scrubber progress when postMessage frame exceeds duration (#700)
The postMessage state path set `_currentTime` without clamping, while the
direct timeline path already used `Math.min(currentTime, _duration)`. A
final-frame state message with a frame count slightly past the end would
set `_currentTime > _duration`, causing the progress bar (position:
absolute, no overflow guard) to bleed out of the scrubber track and
visually cover the volume button, and the time display to show values
like "0:05 / 0:04".

- Clamp `_currentTime` in `_onMessage` to match the direct timeline path
- Clamp defensively in `updateTime` so the display layer never overflows
- Add `overflow: hidden` + `min-width: 0` to `.hfp-scrubber` as a CSS
  safety net; remove now-redundant `border-radius` from `.hfp-progress`
  (parent `overflow: hidden` handles clipping to the rounded shape)
- Apply the same `overflow: hidden` fix to `.hfp-volume-slider` for
  consistency; remove redundant `border-radius` from `.hfp-volume-fill`
- Add regression test covering the postMessage over-duration case

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:23:19 -07:00
Miguel ÁngelandClaude Opus 4.6 a7a6648852 feat(player): add volume/mute controls (#651)
* feat(player): add volume/mute controls to the player

Adds a mute toggle button and volume slider to the controls bar,
positioned between the time display and speed selector. The slider
expands on hover for a compact default footprint.

- `volume` attribute/property (0–1, clamped) with `volumechange` event
- `muted` attribute now syncs to the controls UI (icon updates)
- Three volume icons: high, low, muted — updates reactively
- Volume forwarded to parent-frame audio proxies and iframe runtime
  via `set-volume` postMessage control
- 9 new tests covering volume clamping, events, controls rendering,
  mute toggle, and iframe message forwarding

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(player): wire set-volume through runtime bridge + address review feedback

Addresses the blocker from PR review: the iframe runtime now handles
`set-volume` messages end-to-end (types → bridge → init → media sync).

Runtime side:
- Add `set-volume` to RuntimeBridgeControlAction union
- Add `volume` field to RuntimeBridgeControlMessage
- Handle `set-volume` in bridge.ts with [0,1] clamping
- Store bridgeVolume in RuntimeState, apply to media elements
- syncRuntimeMedia composes userVolume × clip author volume

Player side:
- Muted toggle now dispatches `volumechange` (HTML5 spec compliance)
- Volume slider auto-unmutes when scrubbed above 0 while muted
- Touch support on volume slider (touchstart/move/end)

Tests: 5 new (3 bridge, 2 media)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(player): add ARIA keyboard controls, fix icon collision and clipVolume parity

- Volume slider: role="slider", aria-label, aria-valuemin/max/now,
  tabindex=0, arrow key support (5% steps, auto-unmutes)
- Volume=0 unmuted now shows low-volume icon instead of muted icon
- Fix clipVolume divergence: init.ts uses Number.isFinite() matching
  media.ts semantics (preserves data-volume="0")
- 3 new tests: ARIA attributes, volumechange on mute, icon collision

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-05-07 00:38:27 +02:00
James RussoandClaude Opus 4.6 18de86e4bd fix(player): handle Infinity duration; add lint rules for data-duration and Math.ceil overshoot (#243)
* fix(player): handle Infinity duration from runtime gracefully

When compositions have repeating animations without data-duration, the
runtime sends durationInFrames: Infinity. The player now ignores
non-finite duration values instead of displaying "Infinity:NaN" in the
controls. formatTime also returns "0:00" for non-finite inputs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* feat(lint): add data-duration and Math.ceil overshoot rules

- Add root_composition_missing_data_duration warning when the root
  composition element is missing data-duration, which causes the runtime
  to infer Infinity for loop-inflated timelines.
- Add gsap_repeat_ceil_overshoot warning that catches
  repeat: Math.ceil(d/c)-1 patterns which overshoot the intended
  duration. Recommends Math.floor instead.
- Fix gsap_infinite_repeat fixHint to suggest Math.floor (not Math.ceil).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* fix(player): wait for injected runtime before declaring ready

When the player auto-injects the runtime script (because the
composition has GSAP timelines but no runtime), it would immediately
declare ready on the next probe cycle — before the runtime script
finished loading from CDN. This caused play() to send a postMessage
that nobody received, making autoplay silently fail.

Now the probe waits for the runtime bridge (__hf or __player) to
appear before proceeding to the ready state.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-12 10:41:13 -07:00
Miguel Ángel 0da93cea3d feat(player): add speed control with popup menu and CSS theming (#241)
Add playback speed control to the player controls bar:
- Popup menu with logarithmic presets (0.25x-4x)
- Custom presets via speed-presets attribute
- Full CSS custom property theming (--hfp-accent, --hfp-controls-bg, etc.)
- ratechange event dispatch
- Exports: SPEED_PRESETS, formatSpeed, ControlsOptions
- Fix package.json export condition ordering
2026-04-10 20:47:12 +02:00
Miguel Ángel 5655dabff6 feat: allow clip animation + ship <hyperframes-player> web component (#209)
## Summary

Two independent initiatives that improve agent DX and expand HyperFrames' reach.

### Initiative 1: Fix the Clip Animation Footgun

- `gsap_animates_clip_element` lint rule now uses smart detection — only errors when GSAP animates `visibility` or `display` on a clip element
- All other properties (opacity, transform, x, y, scale, etc.) are allowed silently
- This was the #1 agent failure in QA (10/10 agents hit it on v0.2.1)

### Initiative 2: `<hyperframes-player>` Web Component

- New `@hyperframes/player` package — zero dependencies, 3.3KB gzipped
- Iframe-based web component with Shadow DOM for perfect isolation
- Video-like API: `play()`, `pause()`, `seek()`, `currentTime`, `duration`, events
- Controls overlay with play/pause, scrubber (mouse + touch), time display, auto-hide
- Full docs page at `docs/packages/player.mdx`

## Before / After

### Clip animation lint

**Before (10/10 agents hit this):**

```
✗ gsap_animates_clip_element: GSAP animation targets a clip element.
  Selector "#title" resolves to element <div id="title" class="clip">.
  The framework manages clip visibility — animate an inner wrapper instead.
  Fix: Wrap content in a child <div> and target that with GSAP.
```

**After (only errors on actual conflicts):**

```
# This passes lint — no error:
tl.from("#title", { opacity: 0, y: -50, scale: 0.8 }, 0);

# This still errors — actual conflict with runtime:
tl.to("#title", { visibility: "hidden" }, 3);
✗ gsap_animates_clip_element: GSAP animation sets visibility on a clip element.
  Fix: Remove the visibility/display tween. Use opacity for fade effects.
```

### Embeddable player

**Before:** No way to embed a composition in a web page.
**After:**

```html
<script src="https://cdn.jsdelivr.net/npm/@hyperframes/player"></script>
<hyperframes-player src="./composition/index.html" controls></hyperframes-player>
```

```js
const player = document.querySelector('hyperframes-player');
player.play();
player.pause();
player.seek(2.5);
player.addEventListener('ready', (e) => console.log('Duration:', e.detail.duration));
```

## Test plan

- [x] 427 core tests pass (20 GSAP lint tests with smart detection)
- [x] 7 player tests pass (formatTime + element registration)
- [x] TypeScript compiles cleanly (core + player)
- [x] Lint: GSAP animating clip with safe props → 0 errors
- [x] Lint: GSAP animating clip with `visibility` → 1 error (correct)
- [x] Player builds to 3.3KB gzipped ESM
- [x] Lockfile updated for CI
- [x] Docs page added at `docs/packages/player.mdx`
2026-04-06 19:59:39 +02:00