@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.
@@ -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 (GSAP, Lenis, SplitType,
14
- p5.js, ogl).
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 — p5.js, CSS, or Canvas 2D — per motif type, and gives the technique for producing each without hand-typed coordinate guessing.
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) → **p5.js**. Write the motif as a rule —
24
- noise, jitter, growth, a formula-driven point set — not a curve typed
25
- out point by point. The generator is legible and adjustable; a
26
- hand-typed curve is neither.
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 GSAP
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
- - **p5.js** — construct from a formula (sine-based waveform, noise
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 on the same generator,
50
- not a hand-edited copy.
51
- - Use **instance mode** (`new p5(sketch, container)`), not global mode
52
- — a global-mode sketch leaks `setup`/`draw`/every p5 function onto
53
- `window`, which collides with Astro's own module scope and with
54
- GSAP/ScrollTrigger's own render loop running on the same page.
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
- p5.js formula's inputs, a `clip-path` shape's control values, a Canvas
92
- draw's measured inputs — before accepting it. If you can't say why a
93
- value is what it is, it was guessed, not authored — redo it via the
94
- technique's proper primitives.
95
- - **For p5.js, confirm the seed is fixed and committed**, not left to
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 p5.js,
122
- CSS, or Canvas 2D, that's a signal to reconsider the motif's source or
123
- literalness with `landing-systems` — not a reason to fall back to
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 GSAP/ScrollTrigger/Lenis pacing spec the
48
- Builder implements against.
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) · **GSAP + ScrollTrigger**, scoped to
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** (smooth-
72
- scroll feel, the "weight and suspension" dial) · **SplitType**
73
- (line/word/char copy-reveal splitting) · **p5.js** (organic/generative
74
- motifs — described as a rule: noise, jitter, growth — not hand-typed path
75
- data) · **CSS `clip-path` / gradients / `border-radius`** (static
76
- geometric motifs — spines, chevrons, blobs — GSAP-animatable directly,
77
- zero coordinate-guessing risk) · **Canvas 2D with computed coordinates**
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 p5.js rule (noise, jitter,
101
- growth), a static geometric motif is CSS (`clip-path`, gradients,
102
- `border-radius`), and a measured/connective motif is Canvas 2D drawn from
103
- real DOM positions. See the `motif-authoring` skill for the technique
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 (p5.js / CSS / Canvas 2D) + its variation rules
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) —