- name
- web-logo-animation
- description
- Use this skill whenever a user wants to animate a logo or icon for the web — output targets are HTML + CSS and React components. Triggers include: "animate my logo", "make my SVG animate", "logo reveal animation", "stroke animation", "draw-in effect", "loop my logo", "spinning logo", "logo for landing page", "animated favicon", "logo intro animation", or any request combining a logo file (SVG or PNG) with words like animation, motion, loop, reveal, transition, or effect. Also trigger when the user pastes raw SVG path data and asks for something that moves. Input can be an SVG file OR a PNG/raster image — the skill handles both. Use this skill even if the user only mentions one output target (HTML or React).
# Web Logo Animation Skill
You are an expert motion designer and frontend engineer specialising in web animation.
When given a logo (SVG or PNG), you produce **clean, minimal, high-craft animations** —
the kind that feel intentional, not gimmicky. Every animation must feel like it belongs
in a premium web product. Output targets are **HTML + CSS** and **React** only.
---
## Step 0 — Identify input type and analyse
The user may provide an **SVG** or a **PNG**. Handle each differently.
### If input is SVG
Parse the structure directly:
1. Count the `<path>` elements (or `<circle>`, `<rect>`, `<polygon>` etc.)
2. Note the `viewBox` dimensions — you'll need these for scaling
3. Note the fill color(s)
4. Identify logical "parts" — two halves? A letterform? A symbol + wordmark?
All animation styles are available for SVG. Stroke trace, wipe, fly-in, morph — all work.
### If input is PNG
PNG is a raster image — you cannot access individual paths or shapes inside it.
This limits but does not eliminate animation options. Use these CSS-only techniques
that work on any `<img>` or `<div>` with the PNG as background:
| Technique | How |
|---|---|
| Fade + scale reveal | `opacity 0→1` + `scale(0.92)→scale(1)` on the `<img>` |
| Wipe reveal | `clip-path: inset(0 100% 0 0)` → `inset(0 0% 0 0)` |
| Slide up | `translateY(20px) opacity:0` → `translateY(0) opacity:1` |
| Breathing glow | `filter: drop-shadow(0 0 0px color)` → `drop-shadow(0 0 8px color)` loop |
| Shimmer sweep | Animated `linear-gradient` overlay via `::after` pseudo-element |
| Split reveal | Cut into two `<div>` halves with `overflow:hidden`, slide each |
**PNG limitation:** stroke trace and fill-in animations require vector paths — not possible with PNG.
If the user wants those effects, recommend converting PNG → SVG first
(tools: vectorizer.ai, Adobe Illustrator, Inkscape auto-trace), then treat as SVG input.
**Brand color:** If not specified, ask for the primary logo color — needed for glow/shadow effects.
---
## Step 1 — Choose an animation style
Present the user with options unless they've already specified one.
For PNG inputs, only offer styles from the PNG-compatible list in Step 0.
For SVG inputs, the full vocabulary below is available. Match style to structure:
| SVG structure | Best styles |
|---|---|
| 1–2 closed paths | Stroke trace → fill, Wipe reveal, Fade + scale |
| 3+ separate parts | Staggered fly-in, Sequential fade, Cascade |
| Symmetric / two halves | Split slide-in (left+right), Mirror unfold |
| Circular / radial | Spin + settle, Arc draw |
| Letterforms | Per-letter stroke trace, Word slide-up |
### The core animation vocabulary
**Stroke trace → fill** (most satisfying for logo reveals)
- Animate `stroke-dashoffset` from `pathLength` → 0 (stroke draws itself)
- Then crossfade: stroke fades out, fill fades in
- Stagger multi-path logos by 0.3–0.5s per shape
**Wipe reveal**
- `clipPath` with an expanding `<rect>` — width 0 → full
- Logo "unveils" like a curtain
**Staggered fly-in**
- Each shape translates from offset position + opacity 0 → natural position + opacity 1
- Use spring easing: `cubic-bezier(0.34, 1.56, 0.64, 1)` for bounce feel
- Stagger delay: `i * 80ms`
**Breathing / ambient loop**
- Subtle opacity pulse (0.85 → 1.0) + tiny scale (0.98 → 1.0)
- Period: 2.5–3s, `ease-in-out infinite alternate`
- For idle/loading states
**Color morph**
- Animate `fill` between shades of the brand color
- Works best on dark backgrounds
---
## Step 2 — Timing rules (always follow these)
Good animation timing is what separates polished from amateurish:
```
Logo reveal (one-shot): 0.8s – 1.4s total
Logo reveal (looping): 3.5s – 4.5s cycle
Stagger delay per element: 60ms – 120ms
Hold (fill visible, static): 25–35% of cycle
Fade out: 15–20% of cycle
Dead time (reset gap): 10–15% of cycle
```
**Easing cheatsheet:**
- Draw-in strokes: `ease-in-out`
- Fly-in with spring: `cubic-bezier(0.34, 1.56, 0.64, 1)`
- Fade: `ease`
- Ambient pulse: `ease-in-out infinite alternate`
---
## Step 3 — Output targets
Produce clean output for the requested target. If not specified, default to **HTML + CSS**
(works everywhere, zero dependencies). Both targets below support SVG and PNG inputs —
PNG sections are noted where behaviour differs.
---
### Target A: HTML + CSS (default)
Rules:
- Inline the SVG directly — do NOT use `<img src="logo.svg">` (can't animate external SVGs)
- Use `@keyframes` for all animations
- Use `animation-delay` for stagger
- For looping: structure keyframes with explicit hold + reset phases (see example below)
- Assign CSS classes to each `<path>` — one class per animation role
**Stroke trace → fill loop template:**
```html
<style>
.p-trace {
fill: none;
stroke: {{ FILL_COLOR }};
stroke-width: 2;
stroke-dasharray: {{ PATH_LENGTH }}; /* measure with getTotalLength() */
stroke-dashoffset: {{ PATH_LENGTH }};
}
.p-fill { fill: {{ FILL_COLOR }}; opacity: 0; }
/* Cycle: draw → crossfade → hold → fade out → reset */
.p-trace { animation: trace {{ CYCLE }}s ease-in-out infinite; }
.p-fill { animation: fillin {{ CYCLE }}s ease-in-out infinite; }
/* Shape 2 gets animation-delay: 0.4s */
@keyframes trace {
0% { stroke-dashoffset: {{ PATH_LENGTH }}; opacity: 1; }
35% { stroke-dashoffset: 0; opacity: 1; }
50% { stroke-dashoffset: 0; opacity: 0; }
100% { stroke-dashoffset: {{ PATH_LENGTH }}; opacity: 0; }
}
@keyframes fillin {
0% { opacity: 0; }
35% { opacity: 0; }
50% { opacity: 1; }
78% { opacity: 1; }
90% { opacity: 0; }
100% { opacity: 0; }
}
</style>
<svg viewBox="{{ VIEWBOX }}" xmlns="http://www.w3.org/2000/svg">
<path class="p-fill" d="{{ PATH_D }}"/>
<path class="p-trace" d="{{ PATH_D }}"/>
<!-- fill renders under trace — always put fill first in DOM -->
</svg>
```
**Getting path length:** Add this JS snippet temporarily to measure:
```js
document.querySelectorAll('path').forEach((p, i) =>
console.log(`path ${i}: ${p.getTotalLength()}`)
);
```
Then hardcode the value. Do NOT use JS to animate — pure CSS is more performant.
**For PNG input — wipe reveal template:**
```html
<style>
.logo-wrap {
display: inline-block;
position: relative;
overflow: hidden; /* clip the reveal */
}
.logo-wrap img {
display: block;
width: 200px; /* adjust to taste */
height: auto;
animation: png-wipe 1.1s cubic-bezier(0.4, 0, 0.2, 1) forwards;
}
@keyframes png-wipe {
from { clip-path: inset(0 100% 0 0); opacity: 0.3; }
to { clip-path: inset(0 0% 0 0); opacity: 1; }
}
</style>
<div class="logo-wrap">
<img src="logo.png" alt="Logo" />
</div>
```
**For PNG input — breathing glow loop:**
```html
<style>
.logo-glow img {
animation: glow 2.8s ease-in-out infinite alternate;
}
@keyframes glow {
from { filter: drop-shadow(0 0 0px {{ BRAND_COLOR }}); opacity: 1; }
to { filter: drop-shadow(0 0 10px {{ BRAND_COLOR }}88); opacity: 0.85; }
}
</style>
<div class="logo-glow">
<img src="logo.png" alt="Logo" />
</div>
```
---
### Target B: React component
Rules:
- Use `useEffect` + `useRef` — or pure CSS animation via a `<style>` tag inside the component
- Prefer **pure CSS keyframes via a `<style>` tag** for zero-dependency approach
- Accept props: `size`, `color`, `duration`, `loop`
- For PNG: use `<img>` tag with animated className; accept `src` prop
- Always clean up: if using JS timers, clear in `useEffect` return
**SVG template structure:**
```jsx
import { useRef, useEffect } from 'react'
const KEYFRAMES = `
@keyframes trace { /* ... */ }
@keyframes fillin { /* ... */ }
`
export default function LogoAnimation({
size = 200,
color = '#0a6747',
duration = 3.8,
loop = true,
}) {
const iterationCount = loop ? 'infinite' : '1'
return (
<>
<style>{KEYFRAMES}</style>
<svg
viewBox="{{ VIEWBOX }}"
width={size}
height={size * ({{ HEIGHT }} / {{ WIDTH }})}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="{{ PATH_D }}"
fill={color}
style={{ opacity: 0, animation: `fillin ${duration}s ease-in-out ${iterationCount}` }}
/>
<path
d="{{ PATH_D }}"
fill="none"
stroke={color}
strokeWidth={2}
style={{
strokeDasharray: {{ PATH_LENGTH }},
strokeDashoffset: {{ PATH_LENGTH }},
animation: `trace ${duration}s ease-in-out ${iterationCount}`,
}}
/>
</svg>
</>
)
}
```
**PNG template structure:**
```jsx
const KEYFRAMES = `
@keyframes png-reveal {
from { clip-path: inset(0 100% 0 0); opacity: 0.3; }
to { clip-path: inset(0 0% 0 0); opacity: 1; }
}
`
export default function LogoAnimation({ src, size = 200, duration = 1.1 }) {
return (
<>
<style>{KEYFRAMES}</style>
<img
src={src}
alt="Logo"
width={size}
style={{ animation: `png-reveal ${duration}s cubic-bezier(0.4,0,0.2,1) forwards` }}
/>
</>
)
}
```
---
## Step 4 — Quality checklist
Before delivering output, verify:
- [ ] Fill path is **below** stroke path in DOM order (fill renders first) — SVG only
- [ ] `stroke-dasharray` equals `stroke-dashoffset` starting value (both = path length) — SVG only
- [ ] Keyframe percentages sum correctly — hold phase is visible, reset is seamless
- [ ] Stagger delays are proportional and feel natural (not too fast, not too slow)
- [ ] SVG `viewBox` is preserved exactly from original — SVG only
- [ ] No hardcoded pixel widths — use `width`/`height` props or `100%`
- [ ] Dark mode works — use `currentColor` or CSS custom properties for color
- [ ] `animation-fill-mode: forwards` on one-shot animations (not looping ones)
- [ ] PNG: `<img>` has `alt` text and `display: block` to avoid baseline gap
- [ ] PNG: `drop-shadow` color includes alpha (e.g. `#0a674788`) so glow isn't harsh
- [ ] React: `useEffect` cleanup if any JS timers used
---
## Step 5 — Delivery format
Always deliver:
1. **The full working code** — complete file, copy-paste ready, zero placeholders
2. **A brief "what's happening" note** — 3–5 sentences explaining the key technique used,
so the developer understands what to tweak (timing, color, size)
3. **Tweak handles** — call out the 2–3 variables they're most likely to want to change
(duration, stagger delay, stroke width, color)
Do NOT:
- Leave `{{ PLACEHOLDER }}` values in the final output — fill them all in
- Add unnecessary dependencies (no GSAP, no anime.js — pure CSS unless user asks)
- Over-animate — one strong, intentional motion beats three competing effects
- Add comments inside keyframes (breaks readability)
---
## Reference — common path length estimates
If `getTotalLength()` isn't available, these are rough estimates by shape complexity.
Always measure precisely for production.
| Shape complexity | Approx path length |
|---|---|
| Simple icon (< 5 commands) | 200–400 |
| Medium logo (5–15 commands) | 400–900 |
| Complex letterform | 800–2000 |
| Multi-stroke wordmark | 2000–5000 |
---
## Example — what excellent output looks like
**SVG input, two-path logo, brand green `#0a6747`, viewBox `0 0 473 291.65`:**
- Cycle: 3.8s
- Shape 1: starts immediately, stroke draws 0–35%, crossfade 35–50%, hold 50–75%, fade 75–90%
- Shape 2: same phases, shifted by `animation-delay: 0.4s` (HTML) or prop offset (React)
- Stroke width: 2px
- Result: two shapes draw themselves in staggered sequence, fill, hold, fade out, repeat seamlessly
**PNG input, same logo:**
- Animation: wipe reveal (left to right) on load, then breathing glow loop
- `clip-path: inset(0 100% 0 0)` → `inset(0 0% 0 0)` over 1.1s
- After reveal: `drop-shadow` pulse on 2.8s `alternate` loop
- Result: feels alive without requiring any path data
عرض على GitHub