FUTURE FUN: THREE FEATURES TO LAND
==================================

End-of-day backlog of ideas Claude pitched and the user liked. Each
sized for ~one patch bump. Wiggle goes first; the others can wait.


1. WIGGLE — procedural shake modifier (per layer)
-------------------------------------------------

Add a "Wiggle" property to Layer. When on, every sampled frame
adds deterministic noise to position and/or rotation, giving any
layer that classic animator wobble. Pairs thematically with Kaleido:
both are per-layer procedural modifiers stacked on top of the base
keyframe sample.

Data model (src/types/scene.ts):
    wiggle?: {
      amplitude?: number   // pixels for x/y
      rotation?: number    // degrees for rotation
      frequency?: number   // Hz (cycles per second)
    }
    // Defaults when `wiggle` is set but a sub-field is missing:
    // amplitude=8px, rotation=2°, frequency=2Hz. Toggle off ⇒ delete.

Math (src/lib/wiggle.ts) — shared truth:
    function layerSeed(id: string): [number, number, number]
      // 3 stable 32-bit ints derived from the layer id, used as
      // independent seeds for x / y / rotation noise streams.
    function wiggleNoise(seed: number, t: number): number
      // Deterministic [-1, 1] output. Sum of three sin waves with
      // different irrational phase offsets — cheap, smooth, no
      // dependency on a noise lib.
        return (
          sin(seed * 12.9898 + t * 78.233) +
          sin(seed *  4.31   + t * 11.71 ) +
          sin(seed *  7.71   + t * 23.5  )
        ) / 3
    function applyWiggle(props, layer, time): props'
      // Adds amp * noise(seedX, t * freq) to props.x, etc.

Wire-in:
- src/lib/interpolate.ts sampleLayer: post-process the result of
  the keyframe sample with applyWiggle. ALL renderers go through
  sampleLayer (canvas2d, Konva, button-state, clip-internal), so a
  single hook here lights up everywhere except the export engine.
- src/lib/exportPlayer.ts: mirror the math in JS inside the engine
  source so exports look identical. RUNTIME_VERSION bump.

UI (src/components/PropertiesPanel.vue + i18n):
- New "Wiggle" collapsible section, between Mask and Kaleido feels
  natural. Toggle (PhWaveSine icon), then three RangeSliders:
  amplitude (0–64 px), rotation (0–45°), frequency (0–10 Hz).
  Hidden for button-asset layers (consistent with Kaleido).

Store (src/stores/scene.ts):
- setLayerWiggle(id, patch) with same delete-on-default semantics
  as Kaleido (rotation=0 ⇒ delete the field, etc.).

Tests:
- wiggleNoise determinism: noise(seed, t) === noise(seed, t).
- wiggleNoise smoothness: |n(t) - n(t+1/60)| stays bounded.
- sampleLayer with wiggle: x/y/rotation drift around base, return
  to base when wiggle is off.
- Engine source carries wiggleNoise; same sample at the same time
  matches between editor and engine.
- PropertiesPanel renders the new section + writes wiggle fields
  back to the store.


2. MOTION-PATH OVERLAY (selected layer's position keyframes)
------------------------------------------------------------

Visualize a layer's keyframed path on the canvas as a faint dashed
line connecting consecutive position keyframes, with each keyframe
rendered as a draggable dot. The position-keyframe drag-handle
infrastructure (PathFrame, activeSegments in CanvasStage.vue ~2562)
is already 70% there — currently only used when a position-kf is
explicitly being dragged. Promote that overlay to "always on while
the layer is selected".

Scope:
- Toggle in the View menu (or a slash-key shortcut) called "Motion
  paths" — global flag persisted in localStorage like
  `useShowLocked`.
- For the selected layer (and only the selected layer), draw the
  overlay even when nothing is being dragged. Reuse the existing
  segment / curve-sampling helpers.
- Color follows layer.color so two layers' paths don't collide
  visually.

Bigger lift than Wiggle but mostly leveraging existing code.


3. EYEDROPPER (color sampling from anywhere on the page)
--------------------------------------------------------

Tiny feature using the browser's EyeDropper API. Adds a small
pipette icon next to every color swatch in the PropertiesPanel
(fill, stroke, shadow color). Click → fires `new EyeDropper().open()`
→ user picks any pixel on screen → result sets the swatch.

Caveats:
- Chromium-only API. Hide the button when `window.EyeDropper` is
  undefined (Safari + Firefox) — a clean progressive enhancement.
- Permission prompt is browser-handled, no extra plumbing.

Smallest of the three. Good "polish day" snack.
