diff --git a/.claude/skills/morpho-design/SKILL.md b/.claude/skills/morpho-design/SKILL.md new file mode 100644 index 0000000..b1d4a88 --- /dev/null +++ b/.claude/skills/morpho-design/SKILL.md @@ -0,0 +1,47 @@ +--- +name: morpho-design +description: Design posters, logos, type and motion in Morphotype with the morpho CLI (make → check --fix → vary → video). Use when asked to make, change or judge a design, poster, logo, lettering or animation in the app — not for work on the app's own code. +--- + +# Designing with morpho + +Morphotype designs are made with `npm run morpho -- …` (`scripts/morpho.mjs`). Don't read template, material or effect sources to learn what exists, and don't screenshot the editor: the CLI answers in a few lines of text or one PNG. Full reference: `docs/AGENT-TOOLS.md`. + +## The workflow: aim for 2–3 calls and one image + +1. **Start the daemon once** (drawing then takes about a second): `npm run morpho -- serve &` +2. **Look up only what you need**, in text: `morpho catalog kits`, `catalog fonts`, `catalog presets`, `catalog shapes`, `catalog motion`, or a name (`catalog chrome`). +3. **Write a spec and `make` it.** One call builds the design, places everything against the engine's real outlines, fixes what's mechanical, checks it and draws one PNG: + + ```json + { + "name": "Summer Sound", "canvas": "1080x1350", "kit": "riso-zine", "duration": 5, + "layers": [ + { "shape": "sparkle", "name": "Mark", "fitHeight": 20, "place": "top third" }, + { "text": "SUMMER\nSOUND", "name": "Title", "fitWidth": 82, "place": "centre" }, + { "text": "12 JULY · RIVERSIDE", "name": "Info", "place": "bottom", "margin": 90 } + ], + "motion": { "recipe": "cascade", "layers": ["Mark", "Title", "Info"] } + } + ``` + + `npm run morpho -- make summer.json` → `summer.morpho`, findings, `test-results/morpho/summer-make.png`. +4. **Read the text first.** `make` prints every box and margin, what it fixed and the findings. Look at the PNG once, to judge the look; after a later `make` with no warnings you don't need to look again. +5. **If something's left**, run the commands under "still to do" with `--check` (it prints only what changed in the findings), or edit the spec and `make` again. `check --fix` mends cut-off text, near-centring, loop seams and moves the clock cuts off. +6. **Compare options in one sheet**, not several renders: `morpho vary summer.morpho --vary 'Title.preset=Ice chrome|Plasma'`. +7. **Finish**: `morpho video summer.morpho -o summer.mp4` (moving) or `morpho render summer.morpho --out summer.png` (still). + +Every command ends with `next: …`: the likely next command. + +## Spec essentials + +- Layers are **bottom first**. A layer is `text`, `shape`, `line`, `path` or `group`. +- **Place with words, not pixels**: `fitWidth` / `fitHeight` (% of the canvas), `place` (`top`, `centre`, `bottom`, `top third`, `bottom third`, `left`, `right`, `top left`…), `margin` (px or `"6%"`), `centre` (`x`, `y`, `true`), `contain`, `offset` [x, y]. Stacks and rows: `"layout": [{ "stack": ["Date", "Venue"], "gap": 12, "align": "left", "place": "bottom left" }]`. +- **Kits** (`chrome-night`, `neon-club`, `riso-zine`, `brutal-pop`, `puff-sky`, `editorial`, `holo-y2k`, `arcade`) set the background, fonts and a look per role. The first text is the headline, other text is body, shapes are accent; `role`, `font`, `preset` or `material` override it; `color` (hex or a palette name like `"accent"`) tints the look's main colour. +- **Motion recipes**: `cascade`, `pop`, `type-on`, `slide-in`, `drop`, `drift`, `breathe`, or `enter` / `stagger` / `feel` / `loop` by hand. On a looping clock, things that enter also leave before the end, so loops join. +- Anything else: a layer's `"set": { "gloss": 0.4 }`, `"modifiers": [{ "kind": "wave", "amplitude": 0.04 }]`, or the spec's `"set": ["Title.liquid=1"]`. +- `"style": "abstract"` turns rules of taste (centring, near-misses, clear lead, crowding, hue count) into notes; mistakes stay warnings. + +## Editing an existing design + +`morpho describe f.morpho` (text tree) → `morpho set f.morpho 'Title.gloss=0.4' 'Info.place=bottom left' --check` → `morpho check f.morpho --fix`. Templates: `morpho scene "UFO poster" -o ufo.morpho`. When the design was made from a spec, change the spec and `make` again instead: `make` rebuilds from the spec. diff --git a/CLAUDE.md b/CLAUDE.md index 4872f13..52f47d8 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -4,28 +4,7 @@ A type and logo lab. The geometry engine is Rust (`crates/morpho-core`), compile ## Designing in the app: use morpho -Don't read the template or effect sources to learn what exists, and don't screenshot the editor to judge a design. Use the CLI (`docs/AGENT-TOOLS.md`): - -```sh -npm run morpho -- serve & # once: keeps a browser warm (drawing then takes < 1 s) -npm run morpho -- catalog # what exists; then: catalog chrome / glitch / star / motion / keyframes / layers -npm run morpho -- describe "UFO poster" # a design as text, changed settings only -npm run morpho -- scene "UFO poster" -o ufo.morpho -npm run morpho -- set ufo.morpho 'Title.gloss=0.4' 'Star.x=-120' -npm run morpho -- render ufo.morpho --frames 6 # one filmstrip image -npm run morpho -- vary ufo.morpho --vary 'Title.liquid=0.2,0.6,1.2' -npm run morpho -- check ufo.morpho # problems in words -npm run morpho -- compare ufo.morpho reference.png - -npm run morpho -- new poster.morpho --canvas 1080x1350 --bg "#111111" # from scratch -npm run morpho -- add poster.morpho text "HELLO" --size 200 --preset "Ice chrome" -npm run morpho -- align poster.morpho HELLO # centre by what's drawn -npm run morpho -- layout poster.morpho # boxes and margins as text -npm run morpho -- timing poster.morpho # when everything moves -npm run morpho -- video poster.morpho -o poster.mp4 -``` - -Prefer `check`, `describe` and text output over images. When you do need to look, ask for one contact sheet (`--frames`, `vary`), not several renders. +To make, change or judge a design, use the `morpho` CLI, not the template or effect sources or editor screenshots: write a spec, `npm run morpho -- make spec.json`, then `check --fix`, `vary` and `video`. The `morpho-design` skill (`.claude/skills/morpho-design/SKILL.md`) has the workflow and `docs/AGENT-TOOLS.md` the reference. ## Commands diff --git a/docs/AGENT-TOOLS.md b/docs/AGENT-TOOLS.md index af7cbf5..11de803 100644 --- a/docs/AGENT-TOOLS.md +++ b/docs/AGENT-TOOLS.md @@ -1,58 +1,235 @@ # morpho: the app from the command line -`scripts/morpho.mjs` drives Morphotype without its interface. It's built for AI agents (and scripts) designing in the app: every command answers in a few lines of text or in **one** PNG, so learning what exists, reading a design, editing it and judging it doesn't mean reading source files or taking a pile of screenshots. +`scripts/morpho.mjs` drives Morphotype without its interface. It's built for AI agents (and scripts) designing in the app. A whole design is **one spec file and one command**: `make` builds it, places it against the engine's real outlines, fixes what's mechanical, checks it in words and draws **one** PNG. Every command ends with the likely next command. ```sh +npm run morpho -- serve & # once: keeps a browser warm, so drawing takes about a second npm run morpho -- help ``` -## Commands +## The workflow: make → check (--fix) → vary → video + +```sh +morpho catalog kits # 1. pick a look (optional: kits, fonts, presets, shapes…) +morpho make poster.json # 2. build, place, fix, check, one picture +morpho set poster.morpho 'Info.place=bottom left' --check # 3. only if make left something to do +morpho vary poster.morpho --vary 'Title.preset=Ice chrome|Plasma|Molten' # (optional) compare options in one sheet +morpho video poster.morpho -o poster.mp4 # 4. done +``` + +A good design takes **2–3 calls and one image**: write the spec, `make`, then `video` (or `render`). `make` prints what it built, every layer's box and margins, what it fixed, the findings, and the commands for whatever is left, so the picture is for judging the look, not for finding mistakes. + +To change the design, edit the spec and run `make` again: it rebuilds everything (so a spec is the design's source; `set` edits on the `.morpho` file are lost on the next `make`). + +### Example + +`night.json`: + +```json +{ + "name": "Night Drive", + "canvas": "1080x1350", + "kit": "chrome-night", + "duration": 4, + "layers": [ + { "shape": "ring", "name": "Halo", "color": "accent", "fitWidth": 80, "place": "top third", "motion": { "loop": "pulse", "amount": 6, "period": 4 } }, + { "text": "NIGHT\nDRIVE", "name": "Title", "fitWidth": 78, "place": "top third", "modifiers": [{ "kind": "wave", "amplitude": 0.04, "wavelength": 1.4 }] }, + { "shape": "sparkle", "name": "Spark", "size": 200, "place": "top right", "margin": 110, "motion": { "loop": "spin", "period": 4 } }, + { "text": "LIVE · 21.11 · WAREHOUSE 9", "name": "Info", "place": "bottom", "margin": 120 } + ], + "motion": { "recipe": "cascade", "layers": ["Title", "Info"] } +} +``` + +``` +$ morpho make night.json +wrote night.morpho from night.json +"Night Drive" 1080×1350, 4s loop, kit chrome-night: 4 layers +motion cascade: Title, Info: enter rise 0.15s apart, leaving from 3.17s so the 4s loop joins +motion Halo: loop pulse +motion Spark: loop spin +layout: Halo: fit 80% wide, at top third · Title: fit 78% wide, at top third · Spark: at top right (margin 110) · Info: at bottom (margin 120) +boxes on 1080×1350 (px; x,y from the centre; negative margins are off the canvas), top first: +"Info" 463×25 centre 0,542 margins L 308 R 308 T 1205 B 120 · 35px type +"Spark" 200×200 centre 330,-465 margins L 770 R 110 T 110 B 1040 +"Title" 842×387 centre 0,-225 margins L 119 R 119 T 256 B 706 · 210px type +"Halo" 864×864 centre 0,-225 margins L 108 R 108 T 18 B 468 +"Night Drive" 1080×1350, 4s loop: no problems, 2 warnings +! "Title" and "Spark": rights 8.808px apart, nearly lined up +! "Halo": only 18px from the top edge: crowded (bleed it off or give it room) +· the loop joins smoothly +· visual weight sits upper (16% of the way to the edge) +· palette: 2 main hues (violet, azure), mostly neutrals +· looked at 4 of 4 layers +still to do: + morpho align night.morpho "Spark" --to "Title" --edge right # "Title" and "Spark": rights 8.808px apart, nearly lined up + morpho set night.morpho '"Halo".contain=true' # "Halo": only 18px from the top edge: crowded (bleed it off or give it room) +wrote test-results/morpho/night-make.png (1000×338) +next: run the commands under "still to do" (add --check to see what each changes), or edit night.json and make again +``` + +Run the two commands (each with `--check` to see what it changed), or change the spec (`"margin": 120` on Spark is one way to settle the first) and `make` again. + +The picture is the design at rest, or (when it moves) at rest plus three moments on the clock in one strip. + +## The spec + +JSON. Layers are listed **bottom first**: later ones draw on top. + +| Key | What it is | +| --- | --- | +| `name` | The design's name. | +| `canvas` | `"1080x1350"`, a preset name (`"Instagram post"`, see `catalog canvas`) or `{ "width", "height" }`. | +| `kit` | A style kit (below): background, fonts and a look for each role. | +| `background` | `"#111111"`, `"#a,#b"` (a gradient), a background preset (`"Paper"`) or a background object. Overrides the kit's. | +| `duration`, `loop` | The clock, in seconds (looping unless `"loop": false`). | +| `style` | `poster` (default), `logo` or `abstract` (see `check`). | +| `layers` | The layers, bottom first (below). | +| `layout` | Stacks and rows: `{ "stack": ["Title", "Info"], "gap": 40, "align": "centre", "place": "centre" }`, or `"row": […]`. | +| `motion` | Motion recipes over several layers: `{ "recipe": "cascade", "layers": ["Title", "Info"] }` (or a list of them). | +| `set` | Assignments applied last, as `set` takes them: `["Title.gloss=0.6", "background.grain=0.2"]`. | + +A layer is one of `"text": "HI\nTHERE"`, `"shape": "star"`, `"line": ""`, `"path": ""` (path data in em units, centred on 0,0) or `"group": [ …layers… ]` (with `"merge": true` they fuse into one shape). Then any of: + +| Key | What it is | +| --- | --- | +| `name` | How commands address it. | +| `role` | `headline`, `accent` or `body`: which of the kit's looks it takes. By default the first text is the headline, other text is body, and everything else is accent. | +| `font`, `size`, `tracking` | Text: a bundled font id (`catalog fonts`), px, em. A headline with no size fills 80% of the width. | +| `color` | `#rrggbb`, or a kit palette name (`"accent"`, `"ink"`): the look's one main colour (a flat fill, a neon's glow, an ink), or flat in that colour when the look has none. | +| `preset`, `material`, `recipe` | A material look by name (`catalog presets`), a material id, a modifier recipe. A `preset` or `material` replaces the kit's look. | +| `modifiers` | `[{ "kind": "wave", "amplitude": 0.04 }, "slant"]`. | +| `fitWidth`, `fitHeight`, `place`, `margin`, `centre`, `contain`, `offset` | Layout in words (below). | +| `at`, `rotation`, `scale`, `width` | Plain position (`"x,y"` px from the centre), degrees, scale, and a pen line's stroke width. Use `place` instead of `at` where you can. | +| `set` | Any other setting: `{ "gloss": 0.4, "wave.amplitude": 0.05 }`. | +| `motion` | This layer's motion: a recipe's fields (`{ "enter": "rise", "delay": 0.3 }`, `{ "loop": "float" }`), or motion settings as they're stored (`catalog motion`). | + +## Layout in words + +Measure, then place: every instruction is resolved against the engine's real build boxes (what's drawn, not the anchor), so italic, wavy or warped type lands where it looks like it should. + +| Instruction | What it does | +| --- | --- | +| `fitWidth: 80`, `fitHeight: 30` | Sizes the layer (or group) so its box is that % of the canvas width / height. Both: it fits inside both. Fits happen first; the layer then stays centred where it was. | +| `place: "top"` | `top`, `centre`, `bottom`, `top third`, `bottom third`, `left`, `right`, `top left`, `top right`, `bottom left`, `bottom right`. One word centres the other axis. Thirds put the box's centre on the third line. | +| `margin: 64` | Distance from the edges for `place`, in px or `"6%"` of the short side (default 6%). | +| `centre: "x"` | Centres by ink: `x`, `y` or `both` (`true`). | +| `contain: true` | Shrinks it if it's bigger than the canvas inside the margins, then moves it inside them. | +| `offset: [0, -60]` | A nudge in px after placing. | +| `stack` / `row` | Several layers one under another (or side by side), first listed first, `gap` px apart, aligned `start`, `centre` or `end` across (`left`, `right`, `top`, `bottom` work too), and placed as one block with `place` and `margin`. | + +The same instructions work on files you already have: + +```sh +morpho add poster.morpho text "Sat 14 June" --place "bottom left" --margin 80 +morpho add poster.morpho shape star --fit-height 30 --place centre +morpho set poster.morpho 'Title.place=top third' 'Title.fitWidth=80' 'Info.margin=5%' 'Info.centre=x' +morpho set poster.morpho --stack "Date,Venue" --gap 12 --align left --place "bottom left" +morpho align poster.morpho Title Info # centre by what's drawn (--to Layer, --edge left…) +``` + +## Kits + +A kit gives a design a background, a palette, a font pairing and a look for each role in one word. Anything a layer sets itself wins. `catalog kits` lists them with their contents. + +| Kit | Look | +| --- | --- | +| `chrome-night` | liquid chrome on near-black, a violet glow, grotesk captions | +| `neon-club` | neon tubes on black, pink and cyan | +| `riso-zine` | two-ink risograph on warm paper, tall condensed headline | +| `brutal-pop` | flat colour, thick outlines and hard shadows on a green grid | +| `puff-sky` | puffy inflated letters on deep sky blue, pink accents | +| `editorial` | black serif on paper with a red stamp | +| `holo-y2k` | holographic foil and an acid aura on black | +| `arcade` | pixel type in chunky 3D on electric violet | + +`new --kit name` starts a file with a kit; `add` then gives each layer its role's look (`--role headline|accent|body`). + +## Motion recipes + +Several layers in one go, on today's motion model (entrances, loops, ways out), so nobody works out delays or writes keyframes by hand. On a looping clock everything that enters also leaves before the clock ends (the entrance's own way out: rise leaves rising, grow shrinks away), so the loop joins without a jump. `"leave": "none"` keeps things on screen. + +| Recipe | What it does | +| --- | --- | +| `cascade` | each rises in, one after another | +| `pop` | each grows in with a spring | +| `type-on` | text fades in letter by letter | +| `slide-in` | each slides in from the left | +| `drop` | each drops in from above with a bounce | +| `drift` | everything floats, out of step | +| `breathe` | everything pulses gently, out of step | + +Or by hand: `enter` (`fade`, `rise`, `drop`, `left`, `right`, `grow`, `spin`), `stagger` (s between layers), `start` (s), `feel` (`Smooth`, `Snappy`, `Bouncy`, `Slow`), `duration` (s), `distance`, `letters` (`true` or `"words"`) with `letterStagger`, `loop` with `period`, `amount` and `spread` (phase step between layers), `leave` (`auto`, `none`, `fade`, `rise`…) and `leaveAt`. + +```sh +morpho motion poster.morpho Title Info Date --recipe cascade +morpho motion poster.morpho --enter rise --stagger 0.15 --feel Snappy # every top-level layer, bottom up +morpho motion poster.morpho Star --loop spin --period 4 +``` + +## check, --fix, set --check + +`check` says what's wrong in words, marks what it can mend with `[--fix]`, and prints the rest as commands to run. + +```sh +morpho check poster.morpho # findings; "N can be fixed automatically"; to do: commands +morpho check poster.morpho --fix # applies the mechanical fixes, writes the file, checks again +morpho set poster.morpho 'Title.size=240' --check # just what that edit changed in the findings +``` + +`--fix` mends what has one right answer: + +- **text cut off** by the canvas: shrunk to fit inside the margins and moved in; +- **nearly centred** (0.2–1.2% off): centred by its ink; +- **an entrance that replays at the loop's seam**: a way out timed to end before the clock does (and the entrance brought forward if it would otherwise not hold still); +- **a move the clock cuts off**: started earlier, then its stagger tightened, then made quicker. + +Everything else comes back as a ready-to-run command (`align … --edge left` for a near-miss, `set … contain=true` for something crowding an edge, a size for a headline without a clear lead, a fill for low contrast, `arrange … top` for something covered), or in words when it needs judgment. + +`set --check` (and `make`) remember each version's findings, so `--check` only checks the new one and prints what appeared (`+`), went away (`−`) or changed (`~`). + +**`--style poster|logo|abstract`** (on `new`, `make`, `set`, or one `check`) says what kind of design it is; it's kept in the file. For `abstract` work, rules of taste (near-misses and centring, a clear lead, crowded edges, how many hues) become notes; mistakes (off the canvas, unseen, low contrast, loop jumps, pops) stay warnings. + +### What check looks for + +**From the project and the engine's build:** layers the engine couldn't build; layers entirely or partly off the canvas; text too small to read; unknown materials and invalid colours; settings outside their range; keyframes that point at nothing; entrances or exits timed after the clock ends, or cut off by it. + +**From the boxes (layout notes):** edges and centres that nearly line up, 0.2–1.2% of the canvas apart (closer than that, nobody sees it); layers that are nearly centred; things crowding an edge; a design that sits a little off centre; two headlines of about the same size (no clear lead); too many text sizes. + +**From the pixels:** layers that can't be seen at rest (each layer is hidden in turn and the image compared, scaled to the layer's size so hairlines and tiny marks count); text with low contrast against what's actually behind it (measured on its best-separated quarter, so outlines, hard shadows and lit edges count); a blank, one-colour design; pops in the motion (one frame step far bigger than the steps on both sides of it); a loop that jumps where it starts over; where the visual weight sits, and how many hues the palette really uses. + +## Every command | Command | What it gives back | Browser? | | --- | --- | --- | -| `catalog [query]` | What designs are made of. With no query, a one-screen index. With a section (`materials`, `modifiers`, `shapes`, `motion`, `keyframes`, `layers`, `presets`, `recipes`, `fonts`, `backgrounds`, `canvas`, `templates`), that whole section. With a name (`chrome`, `glitch`, `star`, `"Ice chrome"`), every setting with its range, default and label. Anything else is searched. | no | -| `describe ` | The design as a text tree, top layer first. Each layer is one line: what it is, where it sits, its material, modifiers, motion and keyframes, showing **only settings that differ from the defaults**. | no | +| `make [-o f]` | A whole design from a spec: built, placed, mechanically fixed (`--no-fix` to skip), checked, one PNG. `--kit`, `--style`, `--at` / `--frames` for the picture. | yes | +| `catalog [query]` | What designs are made of. With no query, a one-screen index. With a section (`kits`, `materials`, `modifiers`, `shapes`, `motion`, `keyframes`, `layers`, `presets`, `recipes`, `fonts`, `backgrounds`, `canvas`, `templates`), that whole section. With a name (`chrome`, `glitch`, `star`, `"Ice chrome"`, `riso-zine`), every setting with its range, default and label. Anything else is searched. | no | +| `describe ` | The design as a text tree, top layer first, showing **only settings that differ from the defaults**. | no | | `scene