
Web Cloud Designer
- 122 installs
- 178 repo stars
- Updated July 14, 2026
- erichowens/some_claude_skills
Design cloud architectures—compute, storage, networking, IAM—for scalable web backends with cost and reliability tradeoffs documented.
About
Web cloud designer for SaaS, API, and agent backends. Produces architecture for compute, databases, queues, CDN, and observability on major clouds with clear IAM, networking, and deployment diagrams so teams scale safely without surprise bills.
- Multi-tier and serverless patterns
- IAM, secrets, and network segmentation
- Cost and scaling tradeoff analysis
- HA, backup, and DR planning
- Environment and CI deploy topology
Web Cloud Designer by the numbers
- 122 all-time installs (skills.sh)
- Ranked #534 of 1,039 Cloud & Infrastructure skills by installs in the Skillselion catalog
- Data as of Aug 4, 2026 (Skillselion catalog sync)
npx skills add https://github.com/erichowens/some_claude_skills --skill web-cloud-designerAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 122 |
|---|---|
| repo stars | ★ 178 |
| Last updated | July 14, 2026 |
| Repository | erichowens/some_claude_skills ↗ |
What it does
Design cloud architectures—compute, storage, networking, IAM—for scalable web backends with cost and reliability tradeoffs documented.
Files
Web Cloud Designer
Expert in creating realistic, performant cloud effects for web applications using SVG filters, CSS animations, and layering techniques. Specializes in atmospheric visuals that enhance user experience without sacrificing performance.
When to Use This Skill
Use for:
- Realistic cloud backgrounds and skyboxes
- Weather-themed UI elements and transitions
- Parallax cloud scenes with depth
- Animated atmospheric effects
- Stylized/cartoon cloud designs
- Hero section backgrounds with sky themes
- Loading states with cloud animations
- Game-style cloud layers
Do NOT use for:
- 3D volumetric cloud rendering -> use WebGL/Three.js
- Photo manipulation of real clouds -> use image editing
- Weather data integration -> use weather API skills
- Simple gradient skies without cloud shapes
- Video backgrounds with clouds
Core Techniques Reference
SVG Filter Pipeline
The fundamental cloud effect uses this filter chain:
Source -> feTurbulence -> feDisplacementMap -> feGaussianBlur -> feDiffuseLighting -> Composite1. feTurbulence - The Foundation
Generates Perlin noise that forms cloud shapes.
<feTurbulence
type="fractalNoise" <!-- fractalNoise for clouds (NOT turbulence) -->
baseFrequency="0.01" <!-- 0.005-0.02: lower = larger, rounder shapes -->
numOctaves="4" <!-- 3-5: detail level, >5 diminishing returns -->
seed="42" <!-- Change for shape variation (free!) -->
result="noise"
/>| Parameter | Range | Effect |
|---|---|---|
baseFrequency | 0.005-0.02 | Scale of cloud shapes. 0.005 = giant cumulus, 0.02 = small wisps |
numOctaves | 3-5 | Detail layers. 3 = smooth, 5 = detailed. Above 5 = CPU waste |
seed | 0-999999 | Shape variation. Change this, NOT baseFrequency for variety |
type | fractalNoise | ALWAYS use fractalNoise for clouds (turbulence = fire/water) |
2. feDisplacementMap - Shape Distortion
Creates organic, billowing cloud shapes from the noise.
<feDisplacementMap
in="SourceGraphic"
in2="noise"
scale="80" <!-- 20-170: distortion intensity -->
xChannelSelector="R"
yChannelSelector="G"
/>| Scale Value | Effect |
|---|---|
| 20-50 | Subtle, wispy cirrus |
| 50-100 | Balanced cumulus |
| 100-170 | Dramatic, billowing storm clouds |
3. feGaussianBlur - Edge Softening
CRITICAL: Apply BEFORE displacement for performance (per CSS-Tricks).
<feGaussianBlur
stdDeviation="3" <!-- 2-8 for cloud softness -->
result="blurred"
/>4. feDiffuseLighting - Volumetric Depth
Adds 3D-like shading to flat noise.
<feDiffuseLighting
in="noise"
lighting-color="white"
surfaceScale="2"
result="light"
>
<feDistantLight
azimuth="45" <!-- Sun angle: 0-360 -->
elevation="55" <!-- Sun height: 0-90 -->
/>
</feDiffuseLighting>Cloud Type Recipes
Cumulus (Puffy, Happy Clouds)
<svg width="100%" height="100%">
<defs>
<filter id="cumulus" x="-50%" y="-50%" width="200%" height="200%">
<feTurbulence type="fractalNoise" baseFrequency="0.008"
numOctaves="4" seed="5" result="noise"/>
<feGaussianBlur in="noise" stdDeviation="4" result="blur"/>
<feDisplacementMap in="SourceGraphic" in2="blur" scale="60"/>
</filter>
</defs>
<ellipse cx="200" cy="100" rx="150" ry="80"
fill="white" filter="url(#cumulus)"/>
</svg>Cirrus (Wispy, High Altitude)
<filter id="cirrus">
<feTurbulence type="fractalNoise" baseFrequency="0.02 0.005"
numOctaves="3" seed="12" result="noise"/>
<feGaussianBlur in="noise" stdDeviation="2" result="blur"/>
<feDisplacementMap in="SourceGraphic" in2="blur" scale="25"/>
</filter>Key: Use anisotropic baseFrequency (two values) for stretched, directional wisps.
Stratus (Flat Layers)
<filter id="stratus">
<feTurbulence type="fractalNoise" baseFrequency="0.015 0.003"
numOctaves="3" seed="8" result="noise"/>
<feGaussianBlur stdDeviation="6" result="blur"/>
<feDisplacementMap in="SourceGraphic" in2="blur" scale="30"/>
</filter>Cumulonimbus (Storm Clouds)
<filter id="storm">
<feTurbulence type="fractalNoise" baseFrequency="0.006"
numOctaves="5" seed="99" result="noise"/>
<feGaussianBlur in="noise" stdDeviation="3" result="blur"/>
<feDisplacementMap in="SourceGraphic" in2="blur" scale="150"/>
<feDiffuseLighting in="blur" lighting-color="#8899aa" surfaceScale="3">
<feDistantLight azimuth="230" elevation="25"/>
</feDiffuseLighting>
</filter>Stylized/Cartoon Clouds
<filter id="cartoon">
<feTurbulence type="fractalNoise" baseFrequency="0.012"
numOctaves="2" seed="3" result="noise"/>
<feDisplacementMap in="SourceGraphic" in2="noise" scale="40"/>
<!-- No blur = sharper edges for cartoon look -->
</filter>Layering Strategy
Create depth with multiple cloud layers:
<div class="sky">
<div class="clouds clouds-back"></div>
<div class="clouds clouds-mid"></div>
<div class="clouds clouds-front"></div>
</div>.clouds-back {
filter: url(#cloud-soft);
opacity: 0.3;
animation: drift 120s linear infinite;
transform: scale(1.5);
}
.clouds-mid {
filter: url(#cloud-medium);
opacity: 0.6;
animation: drift 80s linear infinite;
transform: scale(1);
}
.clouds-front {
filter: url(#cloud-sharp);
opacity: 0.9;
animation: drift 50s linear infinite;
transform: scale(0.8);
}Layer Parameter Guide
| Layer | Opacity | Speed | Scale | blur stdDeviation |
|---|---|---|---|---|
| Back (distant) | 0.2-0.4 | 90-120s | 1.3-1.5x | 5-8 |
| Mid | 0.5-0.7 | 50-80s | 1.0x | 3-5 |
| Front (close) | 0.8-1.0 | 30-50s | 0.7-0.9x | 1-3 |
Animation Techniques
CSS Keyframes (Recommended - Best Performance)
@keyframes drift {
from { transform: translateX(-100%); }
to { transform: translateX(100%); }
}
@keyframes morph {
0%, 100% { border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%; }
50% { border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%; }
}
.cloud {
animation:
drift 60s linear infinite,
morph 15s ease-in-out infinite;
}SVG Animate (Use Sparingly - CPU Intensive)
<feTurbulence baseFrequency="0.01" numOctaves="4">
<animate
attributeName="baseFrequency"
values="0.008;0.012;0.008"
dur="20s"
repeatCount="indefinite"
/>
</feTurbulence>WARNING: Animating filter properties recalculates the entire filter. Use only for hero effects, not background loops.
GSAP (Best Control)
gsap.to("#cloud-filter feTurbulence", {
attr: { baseFrequency: 0.015 },
duration: 10,
ease: "sine.inOut",
yoyo: true,
repeat: -1
});3D Parallax (Billboard Technique)
.cloud-layer {
transform-style: preserve-3d;
perspective: 1000px;
}
.cloud {
transform: translateZ(-100px) scale(1.1);
/* Further clouds appear smaller, move slower on scroll */
}Complete Implementation Templates
Template 1: Simple Sky Background
<!DOCTYPE html>
<html>
<head>
<style>
.sky {
position: relative;
width: 100%;
height: 100vh;
background: linear-gradient(180deg, #87CEEB 0%, #E0F6FF 100%);
overflow: hidden;
}
.cloud {
position: absolute;
background: white;
border-radius: 50%;
filter: url(#cloudFilter);
animation: float linear infinite;
}
.cloud-1 { width: 300px; height: 150px; top: 10%; animation-duration: 80s; }
.cloud-2 { width: 400px; height: 180px; top: 30%; animation-duration: 100s; animation-delay: -30s; }
.cloud-3 { width: 250px; height: 120px; top: 50%; animation-duration: 70s; animation-delay: -50s; }
@keyframes float {
from { transform: translateX(-120%); }
to { transform: translateX(120vw); }
}
</style>
</head>
<body>
<svg style="position:absolute;width:0;height:0">
<defs>
<filter id="cloudFilter" x="-50%" y="-50%" width="200%" height="200%">
<feTurbulence type="fractalNoise" baseFrequency="0.01" numOctaves="4" seed="5"/>
<feGaussianBlur stdDeviation="4"/>
<feDisplacementMap in="SourceGraphic" scale="50"/>
</filter>
</defs>
</svg>
<div class="sky">
<div class="cloud cloud-1"></div>
<div class="cloud cloud-2"></div>
<div class="cloud cloud-3"></div>
</div>
</body>
</html>Template 2: Layered Parallax Clouds
<style>
.parallax-sky {
position: relative;
height: 100vh;
background: linear-gradient(to bottom,
#1e3c72 0%,
#2a5298 30%,
#f5af19 90%,
#f12711 100%
);
overflow: hidden;
}
.cloud-layer {
position: absolute;
width: 200%;
height: 100%;
background-repeat: repeat-x;
}
.layer-back {
opacity: 0.3;
filter: url(#cloudBack) blur(2px);
animation: scroll 120s linear infinite;
}
.layer-mid {
opacity: 0.5;
filter: url(#cloudMid);
animation: scroll 80s linear infinite;
}
.layer-front {
opacity: 0.8;
filter: url(#cloudFront);
animation: scroll 45s linear infinite;
}
@keyframes scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}
</style>
<svg style="display:none">
<defs>
<filter id="cloudBack">
<feTurbulence type="fractalNoise" baseFrequency="0.005" numOctaves="3" seed="1"/>
<feDisplacementMap in="SourceGraphic" scale="40"/>
</filter>
<filter id="cloudMid">
<feTurbulence type="fractalNoise" baseFrequency="0.008" numOctaves="4" seed="2"/>
<feDisplacementMap in="SourceGraphic" scale="60"/>
</filter>
<filter id="cloudFront">
<feTurbulence type="fractalNoise" baseFrequency="0.012" numOctaves="4" seed="3"/>
<feDisplacementMap in="SourceGraphic" scale="80"/>
</filter>
</defs>
</svg>Template 3: React Component
import React, { useMemo } from 'react';
interface CloudProps {
type?: 'cumulus' | 'cirrus' | 'stratus' | 'storm';
seed?: number;
className?: string;
}
const CLOUD_CONFIGS = {
cumulus: { baseFrequency: '0.008', numOctaves: 4, scale: 60, blur: 4 },
cirrus: { baseFrequency: '0.02 0.005', numOctaves: 3, scale: 25, blur: 2 },
stratus: { baseFrequency: '0.015 0.003', numOctaves: 3, scale: 30, blur: 6 },
storm: { baseFrequency: '0.006', numOctaves: 5, scale: 150, blur: 3 },
};
export const Cloud: React.FC<CloudProps> = ({
type = 'cumulus',
seed = Math.floor(Math.random() * 1000),
className
}) => {
const filterId = useMemo(() => `cloud-${type}-${seed}`, [type, seed]);
const config = CLOUD_CONFIGS[type];
return (
<>
<svg style={{ position: 'absolute', width: 0, height: 0 }}>
<defs>
<filter id={filterId} x="-50%" y="-50%" width="200%" height="200%">
<feTurbulence
type="fractalNoise"
baseFrequency={config.baseFrequency}
numOctaves={config.numOctaves}
seed={seed}
result="noise"
/>
<feGaussianBlur in="noise" stdDeviation={config.blur} result="blur"/>
<feDisplacementMap in="SourceGraphic" in2="blur" scale={config.scale}/>
</filter>
</defs>
</svg>
<div
className={className}
style={{ filter: `url(#${filterId})` }}
/>
</>
);
};
// Usage:
// <Cloud type="cumulus" seed={42} className="cloud-shape" />Template 4: CSS-Only Box-Shadow Clouds
For simpler, more performant clouds without SVG filters:
.cloud-simple {
width: 200px;
height: 60px;
background: white;
border-radius: 100px;
position: relative;
box-shadow:
/* Main body shadows for volume */
inset -10px -10px 30px rgba(0,0,0,0.05),
inset 10px 10px 30px rgba(255,255,255,0.8),
/* Outer glow */
0 10px 40px rgba(0,0,0,0.1);
}
.cloud-simple::before,
.cloud-simple::after {
content: '';
position: absolute;
background: white;
border-radius: 50%;
}
.cloud-simple::before {
width: 100px;
height: 100px;
top: -50px;
left: 30px;
}
.cloud-simple::after {
width: 70px;
height: 70px;
top: -30px;
left: 100px;
}Performance Optimization
Critical Rules
1. numOctaves 5 or fewer - Above 5 provides diminishing visual returns with exponential CPU cost 2. Blur BEFORE displacement - 40% more efficient than blur after 3. Avoid animating filter properties - Use CSS transforms instead 4. Use `seed` for variation - Free performance vs. changing baseFrequency 5. `will-change: transform` - Only on animated elements, remove when static 6. Batch filter definitions - One <defs> block, reference by ID
Performance Tiers
| Tier | Technique | FPS Target | Use Case |
|---|---|---|---|
| Ultra | CSS box-shadow only | 60fps | Mobile, low-end |
| High | SVG filter, no animation | 60fps | Static backgrounds |
| Medium | SVG filter + CSS transform animation | 45-60fps | Subtle movement |
| Low | SVG filter + <animate> | 30fps | Hero sections only |
Mobile Considerations
@media (prefers-reduced-motion: reduce) {
.cloud {
animation: none;
}
}
@media (max-width: 768px) {
.cloud-layer {
/* Reduce to 2 layers on mobile */
}
.cloud {
filter: url(#cloudSimple); /* Fewer octaves */
}
}Performance Detection
// Detect if device can handle filter animations
const canHandleFilters = () => {
const canvas = document.createElement('canvas');
const gl = canvas.getContext('webgl');
if (!gl) return false;
const debugInfo = gl.getExtension('WEBGL_debug_renderer_info');
const renderer = debugInfo
? gl.getParameter(debugInfo.UNMASKED_RENDERER_WEBGL)
: '';
// Reduce effects on integrated graphics
return !renderer.includes('Intel');
};Framework Integration
Next.js / React
// components/CloudBackground.tsx
'use client';
import { useEffect, useState } from 'react';
export function CloudBackground() {
const [reducedMotion, setReducedMotion] = useState(false);
useEffect(() => {
const mq = window.matchMedia('(prefers-reduced-motion: reduce)');
setReducedMotion(mq.matches);
mq.addEventListener('change', (e) => setReducedMotion(e.matches));
}, []);
return (
<div className="cloud-container">
{/* SVG defs in portal to document head */}
{/* Cloud layers */}
</div>
);
}Vue 3
<template>
<div class="sky-background">
<CloudFilter />
<div
v-for="cloud in clouds"
:key="cloud.id"
class="cloud"
:style="cloud.style"
/>
</div>
</template>
<script setup>
import { computed } from 'vue';
import CloudFilter from './CloudFilter.vue';
const clouds = computed(() =>
Array.from({ length: 5 }, (_, i) => ({
id: i,
style: {
animationDuration: `${60 + i * 20}s`,
animationDelay: `${-i * 15}s`,
top: `${10 + i * 15}%`,
}
}))
);
</script>Tailwind CSS
// tailwind.config.js
module.exports = {
theme: {
extend: {
animation: {
'cloud-drift': 'drift 80s linear infinite',
'cloud-morph': 'morph 15s ease-in-out infinite',
},
keyframes: {
drift: {
from: { transform: 'translateX(-100%)' },
to: { transform: 'translateX(100vw)' },
},
morph: {
'0%, 100%': { borderRadius: '60% 40% 30% 70% / 60% 30% 70% 40%' },
'50%': { borderRadius: '30% 60% 70% 40% / 50% 60% 30% 60%' },
},
},
},
},
};Debugging Tips
Visualize Filter Steps
<!-- Output each filter step to see what's happening -->
<filter id="debug">
<feTurbulence result="step1"/>
<feGaussianBlur in="step1" result="step2"/>
<feDisplacementMap in="SourceGraphic" in2="step2" result="step3"/>
<!-- Tile outputs to see each step -->
<feTile in="step1" result="tile1"/>
<feOffset in="tile1" dx="0" dy="0"/>
</filter>Common Issues
| Problem | Cause | Solution |
|---|---|---|
| Clouds cut off | Filter region too small | Add x="-50%" y="-50%" width="200%" height="200%" |
| Jagged edges | Missing blur | Add feGaussianBlur before displacement |
| No variation | Same seed | Use different seed values |
| Performance issues | Too many octaves | Reduce numOctaves to 3-4 |
| Animation stuttering | Animating filter attrs | Use CSS transform animations instead |
Reference Sources
- CSS-Tricks: "Drawing Realistic Clouds with SVG and CSS"
- LogRocket: "Animated Cloud Generator with SVG CSS"
- Codrops: "SVG Filter Effects with feTurbulence"
- Click to Release: "CSS 3D Clouds" (billboard technique)
- Nephele Cloud Generator tool
- MDN: SVG Filter Primitives documentation
---
Clouds are nature's way of reminding us that even the sky has texture.
Changelog
[1.0.0] - 2026-01-22
Added
- Initial release of web-cloud-designer skill
- Comprehensive SVG filter techniques (feTurbulence, feDisplacementMap, feGaussianBlur, feDiffuseLighting)
- Cloud type recipes: Cumulus, Cirrus, Stratus, Cumulonimbus, Stylized/Cartoon
- Layering strategy for depth with parallax support
- Animation patterns: CSS keyframes, SVG animate, GSAP integration
- Complete implementation templates (vanilla HTML/CSS, React component, Vue 3, Tailwind)
- Performance optimization guidelines and tier system
- Framework integration patterns for Next.js, Vue 3, and Tailwind CSS
- Reference documentation:
references/svg-filter-deep-dive.md- Detailed SVG filter mechanicsreferences/animation-patterns.md- Animation techniques and timingreferences/color-and-lighting.md- Color palettes and lighting effects- Browser compatibility notes
- Debugging tips and common issue solutions
- Accessibility considerations (prefers-reduced-motion support)
Sources Referenced
- CSS-Tricks: "Drawing Realistic Clouds with SVG and CSS"
- LogRocket: "Animated Cloud Generator with SVG CSS"
- Codrops: "SVG Filter Effects with feTurbulence"
- Click to Release: "CSS 3D Clouds" (billboard technique)
- Nephele Cloud Generator tool
- MDN: SVG Filter Primitives documentation
Cloud Animation Patterns
Animation Philosophy
Clouds should move:
- Slowly - Real clouds drift at 10-30 mph, barely perceptible
- Continuously - No stops, no sudden direction changes
- Independently - Each layer at different speeds
- Naturally - Slight variations, not mechanical
CSS Animation Patterns
Basic Drift
@keyframes drift-right {
from { transform: translateX(-100%); }
to { transform: translateX(100vw); }
}
.cloud {
animation: drift-right 80s linear infinite;
}Duration guide:
- Distant clouds: 90-120s
- Mid-layer: 50-80s
- Close clouds: 30-50s
Seamless Loop
For continuous coverage without gaps:
.cloud-track {
width: 200%; /* Double width */
background: url('cloud-pattern.svg') repeat-x;
animation: scroll 60s linear infinite;
}
@keyframes scroll {
from { transform: translateX(0); }
to { transform: translateX(-50%); }
}Subtle Vertical Float
@keyframes float {
0%, 100% { transform: translateY(0) translateX(var(--drift)); }
50% { transform: translateY(-20px) translateX(calc(var(--drift) + 10%)); }
}
.cloud {
--drift: 0%;
animation: float 30s ease-in-out infinite;
}Morphing Shape (CSS Only)
@keyframes morph {
0% {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
transform: rotate(0deg);
}
25% {
border-radius: 50% 50% 40% 60% / 40% 50% 50% 60%;
}
50% {
border-radius: 30% 60% 70% 40% / 50% 60% 30% 60%;
transform: rotate(2deg);
}
75% {
border-radius: 40% 50% 60% 50% / 60% 40% 50% 50%;
}
100% {
border-radius: 60% 40% 30% 70% / 60% 30% 70% 40%;
transform: rotate(0deg);
}
}
.cloud {
animation:
drift-right 80s linear infinite,
morph 20s ease-in-out infinite;
}Scale Breathing
@keyframes breathe {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
.cloud {
animation: breathe 15s ease-in-out infinite;
}Parallax Scroll Patterns
CSS Perspective Parallax
.sky-container {
perspective: 1000px;
perspective-origin: center center;
overflow: hidden;
}
.cloud-layer {
transform-style: preserve-3d;
}
.cloud-distant {
transform: translateZ(-500px) scale(1.5);
}
.cloud-mid {
transform: translateZ(-200px) scale(1.2);
}
.cloud-close {
transform: translateZ(0);
}JavaScript Scroll Parallax
const layers = [
{ el: '.cloud-back', speed: 0.2 },
{ el: '.cloud-mid', speed: 0.5 },
{ el: '.cloud-front', speed: 0.8 },
];
window.addEventListener('scroll', () => {
const scrollY = window.pageYOffset;
layers.forEach(layer => {
const el = document.querySelector(layer.el);
el.style.transform = `translateY(${scrollY * layer.speed}px)`;
});
});Intersection Observer Trigger
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('animate');
}
});
}, { threshold: 0.1 });
document.querySelectorAll('.cloud').forEach(cloud => {
observer.observe(cloud);
});SVG Animate Patterns
Morphing baseFrequency (Use Sparingly)
<feTurbulence baseFrequency="0.01">
<animate
attributeName="baseFrequency"
values="0.008;0.012;0.01;0.008"
dur="30s"
repeatCount="indefinite"
calcMode="spline"
keySplines="0.4 0 0.2 1; 0.4 0 0.2 1; 0.4 0 0.2 1"
/>
</feTurbulence>Warning: Very CPU intensive. Use only for hero sections.
Animating Seed (Not Recommended)
Changing seed causes a full filter recalculation. Instead, cross-fade between elements with different seeds.
Lighting Animation
<feDistantLight azimuth="0" elevation="45">
<animate
attributeName="azimuth"
values="0;360"
dur="120s"
repeatCount="indefinite"
/>
</feDistantLight>This simulates sun movement and is less expensive than turbulence animation.
GSAP Patterns
Timeline-Based Cloud Scene
const tl = gsap.timeline({ repeat: -1 });
// Layer 1: Slow distant clouds
tl.to('.cloud-back', {
x: '100vw',
duration: 120,
ease: 'none',
}, 0);
// Layer 2: Medium speed
tl.to('.cloud-mid', {
x: '100vw',
duration: 80,
ease: 'none',
}, 0);
// Layer 3: Faster foreground
tl.to('.cloud-front', {
x: '100vw',
duration: 50,
ease: 'none',
}, 0);Physics-Based Drift
gsap.to('.cloud', {
x: '100vw',
duration: 60,
ease: 'power1.inOut',
modifiers: {
x: gsap.utils.unitize(x => {
// Add subtle sine wave to path
const progress = parseFloat(x) / window.innerWidth;
const wave = Math.sin(progress * Math.PI * 2) * 20;
return parseFloat(x) + wave;
})
},
repeat: -1,
});Interactive Wind Effect
let windStrength = 0;
document.addEventListener('mousemove', (e) => {
windStrength = (e.clientX / window.innerWidth - 0.5) * 2;
});
gsap.ticker.add(() => {
gsap.to('.cloud', {
x: `+=${windStrength * 2}`,
duration: 0.5,
overwrite: 'auto',
});
});Weather Transition Patterns
Clear to Cloudy
function transitionToCloudy() {
gsap.timeline()
.to('.cloud', {
opacity: 1,
scale: 1,
stagger: 0.5,
duration: 2,
ease: 'power2.out',
})
.to('.sky', {
background: 'linear-gradient(180deg, #87CEEB 0%, #B0C4DE 100%)',
duration: 3,
}, '-=1');
}Storm Buildup
function buildStorm() {
const tl = gsap.timeline();
// Darken sky
tl.to('.sky', {
background: 'linear-gradient(180deg, #2c3e50 0%, #34495e 100%)',
duration: 5,
});
// Speed up clouds
tl.to('.cloud', {
timeScale: 3,
duration: 2,
}, '-=3');
// Increase turbulence (if using SVG animate)
tl.to('.storm-filter feTurbulence', {
attr: { numOctaves: 5 },
duration: 3,
}, '-=2');
// Flash effect
tl.to('.lightning', {
opacity: 1,
duration: 0.1,
repeat: 3,
repeatDelay: 2,
});
return tl;
}Performance-Optimized Patterns
RequestAnimationFrame Loop
let cloudX = -200;
const speed = 0.5; // pixels per frame
function animateClouds() {
cloudX += speed;
if (cloudX > window.innerWidth + 200) {
cloudX = -200;
}
clouds.forEach((cloud, i) => {
const offset = i * 100;
cloud.style.transform = `translateX(${cloudX + offset}px)`;
});
requestAnimationFrame(animateClouds);
}
requestAnimationFrame(animateClouds);CSS Variables for Dynamic Control
.cloud {
--speed: 80s;
--delay: 0s;
animation: drift var(--speed) linear var(--delay) infinite;
}// Control from JavaScript without forcing reflow
document.querySelectorAll('.cloud').forEach((cloud, i) => {
cloud.style.setProperty('--speed', `${60 + i * 20}s`);
cloud.style.setProperty('--delay', `${-i * 15}s`);
});Reduced Motion Support
@media (prefers-reduced-motion: reduce) {
.cloud {
animation: none;
/* Static position */
transform: translateX(20vw);
}
.cloud:nth-child(2) { transform: translateX(50vw); }
.cloud:nth-child(3) { transform: translateX(80vw); }
}const prefersReducedMotion = window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (prefersReducedMotion) {
gsap.globalTimeline.pause();
}Timing Functions Reference
| Easing | Use Case |
|---|---|
linear | Continuous drift |
ease-in-out | Floating, breathing |
power1.inOut | Gentle acceleration |
sine.inOut | Very smooth, natural |
elastic.out | Bouncy cloud appear |
back.out | Overshoot entrance |
Frame Rate Considerations
Target frame rates by animation type:
| Animation | Target FPS | Acceptable FPS |
|---|---|---|
| CSS transform | 60 | 45 |
| SVG animate on transform | 60 | 45 |
| SVG animate on filter | 30 | 24 |
| Filter + transform | 45 | 30 |
If dropping below acceptable FPS: 1. Reduce numOctaves 2. Remove SVG filter animation 3. Use CSS-only clouds 4. Reduce layer count
Cloud Color and Lighting Guide
Natural Cloud Colors
Clouds are never pure white. Their color depends on:
- Time of day (sun angle)
- Atmospheric conditions
- Cloud density
- Surrounding environment
Base Cloud Colors by Condition
| Condition | Highlight | Mid-tone | Shadow | Example Hex |
|---|---|---|---|---|
| Midday sun | Pure white | Light gray | Blue-gray | #FFFFFF, #E8E8E8, #B8C4CE |
| Morning | Warm white | Cream | Soft purple | #FFF8F0, #FFE4CC, #D4C4D8 |
| Sunset | Orange-pink | Coral | Deep purple | #FFB366, #FF8C66, #6B4C7A |
| Overcast | Blue-white | Gray | Blue-gray | #E8EEF4, #C4CCD4, #8899AA |
| Storm | Gray-blue | Dark gray | Near black | #7A8999, #4A5568, #2D3748 |
| Night | Blue-gray | Dark blue | Deep navy | #4A5568, #2D3748, #1A202C |
CSS Custom Properties System
:root {
/* Midday palette */
--cloud-highlight: #FFFFFF;
--cloud-mid: #E8E8E8;
--cloud-shadow: #B8C4CE;
--sky-top: #87CEEB;
--sky-bottom: #E0F6FF;
}
[data-time="sunset"] {
--cloud-highlight: #FFB366;
--cloud-mid: #FF8C66;
--cloud-shadow: #6B4C7A;
--sky-top: #FF6B6B;
--sky-bottom: #FFA07A;
}
[data-time="storm"] {
--cloud-highlight: #7A8999;
--cloud-mid: #4A5568;
--cloud-shadow: #2D3748;
--sky-top: #2C3E50;
--sky-bottom: #34495E;
}
.cloud {
background: linear-gradient(
180deg,
var(--cloud-highlight) 0%,
var(--cloud-mid) 50%,
var(--cloud-shadow) 100%
);
}Sky Gradients
Time of Day Gradients
/* Dawn */
.sky-dawn {
background: linear-gradient(180deg,
#1a1a2e 0%, /* Deep night at top */
#4a3f6b 20%, /* Purple transition */
#f9a825 60%, /* Golden horizon */
#ffcc80 90%, /* Warm glow */
#fff3e0 100% /* Bright horizon line */
);
}
/* Midday */
.sky-midday {
background: linear-gradient(180deg,
#1565c0 0%, /* Deep blue zenith */
#42a5f5 30%, /* Bright blue */
#90caf9 60%, /* Light blue */
#e3f2fd 90%, /* Pale blue horizon */
#ffffff 100% /* Hazy white */
);
}
/* Sunset */
.sky-sunset {
background: linear-gradient(180deg,
#1a237e 0%, /* Indigo top */
#4527a0 15%, /* Deep purple */
#7b1fa2 30%, /* Purple */
#e91e63 50%, /* Pink */
#ff5722 70%, /* Orange */
#ffc107 90%, /* Golden */
#fff59d 100% /* Yellow horizon */
);
}
/* Night */
.sky-night {
background: linear-gradient(180deg,
#000428 0%, /* Nearly black */
#004e92 100% /* Dark blue horizon */
);
}
/* Overcast */
.sky-overcast {
background: linear-gradient(180deg,
#78909c 0%, /* Gray-blue */
#b0bec5 50%, /* Light gray */
#cfd8dc 100% /* Pale gray */
);
}Multi-Stop Realistic Sky
.sky-realistic {
background:
/* Sun glow effect */
radial-gradient(
ellipse 80% 50% at 50% 100%,
rgba(255, 200, 100, 0.3) 0%,
transparent 50%
),
/* Main sky gradient */
linear-gradient(180deg,
#0066cc 0%,
#4da6ff 30%,
#99ccff 60%,
#e6f2ff 85%,
#ffffff 100%
);
}SVG Lighting Effects
Diffuse Lighting for Volume
<filter id="volumetricCloud">
<feTurbulence type="fractalNoise" baseFrequency="0.01"
numOctaves="4" result="noise"/>
<feDiffuseLighting in="noise" lighting-color="#FFF8F0"
surfaceScale="3" result="light">
<feDistantLight azimuth="135" elevation="45"/>
</feDiffuseLighting>
<feComposite in="SourceGraphic" in2="light"
operator="arithmetic" k1="1" k2="0" k3="0" k4="0"/>
</filter>Specular Highlights
<filter id="shinyCloud">
<feTurbulence type="fractalNoise" baseFrequency="0.01"
numOctaves="4" result="noise"/>
<feSpecularLighting in="noise" specularConstant="1.5"
specularExponent="20" lighting-color="white"
result="specular">
<feDistantLight azimuth="225" elevation="60"/>
</feSpecularLighting>
<feComposite in="SourceGraphic" in2="specular" operator="in"/>
</filter>Combined Diffuse + Specular
<filter id="realisticLighting">
<feTurbulence type="fractalNoise" baseFrequency="0.01"
numOctaves="4" result="noise"/>
<!-- Diffuse for overall shading -->
<feDiffuseLighting in="noise" surfaceScale="2"
lighting-color="#FFFEF0" result="diffuse">
<feDistantLight azimuth="225" elevation="55"/>
</feDiffuseLighting>
<!-- Specular for highlights -->
<feSpecularLighting in="noise" specularConstant="0.8"
specularExponent="30" result="specular">
<feDistantLight azimuth="225" elevation="55"/>
</feSpecularLighting>
<!-- Combine -->
<feComposite in="diffuse" in2="SourceGraphic" operator="in" result="lit"/>
<feComposite in="specular" in2="lit" operator="arithmetic"
k1="0" k2="1" k3="1" k4="0"/>
</filter>Sun Position and Lighting
Azimuth Reference
90 (top/north)
|
180 (left) + 0/360 (right)
|
270 (bottom/south)Elevation Reference
90 = directly overhead (noon)
45 = typical afternoon
15 = near horizon (sunrise/sunset)
0 = at horizonTime-Based Lighting Presets
const lightingPresets = {
dawn: { azimuth: 90, elevation: 10, color: '#FFE4B5' },
morning: { azimuth: 120, elevation: 30, color: '#FFF8DC' },
noon: { azimuth: 180, elevation: 75, color: '#FFFFFF' },
afternoon: { azimuth: 225, elevation: 45, color: '#FFFAF0' },
sunset: { azimuth: 270, elevation: 15, color: '#FF8C00' },
dusk: { azimuth: 270, elevation: 5, color: '#483D8B' },
night: { azimuth: 270, elevation: -10, color: '#191970' },
};CSS Gradient Clouds (No SVG)
Box-Shadow Layered Clouds
.cloud-css {
width: 200px;
height: 80px;
background: linear-gradient(
180deg,
var(--cloud-highlight) 0%,
var(--cloud-mid) 60%,
var(--cloud-shadow) 100%
);
border-radius: 100px;
box-shadow:
/* Inner highlights */
inset -5px -5px 20px rgba(255,255,255,0.8),
inset 5px 5px 15px rgba(0,0,0,0.05),
/* Outer shadow */
0 10px 30px rgba(0,0,0,0.1),
/* Ground reflection (optional) */
0 50px 50px -30px rgba(100,150,200,0.2);
}Radial Gradient Puffs
.cloud-puff {
width: 150px;
height: 100px;
background:
radial-gradient(circle at 30% 30%, white 0%, transparent 60%),
radial-gradient(circle at 70% 40%, white 0%, transparent 50%),
radial-gradient(circle at 50% 60%, white 0%, transparent 70%),
radial-gradient(circle at 20% 70%, rgba(200,220,240,0.8) 0%, transparent 60%);
filter: blur(5px);
}Color Transitions
Smooth Time Transition
function interpolateColor(color1, color2, factor) {
const c1 = hexToRgb(color1);
const c2 = hexToRgb(color2);
const r = Math.round(c1.r + (c2.r - c1.r) * factor);
const g = Math.round(c1.g + (c2.g - c1.g) * factor);
const b = Math.round(c1.b + (c2.b - c1.b) * factor);
return rgbToHex(r, g, b);
}
function updateSkyColors(hour) {
// hour is 0-24
const timeOfDay = hour / 24;
// Define color stops
const stops = [
{ time: 0, sky: '#000428', cloud: '#2D3748' },
{ time: 0.25, sky: '#FF6B6B', cloud: '#FFB366' }, // 6am
{ time: 0.5, sky: '#87CEEB', cloud: '#FFFFFF' }, // noon
{ time: 0.75, sky: '#FF6B6B', cloud: '#FF8C66' }, // 6pm
{ time: 1, sky: '#000428', cloud: '#2D3748' },
];
// Find surrounding stops and interpolate
// ... implementation
}CSS Animation for Day Cycle
@keyframes dayNightCycle {
0%, 100% { /* Midnight */
--sky-top: #000428;
--sky-bottom: #004e92;
--cloud-color: #2D3748;
}
25% { /* Dawn */
--sky-top: #FF6B6B;
--sky-bottom: #FFA07A;
--cloud-color: #FFB366;
}
50% { /* Noon */
--sky-top: #87CEEB;
--sky-bottom: #E0F6FF;
--cloud-color: #FFFFFF;
}
75% { /* Dusk */
--sky-top: #4527a0;
--sky-bottom: #ff5722;
--cloud-color: #FF8C66;
}
}
.sky {
animation: dayNightCycle 60s linear infinite;
background: linear-gradient(
180deg,
var(--sky-top),
var(--sky-bottom)
);
}Atmospheric Effects
Haze/Fog Layer
.atmosphere-haze {
position: absolute;
inset: 0;
background: linear-gradient(
180deg,
transparent 0%,
transparent 60%,
rgba(200, 220, 255, 0.3) 80%,
rgba(200, 220, 255, 0.6) 100%
);
pointer-events: none;
}Distance Fog on Clouds
.cloud-distant {
filter:
url(#cloudFilter)
brightness(1.1)
contrast(0.9)
saturate(0.8);
opacity: 0.7;
}
.cloud-mid {
filter: url(#cloudFilter);
}
.cloud-close {
filter:
url(#cloudFilter)
brightness(0.95)
contrast(1.1);
}Color Accessibility
Ensure Readable Text Over Clouds
.text-over-clouds {
/* Dark text with subtle shadow */
color: #1a1a1a;
text-shadow:
0 1px 2px rgba(255,255,255,0.8),
0 0 20px rgba(255,255,255,0.6);
}
/* Or use backdrop */
.text-backdrop {
backdrop-filter: blur(10px) brightness(1.2);
background: rgba(255,255,255,0.3);
padding: 1rem 2rem;
border-radius: 8px;
}Contrast Ratios
Minimum contrast for text over cloud backgrounds:
| Text Size | Minimum Ratio | Recommended |
|---|---|---|
| Body (16px) | 4.5:1 | 7:1 |
| Large (24px+) | 3:1 | 4.5:1 |
| UI elements | 3:1 | 4.5:1 |
Use a contrast checker tool when placing text over variable cloud backgrounds.
SVG Filter Deep Dive for Cloud Effects
Filter Coordinate Systems
filterUnits
<!-- userSpaceOnUse: coordinates in user units (pixels) -->
<filter filterUnits="userSpaceOnUse" x="0" y="0" width="800" height="600">
<!-- objectBoundingBox (default): coordinates as percentages of element -->
<filter filterUnits="objectBoundingBox" x="-50%" y="-50%" width="200%" height="200%">For clouds, always use objectBoundingBox with expanded region to prevent clipping.
primitiveUnits
<!-- objectBoundingBox: filter primitive values scale with element -->
<filter primitiveUnits="objectBoundingBox">
<!-- stdDeviation of 0.05 = 5% of element size -->
<feGaussianBlur stdDeviation="0.05"/>
</filter>
<!-- userSpaceOnUse: filter primitive values in pixels -->
<filter primitiveUnits="userSpaceOnUse">
<!-- stdDeviation in actual pixels -->
<feGaussianBlur stdDeviation="10"/>
</filter>feTurbulence In-Depth
Perlin vs Turbulence
<!-- fractalNoise: smooth, cloudy, organic -->
<feTurbulence type="fractalNoise"/>
<!-- turbulence: sharp, watery, fiery -->
<feTurbulence type="turbulence"/>Always use `fractalNoise` for clouds.
baseFrequency Math
The frequency controls the "zoom level" of the noise:
visual_size = 1 / baseFrequency| baseFrequency | Visual Size | Best For |
|---|---|---|
| 0.003 | ~333px features | Giant cumulus |
| 0.008 | ~125px features | Standard cumulus |
| 0.015 | ~67px features | Smaller clouds |
| 0.03 | ~33px features | Wispy details |
Anisotropic Frequency
Two values create directional stretch:
<!-- Horizontal stretch (cirrus-like) -->
<feTurbulence baseFrequency="0.02 0.005"/>
<!-- Vertical stretch (towering clouds) -->
<feTurbulence baseFrequency="0.005 0.02"/>numOctaves Performance
Each octave doubles the computation:
| octaves | Relative Cost | Visual Impact |
|---|---|---|
| 1 | 1x | Blurry blobs |
| 2 | 2x | Basic shapes |
| 3 | 4x | Good detail |
| 4 | 8x | Fine detail |
| 5 | 16x | Maximum useful |
| 6+ | 32x+ | Diminishing returns |
Seed Strategy
// Generate variety without performance cost
const seeds = [1, 42, 137, 256, 512, 789];
// Use seed based on position for consistent regeneration
const getSeed = (x, y) => Math.abs((x * 31 + y * 17) % 1000);feDisplacementMap Mechanics
Channel Selection
<feDisplacementMap
xChannelSelector="R" <!-- R, G, B, or A -->
yChannelSelector="G"
/>| Channel Combo | Effect |
|---|---|
| R, G | Standard displacement |
| R, R | Horizontal-only stretch |
| G, G | Vertical-only stretch |
| A, A | Uniform scaling |
Scale Values
The scale attribute is in pixels:
<!-- Displacement range: -scale/2 to +scale/2 -->
<feDisplacementMap scale="100"/>
<!-- Pixels displaced from -50 to +50 -->Displacement Formula
P'(x,y) = P(x + scale * (XC(x,y) - 0.5), y + scale * (YC(x,y) - 0.5))Where XC and YC are the selected channel values normalized to 0-1.
feDiffuseLighting for Volume
Light Types
<!-- Distant light: sun-like, parallel rays -->
<feDistantLight azimuth="45" elevation="55"/>
<!-- Point light: nearby source, radial -->
<fePointLight x="100" y="100" z="200"/>
<!-- Spot light: focused beam -->
<feSpotLight x="100" y="100" z="200"
pointsAtX="200" pointsAtY="200" pointsAtZ="0"
limitingConeAngle="30"/>Azimuth and Elevation
azimuth: 0 = right, 90 = top, 180 = left, 270 = bottom
elevation: 0 = horizon, 90 = directly aboveCommon sky lighting:
- Morning: azimuth=90, elevation=15-30
- Noon: azimuth=any, elevation=70-90
- Evening: azimuth=270, elevation=15-30
surfaceScale
Controls how much the input affects lighting:
<feDiffuseLighting surfaceScale="5">
<!-- Higher = more dramatic shadows -->
</feDiffuseLighting>| surfaceScale | Effect |
|---|---|
| 1-2 | Subtle shading |
| 3-5 | Normal clouds |
| 6-10 | Dramatic storm clouds |
Compositing Filters
feComposite Operations
<!-- Multiply: darken overlaps -->
<feComposite operator="arithmetic" k1="0" k2="1" k3="1" k4="0"/>
<!-- Screen: lighten overlaps -->
<feComposite operator="over"/>
<!-- Intersection: only where both exist -->
<feComposite operator="in"/>Building Complex Clouds
<filter id="complexCloud">
<!-- Base cloud shape -->
<feTurbulence type="fractalNoise" baseFrequency="0.008"
numOctaves="4" seed="1" result="mainNoise"/>
<!-- Detail overlay -->
<feTurbulence type="fractalNoise" baseFrequency="0.02"
numOctaves="2" seed="2" result="detailNoise"/>
<!-- Combine noises -->
<feComposite in="mainNoise" in2="detailNoise"
operator="arithmetic" k1="0.7" k2="0.3" k3="0" k4="0"
result="combinedNoise"/>
<!-- Apply displacement -->
<feDisplacementMap in="SourceGraphic" in2="combinedNoise" scale="80"/>
</filter>Performance Profiling
Chrome DevTools
1. Open Performance tab 2. Record while scrolling/animating 3. Look for "Recalculate Style" and "Paint" events 4. Filter events by "filter" keyword
Optimization Checklist
[ ] numOctaves <= 5
[ ] Filter region not excessively large
[ ] No filter animations (use transforms)
[ ] Filters defined once, referenced by ID
[ ] will-change only on animated elements
[ ] Reduced motion media query respected
[ ] Mobile uses simplified filtersMemory Considerations
Each filter creates intermediate buffers:
Buffer size = width * height * 4 bytes * (number of filter steps)A 1920x1080 filter with 5 steps uses ~40MB.
Browser Compatibility
| Feature | Chrome | Firefox | Safari | Edge |
|---|---|---|---|---|
| feTurbulence | Yes | Yes | Yes | Yes |
| feDisplacementMap | Yes | Yes | Yes | Yes |
| feDiffuseLighting | Yes | Yes | Yes | Yes |
| Filter animations | Yes | Yes | Partial | Yes |
Safari caveats:
<animate>on filter attributes may stutter- Prefer CSS animations with transforms
- Test thoroughly on iOS
Advanced: Animated Morphing
For smooth cloud morphing without animating filter attributes:
// Cross-fade between two filtered elements
const cloud1 = document.querySelector('.cloud-1');
const cloud2 = document.querySelector('.cloud-2');
// Alternate visibility with different seeds
gsap.timeline({ repeat: -1 })
.to(cloud1, { opacity: 1, duration: 5 })
.to(cloud1, { opacity: 0, duration: 5 }, '+=5')
.to(cloud2, { opacity: 1, duration: 5 }, '-=5')
.to(cloud2, { opacity: 0, duration: 5 }, '+=5');This avoids recalculating filters while achieving morphing effect.