Purpose
Render a hand-drawn-feeling SVG line that animates as if being drawn live — used for underlines under headlines, arrow callouts, accent squiggles around logo lockups, or motion-line trails behind a traveling element-handoff object.
The visual signal of "not perfectly vector" is a critical anti-AI tell across reference videos. Sterile straight lines look algorithmic; squiggles with light per-vertex jitter feel human.
Params (params.schema.json)
{
"id": "squiggle-under-headline",
"path_kind": "underline | arrow | squiggle | underline-with-arrow | circle-around",
"anchor_target": { "kind": "element-id" | "absolute", "value": "h1" },
"start_x": 60, "start_y": 320, "end_x": 480, "end_y": 320, "control_points": [], // optional bezier control points
"stroke_color": "#0F0F0F", "stroke_width_px": 5, "stroke_linecap": "round",
"wobble_amplitude_px": 6, // per-vertex jitter "wobble_frequency": 0.3, "wobble_seed": 17,
"arrow_tip": true, // hand-drawn arrowhead at end "arrow_size_px": 24,
"draw_duration_frames": 14, "draw_easing": "bezier(0.22, 1, 0.36, 1)", "draw_start_frame": 0 }
## Behaviour
1. Generate an SVG path from `start` → `end` using `control_points` (or a default arc if none given).
2. Apply per-vertex jitter: walk the path at ~12px intervals, perturb each sample by `wobble_amplitude_px * noise(t, seed)`.
3. Compute path total length via `getTotalLength()`.
4. Set `stroke-dasharray = pathLength; stroke-dashoffset = pathLength * (1 - progress)` where progress interpolates 0→1 over `draw_duration_frames`.
5. If `arrow_tip = true`, append a small hand-drawn triangle at the end position, also drawn-on after the line completes (last 4 frames).
Implementation: a React component using SVG, `useCurrentFrame()`, and `interpolate`.
## Style pack overrides
### SP-B (pinterest-saturated)
- `stroke_width_px: 4`, `wobble_amplitude_px: 6`, `arrow_tip: true`.
- Stroke color: ink (#0F0F0F) — high contrast on saturated bgs.
### SP-E (chaos-card)
- `stroke_width_px: 6`, `wobble_amplitude_px: 8` (more chaotic).
- Stroke color: card's complement.
### SP-G (riso-illustration)
- `stroke_width_px: 5`, `wobble_amplitude_px: 6`.
- Path-follows-element: when `anchor_target = "element-handoff:ball"`, the path geometrically traces the ball's keyframe trajectory.
### SP-A (marshmallow)
- Not used. Marshmallow's aesthetic is vector-clean, no doodles.
### SP-C (editorial-cinematic)
- Not used. Editorial pack uses crop-marks and corner ornaments, not squiggles.
## When to use
- Underline accent under a headline (SP-B, SP-G).
- Arrow callout pointing at a UI element (SP-B).
- Decorative squiggles around a logo lockup (SP-B, SP-E, SP-G).
- Motion-line trail tracing an element-handoff path (SP-G).
## When NOT to use
- SP-A or SP-D — pack aesthetic doesn't include hand-drawn elements.
- More than 3 squiggles per scene (reads as clutter).
- As a primary subject (it's an accent, never a hero).
## Quality Checks
- Path renders within canvas bounds at every frame.
- Stroke-dashoffset animation reaches 0 by `draw_start_frame + draw_duration_frames` (line fully drawn).
- Arrow tip renders ON the end of the line, not floating nearby.
- Wobble amplitude is visible but not chaotic — viewer should perceive "drawn by hand" not "earthquake".
## Failure Modes
- **Line draws but never reaches its endpoint.** Stroke-dasharray miscalculated. Verify `pathLength` was measured *after* applying jitter.
- **Wobble looks like a sine wave (too regular).** Use perlin/simplex noise, not pure sin().
- **Arrow tip points the wrong direction.** Compute tangent at path end and orient triangle accordingly.
- **Line clips at canvas edges.** Constrain start/end + jitter to safe area (88% rect).