Install
openclaw skills install pixel2motionConvert raster logos into minimal, smooth SVGs optimized for professional animation, then create choreographed JS-rendered HTML animations applying Disney's...
openclaw skills install pixel2motionPixel → Vector → Motion. This skill fuses two disciplines:
The fusion rests on one insight: minimal smooth geometry IS animatable geometry. A logo built from 3 semantic parts animates cleanly; a logo built from 400 pixel-stair traced points cannot be choreographed or accepted as a professional logo vector. Phase 2 therefore does not merely vectorize — it structures for motion. And the animation must always land exactly on the QA-verified static vector (the Final Frame Contract).
logo.svg — final static vector (motion-ready structure)logo_motion.html — showcase-style standalone HTML with the main animation, atomic motion studies, replay/slow/speed controls, and QA hooksmotion_spec.md — personality words, principles applied, timeline table, easing tokens, atomic motions, tunable controlsoutputs/fit_iterations/*.png + overlay_progress_strip.png — geometry QA evidenceoutputs/motion_frames/*.png + motion_strip.png — motion QA evidencefinal_render.png, html_render.png — static renders; path audit artifacts when smoothness was a concernreferences/motion-personality.md to map words → timing scale, easing tokens, principle emphasis.references/reveal-patterns.md).motion_spec.md. Every later parameter must trace back to it.Follow the lowest-complexity-first workflow. Do not maximize pixel-fit by default; start with the simplest editable geometry that can explain the mark, escalating only when an overlay shows a structural mismatch.
Use the first level that matches the source well enough:
references/ribbon-fitting.md + scripts/fit_ribbon_centerline.py; its report also emits each crossing's arc fractions, which Phase 3's split draw-on needs.scripts/raster_logo_trace.py for measurement/starter masks). Trace output is a measurement aid, never automatically final art. Smooth or refit the trace before delivery.If a higher-complexity version improves only pixel antialiasing, keep the lower one. If a lower-complexity version has wrong endpoints, width profile, center, silhouette, negative space, or visibly stair-stepped edges, move up one level or refit with smooth curves. Prefer live SVG <text> for wordmarks unless exact letterforms are required. Decide the provisional complexity from the source itself and record the reason in motion_spec.md.
IoU is not allowed to hide bad vector craft. A smooth logo source must ship as smooth vector geometry. The final SVG fails geometry acceptance if any intended smooth edge visibly stair-steps, chatters, has pixel-grid orthogonal runs, contains noisy trace knots, or looks like a bitmap mask at 200-400% zoom, even when IoU is numerically high.
motion_spec.md.final_render.png or a dedicated zoom crop) before accepting. If the user would notice stair-stepping in a screenshot or browser zoom, the vector is not accepted.The SVG is a cast of actors, not just a picture. While fitting geometry, enforce:
<g>) per semantic part, with stable ids: #mark, #wordmark, #letter-a, #dot, #swoosh. Animation targets ids/classes — never structural selectors like path:nth-child(3).transform-box: fill-box; transform-origin: center (or a deliberate origin) — set via CSS in Phase 3, but verify each part's bounding box makes that origin sensible now.pathLength="1" so dash animation is stroke-dasharray: 1; stroke-dashoffset: 1 → 0 regardless of true length. Confirm the path's start point and direction match the intended draw direction; reverse the path if not.<text> span; a single <text> cannot cascade.viewBox; render to PNG; generate a cyan overlay over the source; save every overlay under outputs/fit_iterations/NN_name_overlay.png.scripts/svg_path_audit.py (noisy handles, tangent jumps, tiny alternating segments, and stair-step trace runs are failures even at high IoU).motion_spec.md.scripts/overlay_progress_strip.py (source + current-run overlays + final render only).Fitting loops rabbit-hole easily. Default budget: 10 geometry iterations per run (one iteration = one geometry change + render + overlay inspection). The loop stops when either one arrives first: accepted fit (smoothness, structural, and visual checks pass, with IoU pushed as high as practical) or 10 iterations attempted.
NN_name_overlay.png) and record metrics per iteration (IoU, src_only_px, render_only_px, boundary RMS / local residuals, audit warnings, smoothness verdict, visual verdict). Persist the fitting scripts and parameters under outputs/fit_work/ so any iteration can be resumed later — never leave them in throwaway temp paths.motion.css is reusable and only re-packaging (animate_svg_showcase.py) plus the Final Frame Contract re-check are needed.Choreograph against references/twelve-principles-for-logos.md. Minimum bar: every animation consciously applies Staging, Slow In/Slow Out, Timing, Follow Through, and Appeal; the rest as personality demands.
Anticipation : Action : Follow-through = 20% : 50% : 30%
For a 1500ms reveal: ~300ms anticipation, ~750ms main action, ~450ms settle. Stagger overlapping parts by 10–20% of the part's own duration; never let all parts start or stop on the same frame (the #1 mechanical-motion mistake). Heavier/larger parts move slower; the drag hierarchy (root → primary → secondary → tertiary detail) orders the cascade.
Pick the duration band from usage context, easing tokens and exaggeration level from the personality mapping, and the reveal pattern from the part inventory. references/reveal-patterns.md has annotated, principle-tagged patterns: draw-on, staggered assembly, scale-pop with overshoot, mask wipe, morph-from-primitive, letter cascade, plus idle loops and hover states. Define CSS custom properties (--p2m-duration, --p2m-ease-*) once and use them everywhere — brand consistency lives in tokens. Exception that overrides this rule: inside @keyframes, timing functions must be literal (see Implementation) — a token referenced there is silently dropped and the motion degrades to linear without any error.
references/html-delivery-template.md before building logo_motion.html.motion.css, or as a bespoke JS act/phase timeline when the choreography needs measured pivots, live sequencing, or per-letter layout. If using JS, preserve the same QA hooks described below.animation-fill-mode: both or forwards; otherwise deterministic ?t=<ms> frame capture and ?static=1 final-state checks are not reliable.animation-timing-function: var(--token) inside @keyframes does not resolve in Chromium: the declaration is silently dropped and the segment falls back to the animation's base timing function — usually linear, the exact mechanical look the principles forbid. Worse, multi-piece choreography whose pieces were paced by subdivided easings degrades to per-piece constant speeds with speed cliffs at every handoff (a measured 4.3× velocity discontinuity read as a "stutter"). Keep tokens for animation: shorthands (durations/delays resolve fine) and documentation, but write literal cubic-bezier(...) in every keyframe, with a comment naming the token. This failure is invisible in casual playback and in evenly-spaced frame strips — verify with the easing probe (Motion QA step 3).?t=<ms> maps 1:1 to the choreography and probes/captures stay trivial to reason about.scripts/animate_svg_showcase.py — it recreates the main SVG via JavaScript DOM calls, wraps motion CSS for reduced-motion safety, and adds a template shell with atomic motion studies, principles strip, replay button, slow-motion toggle, speed slider, ?t=<ms>, ?static=1, and window.__p2mReady.scripts/animate_svg_html.py remains a minimal fallback for QA or debugging; it is not the preferred final user-facing HTML because it lacks the atomic motions and tuners.logo.svg; atomic variants must reuse the same mark, not separate decorative redraws.width * 0.7, still capped by the viewport). This leaves breathing room around the animation and reduces clipping risk. QA screenshots may override the viewport/crop, but the user-facing template should keep the 0.7x presentation scale.prefers-reduced-motion: reduce the logo must appear immediately in its final static state.A wide masked draw stroke following a self-crossing centerline (∞ marks, script signatures, monograms) prematurely reveals the other branch wherever the paths cross — an "X" pops in before the pen draws it. When the centerline self-intersects, apply the split-fill recipe in references/reveal-patterns.md §1b: cut the fill into pieces between crossing passes (each with its own mask spine), use butt caps with dash pattern 1 1 (round caps make the visible tip lead the pen by half the stroke width — it stalls at every handoff, then the next piece pops in as a cap-radius disk), subdivide the global easing exactly (de Casteljau) so the combined pace equals the design, and bridge the paint-under-ink dead window at the later pass with a tip glint riding offset-path. The dash-math artifact table in the same reference prevents the t=0 cap-dot and tail-leak artifacts. Get the cut fractions from scripts/fit_ribbon_centerline.py's report (it emits each exclusion's arc fractions). Prove the fix with frames around each crossing pass plus the continuity sweep (Motion QA step 4).
The final HTML must follow the provided template pattern:
#logo-root, replayable by click/tap and by the Replay button.#logo-root, ?t=<ms>, ?static=1, and window.__p2mReady compatible with scripts/capture_motion_frames.py.scripts/capture_motion_frames.py (uses the ?t= hook for deterministic seeking): at minimum t=0, end of anticipation, mid-action, peak overshoot, settle, and final — plus every risk window: each crossing pass, piece handoff, and occluder entry/exit gets its own bracketing frames (a strip of evenly-spaced beats hides handoff defects entirely).scripts/probe_motion_continuity.py --probe: read computed values (stroke-dashoffset, offset-distance, …) at 2–3 seeked timestamps and compare against the designed curve. Values matching the LINEAR window fraction at every timestamp mean a keyframe timing function was silently dropped (see the literal-easing rule). The t=0 frame must also be checked for dash artifacts (cap ink-dot, tail leak).--ink-sweep: ink-pixel deltas every ~10ms across handoffs/crossings. A flatline followed by a jump is the stall+pop signature and fails the run. A single near-zero sample where the pen passes under already-painted ink is physical — bridge it perceptually (tip glint), don't leave it bare.final_render.png (the QA-verified static vector) in geometry, color, scale, and position. Use --compare-final for a cross-pipeline pixel-diff number, then run the decisive same-pipeline check: capture ?static=1 and ?t=<end> with the same tool, viewport, and DPR — these must match exactly (0 diff). Cross-pipeline residue (different renderer/DPR/resampling) is noise to confirm visually, not chase numerically. An animation that lands somewhere else than the verified logo fails the run.motion_strip.png as the motion analog of the overlay progress strip.# Phase 2 — measurement / starter trace (inspect & simplify its output; never final art by default)
python3 scripts/raster_logo_trace.py source.png --out outputs
# Phase 2 — render + cyan overlay + IoU metrics in one step (headless Chrome; set CHROME_BIN if needed)
python3 scripts/render_overlay.py logo.svg source.png \
--out outputs/fit_iterations/02_refined_overlay.png \
--render-out outputs/final_render.png --report outputs/fit_metrics.json
# Phase 2 — closed / self-intersecting variable-width ribbons (∞ marks, scripts):
# centerline scaffold + auto-recenter + source edge snap; report includes each
# exclusion's arc fractions (= split-cut parameters for Phase 3)
python3 scripts/fit_ribbon_centerline.py source.png --seeds seeds.json --out-dir outputs/ribbon_fit
# Phase 2 — Bezier smoothness audit before accepting complex paths
python3 scripts/svg_path_audit.py logo.svg --out-svg bezier_segments.svg --report bezier_audit.json
# Phase 2 — geometry QA strip
python3 scripts/overlay_progress_strip.py --source source.png --dir outputs/fit_iterations \
--pattern "*overlay*.png" --final-image outputs/final_render.png --out outputs/overlay_progress_strip.png
# Phase 3 — static HTML (intermediate check of JS DOM reconstruction)
python3 scripts/svg_to_js_html.py logo.svg --out logo_static.html --title "Logo"
# Phase 3 — animated showcase HTML deliverable
python3 scripts/animate_svg_showcase.py logo.svg --css motion.css --out logo_motion.html \
--title "Logo Motion" --duration-hint 1500
# Phase 3 — minimal fallback HTML (debug/QA only, not preferred final delivery)
python3 scripts/animate_svg_html.py logo.svg --css motion.css --out logo_motion_minimal.html \
--title "Logo Motion" --duration-hint 1500
# Phase 3 — deterministic frame capture + strip + final-frame diff (requires playwright)
python3 scripts/capture_motion_frames.py logo_motion.html \
--times 0,300,700,1000,1250,1500 --out outputs/motion_frames \
--strip outputs/motion_strip.png --compare-final outputs/final_render.png
# Phase 3 — easing probe: is the designed curve the one the browser runs? (requires playwright)
python3 scripts/probe_motion_continuity.py logo_motion.html \
--times 500,700,900 --probe "#draw-stroke:stroke-dashoffset,#pen-glint:offset-distance"
# Phase 3 — ink-delta continuity sweep across handoffs/crossings (requires playwright)
python3 scripts/probe_motion_continuity.py logo_motion.html --ink-sweep 850:1010:10
Environment notes: Pillow/numpy via a venv when system Python is externally managed (python3 -m venv .venv && .venv/bin/pip install pillow numpy); geometry rendering uses headless Chrome (CHROME_BIN if needed), while motion frame capture requires Playwright or equivalent deterministic browser screenshot tooling.
If Playwright is unavailable, use any real-browser screenshot tooling available in the environment with the same ?t=<ms> URLs; wall-clock screenshots of a running animation are not acceptable evidence (non-deterministic).
Completion requires evidence, not claims:
Geometry (inherited):
logo.svg exists, renders, and uses the lowest complexity that passes overlay QA and the smoothness gate; structural mismatches (center, scale, endpoints, width profile, spacing, negative space, silhouette) are absent; smooth marks pass visual zoom inspection and path audit when budgeted or when curves felt uneven.overlay_progress_strip.png shows source → current-run iterations → final render.outputs/fit_work/, and offer continued refinement. The Final Frame Contract applies to whatever geometry shipped.Structure (fusion):
pathLength="1"; the part inventory in motion_spec.md matches the SVG structure.Motion (new):
logo_motion.html follows the showcase template: main animation in #logo-root, atomic motion studies, replay/slow/speed controls, principles strip, dependency-free execution, prefers-reduced-motion, ?t= seeking, ?static=1, and window.__p2mReady.logo.svg.motion_spec.md; timeline follows the 20/50/30 shape (or documents why not); no two parts share identical start+end times unless intentional staging.motion_strip.png exists and has been inspected with multimodal vision; nothing clips mid-flight.?static=1 vs ?t=<end> exact). Loops are seam-checked.references/twelve-principles-for-logos.md — each Disney principle with logo-specific application and parameter rangesreferences/motion-personality.md — brand personality → timing scale, easing tokens, exaggeration bounds, principle emphasisreferences/reveal-patterns.md — choreography pattern library: reveals, idle loops, hover states, with timing tables and CSS skeletons; §1b split-fill draw-on for self-crossing marks + dash-math artifact tablereferences/html-delivery-template.md — required final HTML structure: main animation, atomic motions, tuners, principles strip, QA hooksreferences/ribbon-fitting.md — closed/self-intersecting variable-width ribbon fitting (centerline scaffold + recenter + source edge snap) and wordmark font matchingHardened from a production run (calligraphic ∞ + bead + serif wordmark) where the draw-on visibly stuttered at the second self-crossing. Three stacked causes were isolated and each produced a new rule or tool:
var() timing functions inside @keyframes silently degrade to linear in Chromium → literal-easing rule + easing probe (scripts/probe_motion_continuity.py --probe). This was the dominant cause: per-piece linear pacing created a 4.3× speed cliff at the piece handoff.--ink-sweep).Also new: split-fill recipe for self-crossing draw-ons with exact de Casteljau easing subdivision (reveal-patterns §1b), scripts/fit_ribbon_centerline.py + references/ribbon-fitting.md for closed variable-width ribbon geometry (its report emits the split-cut arc fractions Phase 3 needs), same-pipeline Final Frame Contract check (exact-0 expectation), and risk-window frame bracketing in Motion QA.