feat: 5-frame button assets + timeline/sidebar polish

Button assets
- Re-introduce button-asset type with 4 content states (normal/hover/
  pressed/disabled) stored as keyframes at BUTTON_STATE_TIMES, plus a
  separate hit-area editor mode.
- useButtonRuntime composable for per-instance state/disabled flags on
  the main stage.
- Sidebar library lists button assets with live 80×80 thumbnails;
  renderButtonThumbnail now accepts an images map so image-kind layers
  inside a button render instead of producing a fully-transparent PNG.
- Compact (no-thumbnail) mode aligns button rows to the library-row
  height.

Scope footer
- Button-edit mode now uses the same sidebar scope-footer pattern as
  clip-edit (back button to "Main timeline" + active scope chip);
  removed the inline exit button from the timeline scrub-bar.

Timeline
- Track-name column default bumped to 260px to match the assets sidebar
  above it (storage key bumped to :v2 so stale values reset).
- scrub-bar z-index raised above sticky track-name columns so rows
  scroll under it instead of over.
- scrub-handle flips z-index based on horizontal scroll: above
  name-spacer when the playhead is in view (no half-clip at t=0),
  below when the playhead has scrolled off the left edge (so the
  toggle row cleanly covers it).
- name-spacer extends ±3/1px to fully cover the obscured handle.
- scrub-handle head pokes 3px above the ruler with rounded top corners
  via clip-path path().

Track context menu
- Added missing i18n keys (renameScriptTrack / deleteScriptTrack /
  renameScriptTrackDialog.{title,confirm}).
- Rename items show a visible pencil icon; empty .check slot removed
  so items line up flush-left; toggle items place their active ✓ on
  the right (kf-menu-trailing-check).

Dev HUD
- Test progress bar flips to --danger as soon as the first failure is
  recorded, instead of only at the end of the run.

Regression tests
- renderButtonThumbnail: null on empty button, renders shape layers,
  accepts custom images map.
- i18n: renameScriptTrack / deleteScriptTrack / dialog keys resolve.
- scrub-handle-obscured logic (visible at t=0, hidden when playhead is
  scrolled off-screen, still visible when playhead is far to the
  right of the scroll offset).
- Updated button state-scrubber expectations to match
  BUTTON_STATE_TIMES (normal=1, hover=2, pressed=3, disabled=4) and
  that hit-mode parks at normal=1 rather than 0.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-21 19:19:39 +02:00
1 parent 31604052ca
commit c3764201b8
16 files changed
+2560 -349

No files matched your search

+48
View File
@@ -67,6 +67,28 @@ export const en = {
polygon: 'Polygon',
freeform: 'Freeform (click to draw, Enter to finish, Esc to cancel)',
freeformActive: 'Drawing… (Enter to finish, Esc to cancel)',
button: 'Button (5-frame asset)',
},
buttons: 'Buttons',
noButtons: 'No buttons yet',
createButtonTooltip: 'Create new button asset',
hitModeDisabled: 'Switch back to button states to add assets.',
newButtonDialog: {
title: 'New button',
nameMessage: 'Button name:',
defaultName: 'Button',
next: 'Next',
sizeMessage: 'Canvas size (width × height):',
confirm: 'Create',
},
renameButtonDialog: {
title: 'Rename button',
confirm: 'Apply',
},
deleteButtonDialog: {
title: 'Delete button?',
message: 'The button asset will be removed. Any layers that reference it will no longer render.',
confirm: 'Delete',
},
newTextDialog: {
title: 'New text object',
@@ -112,6 +134,7 @@ export const en = {
text: 'Text',
polygon: 'Polygon',
appearance: 'Appearance',
button: 'On Click',
},
polygonPoints: 'Points',
polygonPointsHint: 'Changing the point count rewrites all keyframes; new points are inserted at the midpoint of the last edge.',
@@ -163,6 +186,17 @@ export const en = {
scriptTrackName: 'Script track',
addScriptTrackTooltip: 'Create a new script track',
scriptFrameTooltip: 'Script frame — {n} action(s)',
buttonEditLabel: 'Editing button:',
buttonEditExit: 'Exit',
emptyButtonState: 'Empty state — drop shapes or images from the sidebar.',
buttonModeStates: 'Edit button states (normal / hover / pressed / disabled)',
buttonModeStatesShort: 'States',
buttonModeHit: 'Edit the click hit area',
buttonModeHitShort: 'Hit area',
hitAreaHeader: 'Hit area',
hitAreaHint: 'Click the canvas tools to add shapes',
hitAreaEmpty: 'No hit shapes yet — add a rect or circle from the canvas.',
hitTrackDelete: 'Delete hit shape',
addKeyframeTooltip: 'Add keyframe for the selected track at the playhead',
groupChildCount: '{n} tracks',
groupEmpty: 'Drop tracks here',
@@ -170,8 +204,13 @@ export const en = {
title: 'Rename group',
confirm: 'Apply',
},
renameScriptTrackDialog: {
title: 'Rename script track',
confirm: 'Apply',
},
ungroup: 'Ungroup',
renameGroup: 'Rename group',
renameScriptTrack: 'Rename script track',
resizeTrackColumn: 'Drag to resize track name column',
clipTrackName: 'Clip track',
toggleTimeUnit: 'Toggle time unit',
@@ -207,6 +246,7 @@ export const en = {
unlock: 'Unlock',
deleteLayer: 'Delete layer',
deleteClipTrack: 'Delete clip track',
deleteScriptTrack: 'Delete script track',
separate: 'Separate Position and Rotation',
rotation: 'Rotation',
position: 'Position',
@@ -227,9 +267,17 @@ export const en = {
cropOff: 'Show everything',
maskPreviewOn: 'Preview masks (render view)',
maskPreviewOff: 'Back to edit view',
stagePreviewOn: 'Turn stage preview on — buttons become interactive',
stagePreviewOff: 'Turn stage preview off — buttons behave as normal assets',
vertexCorner: 'Corner',
vertexCurve: 'Curve (smooth)',
vertexSplitCurve: 'Split curve',
hit: {
addRect: 'Add rect hit area',
addCircle: 'Add circle hit area',
addPolygon: 'Add polygon hit area',
remove: 'Remove selected hit area',
},
},
settings: {