Specifies the V1 animation system end-to-end. Authored after two
Deep Research passes (preserved as ANIMATION_V1_RESEARCH.md and
ANIMATION_V1_DESIGN_DECISIONS.md for provenance).
ANIMATION_V1.md covers:
- Hard constraints: Chrome Heavy Ad Intervention (4MB / 15s burst /
60s total CPU), composite-only animation, 150KB initial-load cap,
GSAP via s0.2mdn.net CDN, free-tier only.
- Custom JSON schema (not Lottie) — block-based timeline, absolute
start times, preset references only, no inline keyframes. Designed
for AI authoring and human-readable diffs.
- 25-preset library across entrance / exit / emphasis / typography /
mask / list categories. Each preset specifies start state, end
state, default ease, default duration, and split/mask requirements.
- 9-category easing matrix using GSAP stock eases; bounce, slow,
rough, and circ excluded from the V1 surface.
- Mask system: mask is a property on the masked layer (not a
standalone layer). clip-path mandatory over interactive elements
to prevent ghost-click failures. Konva ↔ HTML parity table.
- Per-character animation: SplitType at render time, Dropflow at
spec time, automated aria-label / aria-hidden contract, 150-node
ceiling enforced by QA gate.
- Animated bounding-box math: discrete sampling at 30 fps,
unionBoundingBox() called from asset selection, render worker,
and QA gate. Adds required_source_size to ResolvedLayer.
- 12 QA gates (G1-G12) covering schema, performance, asset,
accessibility, and parity.
ARCHITECTURE.md updates:
- Forward-notes section at the top pointing to ANIMATION_V1.md and
RESOLVED_FEED.md, matching the existing Part 7 forward-note style.
- Inline forward note in the Part 3 animation stack block.
- Old content preserved as historical record.
Decisions baked in (resolved during draft):
- Loops are global (max 3), not per-block. Per-block loops invite
nested-infinite-loop bugs in AI-generated specs.
- Block triggers are time-anchored only. Event/interaction triggers
wait for V2 rich media.
- blur_in and shake_horizontal dropped from the 27-preset research
list. Blur is a video pattern; shake reads as a rendering error.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Expands the slice from a single 300x250 banner to four IAB sizes
(300x600, 300x250, 728x90, 160x600) driven by a designer-authored
TypeSystem and a per-row strip review surface.
Layout engine
- TypeSystem with role-based typography (headline/subheadline/cta/legal)
and piecewise size-class derivation: half_page / rectangle /
leaderboard / skyscraper / mobile_banner.
- resolveLayout now derives per-size font/leading from the role +
artboard size, then clamps to a legibility floor and emits a
constraint_signal when copy does not fit at the floor.
- Four reference templates with character constraints per size.
AI pipeline (Shape B)
- One extract + one generate per feed row; generate returns per-size
copy keyed by artboard_id plus a shared rationale block.
- Constraint-signal retry: orchestrator tightens per-(artboard, field)
limits and re-calls generate before giving up.
- orchestrateRow returns specs[] + rationale + constraint_signals.
Review UI
- /review renders one strip per feed row, all four sizes side-by-side
at true pixel dimensions, synced on a single GSAP master timeline.
- AiReasoningDrawer shows a per-size copy table, shared rationale, and
any constraint signals that fired.
- /api/generate response grouped by row; /api/export accepts the same
shape and writes exports/row-N/artboard_id.zip.
Render worker
- render-to-zip / render-many accept optional subdir + filename
overrides so multi-size exports can be grouped by feed row.
Docs
- VERTICAL_SLICE and BUILD_SEQUENCE updated for the multi-size scope.
- RESOLVED_FEED.md documents the V1 Resolved Creative Feed proposal.
- SLICE_DEVIATIONS.md records where the slice diverges from V1.
Tests: 56 pass (28 layout-engine + 14 api-lib + 14 render-worker).
Web app: tsc clean, next build succeeds.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Day 1 (monorepo + Node layout engine):
- Turborepo + pnpm workspaces with apps/web, apps/render-worker, and
packages for types, layout-engine, prompts, api-lib.
- @banner-studio/types: BannerSpec contract, every layer kind, ResolvedLayer,
zod schemas mirroring each interface.
- @banner-studio/layout-engine: Dropflow WASM wrapper, text measurement,
shrink-to-fit, push_siblings, resolveLayout. Snapshot-tested.
Day 2 (browser parity + AI pipeline):
- Layout engine ./browser subpath: same resolveLayout in the browser via
Dropflow WASM build. Quarantined wasm-locator import (dropflow 0.5.1
exports gap).
- Cross-group push_siblings bug fix: deltas now thread through group
recursion via a shared accumulator; regression test added.
- DEMO_TEMPLATE_300x250 promoted to packages/layout-engine/src/templates/.
- @banner-studio/prompts: versioned extract + generate prompts with
zod-defined tool schemas (claude-sonnet-4-6, forced tool-use).
- @banner-studio/api-lib: CSV feed loader, extract/generate/route-node/
assemble agents, orchestrator returning fully-resolved BannerSpec.
Generate agent retries on character-limit overflow.
- apps/web (Next.js 14 App Router): /api/generate route, /parity diff page,
promise-singleton browser engine init.
- feeds/demo.csv with five hand-authored rows of varied length.
- SLICE_DEVIATIONS.md documents the five intentional gaps from
ARCHITECTURE.md with V1 reversal paths.
Verified end-to-end: POST /api/generate against the live Claude API
returns three resolved BannerSpecs and two honestly-skipped rows
(overflow after two attempts). 26 unit + integration tests passing.