@a-company/atelier 0.36.0 → 0.38.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (99) hide show
  1. package/dist/{chunk-JPZ4F4PW.js → chunk-3ARBOSWY.js} +64 -5
  2. package/dist/chunk-3ARBOSWY.js.map +1 -0
  3. package/dist/cli.js +11469 -413
  4. package/dist/cli.js.map +1 -1
  5. package/dist/{dist-M67UZGFQ.js → dist-3YQK6PI6.js} +2 -2
  6. package/dist/index.cjs +3193 -227
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.cts +701 -8
  9. package/dist/index.d.ts +701 -8
  10. package/dist/index.js +7237 -72
  11. package/dist/index.js.map +1 -1
  12. package/dist/mcp.js +2898 -507
  13. package/dist/mcp.js.map +1 -1
  14. package/package.json +14 -9
  15. package/src/web/inline-app.ts +55 -4
  16. package/src/web/timeline-state-types.ts +28 -0
  17. package/src/web/timeline-view.test.ts +99 -0
  18. package/src/web/timeline-view.ts +339 -0
  19. package/src/web/workspace-app.ts +3146 -0
  20. package/templates/workspace/.claude/agents/atelier-iris.md +75 -0
  21. package/templates/workspace/.claude/agents/atelier-lux.md +67 -0
  22. package/templates/workspace/.claude/agents/atelier-quill.md +61 -0
  23. package/templates/workspace/.gitignore +30 -0
  24. package/templates/workspace/.paradigm/personas/_shared/cascade-merge.md +172 -0
  25. package/templates/workspace/CLAUDE.md +93 -0
  26. package/templates/workspace/README.md +75 -0
  27. package/templates/workspace/SETUP.md +127 -0
  28. package/templates/workspace/_brand/.atelier-brand.yaml +34 -0
  29. package/templates/workspace/_brand/DESIGN.md +56 -0
  30. package/templates/workspace/_brand/SCRIPT.md +41 -0
  31. package/templates/workspace/_brand/STORYBOARD.md +33 -0
  32. package/templates/workspace/_packs/README.md +54 -0
  33. package/templates/workspace/projects/README.md +49 -0
  34. package/templates/workspace/workspace.atelier +22 -0
  35. package/university/content/notes/N-atel-001-first-render.md +114 -0
  36. package/university/content/notes/N-atel-001-install-and-launch.md +84 -0
  37. package/university/content/notes/N-atel-001-what-is-atelier.md +51 -0
  38. package/university/content/notes/N-atel-101-easings.md +97 -0
  39. package/university/content/notes/N-atel-101-layers.md +106 -0
  40. package/university/content/notes/N-atel-101-states-and-deltas.md +94 -0
  41. package/university/content/notes/N-atel-101-the-atelier-format.md +72 -0
  42. package/university/content/notes/N-atel-201-authoring-tools.md +141 -0
  43. package/university/content/notes/N-atel-201-mcp-overview.md +86 -0
  44. package/university/content/notes/N-atel-201-patterns.md +108 -0
  45. package/university/content/notes/N-atel-201-visual-and-effects.md +125 -0
  46. package/university/content/notes/N-atel-301-composition-and-overlays.md +141 -0
  47. package/university/content/notes/N-atel-301-effects.md +136 -0
  48. package/university/content/notes/N-atel-301-images-and-video.md +126 -0
  49. package/university/content/notes/N-atel-301-shapes-and-text.md +118 -0
  50. package/university/content/notes/N-atel-401-hierarchical-states.md +71 -0
  51. package/university/content/notes/N-atel-401-motion-deep-dive.md +106 -0
  52. package/university/content/notes/N-atel-401-presets-and-templates.md +98 -0
  53. package/university/content/notes/N-atel-401-transitions.md +94 -0
  54. package/university/content/notes/N-atel-501-detected-vs-user-edited.md +76 -0
  55. package/university/content/notes/N-atel-501-layer-tag-isolation.md +62 -0
  56. package/university/content/notes/N-atel-501-silence-trim.md +98 -0
  57. package/university/content/notes/N-atel-501-transcribe-and-captions.md +98 -0
  58. package/university/content/notes/N-atel-601-carousel.md +71 -0
  59. package/university/content/notes/N-atel-601-overlay-rules.md +96 -0
  60. package/university/content/notes/N-atel-601-recipe-tools-and-apply.md +84 -0
  61. package/university/content/notes/N-atel-601-studio-recipe.md +103 -0
  62. package/university/content/notes/N-atel-701-choosing-output.md +68 -0
  63. package/university/content/notes/N-atel-701-png-and-frames.md +84 -0
  64. package/university/content/notes/N-atel-701-vector.md +85 -0
  65. package/university/content/notes/N-atel-701-video.md +88 -0
  66. package/university/content/notes/N-atel-801-editing-surface.md +69 -0
  67. package/university/content/notes/N-atel-801-live-bridge.md +84 -0
  68. package/university/content/notes/N-atel-801-studio-app.md +72 -0
  69. package/university/content/notes/N-atel-801-symbiotic-loop.md +56 -0
  70. package/university/content/paths/LP-atel-001.yaml +21 -0
  71. package/university/content/paths/LP-atel-101.yaml +22 -0
  72. package/university/content/paths/LP-atel-201.yaml +23 -0
  73. package/university/content/paths/LP-atel-301.yaml +22 -0
  74. package/university/content/paths/LP-atel-401.yaml +22 -0
  75. package/university/content/paths/LP-atel-501.yaml +22 -0
  76. package/university/content/paths/LP-atel-601.yaml +22 -0
  77. package/university/content/paths/LP-atel-701.yaml +22 -0
  78. package/university/content/paths/LP-atel-801.yaml +22 -0
  79. package/university/content/quizzes/Q-atel-001-orientation.yaml +66 -0
  80. package/university/content/quizzes/Q-atel-101-document-model.yaml +66 -0
  81. package/university/content/quizzes/Q-atel-201-mcp-authoring.yaml +66 -0
  82. package/university/content/quizzes/Q-atel-301-visual-system.yaml +66 -0
  83. package/university/content/quizzes/Q-atel-401-state-machines.yaml +66 -0
  84. package/university/content/quizzes/Q-atel-501-video-pipeline.yaml +66 -0
  85. package/university/content/quizzes/Q-atel-601-recipes.yaml +66 -0
  86. package/university/content/quizzes/Q-atel-701-export.yaml +66 -0
  87. package/university/content/quizzes/Q-atel-801-studio-loop.yaml +66 -0
  88. package/university/index.yaml +720 -0
  89. package/university/pack.yaml +21 -0
  90. package/dist/chunk-5QQESXI6.js +0 -4432
  91. package/dist/chunk-5QQESXI6.js.map +0 -1
  92. package/dist/chunk-JPZ4F4PW.js.map +0 -1
  93. package/dist/cli.cjs +0 -6313
  94. package/dist/cli.cjs.map +0 -1
  95. package/dist/cli.d.cts +0 -1
  96. package/dist/cli.d.ts +0 -1
  97. package/dist/mcp.cjs +0 -5462
  98. package/dist/mcp.cjs.map +0 -1
  99. /package/dist/{dist-M67UZGFQ.js.map → dist-3YQK6PI6.js.map} +0 -0
