@skyf0xx/hedgehog 2.0.7 → 2.0.9
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.
- package/README.md +1 -1
- package/package.json +1 -1
- package/src/agents/landing-builder.md +13 -11
- package/src/agents/landing-critic.md +1 -1
- package/src/agents/landing-sequencer.md +8 -8
- package/src/agents/landing-systems.md +6 -5
- package/src/golden-cores/landing-page/package.json +7 -7
- package/src/golden-cores/landing-page/pnpm-lock.yaml +155 -225
- package/src/skills/hedgehog-bootstrap-landing-page-core/SKILL.md +2 -2
- package/src/skills/motif-authoring/SKILL.md +28 -39
- package/src/skills/paper-js-motifs/SKILL.md +167 -0
- package/src/templates/CLAUDE.core.landing-page.md +17 -16
|
@@ -10,8 +10,8 @@ add-on layer to run after it, unlike `full-stack-app`'s
|
|
|
10
10
|
`hedgehog-bootstrap` — by copying a pre-built, pre-verified workspace
|
|
11
11
|
(`src/golden-cores/landing-page/` in the Hedgehog package) rather than
|
|
12
12
|
generating it live: Astro workspace, Tailwind v4 CSS-first token-layer
|
|
13
|
-
config, the library set this core is built on (
|
|
14
|
-
|
|
13
|
+
config, the library set this core is built on (Motion, Lenis, SplitType,
|
|
14
|
+
Paper.js, ogl).
|
|
15
15
|
This piece is deterministic — the same commands produce the same output
|
|
16
16
|
on every project — so the output is committed once, upstream, and copied
|
|
17
17
|
here instead of re-derived by an agent on every run.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: motif-authoring
|
|
3
|
-
description: Use whenever `landing-systems` authors the signature motif (Chain Method step 6, `hedgehog-landing-loop`) or `landing-builder` implements one from `src/motifs/`. Trigger on "draw the motif", "author the motif", "write src/motifs/". Picks the right construction technique —
|
|
3
|
+
description: Use whenever `landing-systems` authors the signature motif (Chain Method step 6, `hedgehog-landing-loop`) or `landing-builder` implements one from `src/motifs/`. Trigger on "draw the motif", "author the motif", "write src/motifs/". Picks the right construction technique — Paper.js, CSS, or Canvas 2D — per motif type, and gives the technique for producing each without hand-typed coordinate guessing.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# Motif Authoring
|
|
@@ -20,15 +20,18 @@ continuity, scale range, and literalness. That spec determines which of
|
|
|
20
20
|
three techniques applies — pick one, don't mix by default:
|
|
21
21
|
|
|
22
22
|
1. **Organic / generative** (a material's grain, a growth pattern, a
|
|
23
|
-
natural form that evolves) → **
|
|
24
|
-
noise, jitter, growth, a formula-driven point set —
|
|
25
|
-
|
|
26
|
-
hand-typed curve is
|
|
23
|
+
natural form that evolves) → **Paper.js**. Write the motif as a rule —
|
|
24
|
+
noise, jitter, growth, a formula-driven point set — driving a scene
|
|
25
|
+
graph of `Path`/`Group` objects, not a curve typed out point by point.
|
|
26
|
+
The generator is legible and adjustable; a hand-typed curve is
|
|
27
|
+
neither. See `paper-js-motifs` for the concrete API patterns
|
|
28
|
+
(`PaperScope` setup, seeded randomness, the `params` object,
|
|
29
|
+
setter-based redraw) once this technique is chosen.
|
|
27
30
|
2. **Static geometric** (a spine, a chevron, a blob, a simple silhouette
|
|
28
31
|
that doesn't need to evolve) → **CSS** (`clip-path`, gradients,
|
|
29
32
|
`border-radius`). Build the shape from `clip-path` polygon/shape
|
|
30
33
|
functions, layered gradients, or `border-radius` percentage strings —
|
|
31
|
-
values you can reason about exactly, animatable directly by
|
|
34
|
+
values you can reason about exactly, animatable directly by Motion
|
|
32
35
|
(e.g. tweening a `border-radius` string in place of shape morphing).
|
|
33
36
|
3. **Measured / connective** (a thread, a line, a connector that must
|
|
34
37
|
align to real element positions across the page) → **Canvas 2D with
|
|
@@ -42,33 +45,16 @@ three techniques applies — pick one, don't mix by default:
|
|
|
42
45
|
**Never hand-write multi-point geometry as raw guessed numbers**, in any
|
|
43
46
|
of the three:
|
|
44
47
|
|
|
45
|
-
- **
|
|
48
|
+
- **Paper.js** — construct from a formula (sine-based waveform, noise
|
|
46
49
|
function, L-system, Voronoi cell, particle rule) with named parameters,
|
|
47
50
|
not a manually plotted point list. Express motif variation
|
|
48
51
|
(augmentation/inversion/retrograde, per `landing-systems` step 6's
|
|
49
|
-
music-theory vocabulary) as parameter changes
|
|
50
|
-
not a hand-edited copy.
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
- Seed every source of randomness explicitly —
|
|
56
|
-
`p.randomSeed(seed); p.noiseSeed(seed);` — with a fixed, committed
|
|
57
|
-
seed value. This is what makes "derive from a formula" mean anything
|
|
58
|
-
in practice: the same seed must always reproduce the exact same
|
|
59
|
-
motif, so a reviewer (or `landing-critic`) can re-run it and get the
|
|
60
|
-
committed result, not a fresh roll.
|
|
61
|
-
- Collect the formula's inputs (particle count, noise scale, growth
|
|
62
|
-
rate, angle — whatever the generator exposes) into one named
|
|
63
|
-
`params` object at the top of the sketch, not scattered magic
|
|
64
|
-
numbers through `draw()`. This is the same object the "state the
|
|
65
|
-
parameters and what each one is for" verification step below reads
|
|
66
|
-
from — an ungrouped generator is harder to audit, not just harder to
|
|
67
|
-
read.
|
|
68
|
-
- Mount inside a `client:*` island only where the sequencer's beat
|
|
69
|
-
structure actually calls for the motif to animate; a motif that's
|
|
70
|
-
static per section doesn't need a live p5 instance at all — render
|
|
71
|
-
once and let CSS/GSAP handle any transform-level movement instead.
|
|
52
|
+
music-theory vocabulary) as parameter changes that update the same
|
|
53
|
+
scene graph's object properties, not a hand-edited copy. See
|
|
54
|
+
`paper-js-motifs` for the concrete implementation patterns (scoped
|
|
55
|
+
`PaperScope`, seeded PRNG, the `params` object, setter-based redraw)
|
|
56
|
+
— this skill governs the choice and the audit, that one governs the
|
|
57
|
+
code.
|
|
72
58
|
- **CSS** — build from `clip-path`'s named shape functions
|
|
73
59
|
(`polygon()`, `circle()`, `ellipse()`, `inset()` with rounded corners)
|
|
74
60
|
or `border-radius`'s percentage syntax, composed via layering and
|
|
@@ -87,12 +73,15 @@ A motif is not correct because it compiles. Before writing it into
|
|
|
87
73
|
- **Render it and look.** Use the Bash tool to run the dev server or a
|
|
88
74
|
standalone preview and view the actual output — don't judge geometry
|
|
89
75
|
or a Canvas draw call from the source alone.
|
|
76
|
+
- **If the scene graph exposes setters, call every one of them at least
|
|
77
|
+
once before judging it correct** — confirm each produces the visual
|
|
78
|
+
update it's meant to, not just that the initial render looks right.
|
|
90
79
|
- **State the construction's parameters and what each one is for** — a
|
|
91
|
-
|
|
92
|
-
draw's measured inputs — before accepting it. If you can't say
|
|
93
|
-
value is what it is, it was guessed, not authored — redo it via
|
|
94
|
-
technique's proper primitives.
|
|
95
|
-
- **For
|
|
80
|
+
Paper.js formula's inputs, a `clip-path` shape's control values, a
|
|
81
|
+
Canvas draw's measured inputs — before accepting it. If you can't say
|
|
82
|
+
why a value is what it is, it was guessed, not authored — redo it via
|
|
83
|
+
the technique's proper primitives.
|
|
84
|
+
- **For Paper.js, confirm the seed is fixed and committed**, not left to
|
|
96
85
|
default/time-based randomness — re-running the sketch must reproduce
|
|
97
86
|
the exact motif that was reviewed, not a new variation each load.
|
|
98
87
|
- **Check symmetry/proportion deliberately** where the source motif
|
|
@@ -118,10 +107,10 @@ A motif is not correct because it compiles. Before writing it into
|
|
|
118
107
|
numbers are unreadable — there's no way to tell what a curve looks
|
|
119
108
|
like from its `d` string while writing it, so hand-typed paths drift
|
|
120
109
|
into lumpy, asymmetric, or self-intersecting geometry regardless of
|
|
121
|
-
how carefully they're written. If a motif genuinely doesn't fit
|
|
122
|
-
CSS, or Canvas 2D, that's a signal to reconsider the motif's
|
|
123
|
-
literalness with `landing-systems` — not a reason to fall
|
|
124
|
-
hand-authored SVG.
|
|
110
|
+
how carefully they're written. If a motif genuinely doesn't fit
|
|
111
|
+
Paper.js, CSS, or Canvas 2D, that's a signal to reconsider the motif's
|
|
112
|
+
source or literalness with `landing-systems` — not a reason to fall
|
|
113
|
+
back to hand-authored SVG.
|
|
125
114
|
- `landing-builder` implements the motif exactly as authored here — a
|
|
126
115
|
motif that's hard to render cleanly should be fixed at this step, not
|
|
127
116
|
smoothed over during build.
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: paper-js-motifs
|
|
3
|
+
description: Use whenever `landing-builder` implements a motif that `motif-authoring` has routed to Paper.js (organic/generative source — noise, jitter, growth, a formula-driven point set). Trigger on "implement the motif in Paper.js", "write the PaperScope sketch", "src/motifs/*.ts" for an organic motif. Gives the concrete API patterns — scoping, seeded randomness, the params object, setter-based redraw — that keep a Paper.js sketch a formula instead of hand-plotted geometry.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Paper.js Motifs
|
|
7
|
+
|
|
8
|
+
`motif-authoring` decides *that* a motif is organic/generative and
|
|
9
|
+
therefore Paper.js; this skill covers *how* to write the Paper.js sketch
|
|
10
|
+
itself once that decision is made. Every pattern below exists to keep
|
|
11
|
+
the sketch a legible, re-runnable formula — not a one-off script that
|
|
12
|
+
happens to draw the right thing once.
|
|
13
|
+
|
|
14
|
+
## Scope the sketch to its own `PaperScope`
|
|
15
|
+
|
|
16
|
+
Never call the global `paper.setup(canvas)` API. A landing page runs an
|
|
17
|
+
Astro island's own module scope, Motion's render loop, and potentially
|
|
18
|
+
more than one motif on the same page — the global `paper` object is
|
|
19
|
+
shared mutable state across all of them, and one sketch's `paper.project`
|
|
20
|
+
silently becomes another's.
|
|
21
|
+
|
|
22
|
+
```ts
|
|
23
|
+
import paper from 'paper';
|
|
24
|
+
|
|
25
|
+
const scope = new paper.PaperScope();
|
|
26
|
+
scope.setup(canvasEl);
|
|
27
|
+
|
|
28
|
+
scope.activate();
|
|
29
|
+
// construct the scene graph here — every paper.* call inside this
|
|
30
|
+
// block resolves against `scope`, not the global instance
|
|
31
|
+
scope.view.draw();
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Every exported function this skill's patterns produce (draw, and any
|
|
35
|
+
setter) must call `scope.activate()` before touching `paper.*`,
|
|
36
|
+
including on repeat calls — a setter invoked after some other scope
|
|
37
|
+
activated in between will otherwise mutate the wrong project.
|
|
38
|
+
|
|
39
|
+
## Seed every source of randomness
|
|
40
|
+
|
|
41
|
+
Paper.js has no built-in seeded noise or random. Bring a small,
|
|
42
|
+
dependency-free PRNG (a mulberry32 or sfc32-style function is enough —
|
|
43
|
+
a few lines, no package) and seed it with a fixed literal committed in
|
|
44
|
+
the sketch:
|
|
45
|
+
|
|
46
|
+
```ts
|
|
47
|
+
function mulberry32(seed: number) {
|
|
48
|
+
return function () {
|
|
49
|
+
seed |= 0;
|
|
50
|
+
seed = (seed + 0x6d2b79f5) | 0;
|
|
51
|
+
let t = Math.imul(seed ^ (seed >>> 15), 1 | seed);
|
|
52
|
+
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
53
|
+
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const rand = mulberry32(0x5eed); // fixed seed — never Date.now() or Math.random()
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Use `rand()` for every stochastic input the formula needs (jitter
|
|
61
|
+
amount, particle position, growth angle). This is what makes "derive
|
|
62
|
+
from a formula" a real guarantee rather than a one-time roll: re-running
|
|
63
|
+
the sketch, in dev or in `landing-critic`'s review, must reproduce the
|
|
64
|
+
exact motif that was committed — never a fresh variation per load.
|
|
65
|
+
|
|
66
|
+
## Collect inputs into one `params` object
|
|
67
|
+
|
|
68
|
+
```ts
|
|
69
|
+
const params = {
|
|
70
|
+
pointCount: 48,
|
|
71
|
+
noiseScale: 0.08,
|
|
72
|
+
growthRate: 1.4,
|
|
73
|
+
baseAngle: Math.PI / 6,
|
|
74
|
+
seed: 0x5eed,
|
|
75
|
+
};
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Every magic number the construction function reads comes from this
|
|
79
|
+
object, not a literal scattered through the formula. This object is
|
|
80
|
+
also the answer to "state the construction's parameters and what each
|
|
81
|
+
one is for" — `motif-authoring`'s verification step reads directly from
|
|
82
|
+
it, so an ungrouped generator is a review blocker, not just a style
|
|
83
|
+
complaint.
|
|
84
|
+
|
|
85
|
+
## Build the scene graph from the formula, once
|
|
86
|
+
|
|
87
|
+
Construct `Path`/`Group` objects by iterating the formula's output —
|
|
88
|
+
never by typing coordinates by hand:
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
scope.activate();
|
|
92
|
+
|
|
93
|
+
const path = new scope.Path({
|
|
94
|
+
strokeColor: 'var(--color-accent)',
|
|
95
|
+
strokeWidth: 2,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
for (let i = 0; i < params.pointCount; i++) {
|
|
99
|
+
const t = i / params.pointCount;
|
|
100
|
+
const angle = params.baseAngle * i + rand() * params.noiseScale;
|
|
101
|
+
const radius = 40 + params.growthRate * i;
|
|
102
|
+
path.add(
|
|
103
|
+
new scope.Point(Math.cos(angle) * radius, Math.sin(angle) * radius),
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
path.smooth();
|
|
107
|
+
|
|
108
|
+
scope.view.draw();
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Don't add a `view.onFrame` handler unless `landing-sequencer`'s beat
|
|
112
|
+
structure specifies continuous motion for this motif. A motif that's
|
|
113
|
+
static per section needs exactly this: build once, `view.draw()` once.
|
|
114
|
+
|
|
115
|
+
## Setters mutate scene graph state directly — never replay the script
|
|
116
|
+
|
|
117
|
+
Motif variation (augmentation/inversion/retrograde, per
|
|
118
|
+
`landing-systems` step 6's vocabulary) is a property assignment on the
|
|
119
|
+
already-built objects, followed by a redraw — not a second call into the
|
|
120
|
+
construction function:
|
|
121
|
+
|
|
122
|
+
```ts
|
|
123
|
+
export function setGrowth(factor: number) {
|
|
124
|
+
scope.activate();
|
|
125
|
+
path.segments.forEach((seg, i) => {
|
|
126
|
+
seg.point = seg.point.multiply(factor);
|
|
127
|
+
});
|
|
128
|
+
scope.view.draw();
|
|
129
|
+
}
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
There's no transform/style stack to reset before a setter runs, because
|
|
133
|
+
nothing replays — the scene graph holds current state directly, and the
|
|
134
|
+
setter changes that state in place. If a redraw needs a genuinely
|
|
135
|
+
different point set (not a transform of the existing one), recompute
|
|
136
|
+
from `params` and reassign `path.segments = newSegments`, still followed
|
|
137
|
+
by `scope.view.draw()`, still without reconstructing `path` itself.
|
|
138
|
+
|
|
139
|
+
## Common mistakes this skill exists to prevent
|
|
140
|
+
|
|
141
|
+
- **Calling `paper.setup()` on the global scope** instead of a scoped
|
|
142
|
+
`PaperScope` instance — breaks the moment a second motif or another
|
|
143
|
+
Paper.js consumer runs on the same page.
|
|
144
|
+
- **Using `Math.random()` or a time-based seed** — makes the motif
|
|
145
|
+
non-reproducible; every reload or every review produces a different
|
|
146
|
+
shape.
|
|
147
|
+
- **A draw loop (`view.onFrame`) on a motif the spec says is static** —
|
|
148
|
+
wastes render budget and fights Motion's own scroll-driven timeline
|
|
149
|
+
for the same frame budget.
|
|
150
|
+
- **Reconstructing the whole `Path` inside a setter** instead of mutating
|
|
151
|
+
existing segments/properties — loses the "retained-mode scene graph"
|
|
152
|
+
property that's the entire reason `motif-authoring` picked Paper.js
|
|
153
|
+
over an imperative Canvas redraw.
|
|
154
|
+
- **A literal scattered through the loop body** instead of a named field
|
|
155
|
+
on `params` — indistinguishable from a guessed number to a reviewer,
|
|
156
|
+
even when it was in fact derived from something real.
|
|
157
|
+
|
|
158
|
+
## Constraints
|
|
159
|
+
|
|
160
|
+
- This skill covers Paper.js API usage only. Whether a given motif
|
|
161
|
+
belongs in Paper.js at all — versus CSS or Canvas 2D — is
|
|
162
|
+
`motif-authoring`'s decision (source, persistence, continuity, scale
|
|
163
|
+
range, literalness from `landing-systems` step 6), not this skill's.
|
|
164
|
+
- `paper` is already a pinned dependency in the `landing-page` core
|
|
165
|
+
(`src/golden-cores/landing-page/package.json`) — never add a
|
|
166
|
+
competing geometry/animation library to work around something this
|
|
167
|
+
skill's patterns don't cover.
|
|
@@ -44,8 +44,8 @@ edited after a phase closes.
|
|
|
44
44
|
the token system that reconciles them, and the signature motif. Owns
|
|
45
45
|
everything that becomes a Tailwind token or a copy rule.
|
|
46
46
|
- **`landing-sequencer`** — per-section transition type, weight, spacing,
|
|
47
|
-
and beat structure — the
|
|
48
|
-
|
|
47
|
+
and beat structure — the Motion/Lenis pacing spec the Builder
|
|
48
|
+
implements against.
|
|
49
49
|
- **`landing-copywriter`** — the final page copy: headline (2 backups),
|
|
50
50
|
every section's body text, CTA text — written to the voice spec and
|
|
51
51
|
the sequence's beat structure. Presented as its own artifact for the
|
|
@@ -66,15 +66,16 @@ edited after a phase closes.
|
|
|
66
66
|
|
|
67
67
|
**Astro** (zero-JS-by-default shell, islands only where interaction is
|
|
68
68
|
genuinely needed) · **Tailwind v4, CSS-first** (config as token layer
|
|
69
|
-
only — no component library on top) · **
|
|
69
|
+
only — no component library on top) · **Motion**, scoped to
|
|
70
70
|
CSS/transform targets only, no plugins (primary animation engine; owns
|
|
71
|
-
Sequencer pacing and top/heart/base fade timing) · **Lenis**
|
|
72
|
-
scroll feel, the "weight and suspension" dial) · **SplitType**
|
|
73
|
-
(line/word/char copy-reveal splitting) · **
|
|
74
|
-
motifs — described as a rule: noise, jitter, growth — not hand-typed
|
|
75
|
-
data
|
|
76
|
-
|
|
77
|
-
|
|
71
|
+
Sequencer pacing and top/heart/base fade timing) · **Lenis**
|
|
72
|
+
(smooth-scroll feel, the "weight and suspension" dial) · **SplitType**
|
|
73
|
+
(line/word/char copy-reveal splitting) · **Paper.js** (organic/generative
|
|
74
|
+
motifs — described as a rule: noise, jitter, growth — not hand-typed
|
|
75
|
+
path data; see the `paper-js-motifs` skill for implementation patterns)
|
|
76
|
+
· **CSS `clip-path` / gradients / `border-radius`** (static geometric
|
|
77
|
+
motifs — spines, chevrons, blobs — Motion-animatable directly, zero
|
|
78
|
+
coordinate-guessing risk) · **Canvas 2D with computed coordinates**
|
|
78
79
|
(measured/connective motifs — threads/lines that align to real DOM
|
|
79
80
|
positions, drawn from measured values) · **`ogl`** (lightweight WebGL) or
|
|
80
81
|
a raw shader (a continuous background field spanning the full page
|
|
@@ -97,11 +98,11 @@ Vocabulary and derive the right choice, not to default to something
|
|
|
97
98
|
off-the-shelf.
|
|
98
99
|
|
|
99
100
|
Motifs are always constructed from a formula or a measurement, never
|
|
100
|
-
hand-typed coordinates: an organic motif is a
|
|
101
|
-
growth), a static geometric motif is CSS
|
|
102
|
-
`border-radius`), and a measured/connective
|
|
103
|
-
real DOM positions. See the
|
|
104
|
-
per motif type.
|
|
101
|
+
hand-typed coordinates: an organic motif is a Paper.js scene graph driven
|
|
102
|
+
by a rule (noise, jitter, growth), a static geometric motif is CSS
|
|
103
|
+
(`clip-path`, gradients, `border-radius`), and a measured/connective
|
|
104
|
+
motif is Canvas 2D drawn from real DOM positions. See the
|
|
105
|
+
`motif-authoring` skill for the technique per motif type.
|
|
105
106
|
|
|
106
107
|
### Layout
|
|
107
108
|
|
|
@@ -110,7 +111,7 @@ astro.config.mjs Astro workspace root
|
|
|
110
111
|
src/
|
|
111
112
|
pages/ one file per page (usually just index.astro)
|
|
112
113
|
sections/ one component per page section, in Sequencer order
|
|
113
|
-
motifs/ the signature motif (
|
|
114
|
+
motifs/ the signature motif (Paper.js / CSS / Canvas 2D) + its variation rules
|
|
114
115
|
styles/ global.css — Tailwind v4 CSS-first import + the `@theme` token layer (hex values, type roles, spacing unit, easing family from Step 5)
|
|
115
116
|
.hedgehog/
|
|
116
117
|
BMAD/ vendored BMAD-METHOD shelf's raw output (brief, PR-FAQ, PRD, UX spec, research) —
|