From b961d19391108ec1da9d502d848646410c7de397 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 09:16:17 +0000 Subject: [PATCH 1/7] feat: Add cybernetic mad scientist themes and 5 animation styles for repo cards This commit introduces a creative suite of animations and themes inspired by a cybernetic mad scientist laboratory aesthetic: New Themes (5): - mad_scientist: Electric blue laboratory with bright cyan tones - mad_scientist_dark: Deep cyan with near-black background - cybernetic_lab: Classic blue tech with laboratory feel - robot_blue: Inspired by blue robot avatar aesthetic - electric_laboratory: High-contrast electric cyan New Animation Styles (5): - bubbles: Fishtank-style floating bubbles rising from bottom - embers: Glowing particles pulsing like burning embers - radiant: Pulsing sun with rays emanating from center - circuit: Dots traveling around edges like circuit paths - sparks: Electric sparks flashing randomly Features: - All animations are pure CSS/SVG (no JavaScript) - Lightweight and GPU-accelerated where possible - Can be disabled with disable_animations parameter - Animations automatically use theme colors - Fully compatible with all existing repo card parameters API Changes: - Added animation_style parameter (bubbles|embers|radiant|circuit|sparks|none) - Added disable_animations parameter (true|false) Documentation: - Added ANIMATION_EXAMPLES.md with comprehensive usage guide - Includes recommended theme + animation combinations - Full parameter reference and examples --- ANIMATION_EXAMPLES.md | 258 ++++++++++++++++++++++++++++++++++++++++ api/pin.js | 4 + src/cards/repo.js | 269 +++++++++++++++++++++++++++++++++++++++++- themes/index.js | 35 ++++++ 4 files changed, 565 insertions(+), 1 deletion(-) create mode 100644 ANIMATION_EXAMPLES.md diff --git a/ANIMATION_EXAMPLES.md b/ANIMATION_EXAMPLES.md new file mode 100644 index 0000000000000..852256c589805 --- /dev/null +++ b/ANIMATION_EXAMPLES.md @@ -0,0 +1,258 @@ +# ๐ŸŽจ Cybernetic Mad Scientist Animation Styles + +This document showcases the new animation styles and cybernetic themes for repo cards! + +## ๐Ÿค– Cybernetic Themes + +Five new themes inspired by a cybernetic mad scientist laboratory aesthetic: + +### 1. **mad_scientist** - Electric Blue Laboratory +Bright cyan and blue tones with a dark space background. +``` +theme=mad_scientist +``` +- Title: `#00d9ff` (bright cyan) +- Text: `#7dd3fc` (sky blue) +- Icons: `#38bdf8` (blue) +- Border: `#0ea5e9` (deep blue) +- Background: `#0c1021` (dark navy) + +### 2. **mad_scientist_dark** - Deep Laboratory +Darker, more intense cyan with near-black background. +``` +theme=mad_scientist_dark +``` +- Title: `#22d3ee` (cyan) +- Text: `#67e8f9` (light cyan) +- Icons: `#06b6d4` (darker cyan) +- Border: `#0891b2` (teal) +- Background: `#020617` (almost black) + +### 3. **cybernetic_lab** - Classic Blue Tech +Traditional tech blue with laboratory feel. +``` +theme=cybernetic_lab +``` +- Title: `#3b82f6` (blue) +- Text: `#60a5fa` (light blue) +- Icons: `#2563eb` (royal blue) +- Border: `#1d4ed8` (deep blue) +- Background: `#0a0e1a` (dark blue-black) + +### 4. **robot_blue** - Robot Head Inspired +Inspired by the blue robot avatar aesthetic. +``` +theme=robot_blue +``` +- Title: `#0ea5e9` (sky blue) +- Text: `#7dd3fc` (light sky) +- Icons: `#38bdf8` (bright blue) +- Border: `#0284c7` (ocean blue) +- Background: `#082f49` (dark teal) + +### 5. **electric_laboratory** - Electric Cyan +High-contrast electric cyan with modern lab feel. +``` +theme=electric_laboratory +``` +- Title: `#00ffff` (pure cyan) +- Text: `#5eead4` (teal) +- Icons: `#2dd4bf` (turquoise) +- Border: `#14b8a6` (dark teal) +- Background: `#0f172a` (slate black) + +--- + +## โšก Animation Styles + +Five unique animation effects for your repo cards: + +### 1. **bubbles** - Fishtank Effect ๐Ÿ  +Bubbles float up from the bottom like in an aquarium. +``` +animation_style=bubbles +``` +- 8 bubbles with varying sizes +- Float upward with fade effect +- Staggered animation delays +- 3-5 second animation cycles +- Perfect for: Calm, steady progress projects + +### 2. **embers** - Burning Particles ๐Ÿ”ฅ +Glowing particles pulse and float like hot embers. +``` +animation_style=embers +``` +- 12 glowing particles +- Pulsing glow effect with blur +- Gentle floating motion +- 2-4 second animation cycles +- Perfect for: Active, hot projects + +### 3. **radiant** - Pulsing Sun โ˜€๏ธ +Radiant rays emanate from the center with a pulsing core. +``` +animation_style=radiant +``` +- 16 rays radiating from center +- Pulsing central core +- Sequential wave animation +- 2 second pulse cycle +- Perfect for: Central, important projects + +### 4. **circuit** - Edge Traveler ๐Ÿ”Œ +Dots travel around the card edges like signals in a circuit. +``` +animation_style=circuit +``` +- 6 glowing dots traveling the perimeter +- Glowing edge trail effects +- Continuous loop motion +- 4 second travel cycle +- Perfect for: Tech, systematic projects + +### 5. **sparks** - Electric Sparks โšก +Electric sparks flash randomly across the card. +``` +animation_style=sparks +``` +- 10 electric spark bursts +- Random positions +- Flash and fade effect +- 5 second cycle with stagger +- Perfect for: Energetic, innovative projects + +--- + +## ๐ŸŽฏ Usage Examples + +### Basic Animation +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&animation_style=bubbles) +``` + +### With Cybernetic Theme +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=mad_scientist&animation_style=circuit) +``` + +### Full Customization +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=robot_blue&animation_style=sparks&show_owner=true&all_stats=true) +``` + +### Disable Animations (for static images) +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=electric_laboratory&disable_animations=true) +``` + +--- + +## ๐ŸŽจ Recommended Combinations + +Here are some great theme + animation pairings: + +### The Scientist's Lab +``` +theme=mad_scientist&animation_style=bubbles +``` +Blue laboratory with gentle bubbles rising - perfect for research projects. + +### The Robot Workshop +``` +theme=robot_blue&animation_style=circuit +``` +Robot-inspired blues with circuit paths - ideal for robotics/automation. + +### Electric Experiment +``` +theme=electric_laboratory&animation_style=sparks +``` +High-voltage cyan with electric sparks - great for exciting new projects. + +### Burning Innovation +``` +theme=cybernetic_lab&animation_style=embers +``` +Tech blue with glowing embers - perfect for hot, active development. + +### Radiant Core +``` +theme=mad_scientist_dark&animation_style=radiant +``` +Dark background with pulsing radiant center - excellent for core libraries. + +--- + +## ๐Ÿ“ Parameters Reference + +### Animation Parameters +- `animation_style` - Animation effect to use + - Options: `none`, `bubbles`, `embers`, `radiant`, `circuit`, `sparks` + - Default: `none` + +- `disable_animations` - Disable all animations + - Options: `true`, `false` + - Default: `false` + +### All Compatible Parameters +You can combine animations with all existing repo card parameters: +- `theme` - Choose from 65+ themes (including 5 new cybernetic ones) +- `title_color`, `icon_color`, `text_color`, `bg_color`, `border_color` - Custom colors +- `hide_border`, `hide_title`, `hide_text` - Hide elements +- `show_owner` - Show full username/repo +- `show_issues`, `show_prs`, `show_age` - Show extra stats +- `all_stats` - Show all available stats +- `border_radius` - Customize corner rounding +- `locale` - Set language + +--- + +## ๐ŸŽฌ Animation Performance + +All animations are: +- โœ… Pure CSS/SVG (no JavaScript required) +- โœ… Lightweight (minimal impact on file size) +- โœ… Smooth (GPU-accelerated where possible) +- โœ… Accessible (can be disabled with `disable_animations=true`) +- โœ… Compatible with all modern browsers + +--- + +## ๐Ÿš€ Quick Start + +1. Choose a theme from the cybernetic collection +2. Pick an animation style that matches your project vibe +3. Add to your README: + +```markdown +[![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=mad_scientist&animation_style=circuit)](https://github.com/hesreallyhim/your-repo) +``` + +--- + +## ๐Ÿ’ก Tips + +1. **For READMEs viewed on GitHub**: All animations work perfectly in SVG! +2. **For static documentation**: Use `disable_animations=true` +3. **Performance**: Animations use minimal resources and won't slow page load +4. **Accessibility**: Users with `prefers-reduced-motion` should disable animations +5. **Caching**: Animation style is included in cache key, so changes update immediately + +--- + +## ๐ŸŽจ Color Customization + +You can override theme colors while keeping animations: + +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&animation_style=bubbles&title_color=ff00ff&icon_color=00ffff&bg_color=000000) +``` + +Animations will automatically use your custom colors! + +--- + +## ๐Ÿงช Experiment! + +Don't be afraid to mix and match! Try different combinations to find the perfect look for your project. The cybernetic mad scientist aesthetic is all about creative experimentation! ๐Ÿ”ฌโšก๐Ÿค– diff --git a/api/pin.js b/api/pin.js index 6690ca9206aec..a4073b630791e 100644 --- a/api/pin.js +++ b/api/pin.js @@ -42,6 +42,8 @@ export default async (req, res) => { show_prs, show_age, age_metric, + animation_style, + disable_animations, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -116,6 +118,8 @@ export default async (req, res) => { show_prs: finalShowPrs, show_age: finalShowAge, age_metric: age_metric || "first", + animation_style: animation_style || "none", + disable_animations: parseBoolean(disable_animations), }), ); } catch (err) { diff --git a/src/cards/repo.js b/src/cards/repo.js index 12b450a6f5bec..16271e171058f 100644 --- a/src/cards/repo.js +++ b/src/cards/repo.js @@ -19,6 +19,260 @@ const ICON_SIZE = 16; const DESCRIPTION_LINE_WIDTH = 59; const DESCRIPTION_MAX_LINES = 3; +/** + * Generates animation styles and SVG elements for different effects. + * + * @param {string} style Animation style: bubbles, embers, radiant, circuit, sparks. + * @param {object} colors Card colors for theming animations. + * @param {number} width Card width. + * @param {number} height Card height. + * @returns {{css: string, svg: string}} Animation CSS and SVG elements. + */ +const getAnimationStyle = (style, colors, width, height) => { + if (!style || style === "none") { + return { css: "", svg: "" }; + } + + const iconColor = colors.iconColor || "38bdf8"; + const titleColor = colors.titleColor || "00d9ff"; + + switch (style) { + case "bubbles": { + // Fishtank-style floating bubbles + const bubbles = Array.from({ length: 8 }, (_, i) => { + const x = (width * (i + 1)) / 9; + const size = 3 + (i % 3) * 2; + const delay = i * 0.4; + const duration = 3 + (i % 3); + return ` + `; + }).join(""); + + const css = ` + @keyframes bubbleFloat { + 0% { transform: translateY(0) scale(1); opacity: 0.3; } + 50% { opacity: 0.5; } + 100% { transform: translateY(-${height + 20}px) scale(0.5); opacity: 0; } + } + .bubble { + animation: bubbleFloat 3s infinite ease-in-out; + }`; + + return { css, svg: `${bubbles}` }; + } + + case "embers": { + // Glowing particles like burning embers + const embers = Array.from({ length: 12 }, (_, i) => { + const x = 10 + Math.random() * (width - 20); + const y = height * 0.2 + Math.random() * (height * 0.6); + const size = 1.5 + Math.random() * 2; + const delay = i * 0.3; + return ` + `; + }).join(""); + + const css = ` + @keyframes emberGlow { + 0%, 100% { opacity: 0.2; filter: blur(0px); } + 25% { opacity: 0.8; filter: blur(1px); } + 50% { opacity: 0.4; filter: blur(0.5px); } + 75% { opacity: 0.9; filter: blur(1.5px); } + } + @keyframes emberFloat { + 0%, 100% { transform: translate(0, 0); } + 33% { transform: translate(3px, -5px); } + 66% { transform: translate(-3px, 5px); } + } + .ember { + animation: emberGlow 2s infinite ease-in-out, emberFloat 4s infinite ease-in-out; + }`; + + return { css, svg: `${embers}` }; + } + + case "radiant": { + // Radiant sun with pulsing rays + const rays = Array.from({ length: 16 }, (_, i) => { + const angle = (i * 360) / 16; + const length = 80; + const x1 = width / 2; + const y1 = height / 2; + const x2 = x1 + Math.cos((angle * Math.PI) / 180) * length; + const y2 = y1 + Math.sin((angle * Math.PI) / 180) * length; + const delay = i * 0.05; + return ` + `; + }).join(""); + + const core = ` + `; + + const css = ` + @keyframes rayPulse { + 0%, 100% { opacity: 0.1; stroke-width: 1; } + 50% { opacity: 0.4; stroke-width: 2; } + } + @keyframes corePulse { + 0%, 100% { opacity: 0.2; transform: scale(1); } + 50% { opacity: 0.5; transform: scale(1.2); } + } + .ray { + animation: rayPulse 2s infinite ease-in-out; + transform-origin: ${width / 2}px ${height / 2}px; + } + .radiant-core { + animation: corePulse 2s infinite ease-in-out; + transform-origin: ${width / 2}px ${height / 2}px; + }`; + + return { css, svg: `${rays}${core}` }; + } + + case "circuit": { + // Elements traveling around the edges like circuit paths + const dotCount = 6; + const dots = Array.from({ length: dotCount }, (_, i) => { + const delay = i * 0.8; + return ` + + + `; + }).join(""); + + // Glowing trail effect + const trail = ` + + + + `; + + const css = ` + @keyframes circuitGlow { + 0%, 100% { opacity: 0.1; } + 50% { opacity: 0.4; } + } + .circuit-dot { + filter: drop-shadow(0 0 2px ${titleColor}); + } + [class^="circuit-glow-"] { + animation: circuitGlow 2s infinite ease-in-out; + }`; + + return { css, svg: `${trail}${dots}` }; + } + + case "sparks": { + // Electric sparks appearing randomly + const sparks = Array.from({ length: 10 }, (_, i) => { + const x = 20 + Math.random() * (width - 40); + const y = 20 + Math.random() * (height - 40); + const delay = i * 0.5; + const rotation = Math.random() * 360; + return ` + + + + + + `; + }).join(""); + + const css = ` + @keyframes sparkFlash { + 0%, 90%, 100% { opacity: 0; transform: scale(0); } + 5% { opacity: 1; transform: scale(1.2); } + 10% { opacity: 0.8; transform: scale(0.9); } + 15% { opacity: 0; transform: scale(0.6); } + } + .spark { + animation: sparkFlash 5s infinite ease-in-out; + transform-origin: center; + }`; + + return { css, svg: `${sparks}` }; + } + + default: + return { css: "", svg: "" }; + } +}; + /** * Retrieves the repository description and wraps it to fit the card width. * @@ -83,6 +337,8 @@ const renderRepoCard = (repo, options = {}) => { show_prs = false, show_age = false, age_metric = "first", + animation_style = "none", + disable_animations = false, } = options; const lineHeight = 10; @@ -269,7 +525,15 @@ const renderRepoCard = (repo, options = {}) => { colors, }); - card.disableAnimations(); + // Get animation styles if enabled + const hasAnimation = !disable_animations && animation_style !== "none"; + const animationData = hasAnimation + ? getAnimationStyle(animation_style, colors, 400, cardHeight) + : { css: "", svg: "" }; + + if (disable_animations) { + card.disableAnimations(); + } card.setHideBorder(hide_border); card.setHideTitle(shouldHideTitle); if (compactStatsOnlyLayout) { @@ -281,9 +545,12 @@ const renderRepoCard = (repo, options = {}) => { .icon { fill: ${colors.iconColor} } .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; } .badge rect { opacity: 0.2 } + ${animationData.css} `); return card.render(` + ${animationData.svg} + ${ isTemplate ? getBadgeSVG(i18n.t("repocard.template"), colors.textColor) diff --git a/themes/index.js b/themes/index.js index f5d8d9160fd1b..b3f6f7c02074f 100644 --- a/themes/index.js +++ b/themes/index.js @@ -462,6 +462,41 @@ export const themes = { icon_color: "ffffff", bg_color: "35,4158d0,c850c0,ffcc70", }, + mad_scientist: { + title_color: "00d9ff", + text_color: "7dd3fc", + icon_color: "38bdf8", + border_color: "0ea5e9", + bg_color: "0c1021", + }, + mad_scientist_dark: { + title_color: "22d3ee", + text_color: "67e8f9", + icon_color: "06b6d4", + border_color: "0891b2", + bg_color: "020617", + }, + cybernetic_lab: { + title_color: "3b82f6", + text_color: "60a5fa", + icon_color: "2563eb", + border_color: "1d4ed8", + bg_color: "0a0e1a", + }, + robot_blue: { + title_color: "0ea5e9", + text_color: "7dd3fc", + icon_color: "38bdf8", + border_color: "0284c7", + bg_color: "082f49", + }, + electric_laboratory: { + title_color: "00ffff", + text_color: "5eead4", + icon_color: "2dd4bf", + border_color: "14b8a6", + bg_color: "0f172a", + }, }; export default themes; From e5a6b832bc00203ace997d0547f23bd45c13a079 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 09:37:16 +0000 Subject: [PATCH 2/7] feat: Enhance bubbles animation with glowing jellyfish and drifting starfish Transform the bubbles animation into a complete aquarium experience by adding: Glowing Jellyfish: - 2 jellyfish with pulsing glow effects - 6 wavy tentacles per jellyfish that gently sway - Drift from left to right with curved path - Appear every ~12 seconds with staggered timing - Gaussian blur glow filter for ethereal effect - Uses theme titleColor for bioluminescent glow Drifting Starfish: - 2 five-pointed starfish with outlined edges - Slow rotation while drifting (15s rotation cycle) - Travel right to left with gentle wave motion - Appear every ~15 seconds, offset from jellyfish - Uses theme iconColor with titleColor outline Animation Details: - All creatures layered behind text for depth - Long delays prevent overcrowding (12s and 15s cycles) - Smooth fade in/out for natural appearance - Tentacles wave independently for realistic movement - Curved motion paths create organic flow - Theme-colored for seamless integration The aquarium now feels alive with multiple layers of movement! --- ANIMATION_EXAMPLES.md | 14 +++-- src/cards/repo.js | 135 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 7 deletions(-) diff --git a/ANIMATION_EXAMPLES.md b/ANIMATION_EXAMPLES.md index 852256c589805..cf37f71341986 100644 --- a/ANIMATION_EXAMPLES.md +++ b/ANIMATION_EXAMPLES.md @@ -68,15 +68,17 @@ theme=electric_laboratory Five unique animation effects for your repo cards: ### 1. **bubbles** - Fishtank Effect ๐Ÿ  -Bubbles float up from the bottom like in an aquarium. +A complete aquarium experience with bubbles, glowing jellyfish, and drifting starfish. ``` animation_style=bubbles ``` -- 8 bubbles with varying sizes -- Float upward with fade effect -- Staggered animation delays -- 3-5 second animation cycles -- Perfect for: Calm, steady progress projects +- 8 bubbles floating upward with varying sizes and speeds +- 2 glowing jellyfish with wavy tentacles drifting left to right +- 2 starfish slowly rotating and drifting right to left +- Jellyfish appear every ~12 seconds with gentle pulsing glow +- Starfish drift across every ~15 seconds with slow rotation +- All creatures layered behind text for depth +- Perfect for: Calm, steady progress projects, marine/ocean themes ### 2. **embers** - Burning Particles ๐Ÿ”ฅ Glowing particles pulse and float like hot embers. diff --git a/src/cards/repo.js b/src/cards/repo.js index 16271e171058f..b4cc5b8a3dea8 100644 --- a/src/cards/repo.js +++ b/src/cards/repo.js @@ -56,17 +56,150 @@ const getAnimationStyle = (style, colors, width, height) => { />`; }).join(""); + // Glowing jellyfish that floats across + const jellyfishCount = 2; + const jellyfish = Array.from({ length: jellyfishCount }, (_, i) => { + const startY = height * 0.3 + i * height * 0.25; + const delay = i * 12 + 2; // Appear every 12 seconds, staggered + const bellSize = 12 + i * 3; + + return ` + + + + + + ${Array.from({ length: 6 }, (_, t) => { + const tentacleX = -bellSize * 0.6 + t * bellSize * 0.24; + return ` + `; + }).join("")} + + + `; + }).join(""); + + // Starfish that drifts across + const starfishCount = 2; + const starfish = Array.from({ length: starfishCount }, (_, i) => { + const startY = height * 0.5 + i * height * 0.2; + const delay = i * 15 + 7; // Offset from jellyfish timing + const size = 8 + i * 2; + + // Create 5-pointed star path + const points = + Array.from({ length: 5 }, (_, p) => { + const angle = ((p * 72 - 90) * Math.PI) / 180; + const outerX = Math.cos(angle) * size; + const outerY = Math.sin(angle) * size; + const innerAngle = ((p * 72 + 36 - 90) * Math.PI) / 180; + const innerX = Math.cos(innerAngle) * size * 0.4; + const innerY = Math.sin(innerAngle) * size * 0.4; + return `${p === 0 ? "M" : "L"} ${outerX},${outerY} L ${innerX},${innerY}`; + }).join(" ") + " Z"; + + return ` + + + + + + + `; + }).join(""); + const css = ` @keyframes bubbleFloat { 0% { transform: translateY(0) scale(1); opacity: 0.3; } 50% { opacity: 0.5; } 100% { transform: translateY(-${height + 20}px) scale(0.5); opacity: 0; } } + @keyframes jellyfishPulse { + 0%, 100% { opacity: 0; } + 10%, 90% { opacity: 1; } + 50% { opacity: 0.8; } + } + @keyframes tentacleWave { + 0%, 100% { transform: translateX(0); } + 50% { transform: translateX(2px); } + } + @keyframes starfishDrift { + 0%, 100% { opacity: 0; } + 10%, 90% { opacity: 1; } + } .bubble { animation: bubbleFloat 3s infinite ease-in-out; + } + .jellyfish { + animation: jellyfishPulse 20s infinite ease-in-out; + filter: drop-shadow(0 0 4px ${titleColor}40); + } + .tentacle { + animation: tentacleWave 2s infinite ease-in-out; + } + .starfish { + animation: starfishDrift 25s infinite ease-in-out; }`; - return { css, svg: `${bubbles}` }; + // SVG filter for jellyfish glow + const filters = ` + + + + + + + + + `; + + return { + css, + svg: `${filters}${jellyfish}${starfish}${bubbles}`, + }; } case "embers": { From b9cfef11ff1171a96fabd6c7a3d22bc8bac19c7d Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 09:48:26 +0000 Subject: [PATCH 3/7] feat: Add floating wave effect to text in bubbles animation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Make the entire card feel like it's underwater by adding gentle wave animations to all text elements in the bubbles animation style. Text Animation Details: - Title: Floats with 4s wave cycle - Description lines: Each line has independent wave with staggered delays - Line 1: 3.8s cycle, 0s delay - Line 2: 3.5s cycle, 0.3s delay - Line 3: 4.2s cycle, 0.6s delay - Stats: Float with 3.8s cycle, 0.4s delay - All other text: 3.2s cycle with 0.2s delay Wave Patterns: - textFloatWave: Smooth sine wave (down โ†’ up โ†’ down) - textFloatWave2: Inverted wave (up โ†’ down โ†’ up) - textFloatWave3: Gentler variant with 1.5px amplitude Technical Implementation: - Uses CSS translateY transforms for smooth animation - Each element has unique timing to prevent synchronization - Amplitudes kept small (1.5-2px) for subtle, non-distracting effect - Combines with existing fadeIn animation for title - All timing uses ease-in-out for organic feel The result is a complete underwater immersion effect where text, creatures, and bubbles all move independently like they're floating in a cybernetic aquarium laboratory! --- ANIMATION_EXAMPLES.md | 8 ++++++-- src/cards/repo.js | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 2 deletions(-) diff --git a/ANIMATION_EXAMPLES.md b/ANIMATION_EXAMPLES.md index cf37f71341986..c955f64651b41 100644 --- a/ANIMATION_EXAMPLES.md +++ b/ANIMATION_EXAMPLES.md @@ -68,17 +68,21 @@ theme=electric_laboratory Five unique animation effects for your repo cards: ### 1. **bubbles** - Fishtank Effect ๐Ÿ  -A complete aquarium experience with bubbles, glowing jellyfish, and drifting starfish. +A complete aquarium experience with bubbles, glowing jellyfish, drifting starfish, and floating text. ``` animation_style=bubbles ``` - 8 bubbles floating upward with varying sizes and speeds - 2 glowing jellyfish with wavy tentacles drifting left to right - 2 starfish slowly rotating and drifting right to left +- **โœจ Floating text effect**: All text gently bobs up and down like it's underwater + - Each line waves independently with different timing (3.2s - 4.2s cycles) + - Title, description, and stats all float at different rates + - Creates realistic water immersion effect - Jellyfish appear every ~12 seconds with gentle pulsing glow - Starfish drift across every ~15 seconds with slow rotation - All creatures layered behind text for depth -- Perfect for: Calm, steady progress projects, marine/ocean themes +- Perfect for: Calm, steady progress projects, marine/ocean themes, underwater aesthetics ### 2. **embers** - Burning Particles ๐Ÿ”ฅ Glowing particles pulse and float like hot embers. diff --git a/src/cards/repo.js b/src/cards/repo.js index b4cc5b8a3dea8..a2ca191df53b1 100644 --- a/src/cards/repo.js +++ b/src/cards/repo.js @@ -170,6 +170,23 @@ const getAnimationStyle = (style, colors, width, height) => { 0%, 100% { opacity: 0; } 10%, 90% { opacity: 1; } } + @keyframes textFloatWave { + 0%, 100% { transform: translateY(0px); } + 25% { transform: translateY(-2px); } + 50% { transform: translateY(0px); } + 75% { transform: translateY(2px); } + } + @keyframes textFloatWave2 { + 0%, 100% { transform: translateY(0px); } + 25% { transform: translateY(2px); } + 50% { transform: translateY(0px); } + 75% { transform: translateY(-2px); } + } + @keyframes textFloatWave3 { + 0%, 100% { transform: translateY(0px); } + 33% { transform: translateY(-1.5px); } + 66% { transform: translateY(1.5px); } + } .bubble { animation: bubbleFloat 3s infinite ease-in-out; } @@ -182,6 +199,29 @@ const getAnimationStyle = (style, colors, width, height) => { } .starfish { animation: starfishDrift 25s infinite ease-in-out; + } + /* Floating text effects - like text bobbing in water */ + .header { + animation: fadeInAnimation 0.8s ease-in-out forwards, textFloatWave 4s ease-in-out infinite !important; + } + .description { + animation: textFloatWave2 3.5s ease-in-out infinite; + } + .description tspan:nth-child(1) { + animation: textFloatWave 3.8s ease-in-out infinite; + } + .description tspan:nth-child(2) { + animation: textFloatWave2 3.5s ease-in-out 0.3s infinite; + } + .description tspan:nth-child(3) { + animation: textFloatWave3 4.2s ease-in-out 0.6s infinite; + } + .gray { + animation: textFloatWave3 3.2s ease-in-out 0.2s infinite; + } + /* Stats floating */ + g[data-testid="main-card-body"] > g { + animation: textFloatWave2 3.8s ease-in-out 0.4s infinite; }`; // SVG filter for jellyfish glow From 63e958347b294951d6982cba90542b7cf9458f4e Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 09:58:54 +0000 Subject: [PATCH 4/7] feat: Add horizontal wave effect to text in bubbles animation Implement a true character-by-character wave animation that ripples across the title text from left to right, creating a mesmerizing underwater effect. Implementation Details: - Created wrapTextInWave() helper function that wraps each character in a tspan element with staggered animation delays - Each character has a 0.05s delay from the previous one - Characters animate vertically (3px amplitude) in a 2s cycle - Wave travels smoothly from left to right across the entire title Technical Approach: - Custom wave title rendering for bubbles animation only - Hides default Card title via CSS when wave is active - Preserves card height by not using setHideTitle for wave mode - Maintains Firefox font size compatibility (15.5px) - Uses CSS :not(:has()) selector to conditionally hide default title Character Handling: - Spaces converted to non-breaking spaces (U+00A0) to maintain spacing - Each character gets unique animation-delay based on position - Works with any length title (auto-truncates at 35 chars like normal) Result: A smooth, flowing wave that travels across the text like ripples on water, perfectly complementing the jellyfish, starfish, and bubbles in the aquarium! --- ANIMATION_EXAMPLES.md | 11 ++--- src/cards/repo.js | 96 ++++++++++++++++++++++++++----------------- 2 files changed, 65 insertions(+), 42 deletions(-) diff --git a/ANIMATION_EXAMPLES.md b/ANIMATION_EXAMPLES.md index c955f64651b41..7de398f31c613 100644 --- a/ANIMATION_EXAMPLES.md +++ b/ANIMATION_EXAMPLES.md @@ -68,17 +68,18 @@ theme=electric_laboratory Five unique animation effects for your repo cards: ### 1. **bubbles** - Fishtank Effect ๐Ÿ  -A complete aquarium experience with bubbles, glowing jellyfish, drifting starfish, and floating text. +A complete aquarium experience with bubbles, glowing jellyfish, drifting starfish, and a mesmerizing wave text effect. ``` animation_style=bubbles ``` - 8 bubbles floating upward with varying sizes and speeds - 2 glowing jellyfish with wavy tentacles drifting left to right - 2 starfish slowly rotating and drifting right to left -- **โœจ Floating text effect**: All text gently bobs up and down like it's underwater - - Each line waves independently with different timing (3.2s - 4.2s cycles) - - Title, description, and stats all float at different rates - - Creates realistic water immersion effect +- **๐ŸŒŠ Horizontal wave text effect**: Letters ripple like a wave traveling across the text + - Each character animates individually with a staggered delay (0.05s per letter) + - Creates a smooth left-to-right wave motion across the title + - 2-second wave cycle with 3px vertical movement + - Like text floating and undulating in water - Jellyfish appear every ~12 seconds with gentle pulsing glow - Starfish drift across every ~15 seconds with slow rotation - All creatures layered behind text for depth diff --git a/src/cards/repo.js b/src/cards/repo.js index a2ca191df53b1..e2d4e72342a37 100644 --- a/src/cards/repo.js +++ b/src/cards/repo.js @@ -19,6 +19,25 @@ const ICON_SIZE = 16; const DESCRIPTION_LINE_WIDTH = 59; const DESCRIPTION_MAX_LINES = 3; +/** + * Wraps each character of text in a tspan with staggered animation delay for wave effect. + * + * @param {string} text The text to wrap. + * @param {number} baseDelay Base delay in seconds before wave starts. + * @param {number} delayPerChar Delay in seconds between each character. + * @returns {string} SVG tspan elements with wave animation. + */ +const wrapTextInWave = (text, baseDelay = 0, delayPerChar = 0.05) => { + return Array.from(text) + .map((char, i) => { + const delay = baseDelay + i * delayPerChar; + // Preserve spaces + const displayChar = char === " " ? "\u00A0" : char; + return `${displayChar}`; + }) + .join(""); +}; + /** * Generates animation styles and SVG elements for different effects. * @@ -170,22 +189,9 @@ const getAnimationStyle = (style, colors, width, height) => { 0%, 100% { opacity: 0; } 10%, 90% { opacity: 1; } } - @keyframes textFloatWave { + @keyframes letterWave { 0%, 100% { transform: translateY(0px); } - 25% { transform: translateY(-2px); } - 50% { transform: translateY(0px); } - 75% { transform: translateY(2px); } - } - @keyframes textFloatWave2 { - 0%, 100% { transform: translateY(0px); } - 25% { transform: translateY(2px); } - 50% { transform: translateY(0px); } - 75% { transform: translateY(-2px); } - } - @keyframes textFloatWave3 { - 0%, 100% { transform: translateY(0px); } - 33% { transform: translateY(-1.5px); } - 66% { transform: translateY(1.5px); } + 50% { transform: translateY(-3px); } } .bubble { animation: bubbleFloat 3s infinite ease-in-out; @@ -200,28 +206,9 @@ const getAnimationStyle = (style, colors, width, height) => { .starfish { animation: starfishDrift 25s infinite ease-in-out; } - /* Floating text effects - like text bobbing in water */ - .header { - animation: fadeInAnimation 0.8s ease-in-out forwards, textFloatWave 4s ease-in-out infinite !important; - } - .description { - animation: textFloatWave2 3.5s ease-in-out infinite; - } - .description tspan:nth-child(1) { - animation: textFloatWave 3.8s ease-in-out infinite; - } - .description tspan:nth-child(2) { - animation: textFloatWave2 3.5s ease-in-out 0.3s infinite; - } - .description tspan:nth-child(3) { - animation: textFloatWave3 4.2s ease-in-out 0.6s infinite; - } - .gray { - animation: textFloatWave3 3.2s ease-in-out 0.2s infinite; - } - /* Stats floating */ - g[data-testid="main-card-body"] > g { - animation: textFloatWave2 3.8s ease-in-out 0.4s infinite; + /* Character-by-character wave effect */ + .wave-char { + animation: letterWave 2s ease-in-out infinite; }`; // SVG filter for jellyfish glow @@ -704,11 +691,17 @@ const renderRepoCard = (repo, options = {}) => { ? getAnimationStyle(animation_style, colors, 400, cardHeight) : { css: "", svg: "" }; + // Check if we should add wave effect to text + const useBubblesWave = animation_style === "bubbles" && !disable_animations; + if (disable_animations) { card.disableAnimations(); } card.setHideBorder(hide_border); + + // Only hide title if explicitly requested (not for wave effect) card.setHideTitle(shouldHideTitle); + if (compactStatsOnlyLayout) { card.paddingX = 25; } @@ -718,11 +711,40 @@ const renderRepoCard = (repo, options = {}) => { .icon { fill: ${colors.iconColor} } .badge { font: 600 11px 'Segoe UI', Ubuntu, Sans-Serif; } .badge rect { opacity: 0.2 } + .wave-title { font: 600 18px 'Segoe UI', Ubuntu, Sans-Serif; fill: ${colors.titleColor}; } + @supports(-moz-appearance: auto) { + .wave-title { font-size: 15.5px; } + } + ${useBubblesWave ? `g[data-testid="card-title"]:not(:has(.wave-title)) { display: none; }` : ""} ${animationData.css} `); + // Create custom wave title if needed + const customWaveTitle = + useBubblesWave && !shouldHideTitle + ? ` + + + ${icons.contribs} + + + ${wrapTextInWave(header.length > 35 ? `${header.slice(0, 35)}...` : header)} + + + ` + : ""; + return card.render(` ${animationData.svg} + ${customWaveTitle} ${ isTemplate From e836ae02f5d4c810e590516ca3ac8ddaaa46058b Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 10:05:50 +0000 Subject: [PATCH 5/7] feat: Add parameterization and color-morphing to wave animations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enable full customization of the wave effect in bubbles animation with new parameters and add a mesmerizing color-morphing gradient option. New Parameters: 1. wave_speed (default: 2) - Controls wave cycle duration in seconds - Lower values = faster waves - Range: 0.5 to 5 seconds recommended 2. wave_amplitude (default: 3) - Controls vertical movement height in pixels - Higher values = bigger waves - Range: 1 to 10 pixels recommended 3. wave_delay (default: 0.05) - Controls delay between each character in seconds - Determines how fast wave travels horizontally - Range: 0.01 to 0.2 seconds recommended 4. color_morph (default: false) - Enables color morphing gradient effect - Letters cycle through theme colors smoothly - Animation cycle is 3x the wave_speed Color Morphing Implementation: - Animates through titleColor โ†’ iconColor โ†’ textColor โ†’ iconColor โ†’ titleColor - Uses CSS fill animation for smooth transitions - Combines with wave motion for dual-animation effect - Automatically uses theme colors for perfect integration Technical Details: - Parameters passed through API โ†’ renderRepoCard โ†’ getAnimationStyle - wrapTextInWave updated to conditionally add wave-char-morph class - CSS keyframes dynamically generated with user parameters - Proper type conversion (parseFloat) for numeric params in API - Maintains backward compatibility with default values Example Usage: - Fast big waves: wave_speed=1&wave_amplitude=6&wave_delay=0.03 - Slow gentle: wave_speed=3&wave_amplitude=2&wave_delay=0.08 - Color morph: color_morph=true (combines with any wave params) Documentation: - Added Wave Customization Parameters section - New usage examples showing different combinations - Parameter reference with ranges and recommendations - Examples of fast, slow, and color-morphing variations --- ANIMATION_EXAMPLES.md | 57 ++++++++++++++++++++++++++++++++++++++++--- api/pin.js | 8 ++++++ src/cards/repo.js | 48 ++++++++++++++++++++++++++++++------ 3 files changed, 103 insertions(+), 10 deletions(-) diff --git a/ANIMATION_EXAMPLES.md b/ANIMATION_EXAMPLES.md index 7de398f31c613..4b53154978d1c 100644 --- a/ANIMATION_EXAMPLES.md +++ b/ANIMATION_EXAMPLES.md @@ -76,15 +76,29 @@ animation_style=bubbles - 2 glowing jellyfish with wavy tentacles drifting left to right - 2 starfish slowly rotating and drifting right to left - **๐ŸŒŠ Horizontal wave text effect**: Letters ripple like a wave traveling across the text - - Each character animates individually with a staggered delay (0.05s per letter) + - Each character animates individually with a staggered delay - Creates a smooth left-to-right wave motion across the title - - 2-second wave cycle with 3px vertical movement - - Like text floating and undulating in water + - Fully customizable wave parameters (see below) + - Optional color-morphing gradient effect - Jellyfish appear every ~12 seconds with gentle pulsing glow - Starfish drift across every ~15 seconds with slow rotation - All creatures layered behind text for depth - Perfect for: Calm, steady progress projects, marine/ocean themes, underwater aesthetics +**Wave Customization Parameters:** +- `wave_speed` - Wave cycle duration in seconds (default: `2`) + - Lower = faster wave, Higher = slower wave + - Example: `wave_speed=1.5` for faster waves +- `wave_amplitude` - Vertical movement in pixels (default: `3`) + - How high each letter bounces + - Example: `wave_amplitude=5` for bigger waves +- `wave_delay` - Delay between each character in seconds (default: `0.05`) + - Controls how quickly wave travels horizontally + - Example: `wave_delay=0.08` for slower wave travel +- `color_morph` - Enable color morphing gradient (default: `false`) + - Letters cycle through theme colors + - Example: `color_morph=true` + ### 2. **embers** - Burning Particles ๐Ÿ”ฅ Glowing particles pulse and float like hot embers. ``` @@ -148,6 +162,21 @@ animation_style=sparks ![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=robot_blue&animation_style=sparks&show_owner=true&all_stats=true) ``` +### Custom Wave Effect (Fast & Big) +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=mad_scientist&animation_style=bubbles&wave_speed=1&wave_amplitude=6&wave_delay=0.03) +``` + +### Color Morphing Wave +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=electric_laboratory&animation_style=bubbles&color_morph=true) +``` + +### Slow Gentle Wave +```markdown +![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=robot_blue&animation_style=bubbles&wave_speed=3&wave_amplitude=2&wave_delay=0.08) +``` + ### Disable Animations (for static images) ```markdown ![Repo Card](https://your-domain.vercel.app/api/pin?username=hesreallyhim&repo=your-repo&theme=electric_laboratory&disable_animations=true) @@ -202,6 +231,28 @@ Dark background with pulsing radiant center - excellent for core libraries. - Options: `true`, `false` - Default: `false` +### Wave Customization Parameters (bubbles only) +- `wave_speed` - Duration of one wave cycle in seconds + - Range: `0.5` to `5` (recommended) + - Default: `2` + - Example: `wave_speed=1.5` (faster) + +- `wave_amplitude` - Vertical movement height in pixels + - Range: `1` to `10` (recommended) + - Default: `3` + - Example: `wave_amplitude=5` (bigger waves) + +- `wave_delay` - Delay between each character in seconds + - Range: `0.01` to `0.2` (recommended) + - Default: `0.05` + - Example: `wave_delay=0.08` (slower horizontal travel) + +- `color_morph` - Enable color morphing gradient effect + - Options: `true`, `false` + - Default: `false` + - Cycles through title, icon, and text colors + - Example: `color_morph=true` + ### All Compatible Parameters You can combine animations with all existing repo card parameters: - `theme` - Choose from 65+ themes (including 5 new cybernetic ones) diff --git a/api/pin.js b/api/pin.js index a4073b630791e..d988e37d1b4e0 100644 --- a/api/pin.js +++ b/api/pin.js @@ -44,6 +44,10 @@ export default async (req, res) => { age_metric, animation_style, disable_animations, + wave_speed, + wave_amplitude, + wave_delay, + color_morph, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -120,6 +124,10 @@ export default async (req, res) => { age_metric: age_metric || "first", animation_style: animation_style || "none", disable_animations: parseBoolean(disable_animations), + wave_speed: wave_speed ? parseFloat(wave_speed) : 2, + wave_amplitude: wave_amplitude ? parseFloat(wave_amplitude) : 3, + wave_delay: wave_delay ? parseFloat(wave_delay) : 0.05, + color_morph: parseBoolean(color_morph), }), ); } catch (err) { diff --git a/src/cards/repo.js b/src/cards/repo.js index e2d4e72342a37..dedca620d00dc 100644 --- a/src/cards/repo.js +++ b/src/cards/repo.js @@ -25,15 +25,22 @@ const DESCRIPTION_MAX_LINES = 3; * @param {string} text The text to wrap. * @param {number} baseDelay Base delay in seconds before wave starts. * @param {number} delayPerChar Delay in seconds between each character. + * @param {boolean} colorMorph Whether to enable color morphing effect. * @returns {string} SVG tspan elements with wave animation. */ -const wrapTextInWave = (text, baseDelay = 0, delayPerChar = 0.05) => { +const wrapTextInWave = ( + text, + baseDelay = 0, + delayPerChar = 0.05, + colorMorph = false, +) => { return Array.from(text) .map((char, i) => { const delay = baseDelay + i * delayPerChar; // Preserve spaces const displayChar = char === " " ? "\u00A0" : char; - return `${displayChar}`; + const morphClass = colorMorph ? " wave-char-morph" : ""; + return `${displayChar}`; }) .join(""); }; @@ -45,15 +52,21 @@ const wrapTextInWave = (text, baseDelay = 0, delayPerChar = 0.05) => { * @param {object} colors Card colors for theming animations. * @param {number} width Card width. * @param {number} height Card height. + * @param {object} waveParams Wave animation parameters. * @returns {{css: string, svg: string}} Animation CSS and SVG elements. */ -const getAnimationStyle = (style, colors, width, height) => { +const getAnimationStyle = (style, colors, width, height, waveParams = {}) => { if (!style || style === "none") { return { css: "", svg: "" }; } const iconColor = colors.iconColor || "38bdf8"; const titleColor = colors.titleColor || "00d9ff"; + const textColor = colors.textColor || "434d58"; + + // Wave parameters with defaults + const waveSpeed = waveParams.speed || 2; // seconds + const waveAmplitude = waveParams.amplitude || 3; // pixels switch (style) { case "bubbles": { @@ -191,7 +204,14 @@ const getAnimationStyle = (style, colors, width, height) => { } @keyframes letterWave { 0%, 100% { transform: translateY(0px); } - 50% { transform: translateY(-3px); } + 50% { transform: translateY(-${waveAmplitude}px); } + } + @keyframes colorMorph { + 0% { fill: #${titleColor}; } + 25% { fill: #${iconColor}; } + 50% { fill: #${textColor}; } + 75% { fill: #${iconColor}; } + 100% { fill: #${titleColor}; } } .bubble { animation: bubbleFloat 3s infinite ease-in-out; @@ -208,7 +228,11 @@ const getAnimationStyle = (style, colors, width, height) => { } /* Character-by-character wave effect */ .wave-char { - animation: letterWave 2s ease-in-out infinite; + animation: letterWave ${waveSpeed}s ease-in-out infinite; + } + /* Color morphing effect */ + .wave-char-morph { + animation: letterWave ${waveSpeed}s ease-in-out infinite, colorMorph ${waveSpeed * 3}s ease-in-out infinite; }`; // SVG filter for jellyfish glow @@ -499,6 +523,10 @@ const renderRepoCard = (repo, options = {}) => { age_metric = "first", animation_style = "none", disable_animations = false, + wave_speed = 2, + wave_amplitude = 3, + wave_delay = 0.05, + color_morph = false, } = options; const lineHeight = 10; @@ -687,8 +715,14 @@ const renderRepoCard = (repo, options = {}) => { // Get animation styles if enabled const hasAnimation = !disable_animations && animation_style !== "none"; + const waveParams = { + speed: wave_speed, + amplitude: wave_amplitude, + delay: wave_delay, + colorMorph: color_morph, + }; const animationData = hasAnimation - ? getAnimationStyle(animation_style, colors, 400, cardHeight) + ? getAnimationStyle(animation_style, colors, 400, cardHeight, waveParams) : { css: "", svg: "" }; // Check if we should add wave effect to text @@ -736,7 +770,7 @@ const renderRepoCard = (repo, options = {}) => { ${icons.contribs} - ${wrapTextInWave(header.length > 35 ? `${header.slice(0, 35)}...` : header)} + ${wrapTextInWave(header.length > 35 ? `${header.slice(0, 35)}...` : header, 0, wave_delay, color_morph)} ` From b391d238b171d41ebc17f23d036f18698e61a8d2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 10:52:11 +0000 Subject: [PATCH 6/7] feat: Add blinking eye and ring of fire animations to stats card rank circle Add two creative animation styles that replace the standard rank circle in stats cards: - "eye": Blinking eyeball with animated eyelids, dilating pupil, and iris colored by theme - "fire": Ring of 12 flickering flames with glowing center and animated rank letter Features: - Animated eyelid blink effect (4s cycle) with synchronized top/bottom movement - Pupil dilation animation (3s cycle) for organic eye effect - Ring of fire with 12 flames positioned around 40px radius circle - Flame flicker animation with scale and position variations - Glowing effects using radial gradients and filters - Theme-aware coloring that adapts to card's title and ring colors - New rank_animation parameter in API: "default", "eye", or "fire" All animations are pure CSS/SVG with no JavaScript required. --- api/index.js | 2 + src/cards/stats.js | 195 ++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 186 insertions(+), 11 deletions(-) diff --git a/api/index.js b/api/index.js index 6ea4ffe0c20e7..b7a4d53d237ed 100644 --- a/api/index.js +++ b/api/index.js @@ -48,6 +48,7 @@ export default async (req, res) => { border_color, rank_icon, show, + rank_animation, } = req.query; res.setHeader("Content-Type", "image/svg+xml"); @@ -131,6 +132,7 @@ export default async (req, res) => { disable_animations: parseBoolean(disable_animations), rank_icon, show: showStats, + rank_animation: rank_animation || "default", }), ); } catch (err) { diff --git a/src/cards/stats.js b/src/cards/stats.js index 6b428d48c34ae..4ee0377a85257 100644 --- a/src/cards/stats.js +++ b/src/cards/stats.js @@ -51,6 +51,153 @@ const LONG_LOCALES = [ "zh-tw", ]; +/** + * Generates creative rank circle animations. + * + * @param {string} style Animation style: eye, fire, default. + * @param {object} colors Card colors for theming. + * @param {string} rankLevel The rank level (A, B, C, etc.). + * @returns {{svg: string, css: string}} Animation SVG and CSS. + */ +const getRankAnimation = (style, colors, rankLevel) => { + const ringColor = colors.ringColor || "4c71f2"; + const titleColor = colors.titleColor || "2f80ed"; + + if (style === "eye") { + // Blinking eyeball animation! + return { + svg: ` + + + + + + + + + + + + + + + + + + + + `, + css: ` + @keyframes blink { + 0%, 45%, 55%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(38px); + } + } + @keyframes blinkBottom { + 0%, 45%, 55%, 100% { + transform: translateY(0); + } + 50% { + transform: translateY(-38px); + } + } + @keyframes pupilDilate { + 0%, 100% { r: 8px; } + 50% { r: 10px; } + } + .eyelid-top { + animation: blink 4s infinite ease-in-out; + } + .eyelid-bottom { + animation: blinkBottom 4s infinite ease-in-out; + } + .pupil { + animation: pupilDilate 3s infinite ease-in-out; + } + `, + }; + } else if (style === "fire") { + // Ring of fire animation! + const flames = Array.from({ length: 12 }, (_, i) => { + const angle = (i * 360) / 12; + const x = -10 + Math.cos((angle * Math.PI) / 180) * 45; + const y = 8 + Math.sin((angle * Math.PI) / 180) * 45; + const delay = i * 0.1; + return ` + + + `; + }).join(""); + + return { + svg: ` + + + + + + + + + + + + + + + + + + + + ${flames} + + + + ${rankLevel} + + + + + + + + + + + + `, + css: ` + @keyframes flicker { + 0%, 100% {transform: scale(1) translateY(0); opacity: 0.9;} + 25% { transform: scale(1.1) translateY(-2px); opacity: 1; } + 50% { transform: scale(0.95) translateY(1px); opacity: 0.8; } + 75% { transform: scale(1.05) translateY(-1px); opacity: 0.95; } + } + @keyframes fireGlow { + 0%, 100% { opacity: 0.6; } + 50% { opacity: 0.9; } + } + .flame { + animation: flicker 1.5s infinite ease-in-out; + } + .fire-rank-text { + animation: fireGlow 2s infinite ease-in-out; + } + `, + }; + } + + // Default: return empty (will use standard rank circle) + return { svg: "", css: "" }; +}; + /** * Create a stats card text item. * @@ -175,6 +322,7 @@ const getStyles = ({ ringColor, show_icons, progress, + rankAnimationCss = "", }) => { return ` .stat { @@ -198,7 +346,7 @@ const getStyles = ({ .rank-percentile-text { font-size: 16px; } - + .not_bold { font-weight: 400 } .bold { font-weight: 700 } .icon { @@ -224,6 +372,7 @@ const getStyles = ({ animation: rankAnimation 1s forwards ease-in-out; } ${process.env.NODE_ENV === "test" ? "" : getProgressAnimation({ progress })} + ${rankAnimationCss} `; }; @@ -294,6 +443,7 @@ const renderStatsCard = (stats, options = {}) => { locale, disable_animations = false, rank_icon = "default", + rank_animation = "default", show = [], } = options; @@ -451,6 +601,21 @@ const renderStatsCard = (stats, options = {}) => { // the lower the user's percentile the better const progress = 100 - rank.percentile; + + // Get rank animation if specified + const rankAnimationData = + rank_animation === "default" + ? { svg: "", css: "" } + : getRankAnimation( + rank_animation, + { + titleColor, + ringColor, + textColor, + }, + rank.level, + ); + const cssStyles = getStyles({ titleColor, ringColor, @@ -458,6 +623,7 @@ const renderStatsCard = (stats, options = {}) => { iconColor, show_icons, progress, + rankAnimationCss: rankAnimationData.css, }); const calculateTextWidth = () => { @@ -553,16 +719,23 @@ const renderStatsCard = (stats, options = {}) => { // Conditionally rendered elements const rankCircle = hide_rank ? "" - : ` - - - - ${rankIcon(rank_icon, rank?.level, rank?.percentile)} - - `; + : rank_animation === "default" + ? ` + + + + ${rankIcon(rank_icon, rank?.level, rank?.percentile)} + + ` + : ` + ${rankAnimationData.svg} + `; // Accessibility Labels const labels = Object.keys(STATS) From 123356ac7744575e37ec98b4162837ef6d057412 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 17 Nov 2025 11:06:18 +0000 Subject: [PATCH 7/7] fix: Correct eye animation eyelid physics to use horizontal sliding doors MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace ellipse-based eyelids with rectangular "doors" that properly simulate blinking motion. The eyelids now: - Use rectangles instead of ellipses to avoid balloon effect - Top eyelid slides down from above (translateY: -38px โ†’ 0) - Bottom eyelid slides up from below (translateY: 38px โ†’ 0) - Both meet horizontally in the middle when closed - Create proper door-opening effect when eye opens This matches the natural physics of eyelids closing horizontally across the eye rather than spherical objects overlapping. --- src/cards/stats.js | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/src/cards/stats.js b/src/cards/stats.js index 4ee0377a85257..0461dd6c36757 100644 --- a/src/cards/stats.js +++ b/src/cards/stats.js @@ -64,11 +64,11 @@ const getRankAnimation = (style, colors, rankLevel) => { const titleColor = colors.titleColor || "2f80ed"; if (style === "eye") { - // Blinking eyeball animation! + // Blinking eyeball animation - eyelids that slide together like doors! return { svg: ` - + @@ -79,29 +79,28 @@ const getRankAnimation = (style, colors, rankLevel) => { - - - - - - - + + + + + + `, css: ` - @keyframes blink { + @keyframes blinkTop { 0%, 45%, 55%, 100% { - transform: translateY(0); + transform: translateY(-38px); } 50% { - transform: translateY(38px); + transform: translateY(0); } } @keyframes blinkBottom { 0%, 45%, 55%, 100% { - transform: translateY(0); + transform: translateY(38px); } 50% { - transform: translateY(-38px); + transform: translateY(0); } } @keyframes pupilDilate { @@ -109,7 +108,7 @@ const getRankAnimation = (style, colors, rankLevel) => { 50% { r: 10px; } } .eyelid-top { - animation: blink 4s infinite ease-in-out; + animation: blinkTop 4s infinite ease-in-out; } .eyelid-bottom { animation: blinkBottom 4s infinite ease-in-out;