@@ -0,0 +1,108 @@
1
+ ---
2
+ id: N-atel-201-patterns
3
+ title: Authoring patterns and decision rules
4
+ type: note
5
+ track: ATEL-201
6
+ author: atelier
7
+ created: '2026-05-18'
8
+ updated: '2026-05-18'
9
+ tags:
10
+ - course
11
+ - atel-201
12
+ - mcp
13
+ - patterns
14
+ - best-practices
15
+ difficulty: intermediate
16
+ estimatedMinutes: 4
17
+ prerequisites:
18
+ - N-atel-201-visual-and-effects
19
+ summary: The high-frequency patterns — build-then-animate, snapshot-edit-restore, batch-mutate-validate, preset-vs-hand-author. When to reach for which tool, and the four mistakes agents make most.
20
+ ---
21
+
22
+ ## Pattern 1 — Build, then animate
23
+
24
+ Adding deltas before layers is rejected (the delta's target layer doesn't exist yet). Adding all layers first, then all deltas in the order they'll fire, produces clean diffs and easy debugging.
25
+
26
+ Rough order for a fresh document:
27
+
28
+ ```
29
+ atelier_create
30
+ ↓ (variables / assets / presets / templates if needed)
31
+ atelier_add_variable (× N)
32
+ atelier_add_asset (× N)
33
+ atelier_define_preset (× N)
34
+
35
+ atelier_add_layer (× N)
36
+ atelier_edit_visual / set_* (× N — visual configuration that doesn't animate)
37
+
38
+ atelier_add_state (× N — if multi-state)
39
+ atelier_set_state_parent (× N — for hierarchical states)
40
+ atelier_configure_transition (× N — for interactive transitions)
41
+
42
+ atelier_add_delta (× N — the motion)
43
+
44
+ atelier_validate
45
+ atelier_preview { frame: N } (sanity-check mid-motion)
46
+ ```
47
+
48
+ Reverse-order works (the validator still catches problems) but produces noisier error logs.
49
+
50
+ ## Pattern 2 — Snapshot, edit, restore
51
+
52
+ When an agent is about to make a destructive change (e.g. restructuring states, replacing a layer type), snapshot first:
53
+
54
+ ```ts
55
+ const before = await atelier_info({ documentId });
56
+ // ... mutations ...
57
+ await atelier_validate({ documentId });
58
+ // if it doesn't pass: reload from the prior state or use the studio's undo stack
59
+ ```
60
+
61
+ The MCP store doesn't have an undo stack of its own (yet — that's Studio-side via `History`). For agent workflows: persist the doc with `atelier_export` before risky changes, restore from the export if validation fails.
62
+
63
+ ## Pattern 3 — Batch-mutate-validate
64
+
65
+ Many small mutations followed by one `atelier_validate` is more efficient than validating after every call. The schema runs on every tool call regardless (you can't bypass it), but `atelier_validate` also reports advisory warnings — long pauses, overly-deep nesting, missing transitions — that you don't want firing 50 times during a build.
66
+
67
+ For very large compositions (50+ layers), the studio bridge benefits from grouping mutations into a "build session" — start with `atelier_create`, end with one validate, broadcast one `doc:patch` instead of 50 incremental `llm:mutation` events. The bridge's `coalesce` flag (when set on the WS envelope) suppresses incremental broadcasts until the session ends.
68
+
69
+ ## Pattern 4 — Preset vs. hand-author
70
+
71
+ If the same motion repeats across three or more layers: define a preset.
72
+
73
+ ```ts
74
+ await atelier_define_preset({ documentId, name: "fade-in-from-below",
75
+ deltas: [
76
+ { property: "opacity", from: 0, to: 1, startFrame: 0, endFrame: 24, easing: "ease-out" },
77
+ { property: "frame.y", from: "${baseY + 24}", to: "${baseY}", startFrame: 0, endFrame: 24, easing: { type: "spring", stiffness: 120, damping: 22 }}
78
+ ]});
79
+
80
+ for (const layerId of ["title", "subtitle", "caption"]) {
81
+ await atelier_apply_preset({ documentId, name: "fade-in-from-below",
82
+ layer: layerId, state: "default", offset: layerId === "title" ? 0 : 6 });
83
+ }
84
+ ```
85
+
86
+ The `offset` parameter shifts the applied deltas by N frames — that's how you stagger entrances without hand-authoring N delta sets.
87
+
88
+ If a motion is one-of-a-kind: hand-author. Don't define a preset for a one-time animation.
89
+
90
+ ## Pattern 5 — Use templates for *layers*, presets for *deltas*
91
+
92
+ Templates produce layer subtrees (with variable substitution). Presets produce delta bundles.
93
+
94
+ If you find yourself defining a "preset" that's actually a `group` layer with three children — that's a template. Switch and use `atelier_instantiate_template`.
95
+
96
+ If you find yourself defining a "template" that's just three deltas on a single layer — that's a preset. Switch and use `atelier_define_preset`.
97
+
98
+ ## The four most common agent mistakes
99
+
100
+ 1. **Adding a delta to a property that doesn't exist on the layer's visual type.** A `TextVisual` has `style.fontSize` but no `style.borderRadius`. The schema rejects it; the agent should read the error message verbatim ("`property "style.borderRadius" is not animatable on a text layer`") and pick a real property.
101
+
102
+ 2. **Forgetting to set `endFrame ≤ state.duration`.** The schema accepts deltas that extend past the state's duration, but the renderer clamps them — you get motion that "freezes" at the boundary value. If you want a 2-second animation, set `state.duration: 120` (at 60fps) before adding deltas.
103
+
104
+ 3. **Overlapping deltas on the same layer + property.** The no-overlap gate rejects it. Fix: split into two adjacent ranges (`0–30` then `30–60`), or use two layers.
105
+
106
+ 4. **Mutating a doc without saving.** The MCP store is in-memory. Closing the session loses unsaved work. Either run inside `atelier studio` (which autosaves), or call `atelier_export({ format: "yaml", out: "session.atelier" })` periodically.
107
+
108
+ That closes ATEL-201. The next track (ATEL-301) covers the visual system in depth — every shape, every fill mode, the typography stack, image handling, and the new overlay namespace.
@@ -0,0 +1,125 @@
1
+ ---
2
+ id: N-atel-201-visual-and-effects
3
+ title: Visual configuration, effects, assets, and refs
4
+ type: note
5
+ track: ATEL-201
6
+ author: atelier
7
+ created: '2026-05-18'
8
+ updated: '2026-05-18'
9
+ tags:
10
+ - course
11
+ - atel-201
12
+ - mcp
13
+ - visual
14
+ - assets
15
+ difficulty: intermediate
16
+ estimatedMinutes: 5
17
+ prerequisites:
18
+ - N-atel-201-authoring-tools
19
+ summary: The visual configuration toolset — shape, fill, stroke, shadow, blend mode, clip path, motion path, tint. Plus assets, variables, presets, templates, interactions, refs, audio, and the overlay namespace.
20
+ ---
21
+
22
+ ## Visual configuration tools
23
+
24
+ These tools mutate a layer's `visual` or layer-level effect properties:
25
+
26
+ ```
27
+ atelier_set_shape — change a ShapeVisual's shape (rect → ellipse → path)
28
+ atelier_set_fill — set/clear fill color, gradient, or pattern
29
+ atelier_set_stroke — set/clear stroke color, width, dash pattern
30
+ atelier_set_shadow — drop shadow / glow on the layer
31
+ atelier_set_blend_mode — Canvas composite operation (multiply, screen, etc.)
32
+ atelier_set_clip_path — restrict rendering to inside a shape
33
+ atelier_set_motion_path — animate position along a path with optional auto-rotate
34
+ atelier_set_tint — color overlay strength 0-1
35
+ ```
36
+
37
+ Each is a single mutation. The tools exist (instead of bundling into `atelier_edit_visual`) because their argument shapes are stable — fill takes a color/gradient/pattern union, stroke takes width + color + dash, shadow takes offset + blur + color. Type narrowing in your IDE makes them friendly to call.
38
+
39
+ **Fill, stroke, and shadow are animatable.** A delta on `fill.color` or `shadow.blur` interpolates the value frame-by-frame. The visual-config tools set the *current* (static) value; deltas animate it.
40
+
41
+ ## Assets
42
+
43
+ ```
44
+ atelier_add_asset — register an image / video / audio asset (assetId + src)
45
+ atelier_list_assets — enumerate registered assets
46
+ atelier_remove_asset — remove (rejects if referenced by a layer)
47
+ ```
48
+
49
+ Assets are referenced by `assetId` from `ImageVisual.assetId`, `VideoVisual.assetId`, and the audio block. Registering an asset doesn't load it — the renderer fetches lazily. Use relative paths (relative to the doc) or absolute file:// URLs; the studio's `/api/assets/:base64path` endpoint serves them with path-traversal protection.
50
+
51
+ ## Variables
52
+
53
+ ```
54
+ atelier_add_variable — declare a named variable with a default value
55
+ atelier_list_variables — enumerate variables
56
+ atelier_remove_variable — remove a variable (rejects if referenced)
57
+ atelier_find_variables — find layers/deltas using ${name} substitution
58
+ ```
59
+
60
+ Variables enable templates and parametric documents. A `${title}` in a text layer's `content` is substituted at resolve time. The recipe system (ATEL-601) uses variables to inject `{current}`/`{total}` for page-number overlays.
61
+
62
+ ## Presets and templates
63
+
64
+ ```
65
+ atelier_define_preset — bundle a set of deltas as a named preset
66
+ atelier_list_presets — enumerate presets
67
+ atelier_apply_preset — apply a preset's deltas to one or more layers
68
+ atelier_instantiate_template — instantiate a named layer subtree with variable substitution
69
+ ```
70
+
71
+ **Preset** = "I want this fade-in pattern on every title." Define it once with `atelier_define_preset`, apply it to N layers with `atelier_apply_preset` — each application substitutes the layer id and produces deltas.
72
+
73
+ **Template** = "I want this layout (logo + handle + page-number) in every carousel page." Define a template subtree with variable holes; `atelier_instantiate_template({ template, variables: { handle: "@me", page: "01/05" }})` produces a fresh subtree with those values.
74
+
75
+ The studio recipe system (ATEL-601) is partially built on templates — applying a recipe instantiates a default template per layer type if no user-authored layer occupies that slot.
76
+
77
+ ## Interactions
78
+
79
+ ```
80
+ atelier_add_interaction — bind an event (hover, click) to an action (state transition)
81
+ atelier_list_interactions — enumerate interactions on the document
82
+ atelier_remove_interaction — remove an interaction
83
+ ```
84
+
85
+ Interactions live on layers and trigger state transitions. `hover` on the title layer transitions the document into the `hover` state with the transition easing defined by `atelier_configure_transition`. The renderer's interactive runtime (used by the studio, by SVG embeds, and by `@atelier/react`) consumes these.
86
+
87
+ For pure video output: skip interactions. They're for interactive embeds.
88
+
89
+ ## Refs (sub-documents)
90
+
91
+ ```
92
+ atelier_set_ref — point a RefVisual layer at another .atelier file
93
+ atelier_resolve_refs — preview the rendered ref tree (for nested refs)
94
+ ```
95
+
96
+ A `RefVisual` layer renders another `.atelier` document inside itself. Use for: reusable lower-thirds across multiple compositions, lock-up logos that need a single source of truth, carousels where every page references a shared layout template.
97
+
98
+ Refs are resolved at render time. The studio re-renders the ref when the source file changes (file watcher), so editing the referenced doc updates every composition that uses it.
99
+
100
+ ## Audio
101
+
102
+ ```
103
+ atelier_set_audio — attach an audio track to the document with sync metadata
104
+ ```
105
+
106
+ For video projects with sound. The `atelier transcribe` pipeline (ATEL-501) writes the transcript and the audio sync metadata that drives caption layer alignment.
107
+
108
+ ## Overlays (Phase 1.5)
109
+
110
+ ```
111
+ atelier_add_handle — create a @handle text overlay layer (tag: overlay)
112
+ atelier_add_page_number — render {current}/{total} into a text overlay layer
113
+ ```
114
+
115
+ These are convenience tools matching the recipe `overlay_rules` schema. Layers carry `tags: ["overlay"]` so they participate in the layer-tag isolation invariant — re-applying an overlay recipe drops only overlay-tagged layers and replaces them; user-authored layers and other pipelines' outputs are untouched.
116
+
117
+ ## Export
118
+
119
+ ```
120
+ atelier_export — top-level export (auto-detects format from --out extension)
121
+ atelier_export_svg — SVG string of one frame
122
+ atelier_export_lottie — Lottie JSON of the full document
123
+ ```
124
+
125
+ Export tools call into `@atelier/canvas`, `@atelier/svg`, `@atelier/lottie`, or `@atelier/video` depending on format. Single-frame PNG, multi-frame MP4/WebM/GIF, vector SVG, and motion-design Lottie all land here.
@@ -0,0 +1,141 @@
1
+ ---
2
+ id: N-atel-301-composition-and-overlays
3
+ title: Composition, refs, and the overlay namespace
4
+ type: note
5
+ track: ATEL-301
6
+ author: atelier
7
+ created: '2026-05-18'
8
+ updated: '2026-05-18'
9
+ tags:
10
+ - course
11
+ - atel-301
12
+ - composition
13
+ - overlays
14
+ - refs
15
+ - groups
16
+ difficulty: intermediate
17
+ estimatedMinutes: 5
18
+ prerequisites:
19
+ - N-atel-301-effects
20
+ summary: GroupVisual + parentId for transform-inherited clusters, RefVisual for sub-documents, and the `overlay` tag namespace — handle and page-number overlays as parameterized text layers protected by the layer-tag isolation invariant.
21
+ ---
22
+
23
+ ## Groups and parentId
24
+
25
+ A `GroupVisual` layer renders nothing of its own. It exists to provide a parent transform for child layers:
26
+
27
+ ```yaml
28
+ - id: card
29
+ visual: { type: group }
30
+ frame: { x: 200, y: 400 }
31
+ rotation: -8
32
+ bounds: { width: 600, height: 800 }
33
+
34
+ - id: card-photo
35
+ parentId: card
36
+ visual: { type: image, assetId: hero-photo }
37
+ frame: { x: 0, y: 0 }
38
+ bounds: { width: 600, height: 600 }
39
+
40
+ - id: card-title
41
+ parentId: card
42
+ visual: { type: text, content: "Title", style: { ... }}
43
+ frame: { x: 24, y: 640 }
44
+ bounds: { width: 552, height: 80 }
45
+ ```
46
+
47
+ The `card` group is positioned at canvas (200, 400) and rotated -8°. Its children render in the group's local coordinate space — the photo is at (0, 0) relative to the rotated card, not the canvas. Animating the group's `rotation` or `frame.x` moves the whole cluster as one unit.
48
+
49
+ **Use groups for:** anything that needs to move/rotate/scale as a unit. Cards, lower-thirds, picture-in-picture clusters, repeated elements you want to manage together.
50
+
51
+ **Don't use groups for:** ordering or styling. Layer z-order is the array index; styling is per-layer.
52
+
53
+ ## Refs — sub-documents
54
+
55
+ ```yaml
56
+ - id: lower-third
57
+ visual:
58
+ type: ref
59
+ src: ./lower-third.atelier
60
+ state: default # optional — defaults to first state
61
+ frame: 0 # optional — defaults to parent frame, clamped to sub-doc duration
62
+ frame: { x: 0, y: 900 }
63
+ bounds: { width: 1920, height: 180 }
64
+ ```
65
+
66
+ A `RefVisual` renders another `.atelier` document inside itself. The sub-document is resolved (via `resolveFrame`) and rendered into the parent's coordinate space.
67
+
68
+ **Use refs for:** brand lock-ups (logo + tagline) reused across compositions, lower-thirds with consistent style, carousel templates where every page references a shared layout, scenes that are themselves composed scenes.
69
+
70
+ The studio file-watches every referenced `.atelier` file. Editing the sub-document updates every parent composition that references it — refs are how Atelier expresses "single source of truth."
71
+
72
+ ## The overlay namespace
73
+
74
+ The `overlay` tag namespace is a Phase 1.5 first-class slot for decorative text layers that mark a composition (handle, page-number, credit, date, QR). Every overlay layer carries `tags: ["overlay"]`.
75
+
76
+ The contract:
77
+
78
+ 1. Pipelines and recipes that produce overlays use `applyRecipeToOverlay`, which drops only `overlay`-tagged layers before re-adding. The layer-tag isolation invariant (ATEL-501) applies.
79
+ 2. User edits to overlay layers (move, restyle, hide) survive re-runs as long as the layer id matches a recipe-defined overlay (the `userEdited` flag is preserved across re-applications).
80
+ 3. MCP convenience tools (`atelier_add_handle`, `atelier_add_page_number`) emit overlay-tagged layers with sensible defaults; recipes generate the same shape declaratively.
81
+
82
+ ### Handle overlay
83
+
84
+ A handle is a static text overlay — typically a social-media handle (`@username`), credit (`by Author`), or watermark. Authored:
85
+
86
+ ```yaml
87
+ overlay_rules:
88
+ handle:
89
+ text: "@username"
90
+ anchor: bottom-left
91
+ margin: 32
92
+ style:
93
+ fontFamily: Inter
94
+ fontSize: 24
95
+ fontWeight: 600
96
+ color: "#F5F5F7"
97
+ ```
98
+
99
+ Applied via `apply-recipe` or `atelier_add_handle`. Layer id: `overlay-handle` (stable, so re-apply replaces in place).
100
+
101
+ ### Page-number overlay
102
+
103
+ A page-number is a parameterized text overlay rendering a template like `{current}/{total}` or `{current:02d} of {total:02d}`. Authored:
104
+
105
+ ```yaml
106
+ overlay_rules:
107
+ page_number:
108
+ format: "{current:02d}/{total:02d}"
109
+ anchor: bottom-right
110
+ margin: 32
111
+ style:
112
+ fontFamily: Inter
113
+ fontSize: 18
114
+ fontWeight: 500
115
+ color: "#9CA3AF"
116
+ ```
117
+
118
+ Applied during carousel batch processing: the renderer threads `currentIndex` and `totalCount` into `applyRecipeToOverlay`, which renders the format string into the layer's content. Layer id: `overlay-page-number`.
119
+
120
+ Single-frame applies (where the carousel context isn't available) skip the page-number layer with a logger warning — by design. A page-number doesn't make sense on a standalone composition.
121
+
122
+ ### Anchor math
123
+
124
+ All four corners are supported. The translator maps anchor → frame/anchorPoint as follows:
125
+
126
+ | `anchor` | `frame` | `anchorPoint` |
127
+ |---|---|---|
128
+ | `top-left` | `{ x: margin, y: margin }` | `{ x: 0, y: 0 }` |
129
+ | `top-right` | `{ x: canvas.width - margin, y: margin }` | `{ x: 1, y: 0 }` |
130
+ | `bottom-left` | `{ x: margin, y: canvas.height - margin }` | `{ x: 0, y: 1 }` |
131
+ | `bottom-right` | `{ x: canvas.width - margin, y: canvas.height - margin }` | `{ x: 1, y: 1 }` |
132
+
133
+ The anchor doubles as the rotation/scale pivot — animating `rotation` on an overlay rotates around the corner the overlay is anchored to.
134
+
135
+ ### Why overlays are their own namespace (and not just "text layers")
136
+
137
+ Because they're **generated and re-generated** by recipes. The layer-tag isolation invariant lets a user tweak an overlay (drag the handle a few pixels, restyle it), re-apply the recipe, and keep the tweak. Untagged user-authored text layers are never touched. Caption-pipeline layers (`tags: ["caption"]`) and silence-trim layers (`tags: ["silence-trim"]`) are never touched either.
138
+
139
+ That isolation is what makes the human↔agent symbiotic loop safe at every level of the pipeline — silence-trim, captions, overlays, and (soon) carousel-batch page-numbers.
140
+
141
+ That closes ATEL-301. You now know every visual primitive Atelier renders and how composition + overlays slot into the layer-tag invariant. The remaining tracks (ATEL-401 motion deep-dive, ATEL-501 video pipelines, ATEL-601 recipes, ATEL-701 export) layer on top of this foundation.
@@ -0,0 +1,136 @@
1
+ ---
2
+ id: N-atel-301-effects
3
+ title: Effects — fills, strokes, shadows, blends, clips, motion paths, tints
4
+ type: note
5
+ track: ATEL-301
6
+ author: atelier
7
+ created: '2026-05-18'
8
+ updated: '2026-05-18'
9
+ tags:
10
+ - course
11
+ - atel-301
12
+ - effects
13
+ - rendering
14
+ difficulty: intermediate
15
+ estimatedMinutes: 5
16
+ prerequisites:
17
+ - N-atel-301-images-and-video
18
+ summary: The non-geometry visual surface — paint (fills/strokes/gradients), depth (shadows), compositing (blends), masking (clip paths), trajectory (motion paths), color overlays (tints). What each does, when each animates well.
19
+ ---
20
+
21
+ ## Fills
22
+
23
+ ```yaml
24
+ fill:
25
+ type: color # or: linear-gradient | radial-gradient | pattern
26
+ color: "#3B82F6"
27
+ ```
28
+
29
+ Four fill types:
30
+
31
+ | `type` | Shape |
32
+ |---|---|
33
+ | `color` | `color: "#RRGGBB"` or `"rgba(...)"` |
34
+ | `linear-gradient` | `stops: [{offset, color}, ...]`, `angle: degrees` |
35
+ | `radial-gradient` | `stops: [{offset, color}, ...]`, `center: {x, y}`, `radius: number` |
36
+ | `pattern` | `assetId` (image), optional `repeat: 'repeat' \| 'no-repeat'` |
37
+
38
+ Apply via `atelier_set_fill({ documentId, layerId, fill })`. Animatable: stop colors and offsets via deltas on `fill.stops[N].color` and `fill.stops[N].offset`. Solid colors animate via `fill.color`.
39
+
40
+ ## Strokes
41
+
42
+ ```yaml
43
+ stroke:
44
+ color: "#F5F5F7"
45
+ width: 2
46
+ dash: [4, 4] # optional dash pattern
47
+ lineJoin: round # 'miter' | 'round' | 'bevel'
48
+ lineCap: round # 'butt' | 'round' | 'square'
49
+ ```
50
+
51
+ Animatable: `width`, `color`, `dash` (animate `dash[0]` and `dash[1]` for marching-ants effects).
52
+
53
+ ## Shadows
54
+
55
+ ```yaml
56
+ shadow:
57
+ color: "#000000AA"
58
+ blur: 20
59
+ offsetX: 0
60
+ offsetY: 8
61
+ spread: 0 # optional — extends the shadow before blur
62
+ ```
63
+
64
+ Drop shadows (`offsetX/Y > 0`) and glows (`offsetX/Y === 0` with high `blur`) both use the same primitive. Animate `blur` and `spread` for pulse effects.
65
+
66
+ Applied per-layer via `atelier_set_shadow({ documentId, layerId, shadow })`. Removing: pass `shadow: null`.
67
+
68
+ ## Blend modes
69
+
70
+ ```yaml
71
+ blendMode: multiply # or any Canvas composite operation
72
+ ```
73
+
74
+ Layer-level. The full list: `normal`, `multiply`, `screen`, `overlay`, `darken`, `lighten`, `color-dodge`, `color-burn`, `hard-light`, `soft-light`, `difference`, `exclusion`, `hue`, `saturation`, `color`, `luminosity`.
75
+
76
+ Most common in practice:
77
+ - `multiply` — color over background (good for grading photos)
78
+ - `screen` — lighten effect (good for flares, highlights)
79
+ - `overlay` — contrast boost
80
+ - `color` — preserve luminosity of base, take hue+saturation of overlay (good for color grading)
81
+
82
+ Animatable indirectly: animate the layer's `opacity` with the blend mode held constant. The blend mode itself is not interpolated (it's a discrete switch).
83
+
84
+ ## Clip paths
85
+
86
+ ```yaml
87
+ clipPath:
88
+ kind: ellipse # any Shape
89
+ radiusX: 200
90
+ radiusY: 200
91
+ ```
92
+
93
+ Restricts rendering to inside the clip shape. Useful for: avatar circles, ken-burns inside a non-rectangular frame, transition reveals.
94
+
95
+ The clip path is in the layer's local coordinate space (same as visual shapes). Animatable: animate the clip shape's properties (e.g. `clipPath.radiusX`) for reveal animations.
96
+
97
+ ## Motion paths
98
+
99
+ ```yaml
100
+ motionPath:
101
+ points:
102
+ - { x: 100, y: 400 }
103
+ - { x: 600, y: 100, outControl: { x: 800, y: 100 }}
104
+ - { x: 1100, y: 400, inControl: { x: 900, y: 400 }}
105
+ closed: false
106
+ autoRotate: true # layer rotates to follow path tangent
107
+ autoRotateOffset: 0 # degrees added to the auto-rotation
108
+ ```
109
+
110
+ The layer's `frame.x` / `frame.y` is overridden by the motion path. Deltas that animate `frame.x` or `frame.y` are ignored when a motion path is present (the path is the trajectory; the delta on a parametric position would conflict). To control speed along the path, animate `motionProgress` (0..1) with a delta.
111
+
112
+ If `autoRotate: true`, the layer's `rotation` follows the path's tangent — good for arrows tracing a curve, cars driving along a road.
113
+
114
+ ## Tints
115
+
116
+ ```yaml
117
+ tint:
118
+ color: "#3B82F6"
119
+ amount: 0.4 # 0..1, strength of the tint overlay
120
+ ```
121
+
122
+ A solid color overlay applied to the rendered layer. Animatable: `tint.amount` produces a tint fade-in/out. `tint.color` interpolates RGB → animatable for color-cycling.
123
+
124
+ Distinct from `blendMode: color` — tint is a per-layer overlay; blend mode is a composite operation between layers.
125
+
126
+ ## When to use what
127
+
128
+ - **Color emphasis on a shape:** fill.
129
+ - **Outline:** stroke.
130
+ - **Depth:** shadow.
131
+ - **Photo grading:** blendMode (`multiply`, `color`).
132
+ - **Reveal mask:** clipPath.
133
+ - **Curved trajectory:** motionPath.
134
+ - **Color cycle without changing assets:** tint.
135
+
136
+ For carousel social posts (the v1 headline use case): fills + a subtle `blendMode: multiply` on a dark overlay layer above the photo for caption legibility, with `tint` reserved for brand-color accents on overlay text.
@@ -0,0 +1,126 @@
1
+ ---
2
+ id: N-atel-301-images-and-video
3
+ title: Images and video — assets, cropping, spritesheets, playback
4
+ type: note
5
+ track: ATEL-301
6
+ author: atelier
7
+ created: '2026-05-18'
8
+ updated: '2026-05-18'
9
+ tags:
10
+ - course
11
+ - atel-301
12
+ - images
13
+ - video
14
+ - assets
15
+ difficulty: intermediate
16
+ estimatedMinutes: 5
17
+ prerequisites:
18
+ - N-atel-301-shapes-and-text
19
+ summary: ImageVisual and VideoVisual. The asset system, cropping via sourceRect, spritesheet animation, video playback controls, and the objectFit rules that decide what "fitting" actually means.
20
+ ---
21
+
22
+ ## ImageVisual
23
+
24
+ ```yaml
25
+ visual:
26
+ type: image
27
+ assetId: hero-photo # registered via atelier_add_asset
28
+ src: assets/hero.jpg # optional — direct URL or data URL for inline use
29
+ sourceRect: # optional — crop region in source pixels
30
+ x: 100
31
+ y: 50
32
+ width: 800
33
+ height: 600
34
+ spritesheet: # optional — for spritesheet animation
35
+ columns: 4
36
+ rows: 4
37
+ frameWidth: 256
38
+ frameHeight: 256
39
+ frameCount: 16 # optional, defaults to columns*rows
40
+ frameIndex: 0 # animatable for spritesheet playback
41
+ ```
42
+
43
+ **Crop with `sourceRect`.** The 9-arg form of canvas `drawImage` is exposed directly — pick a sub-rectangle from the source image and draw it into the layer's bounds. The crop is in source-image pixels (not normalized 0–1). Animatable: deltas on `sourceRect.x`, `sourceRect.y`, `sourceRect.width`, `sourceRect.height` produce ken-burns-style pan/zoom.
44
+
45
+ **Spritesheet animation.** Set `spritesheet` and animate `frameIndex` with a `step()` easing for sprite-frame indexing:
46
+
47
+ ```yaml
48
+ deltas:
49
+ - layer: walking-character
50
+ property: frameIndex
51
+ from: 0
52
+ to: 15
53
+ startFrame: 0
54
+ endFrame: 120
55
+ easing:
56
+ type: step
57
+ count: 16
58
+ position: jump-none
59
+ ```
60
+
61
+ That walks through 16 spritesheet frames over 2 seconds at 60fps.
62
+
63
+ ## VideoVisual
64
+
65
+ ```yaml
66
+ visual:
67
+ type: video
68
+ assetId: hero-clip
69
+ src: assets/hero.mp4
70
+ startFrame: 30 # composition frame when clip begins playing
71
+ sourceOffset: 5.2 # seconds into source video to start from
72
+ sourceEnd: 12.7 # seconds into source to stop (undefined = play to end)
73
+ playbackRate: 1.0 # 1.0 = normal speed
74
+ volume: 0.8 # 0–1
75
+ muted: false
76
+ objectFit: contain # 'contain' | 'cover' | 'fill'
77
+ ```
78
+
79
+ - **`startFrame`** anchors the video to the composition timeline. Frame `0` of the composition shows the video frame at `sourceOffset` seconds.
80
+ - **`sourceOffset` / `sourceEnd`** trim the source. The `atelier trim` pipeline writes these values onto `clip-trim-N` layers (with `tags: ["silence-trim"]`) — the layer-tag isolation invariant covered in ATEL-501.
81
+ - **`playbackRate`** changes the source playback speed. `2.0` is 2x faster; `0.5` is half-speed. The audio is pitch-shifted accordingly (or muted if `muted: true`).
82
+ - **`objectFit`** decides what to do when the video's intrinsic aspect doesn't match the layer's bounds:
83
+ - `contain` — fit entirely inside bounds, possibly with letterboxing
84
+ - `cover` — fill bounds entirely, possibly cropping
85
+ - `fill` — stretch to fit (distorts aspect)
86
+
87
+ For most layouts: `cover`. For preserving aspect: `contain`. `fill` is rarely correct.
88
+
89
+ ## The asset system
90
+
91
+ ```
92
+ atelier_add_asset — register an asset with id + src + kind
93
+ atelier_list_assets — enumerate
94
+ atelier_remove_asset — remove (rejects if referenced by a layer)
95
+ ```
96
+
97
+ ```ts
98
+ await atelier_add_asset({ documentId,
99
+ asset: { id: "hero-photo", kind: "image", src: "assets/hero.jpg" }});
100
+ ```
101
+
102
+ `src` can be:
103
+ - A relative path (relative to the doc's directory). The studio's `/api/assets/:base64path` endpoint serves these with path-traversal protection.
104
+ - An absolute `file://` URL.
105
+ - A `data:` URL (inline base64) for portability — useful when shipping a doc as a single file.
106
+ - An `https://` URL (the renderer fetches it; not recommended for offline rendering).
107
+
108
+ **Why `assetId` + optional `src` (vs just `src`)?** Refactoring. Renaming an asset's source path means updating one entry in `atelier_add_asset`, not every layer that references it.
109
+
110
+ ## Cropping vs scaling — three knobs
111
+
112
+ Atelier gives you three independent ways to control how an image fits into a layer:
113
+
114
+ 1. **`sourceRect`** — pick a sub-rectangle from the source. Crop *what's drawn*.
115
+ 2. **`Layer.scale.x` / `Layer.scale.y`** — scale the layer's transform. Stretch *how it's drawn*.
116
+ 3. **`Layer.bounds.width` / `bounds.height`** — change the destination size. Crop *where it's drawn* (and the renderer interpolates fit per `objectFit` if it's a video).
117
+
118
+ For image carousels with consistent framing: set `bounds` to your target aspect (e.g. 1080×1080), use `sourceRect` to pick the focal area, leave `scale` at 1.0.
119
+
120
+ For ken-burns pan/zoom: animate `sourceRect.x`/`sourceRect.y`/`sourceRect.width`/`sourceRect.height` with deltas. The image stays in place; the visible crop pans and zooms.
121
+
122
+ ## Drag-and-drop in the studio (v1)
123
+
124
+ Dropping an image file onto the canvas creates an `ImageVisual` layer with the dropped file inlined as a `data:` URL (small files) or registered as an asset and referenced by `assetId` (larger files). The dropped image is centered on the canvas with bounds matching the image's intrinsic dimensions; you scale and crop from there.
125
+
126
+ This is the headline workflow for v1: drop a photo, add overlays (handle, page-number), tweak crop, export PNG. The studio's first-run scaffolds a `welcome.atelier` with a placeholder image you immediately replace.