mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
feat: integrate animation token system into UI workflow
Fixes critical P0 issue where animation-tokens.json wasn't consumed by the generate command, breaking the value chain. The animation extraction system now properly flows through: animation-extract → tokens → generate → prototypes. Changes: - Added animation-extract command with hybrid CSS extraction + interactive fallback strategy - Updated generate.md to load and inject animation tokens into prototypes - Added CSS animation support (custom properties, keyframes, interactions, accessibility) - Integrated animation extraction into explore-auto and imitate-auto workflows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
677
.claude/commands/workflow/ui-design/animation-extract.md
Normal file
677
.claude/commands/workflow/ui-design/animation-extract.md
Normal file
@@ -0,0 +1,677 @@
|
|||||||
|
---
|
||||||
|
name: animation-extract
|
||||||
|
description: Extract animation and transition patterns from URLs, CSS, or interactive questioning
|
||||||
|
argument-hint: "[--base-path <path>] [--session <id>] [--urls "<list>"] [--mode <auto|interactive>] [--focus "<types>"]"
|
||||||
|
allowed-tools: TodoWrite(*), Read(*), Write(*), Glob(*), Bash(*), Task(ui-design-agent), mcp__chrome-devtools__navigate_page(*), mcp__chrome-devtools__evaluate_script(*)
|
||||||
|
---
|
||||||
|
|
||||||
|
# Animation Extraction Command
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Extract animation and transition patterns from web pages using CSS extraction, visual analysis, or interactive questioning. This command generates production-ready animation tokens and guidelines that integrate with design systems.
|
||||||
|
|
||||||
|
**Strategy**: Hybrid Extraction with Interactive Fallback
|
||||||
|
|
||||||
|
- **Auto Mode (Priority 1)**: Extract from CSS via Chrome DevTools when URLs provided
|
||||||
|
- **Visual Mode (Priority 2)**: Analyze screenshots for motion cues (blur, position changes)
|
||||||
|
- **Interactive Mode (Priority 3)**: Guided questioning when extraction insufficient
|
||||||
|
- **Output**: `animation-tokens.json` + `animation-guide.md`
|
||||||
|
|
||||||
|
## Phase 0: Setup & Input Validation
|
||||||
|
|
||||||
|
### Step 1: Detect Input Mode & Base Path
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Detect input source
|
||||||
|
# Priority: --urls → url mode | --mode interactive → question mode
|
||||||
|
|
||||||
|
# Parse URLs if provided (format: "target:url,target:url,...")
|
||||||
|
IF --urls:
|
||||||
|
url_list = []
|
||||||
|
FOR pair IN split(--urls, ","):
|
||||||
|
IF ":" IN pair:
|
||||||
|
target, url = pair.split(":", 1)
|
||||||
|
url_list.append({target: target.strip(), url: url.strip()})
|
||||||
|
ELSE:
|
||||||
|
url_list.append({target: "page", url: pair.strip()})
|
||||||
|
|
||||||
|
has_urls = true
|
||||||
|
primary_url = url_list[0].url
|
||||||
|
ELSE:
|
||||||
|
has_urls = false
|
||||||
|
|
||||||
|
# Determine extraction mode
|
||||||
|
extraction_mode = --mode OR (has_urls ? "auto" : "interactive")
|
||||||
|
|
||||||
|
# Parse animation focus (if provided)
|
||||||
|
IF --focus:
|
||||||
|
focus_types = split(--focus, ",") # e.g., "transitions,hover,scroll"
|
||||||
|
ELSE:
|
||||||
|
focus_types = ["all"] # Extract all animation types
|
||||||
|
|
||||||
|
# Determine base path
|
||||||
|
bash(find .workflow -type d -name "design-*" | head -1) # Auto-detect
|
||||||
|
# OR use --base-path / --session parameters
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Load Design Tokens Context
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Load existing design tokens for duration/easing alignment
|
||||||
|
IF exists({base_path}/style-extraction/style-1/design-tokens.json):
|
||||||
|
design_tokens = Read({base_path}/style-extraction/style-1/design-tokens.json)
|
||||||
|
has_design_context = true
|
||||||
|
ELSE:
|
||||||
|
has_design_context = false
|
||||||
|
WARN: "⚠️ No design tokens found - animation tokens will use standalone values"
|
||||||
|
|
||||||
|
# Create output directory
|
||||||
|
bash(mkdir -p {base_path}/animation-extraction)
|
||||||
|
bash(mkdir -p {base_path}/.intermediates/animation-analysis)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Phase 0 Output**: `extraction_mode`, `base_path`, `has_urls`, `url_list[]`, `focus_types[]`, `has_design_context`
|
||||||
|
|
||||||
|
## Phase 1: CSS Animation Extraction (Auto Mode - URL Required)
|
||||||
|
|
||||||
|
### Step 1: Check Extraction Mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# extraction_mode == "interactive" → skip to Phase 2
|
||||||
|
# extraction_mode == "auto" AND has_urls → execute this phase
|
||||||
|
```
|
||||||
|
|
||||||
|
**If interactive mode**: Skip to Phase 2
|
||||||
|
|
||||||
|
### Step 2: Extract Computed Animations (Auto-Trigger)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# AUTO-TRIGGER: If URLs are available, automatically extract CSS animations/transitions
|
||||||
|
|
||||||
|
IF has_urls AND mcp_chrome_devtools_available:
|
||||||
|
REPORT: "🔍 Auto-triggering URL mode: Extracting CSS animations and transitions"
|
||||||
|
|
||||||
|
# Read extraction script
|
||||||
|
script_content = Read(~/.claude/scripts/extract-animations.js)
|
||||||
|
|
||||||
|
# For each URL:
|
||||||
|
FOR url_info IN url_list:
|
||||||
|
target = url_info.target
|
||||||
|
url = url_info.url
|
||||||
|
|
||||||
|
REPORT: " Processing: {target} ({url})"
|
||||||
|
|
||||||
|
# Open page in Chrome DevTools
|
||||||
|
mcp__chrome-devtools__navigate_page(url=url)
|
||||||
|
|
||||||
|
# Wait for page to fully load and animations to initialize
|
||||||
|
bash(sleep 2)
|
||||||
|
|
||||||
|
# Execute extraction script
|
||||||
|
result = mcp__chrome-devtools__evaluate_script(function=script_content)
|
||||||
|
|
||||||
|
# Save raw animation data
|
||||||
|
Write({base_path}/.intermediates/animation-analysis/animations-{target}.json, result)
|
||||||
|
|
||||||
|
REPORT: " ✅ Extracted: {result.summary.total_animations} animations, {result.summary.total_transitions} transitions"
|
||||||
|
|
||||||
|
animations_extracted = true
|
||||||
|
REPORT: " ✅ CSS animation extraction complete"
|
||||||
|
ELSE IF has_urls AND NOT mcp_chrome_devtools_available:
|
||||||
|
animations_extracted = false
|
||||||
|
REPORT: "⚠️ Chrome DevTools MCP not available"
|
||||||
|
REPORT: " Falling back to interactive mode for animation guidance"
|
||||||
|
ELSE:
|
||||||
|
animations_extracted = false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Extraction Script Reference**: `~/.claude/scripts/extract-animations.js`
|
||||||
|
|
||||||
|
**Usage**: Read the script file and use content directly in `mcp__chrome-devtools__evaluate_script()`
|
||||||
|
|
||||||
|
**Script returns**:
|
||||||
|
- `metadata`: Extraction timestamp, URL, method
|
||||||
|
- `transitions`: Array of transition definitions (property, duration, easing, delay)
|
||||||
|
- `animations`: Array of keyframe animations (name, duration, easing, keyframes)
|
||||||
|
- `transforms`: Common transform patterns
|
||||||
|
- `summary`: Statistics (total_animations, total_transitions, unique_easings)
|
||||||
|
|
||||||
|
**Benefits**:
|
||||||
|
- ✅ Real animation values from production sites
|
||||||
|
- ✅ Captures all CSS transitions and @keyframes rules
|
||||||
|
- ✅ Identifies common easing functions and durations
|
||||||
|
- ✅ Maps animations to element selectors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Phase 1 Output**: `animations-{target}.json` (intermediate files)
|
||||||
|
|
||||||
|
## Phase 2: Interactive Animation Specification (Interactive/Fallback Mode)
|
||||||
|
|
||||||
|
### Step 1: Check if Extraction Sufficient
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If animations extracted from CSS, check coverage
|
||||||
|
IF animations_extracted:
|
||||||
|
total_animations = sum([data.summary.total_animations for data in all_extracted])
|
||||||
|
total_transitions = sum([data.summary.total_transitions for data in all_extracted])
|
||||||
|
|
||||||
|
# If sufficient data found, skip interactive mode
|
||||||
|
IF total_animations >= 3 OR total_transitions >= 5:
|
||||||
|
REPORT: "✅ Sufficient animation data extracted from CSS"
|
||||||
|
SKIP to Phase 3
|
||||||
|
ELSE:
|
||||||
|
REPORT: "⚠️ Limited animation data found - launching interactive mode"
|
||||||
|
extraction_insufficient = true
|
||||||
|
ELSE:
|
||||||
|
extraction_insufficient = true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Interactive Question Workflow (Agent)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# If extraction failed or insufficient, use interactive questioning
|
||||||
|
IF extraction_insufficient OR extraction_mode == "interactive":
|
||||||
|
REPORT: "🤔 Launching interactive animation specification mode"
|
||||||
|
|
||||||
|
# Launch ui-design-agent for interactive questioning
|
||||||
|
Task(ui-design-agent): `
|
||||||
|
[ANIMATION_SPECIFICATION_TASK]
|
||||||
|
Guide user through animation design decisions via structured questions
|
||||||
|
|
||||||
|
SESSION: {session_id} | MODE: interactive | BASE_PATH: {base_path}
|
||||||
|
|
||||||
|
## Context
|
||||||
|
- Design tokens available: {has_design_context}
|
||||||
|
- Focus areas: {focus_types}
|
||||||
|
- Extracted data: {animations_extracted ? "Partial CSS data available" : "No CSS data"}
|
||||||
|
|
||||||
|
## Interactive Workflow
|
||||||
|
|
||||||
|
For each animation category, ASK user and WAIT for response:
|
||||||
|
|
||||||
|
### 1. Transition Duration Scale
|
||||||
|
QUESTION: "What timing scale feels right for your design?"
|
||||||
|
OPTIONS:
|
||||||
|
- "Fast & Snappy" (100-200ms transitions)
|
||||||
|
- "Balanced" (200-400ms transitions)
|
||||||
|
- "Smooth & Deliberate" (400-600ms transitions)
|
||||||
|
- "Custom" (specify values)
|
||||||
|
|
||||||
|
### 2. Easing Philosophy
|
||||||
|
QUESTION: "What easing style matches your brand?"
|
||||||
|
OPTIONS:
|
||||||
|
- "Linear" (constant speed, technical feel)
|
||||||
|
- "Ease-Out" (fast start, natural feel)
|
||||||
|
- "Ease-In-Out" (balanced, polished feel)
|
||||||
|
- "Spring/Bounce" (playful, modern feel)
|
||||||
|
- "Custom" (specify cubic-bezier)
|
||||||
|
|
||||||
|
### 3. Common Interactions (Ask for each)
|
||||||
|
FOR interaction IN ["button-hover", "link-hover", "card-hover", "modal-open", "dropdown-toggle"]:
|
||||||
|
QUESTION: "How should {interaction} animate?"
|
||||||
|
OPTIONS:
|
||||||
|
- "Subtle" (color/opacity change only)
|
||||||
|
- "Lift" (scale + shadow increase)
|
||||||
|
- "Slide" (transform translateY)
|
||||||
|
- "Fade" (opacity transition)
|
||||||
|
- "None" (no animation)
|
||||||
|
- "Custom" (describe behavior)
|
||||||
|
|
||||||
|
### 4. Page Transitions
|
||||||
|
QUESTION: "Should page/route changes have animations?"
|
||||||
|
IF YES:
|
||||||
|
ASK: "What style?"
|
||||||
|
OPTIONS:
|
||||||
|
- "Fade" (crossfade between views)
|
||||||
|
- "Slide" (swipe left/right)
|
||||||
|
- "Zoom" (scale in/out)
|
||||||
|
- "None"
|
||||||
|
|
||||||
|
### 5. Loading States
|
||||||
|
QUESTION: "What loading animation style?"
|
||||||
|
OPTIONS:
|
||||||
|
- "Spinner" (rotating circle)
|
||||||
|
- "Pulse" (opacity pulse)
|
||||||
|
- "Skeleton" (shimmer effect)
|
||||||
|
- "Progress Bar" (linear fill)
|
||||||
|
- "Custom" (describe)
|
||||||
|
|
||||||
|
### 6. Micro-interactions
|
||||||
|
QUESTION: "Should form inputs have micro-interactions?"
|
||||||
|
IF YES:
|
||||||
|
ASK: "What interactions?"
|
||||||
|
OPTIONS:
|
||||||
|
- "Focus state animation" (border/shadow transition)
|
||||||
|
- "Error shake" (horizontal shake on error)
|
||||||
|
- "Success check" (checkmark animation)
|
||||||
|
- "All of the above"
|
||||||
|
|
||||||
|
### 7. Scroll Animations
|
||||||
|
QUESTION: "Should elements animate on scroll?"
|
||||||
|
IF YES:
|
||||||
|
ASK: "What scroll animation style?"
|
||||||
|
OPTIONS:
|
||||||
|
- "Fade In" (opacity 0→1)
|
||||||
|
- "Slide Up" (translateY + fade)
|
||||||
|
- "Scale In" (scale 0.9→1 + fade)
|
||||||
|
- "Stagger" (sequential delays)
|
||||||
|
- "None"
|
||||||
|
|
||||||
|
## Output Generation
|
||||||
|
|
||||||
|
Based on user responses, generate structured data:
|
||||||
|
|
||||||
|
1. Create animation-specification.json with user choices:
|
||||||
|
- timing_scale (fast/balanced/slow/custom)
|
||||||
|
- easing_philosophy (linear/ease-out/ease-in-out/spring)
|
||||||
|
- interactions: {interaction_name: {type, properties, timing}}
|
||||||
|
- page_transitions: {enabled, style, duration}
|
||||||
|
- loading_animations: {style, duration}
|
||||||
|
- scroll_animations: {enabled, style, stagger_delay}
|
||||||
|
|
||||||
|
2. Write to {base_path}/.intermediates/animation-analysis/animation-specification.json
|
||||||
|
|
||||||
|
## Critical Requirements
|
||||||
|
- ✅ Use Write() tool immediately for specification file
|
||||||
|
- ✅ Wait for user response after EACH question before proceeding
|
||||||
|
- ✅ Validate responses and ask for clarification if needed
|
||||||
|
- ✅ Provide sensible defaults if user skips questions
|
||||||
|
- ❌ NO external research or MCP calls
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Phase 2 Output**: `animation-specification.json` (user preferences)
|
||||||
|
|
||||||
|
## Phase 3: Animation Token Synthesis (Agent)
|
||||||
|
|
||||||
|
**Executor**: `Task(ui-design-agent)` for token generation
|
||||||
|
|
||||||
|
### Step 1: Load All Input Sources
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Gather all available animation data
|
||||||
|
extracted_animations = []
|
||||||
|
IF animations_extracted:
|
||||||
|
FOR target IN target_list:
|
||||||
|
IF exists({base_path}/.intermediates/animation-analysis/animations-{target}.json):
|
||||||
|
extracted_animations.append(Read(file))
|
||||||
|
|
||||||
|
user_specification = null
|
||||||
|
IF exists({base_path}/.intermediates/animation-analysis/animation-specification.json):
|
||||||
|
user_specification = Read(file)
|
||||||
|
|
||||||
|
design_tokens = null
|
||||||
|
IF has_design_context:
|
||||||
|
design_tokens = Read({base_path}/style-extraction/style-1/design-tokens.json)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Launch Token Generation Task
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Task(ui-design-agent): `
|
||||||
|
[ANIMATION_TOKEN_GENERATION_TASK]
|
||||||
|
Synthesize all animation data into production-ready animation tokens
|
||||||
|
|
||||||
|
SESSION: {session_id} | BASE_PATH: {base_path}
|
||||||
|
|
||||||
|
## Input Sources
|
||||||
|
1. Extracted CSS Animations: {JSON.stringify(extracted_animations) OR "None"}
|
||||||
|
2. User Specification: {JSON.stringify(user_specification) OR "None"}
|
||||||
|
3. Design Tokens Context: {JSON.stringify(design_tokens) OR "None"}
|
||||||
|
|
||||||
|
## Synthesis Rules
|
||||||
|
|
||||||
|
### Priority System
|
||||||
|
1. User specification (highest priority)
|
||||||
|
2. Extracted CSS values (medium priority)
|
||||||
|
3. Industry best practices (fallback)
|
||||||
|
|
||||||
|
### Duration Normalization
|
||||||
|
- Analyze all extracted durations
|
||||||
|
- Cluster into 3-5 semantic scales: instant, fast, normal, slow, very-slow
|
||||||
|
- Align with design token spacing scale if available
|
||||||
|
|
||||||
|
### Easing Standardization
|
||||||
|
- Identify common easing functions from extracted data
|
||||||
|
- Map to semantic names: linear, ease-in, ease-out, ease-in-out, spring
|
||||||
|
- Convert all cubic-bezier values to standard format
|
||||||
|
|
||||||
|
### Animation Categorization
|
||||||
|
Organize into:
|
||||||
|
- transitions: Property-specific transitions (color, transform, opacity)
|
||||||
|
- keyframe_animations: Named @keyframe animations
|
||||||
|
- interactions: Interaction-specific presets (hover, focus, active)
|
||||||
|
- micro_interactions: Small feedback animations
|
||||||
|
- page_transitions: Route/view change animations
|
||||||
|
- scroll_animations: Scroll-triggered animations
|
||||||
|
|
||||||
|
## Generate Files
|
||||||
|
|
||||||
|
### 1. animation-tokens.json
|
||||||
|
Complete animation token structure:
|
||||||
|
|
||||||
|
{
|
||||||
|
"duration": {
|
||||||
|
"instant": "0ms",
|
||||||
|
"fast": "150ms",
|
||||||
|
"normal": "300ms",
|
||||||
|
"slow": "500ms",
|
||||||
|
"very-slow": "800ms"
|
||||||
|
},
|
||||||
|
"easing": {
|
||||||
|
"linear": "linear",
|
||||||
|
"ease-in": "cubic-bezier(0.4, 0, 1, 1)",
|
||||||
|
"ease-out": "cubic-bezier(0, 0, 0.2, 1)",
|
||||||
|
"ease-in-out": "cubic-bezier(0.4, 0, 0.2, 1)",
|
||||||
|
"spring": "cubic-bezier(0.34, 1.56, 0.64, 1)"
|
||||||
|
},
|
||||||
|
"transitions": {
|
||||||
|
"color": {
|
||||||
|
"property": "color, background-color, border-color",
|
||||||
|
"duration": "var(--duration-fast)",
|
||||||
|
"easing": "var(--easing-ease-out)"
|
||||||
|
},
|
||||||
|
"transform": {
|
||||||
|
"property": "transform",
|
||||||
|
"duration": "var(--duration-normal)",
|
||||||
|
"easing": "var(--easing-ease-out)"
|
||||||
|
},
|
||||||
|
"opacity": {
|
||||||
|
"property": "opacity",
|
||||||
|
"duration": "var(--duration-normal)",
|
||||||
|
"easing": "var(--easing-ease-in-out)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"keyframes": {
|
||||||
|
"fadeIn": {
|
||||||
|
"0%": {"opacity": "0"},
|
||||||
|
"100%": {"opacity": "1"}
|
||||||
|
},
|
||||||
|
"slideInUp": {
|
||||||
|
"0%": {"transform": "translateY(20px)", "opacity": "0"},
|
||||||
|
"100%": {"transform": "translateY(0)", "opacity": "1"}
|
||||||
|
},
|
||||||
|
"pulse": {
|
||||||
|
"0%, 100%": {"opacity": "1"},
|
||||||
|
"50%": {"opacity": "0.7"}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"interactions": {
|
||||||
|
"button-hover": {
|
||||||
|
"properties": ["background-color", "transform"],
|
||||||
|
"duration": "var(--duration-fast)",
|
||||||
|
"easing": "var(--easing-ease-out)",
|
||||||
|
"transform": "scale(1.02)"
|
||||||
|
},
|
||||||
|
"card-hover": {
|
||||||
|
"properties": ["box-shadow", "transform"],
|
||||||
|
"duration": "var(--duration-normal)",
|
||||||
|
"easing": "var(--easing-ease-out)",
|
||||||
|
"transform": "translateY(-4px)"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"page_transitions": {
|
||||||
|
"fade": {
|
||||||
|
"duration": "var(--duration-normal)",
|
||||||
|
"enter": "fadeIn",
|
||||||
|
"exit": "fadeOut"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scroll_animations": {
|
||||||
|
"default": {
|
||||||
|
"animation": "fadeInUp",
|
||||||
|
"duration": "var(--duration-slow)",
|
||||||
|
"easing": "var(--easing-ease-out)",
|
||||||
|
"threshold": "0.1",
|
||||||
|
"stagger_delay": "100ms"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
### 2. animation-guide.md
|
||||||
|
Comprehensive usage guide:
|
||||||
|
- Animation philosophy and rationale
|
||||||
|
- Duration scale explanation
|
||||||
|
- Easing function usage guidelines
|
||||||
|
- Interaction animation patterns
|
||||||
|
- Implementation examples (CSS and JS)
|
||||||
|
- Accessibility considerations (prefers-reduced-motion)
|
||||||
|
- Performance best practices
|
||||||
|
|
||||||
|
## Critical Requirements
|
||||||
|
- ✅ Use Write() tool immediately for both files
|
||||||
|
- ✅ Ensure all tokens use CSS Custom Property format: var(--duration-fast)
|
||||||
|
- ✅ Include prefers-reduced-motion media query guidance
|
||||||
|
- ✅ Validate all cubic-bezier values are valid
|
||||||
|
- ❌ NO external research or MCP calls
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Phase 3 Output**: `animation-tokens.json` + `animation-guide.md`
|
||||||
|
|
||||||
|
## Phase 4: Verify Output
|
||||||
|
|
||||||
|
### Step 1: Check Files Created
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Verify animation tokens created
|
||||||
|
bash(test -f {base_path}/animation-extraction/animation-tokens.json && echo "exists")
|
||||||
|
bash(test -f {base_path}/animation-extraction/animation-guide.md && echo "exists")
|
||||||
|
|
||||||
|
# Validate structure
|
||||||
|
bash(cat {base_path}/animation-extraction/animation-tokens.json | grep -q "duration" && echo "valid")
|
||||||
|
bash(cat {base_path}/animation-extraction/animation-tokens.json | grep -q "easing" && echo "valid")
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2: Verify File Sizes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
bash(ls -lh {base_path}/animation-extraction/)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**: 2 files verified (animation-tokens.json, animation-guide.md)
|
||||||
|
|
||||||
|
## Completion
|
||||||
|
|
||||||
|
### Todo Update
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
TodoWrite({todos: [
|
||||||
|
{content: "Setup and input validation", status: "completed", activeForm: "Validating inputs"},
|
||||||
|
{content: "CSS animation extraction (auto mode)", status: "completed", activeForm: "Extracting from CSS"},
|
||||||
|
{content: "Interactive specification (fallback)", status: "completed", activeForm: "Collecting user input"},
|
||||||
|
{content: "Animation token synthesis (agent)", status: "completed", activeForm: "Generating tokens"},
|
||||||
|
{content: "Verify output files", status: "completed", activeForm: "Verifying files"}
|
||||||
|
]});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Output Message
|
||||||
|
|
||||||
|
```
|
||||||
|
✅ Animation extraction complete!
|
||||||
|
|
||||||
|
Configuration:
|
||||||
|
- Session: {session_id}
|
||||||
|
- Extraction Mode: {extraction_mode} (auto/interactive)
|
||||||
|
- Input Sources:
|
||||||
|
{IF animations_extracted:
|
||||||
|
- ✅ CSS extracted from {len(url_list)} URL(s)
|
||||||
|
}
|
||||||
|
{IF user_specification:
|
||||||
|
- ✅ User specification via interactive mode
|
||||||
|
}
|
||||||
|
{IF has_design_context:
|
||||||
|
- ✅ Aligned with existing design tokens
|
||||||
|
}
|
||||||
|
|
||||||
|
Generated Files:
|
||||||
|
{base_path}/animation-extraction/
|
||||||
|
├── animation-tokens.json # Production-ready animation tokens
|
||||||
|
└── animation-guide.md # Usage guidelines and examples
|
||||||
|
|
||||||
|
{IF animations_extracted:
|
||||||
|
Intermediate Analysis:
|
||||||
|
{base_path}/.intermediates/animation-analysis/
|
||||||
|
├── animations-*.json # Extracted CSS data ({len(url_list)} files)
|
||||||
|
}
|
||||||
|
{IF user_specification:
|
||||||
|
└── animation-specification.json # User preferences
|
||||||
|
}
|
||||||
|
|
||||||
|
Extracted Data Summary:
|
||||||
|
- Duration scales: {duration_count} values
|
||||||
|
- Easing functions: {easing_count} types
|
||||||
|
- Interaction presets: {interaction_count} patterns
|
||||||
|
- Keyframe animations: {keyframe_count} animations
|
||||||
|
|
||||||
|
Next: Animation tokens ready for integration
|
||||||
|
• style-extract/layout-extract can reference animation tokens
|
||||||
|
• generate command will include animation CSS
|
||||||
|
• Tokens use var() format for easy customization
|
||||||
|
```
|
||||||
|
|
||||||
|
## Simple Bash Commands
|
||||||
|
|
||||||
|
### Path Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Find design directory
|
||||||
|
bash(find .workflow -type d -name "design-*" | head -1)
|
||||||
|
|
||||||
|
# Create output directories
|
||||||
|
bash(mkdir -p {base_path}/animation-extraction)
|
||||||
|
bash(mkdir -p {base_path}/.intermediates/animation-analysis)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Check if already extracted
|
||||||
|
bash(test -f {base_path}/animation-extraction/animation-tokens.json && echo "exists")
|
||||||
|
|
||||||
|
# Validate JSON structure
|
||||||
|
bash(cat {base_path}/animation-extraction/animation-tokens.json | grep -q "duration" && echo "valid")
|
||||||
|
|
||||||
|
# Count animation types
|
||||||
|
bash(cat animation-tokens.json | grep -c "\"keyframes\":")
|
||||||
|
```
|
||||||
|
|
||||||
|
### File Operations
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Load design tokens context
|
||||||
|
bash(test -f {base_path}/style-extraction/style-1/design-tokens.json && cat it)
|
||||||
|
|
||||||
|
# Verify output
|
||||||
|
bash(ls {base_path}/animation-extraction/)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
{base_path}/
|
||||||
|
├── .intermediates/ # Intermediate analysis files
|
||||||
|
│ └── animation-analysis/
|
||||||
|
│ ├── animations-{target}.json # Extracted CSS (auto mode)
|
||||||
|
│ └── animation-specification.json # User input (interactive mode)
|
||||||
|
└── animation-extraction/ # Final animation tokens
|
||||||
|
├── animation-tokens.json # Production-ready animation tokens
|
||||||
|
└── animation-guide.md # Usage guide and examples
|
||||||
|
```
|
||||||
|
|
||||||
|
## animation-tokens.json Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"duration": {
|
||||||
|
"instant": "0ms",
|
||||||
|
"fast": "150ms",
|
||||||
|
"normal": "300ms",
|
||||||
|
"slow": "500ms",
|
||||||
|
"very-slow": "800ms"
|
||||||
|
},
|
||||||
|
"easing": {
|
||||||
|
"linear": "linear",
|
||||||
|
"ease-in": "cubic-bezier(0.4, 0, 1, 1)",
|
||||||
|
"ease-out": "cubic-bezier(0, 0, 0.2, 1)",
|
||||||
|
"ease-in-out": "cubic-bezier(0.4, 0, 0.2, 1)",
|
||||||
|
"spring": "cubic-bezier(0.34, 1.56, 0.64, 1)"
|
||||||
|
},
|
||||||
|
"transitions": {
|
||||||
|
"color": {"property": "...", "duration": "var(--duration-fast)", "easing": "..."},
|
||||||
|
"transform": {"property": "...", "duration": "...", "easing": "..."}
|
||||||
|
},
|
||||||
|
"keyframes": {
|
||||||
|
"fadeIn": {"0%": {...}, "100%": {...}},
|
||||||
|
"slideInUp": {...}
|
||||||
|
},
|
||||||
|
"interactions": {
|
||||||
|
"button-hover": {"properties": [...], "duration": "...", "transform": "..."},
|
||||||
|
"card-hover": {...}
|
||||||
|
},
|
||||||
|
"page_transitions": {...},
|
||||||
|
"scroll_animations": {...}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Requirements**: CSS var() format, valid cubic-bezier values, prefers-reduced-motion support
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
### Common Errors
|
||||||
|
|
||||||
|
```
|
||||||
|
ERROR: No URL or interactive mode specified
|
||||||
|
→ Provide --urls for auto mode or use --mode interactive
|
||||||
|
|
||||||
|
ERROR: Chrome DevTools unavailable
|
||||||
|
→ Automatically falls back to interactive mode
|
||||||
|
|
||||||
|
ERROR: Insufficient animation data extracted
|
||||||
|
→ Launches interactive mode for supplemental input
|
||||||
|
|
||||||
|
ERROR: Invalid cubic-bezier values
|
||||||
|
→ Validates and corrects to nearest standard easing
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recovery Strategies
|
||||||
|
|
||||||
|
- **CSS extraction failure**: Falls back to interactive mode
|
||||||
|
- **Partial extraction**: Supplements with interactive questioning
|
||||||
|
- **Invalid data**: Validates and uses fallback values
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
- **Auto-Trigger CSS Extraction** - Automatically extracts animations when --urls provided
|
||||||
|
- **Hybrid Strategy** - Combines CSS extraction with interactive specification
|
||||||
|
- **Intelligent Fallback** - Gracefully handles extraction failures
|
||||||
|
- **Context-Aware** - Aligns with existing design tokens
|
||||||
|
- **Production-Ready** - CSS var() format, accessibility support
|
||||||
|
- **Comprehensive Coverage** - Transitions, keyframes, interactions, scroll animations
|
||||||
|
- **Agent-Driven** - Autonomous token generation with ui-design-agent
|
||||||
|
|
||||||
|
## Integration
|
||||||
|
|
||||||
|
**Workflow Position**: Between style extraction and layout extraction (or parallel)
|
||||||
|
|
||||||
|
**New Workflow**:
|
||||||
|
1. `/workflow:ui-design:style-extract` → `design-tokens.json` + `style-guide.md`
|
||||||
|
2. **`/workflow:ui-design:animation-extract`** → `animation-tokens.json` + `animation-guide.md` (NEW)
|
||||||
|
3. `/workflow:ui-design:layout-extract` → `layout-templates.json`
|
||||||
|
4. `/workflow:ui-design:generate`:
|
||||||
|
- Reads: design-tokens.json + animation-tokens.json + layout-templates.json
|
||||||
|
- Generates: Prototypes with animation CSS included
|
||||||
|
|
||||||
|
**Input**: URLs (auto mode) or interactive questioning
|
||||||
|
**Output**: `animation-tokens.json` + `animation-guide.md`
|
||||||
|
**Next**: `/workflow:ui-design:layout-extract` OR `/workflow:ui-design:generate`
|
||||||
|
|
||||||
|
**Note**: This command extracts motion design patterns (animations, transitions) to complement visual style tokens. Can run in parallel with layout-extract.
|
||||||
@@ -20,10 +20,11 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*), Glob(*), Write(*
|
|||||||
1. User triggers: `/workflow:ui-design:explore-auto [params]`
|
1. User triggers: `/workflow:ui-design:explore-auto [params]`
|
||||||
2. Phase 0c: Target confirmation → User confirms → **IMMEDIATELY triggers Phase 1**
|
2. Phase 0c: Target confirmation → User confirms → **IMMEDIATELY triggers Phase 1**
|
||||||
3. Phase 1 (style-extract) → **WAIT for completion** → Auto-continues
|
3. Phase 1 (style-extract) → **WAIT for completion** → Auto-continues
|
||||||
4. Phase 2 (layout-extract) → **WAIT for completion** → Auto-continues
|
4. Phase 2.3 (animation-extract, optional) → **WAIT for completion** → Auto-continues
|
||||||
5. **Phase 3 (ui-assembly)** → **WAIT for completion** → Auto-continues
|
5. Phase 2.5 (layout-extract) → **WAIT for completion** → Auto-continues
|
||||||
6. Phase 4 (design-update) → **WAIT for completion** → Auto-continues
|
6. **Phase 3 (ui-assembly)** → **WAIT for completion** → Auto-continues
|
||||||
7. Phase 5 (batch-plan, optional) → Reports completion
|
7. Phase 4 (design-update) → **WAIT for completion** → Auto-continues
|
||||||
|
8. Phase 5 (batch-plan, optional) → Reports completion
|
||||||
|
|
||||||
**Phase Transition Mechanism**:
|
**Phase Transition Mechanism**:
|
||||||
- **Phase 0c (User Interaction)**: User confirms targets → IMMEDIATELY triggers Phase 1
|
- **Phase 0c (User Interaction)**: User confirms targets → IMMEDIATELY triggers Phase 1
|
||||||
@@ -276,10 +277,26 @@ SlashCommand(command)
|
|||||||
|
|
||||||
# Output: {style_variants} style cards with design_attributes
|
# Output: {style_variants} style cards with design_attributes
|
||||||
# SlashCommand blocks until phase complete
|
# SlashCommand blocks until phase complete
|
||||||
# Upon completion, IMMEDIATELY execute Phase 2 (auto-continue)
|
# Upon completion, IMMEDIATELY execute Phase 2.3 (auto-continue)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 2: Layout Extraction
|
### Phase 2.3: Animation Extraction (Optional - Interactive Mode)
|
||||||
|
```bash
|
||||||
|
# Animation extraction for motion design patterns
|
||||||
|
REPORT: "🚀 Phase 2.3: Animation Extraction (interactive mode)"
|
||||||
|
REPORT: " → Mode: Interactive specification"
|
||||||
|
REPORT: " → Purpose: Define motion design patterns"
|
||||||
|
|
||||||
|
command = "/workflow:ui-design:animation-extract --base-path \"{base_path}\" --mode interactive"
|
||||||
|
|
||||||
|
SlashCommand(command)
|
||||||
|
|
||||||
|
# Output: animation-tokens.json + animation-guide.md
|
||||||
|
# SlashCommand blocks until phase complete
|
||||||
|
# Upon completion, IMMEDIATELY execute Phase 2.5 (auto-continue)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2.5: Layout Extraction
|
||||||
```bash
|
```bash
|
||||||
targets_string = ",".join(inferred_target_list)
|
targets_string = ",".join(inferred_target_list)
|
||||||
command = "/workflow:ui-design:layout-extract --base-path \"{base_path}\" " +
|
command = "/workflow:ui-design:layout-extract --base-path \"{base_path}\" " +
|
||||||
|
|||||||
@@ -57,6 +57,21 @@ Read({base_path}/style-extraction/style-{id}/design-tokens.json)
|
|||||||
|
|
||||||
**Output**: `design_tokens[]` for all style variants
|
**Output**: `design_tokens[]` for all style variants
|
||||||
|
|
||||||
|
### Step 4: Load Animation Tokens (Optional)
|
||||||
|
```bash
|
||||||
|
# Check if animation tokens exist
|
||||||
|
bash(test -f {base_path}/animation-extraction/animation-tokens.json && echo "exists")
|
||||||
|
|
||||||
|
# Load animation tokens if available
|
||||||
|
IF exists({base_path}/animation-extraction/animation-tokens.json):
|
||||||
|
animation_tokens = Read({base_path}/animation-extraction/animation-tokens.json)
|
||||||
|
has_animations = true
|
||||||
|
ELSE:
|
||||||
|
has_animations = false
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**: `animation_tokens` (optional), `has_animations` flag
|
||||||
|
|
||||||
## Phase 2: Assembly (Agent)
|
## Phase 2: Assembly (Agent)
|
||||||
|
|
||||||
**Executor**: `Task(ui-design-agent)` × `T × S × L` tasks (can be batched)
|
**Executor**: `Task(ui-design-agent)` × `T × S × L` tasks (can be batched)
|
||||||
@@ -86,7 +101,15 @@ Task(ui-design-agent): `
|
|||||||
Read("{base_path}/style-extraction/style-{style_id}/design-tokens.json")
|
Read("{base_path}/style-extraction/style-{style_id}/design-tokens.json")
|
||||||
Extract: ALL token values (colors, typography, spacing, borders, shadows, breakpoints)
|
Extract: ALL token values (colors, typography, spacing, borders, shadows, breakpoints)
|
||||||
|
|
||||||
3. Reference Image (AUTO-DETECTED):
|
3. Animation Tokens (OPTIONAL):
|
||||||
|
IF exists("{base_path}/animation-extraction/animation-tokens.json"):
|
||||||
|
Read("{base_path}/animation-extraction/animation-tokens.json")
|
||||||
|
Extract: duration, easing, transitions, keyframes, interactions
|
||||||
|
has_animations = true
|
||||||
|
ELSE:
|
||||||
|
has_animations = false
|
||||||
|
|
||||||
|
4. Reference Image (AUTO-DETECTED):
|
||||||
IF template.source_image_path exists:
|
IF template.source_image_path exists:
|
||||||
Read(template.source_image_path)
|
Read(template.source_image_path)
|
||||||
Purpose: Additional visual context for better placeholder content generation
|
Purpose: Additional visual context for better placeholder content generation
|
||||||
@@ -115,6 +138,14 @@ Task(ui-design-agent): `
|
|||||||
* Typography: tokens.typography.*
|
* Typography: tokens.typography.*
|
||||||
* Shadows: tokens.shadows.*
|
* Shadows: tokens.shadows.*
|
||||||
* Border radius: tokens.border_radius.*
|
* Border radius: tokens.border_radius.*
|
||||||
|
- IF has_animations == true: Inject animation tokens
|
||||||
|
* Add CSS Custom Properties for animations at :root level:
|
||||||
|
--duration-instant, --duration-fast, --duration-normal, etc.
|
||||||
|
--easing-linear, --easing-ease-out, etc.
|
||||||
|
* Add @keyframes rules from animation_tokens.keyframes
|
||||||
|
* Add interaction classes (.button-hover, .card-hover) from animation_tokens.interactions
|
||||||
|
* Add utility classes (.transition-color, .transition-transform) from animation_tokens.transitions
|
||||||
|
* Include prefers-reduced-motion media query for accessibility
|
||||||
- Device-optimized for template.device_type
|
- Device-optimized for template.device_type
|
||||||
|
|
||||||
## Assembly Rules
|
## Assembly Rules
|
||||||
|
|||||||
@@ -20,9 +20,10 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Write(*), Bash(*)
|
|||||||
2. Phase 0: Initialize and parse parameters
|
2. Phase 0: Initialize and parse parameters
|
||||||
3. Phase 1: Screenshot capture (batch or deep mode) → **WAIT for completion** → Auto-continues
|
3. Phase 1: Screenshot capture (batch or deep mode) → **WAIT for completion** → Auto-continues
|
||||||
4. Phase 2: Style extraction (complete design systems) → **WAIT for completion** → Auto-continues
|
4. Phase 2: Style extraction (complete design systems) → **WAIT for completion** → Auto-continues
|
||||||
5. Phase 2.5: Layout extraction (structure templates) → **WAIT for completion** → Auto-continues
|
5. Phase 2.3: Animation extraction (CSS auto mode) → **WAIT for completion** → Auto-continues
|
||||||
6. Phase 3: Batch UI assembly → **WAIT for completion** → Auto-continues
|
6. Phase 2.5: Layout extraction (structure templates) → **WAIT for completion** → Auto-continues
|
||||||
7. Phase 4: Design system integration → Reports completion
|
7. Phase 3: Batch UI assembly → **WAIT for completion** → Auto-continues
|
||||||
|
8. Phase 4: Design system integration → Reports completion
|
||||||
|
|
||||||
**Phase Transition Mechanism**:
|
**Phase Transition Mechanism**:
|
||||||
- `SlashCommand` is BLOCKING - execution pauses until completion
|
- `SlashCommand` is BLOCKING - execution pauses until completion
|
||||||
|
|||||||
243
.claude/scripts/extract-animations.js
Normal file
243
.claude/scripts/extract-animations.js
Normal file
@@ -0,0 +1,243 @@
|
|||||||
|
/**
|
||||||
|
* Animation & Transition Extraction Script
|
||||||
|
*
|
||||||
|
* Extracts CSS animations, transitions, and transform patterns from a live web page.
|
||||||
|
* This script runs in the browser context via Chrome DevTools Protocol.
|
||||||
|
*
|
||||||
|
* @returns {Object} Structured animation data
|
||||||
|
*/
|
||||||
|
(() => {
|
||||||
|
const extractionTimestamp = new Date().toISOString();
|
||||||
|
const currentUrl = window.location.href;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse transition shorthand or individual properties
|
||||||
|
*/
|
||||||
|
function parseTransition(element, computedStyle) {
|
||||||
|
const transition = computedStyle.transition || computedStyle.webkitTransition;
|
||||||
|
|
||||||
|
if (!transition || transition === 'none' || transition === 'all 0s ease 0s') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse shorthand: "property duration easing delay"
|
||||||
|
const transitions = [];
|
||||||
|
const parts = transition.split(/,\s*/);
|
||||||
|
|
||||||
|
parts.forEach(part => {
|
||||||
|
const match = part.match(/^(\S+)\s+([\d.]+m?s)\s+(\S+)(?:\s+([\d.]+m?s))?/);
|
||||||
|
if (match) {
|
||||||
|
transitions.push({
|
||||||
|
property: match[1],
|
||||||
|
duration: match[2],
|
||||||
|
easing: match[3],
|
||||||
|
delay: match[4] || '0s'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return transitions.length > 0 ? transitions : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract animation name and properties
|
||||||
|
*/
|
||||||
|
function parseAnimation(element, computedStyle) {
|
||||||
|
const animationName = computedStyle.animationName || computedStyle.webkitAnimationName;
|
||||||
|
|
||||||
|
if (!animationName || animationName === 'none') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
name: animationName,
|
||||||
|
duration: computedStyle.animationDuration || computedStyle.webkitAnimationDuration,
|
||||||
|
easing: computedStyle.animationTimingFunction || computedStyle.webkitAnimationTimingFunction,
|
||||||
|
delay: computedStyle.animationDelay || computedStyle.webkitAnimationDelay || '0s',
|
||||||
|
iterationCount: computedStyle.animationIterationCount || computedStyle.webkitAnimationIterationCount || '1',
|
||||||
|
direction: computedStyle.animationDirection || computedStyle.webkitAnimationDirection || 'normal',
|
||||||
|
fillMode: computedStyle.animationFillMode || computedStyle.webkitAnimationFillMode || 'none'
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract transform value
|
||||||
|
*/
|
||||||
|
function parseTransform(computedStyle) {
|
||||||
|
const transform = computedStyle.transform || computedStyle.webkitTransform;
|
||||||
|
|
||||||
|
if (!transform || transform === 'none') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get element selector (simplified for readability)
|
||||||
|
*/
|
||||||
|
function getSelector(element) {
|
||||||
|
if (element.id) {
|
||||||
|
return `#${element.id}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (element.className && typeof element.className === 'string') {
|
||||||
|
const classes = element.className.trim().split(/\s+/).slice(0, 2).join('.');
|
||||||
|
if (classes) {
|
||||||
|
return `.${classes}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return element.tagName.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract all stylesheets and find @keyframes rules
|
||||||
|
*/
|
||||||
|
function extractKeyframes() {
|
||||||
|
const keyframes = {};
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Iterate through all stylesheets
|
||||||
|
Array.from(document.styleSheets).forEach(sheet => {
|
||||||
|
try {
|
||||||
|
// Skip external stylesheets due to CORS
|
||||||
|
if (sheet.href && !sheet.href.startsWith(window.location.origin)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Array.from(sheet.cssRules || sheet.rules || []).forEach(rule => {
|
||||||
|
// Check for @keyframes rules
|
||||||
|
if (rule.type === CSSRule.KEYFRAMES_RULE || rule.type === CSSRule.WEBKIT_KEYFRAMES_RULE) {
|
||||||
|
const name = rule.name;
|
||||||
|
const frames = {};
|
||||||
|
|
||||||
|
Array.from(rule.cssRules || []).forEach(keyframe => {
|
||||||
|
const key = keyframe.keyText; // e.g., "0%", "50%", "100%"
|
||||||
|
frames[key] = keyframe.style.cssText;
|
||||||
|
});
|
||||||
|
|
||||||
|
keyframes[name] = frames;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Skip stylesheets that can't be accessed (CORS)
|
||||||
|
console.warn('Cannot access stylesheet:', sheet.href, e.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error extracting keyframes:', e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyframes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Scan visible elements for animations and transitions
|
||||||
|
*/
|
||||||
|
function scanElements() {
|
||||||
|
const elements = document.querySelectorAll('*');
|
||||||
|
const transitionData = [];
|
||||||
|
const animationData = [];
|
||||||
|
const transformData = [];
|
||||||
|
|
||||||
|
const uniqueTransitions = new Set();
|
||||||
|
const uniqueAnimations = new Set();
|
||||||
|
const uniqueEasings = new Set();
|
||||||
|
const uniqueDurations = new Set();
|
||||||
|
|
||||||
|
elements.forEach(element => {
|
||||||
|
// Skip invisible elements
|
||||||
|
const rect = element.getBoundingClientRect();
|
||||||
|
if (rect.width === 0 && rect.height === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const computedStyle = window.getComputedStyle(element);
|
||||||
|
|
||||||
|
// Extract transitions
|
||||||
|
const transitions = parseTransition(element, computedStyle);
|
||||||
|
if (transitions) {
|
||||||
|
const selector = getSelector(element);
|
||||||
|
transitions.forEach(t => {
|
||||||
|
const key = `${t.property}-${t.duration}-${t.easing}`;
|
||||||
|
if (!uniqueTransitions.has(key)) {
|
||||||
|
uniqueTransitions.add(key);
|
||||||
|
transitionData.push({
|
||||||
|
selector,
|
||||||
|
...t
|
||||||
|
});
|
||||||
|
uniqueEasings.add(t.easing);
|
||||||
|
uniqueDurations.add(t.duration);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract animations
|
||||||
|
const animation = parseAnimation(element, computedStyle);
|
||||||
|
if (animation) {
|
||||||
|
const selector = getSelector(element);
|
||||||
|
const key = `${animation.name}-${animation.duration}`;
|
||||||
|
if (!uniqueAnimations.has(key)) {
|
||||||
|
uniqueAnimations.add(key);
|
||||||
|
animationData.push({
|
||||||
|
selector,
|
||||||
|
...animation
|
||||||
|
});
|
||||||
|
uniqueEasings.add(animation.easing);
|
||||||
|
uniqueDurations.add(animation.duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Extract transforms (on hover/active, we only get current state)
|
||||||
|
const transform = parseTransform(computedStyle);
|
||||||
|
if (transform) {
|
||||||
|
const selector = getSelector(element);
|
||||||
|
transformData.push({
|
||||||
|
selector,
|
||||||
|
transform
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
transitions: transitionData,
|
||||||
|
animations: animationData,
|
||||||
|
transforms: transformData,
|
||||||
|
uniqueEasings: Array.from(uniqueEasings),
|
||||||
|
uniqueDurations: Array.from(uniqueDurations)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main extraction function
|
||||||
|
*/
|
||||||
|
function extractAnimations() {
|
||||||
|
const elementData = scanElements();
|
||||||
|
const keyframes = extractKeyframes();
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
timestamp: extractionTimestamp,
|
||||||
|
url: currentUrl,
|
||||||
|
method: 'chrome-devtools',
|
||||||
|
version: '1.0.0'
|
||||||
|
},
|
||||||
|
transitions: elementData.transitions,
|
||||||
|
animations: elementData.animations,
|
||||||
|
transforms: elementData.transforms,
|
||||||
|
keyframes: keyframes,
|
||||||
|
summary: {
|
||||||
|
total_transitions: elementData.transitions.length,
|
||||||
|
total_animations: elementData.animations.length,
|
||||||
|
total_transforms: elementData.transforms.length,
|
||||||
|
total_keyframes: Object.keys(keyframes).length,
|
||||||
|
unique_easings: elementData.uniqueEasings,
|
||||||
|
unique_durations: elementData.uniqueDurations
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute extraction
|
||||||
|
return extractAnimations();
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user