0.8.0: group colour cascade, still-frame past end, project rename, UX polish

Headline:

- Group colour cascades to every non-overridden child (layer or clip
  track). New `colorOverridden` flag on Layer / ClipTrack / ScriptTrack
  / TrackGroup; setTrackColor on a non-group pins the track, clearing
  re-inherits from the parent group. moveTrackIntoGroup also inherits.
  normalizeScene migrates legacy scenes (existing colour ⇒ overridden).
- Keyframes inside a button-asset timeline snap to the 4 state slots
  (normal/hover/pressed/disabled). upsertKeyframe / setKeyframeTime /
  duplicateKeyframe all enforce it; colliding drags refuse the move,
  duplicates walk to the nearest free slot.
- Backspace / Delete on a selected layer removes it (respecting the
  layer's own lock and any enclosing group lock).
- "Still frame" allowed on the last keyframe: the layer stays on stage
  past its final kf. Timeline draws a solid dim tail line from the
  last kf to the end, and single-keyframe tracks now show a full-span
  line so on-stage/off-stage reads at a glance. Height stays 3px.
- Project name moved next to the logo, turns into a rename button on
  hover (pencil fades in). Rename syncs the stored record when the
  project is already persisted.
- Shadow + blur keyframes animate in the preview and exported runtime:
  the runtime's lerpProps used to drop them, so every preview rendered
  shadow=0 between keyframes. RUNTIME_VERSION 11 → 13 (covers the
  button-mask + shadow fixes). Held-last-kf also taught to the runtime.
- Button masks render across every path: edit-mode mask-preview,
  nested buttons on the main stage, sidebar thumbnails, fullscreen
  preview + exports. drawButtonFrame new in render.ts.

Smaller fixes:

- Dragging layer A on the stage with a stale selection on layer B used
  to move B and snap A back. Now the shift-selected branch only runs
  when the dragged layer is itself in the selection.
- Clip-track row was missing its icon — PhFilmStrip was referenced but
  never imported. Added, plus dropped the bespoke `gap` on clip / script
  name rows so their icon+label spacing matches layer rows.
- Clip-art drawer pre-lays-out its 400px grid behind `overflow: hidden`
  so slide-in reveals a finished grid instead of reflowing auto-fill
  columns over 220ms.
- Timeline clip-track dropzone hides until the library has a clip; its
  "drop a clip here" hint stays centred in the visible ruler area.
- Keyframe context menu: check-mark and icons share a single 12px column
  so the ✓ on an easing option lines up with PhPauseCircle / PhCopy /
  PhTrash on the rows below. "Still frame"'s active indicator moved to
  the trailing-check slot.
- Button editor's mask-preview now shows the button's own stage instead
  of leaking through to the main scene. drawButtonFrame drives it.

Regression tests cover all of the above: group-color cascade, pin,
clear-re-inherit, migration, button-kf snap and collision rules,
backspace-deletes-selected-layer (incl. lock guards + editable-target
guard), still-frame-past-end visibility, clipart drawer rigid width,
mask rendering in drawButtonFrame / drawSceneFrame, shadow + blur
interpolation in both canvas and runtime paths.

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 21:26:06 +02:00
1 parent ac89f6762d
commit 1e89ea86e0
15 files changed
+849 -74

No files matched your search

+11 -3
View File
@@ -49,7 +49,7 @@ function onItemDragStart(e: DragEvent, index: number) {
>
<component :is="clipart.open ? PhCaretLeft : PhCaretRight" :size="14" />
</button>
<div v-if="clipart.open" class="drawer-panel">
<div class="drawer-panel">
<header class="drawer-header">
<PhImagesSquare :size="14" class="title-icon" />
<h3>{{ t('clipart.title') }}</h3>
@@ -106,6 +106,11 @@ function onItemDragStart(e: DragEvent, index: number) {
background: var(--border);
border-right: 1px solid var(--border);
position: relative;
/* The drawer-panel is always mounted at its full 400px width so the
thumbnail grid's columns don't reflow mid-animation. `overflow:hidden`
clips it to the drawer's animated width so while closed/hovering the
panel is rendered but invisible. */
overflow: hidden;
}
.clipart-drawer:hover {
@@ -157,8 +162,11 @@ function onItemDragStart(e: DragEvent, index: number) {
}
.drawer-panel {
flex: 1;
min-width: 0;
/* Rigid 400px width — never flex-shrinks. Combined with the drawer's
`overflow: hidden`, this lets the grid be laid out correctly the
moment it mounts and stay that way regardless of the drawer's
current animated width. */
flex: 0 0 400px;
width: 400px;
display: flex;
flex-direction: column;