0.2.0: polygon tool, mask editor preview, collapsible properties

- Polygon asset type with per-vertex animation: new sidebar button inserts a
  hexagon; vertex handles in the editor write per-keyframe `points` arrays
  that interpolate between keyframes (requires matching point counts).
- Canvas mask preview: toggle in the zoom toolbar switches the stage
  between the editable ghost view and a rendered view that applies the
  actual masking via the shared canvas 2D path.
- Properties panel is grouped into collapsible sections (Transform, Text,
  Appearance, Shadow, Mask) with per-section open/closed state persisted
  to localStorage.
- Sidebar shape buttons are icon-only to reclaim width for the Polygon
  entry.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
thandClaude Opus 4.7 committed 2026-04-20 01:29:27 +02:00
1 parent 03b1147a93
commit a05396e734
12 files changed
+611 -173

No files matched your search

+35 -2
View File
@@ -130,6 +130,22 @@ function drawLayers(
ctx.font = `${asset.fontSize}px sans-serif`
ctx.textBaseline = 'top'
ctx.fillText(asset.text, -offX, -offY)
} else if (asset.kind === 'polygon') {
const offX = layer.offsetX ?? 0
const offY = layer.offsetY ?? 0
const fill = p.fill ?? asset.fill
const blur = p.blur ?? asset.blur ?? 0
const pts = p.points ?? asset.points
if (pts.length >= 3) {
ctx.fillStyle = fill
if (blur > 0) ctx.filter = `blur(${blur}px)`
ctx.beginPath()
ctx.moveTo(pts[0].x - offX, pts[0].y - offY)
for (let j = 1; j < pts.length; j++) ctx.lineTo(pts[j].x - offX, pts[j].y - offY)
ctx.closePath()
ctx.fill()
ctx.filter = 'none'
}
} else if (asset.kind === 'image') {
const img = images[asset.id]
if (img) {
@@ -152,7 +168,13 @@ function drawMaskShape(
) {
const asset = assetMap.get(layer.assetId)
if (!asset) return
if (asset.kind !== 'rect' && asset.kind !== 'circle' && asset.kind !== 'text') return
if (
asset.kind !== 'rect' &&
asset.kind !== 'circle' &&
asset.kind !== 'text' &&
asset.kind !== 'polygon'
)
return
const p = sampleLayer(layer, time)
ctx.save()
ctx.translate(p.x, p.y)
@@ -169,12 +191,23 @@ function drawMaskShape(
ctx.beginPath()
ctx.arc(-offX, -offY, asset.radius, 0, Math.PI * 2)
ctx.fill()
} else {
} else if (asset.kind === 'text') {
const offX = layer.offsetX ?? 0
const offY = layer.offsetY ?? 0
ctx.font = `${asset.fontSize}px sans-serif`
ctx.textBaseline = 'top'
ctx.fillText(asset.text, -offX, -offY)
} else {
const offX = layer.offsetX ?? 0
const offY = layer.offsetY ?? 0
const pts = p.points ?? asset.points
if (pts.length >= 3) {
ctx.beginPath()
ctx.moveTo(pts[0].x - offX, pts[0].y - offY)
for (let j = 1; j < pts.length; j++) ctx.lineTo(pts[j].x - offX, pts[j].y - offY)
ctx.closePath()
ctx.fill()
}
}
ctx.restore()
}