深色主题天气动画展示页面设计

By admin, 24 April, 2026
推理
The user wants me to create a beautiful animated weather card system that displays different weather conditions (wind, rain, sunny, snow) with animations. Let me think about the design approach:

1. Dark background design
2. Cards side by side
3. Different animations for each weather type
4. Toggle functionality to switch between weather states
5. Premium feel - worth $20/month

Let me first get design system recommendations and assets.

Let me think about the design direction:
- Dark theme with glassmorphism or neumorphism
- Premium, polished feel
- Smooth animations
- Modern Apple-like aesthetic

Let me call the design system and assets tools.The design system returned minimalism and Inter font. But I want to make this more unique and premium. Let me think about a custom approach:

For a premium weather app, I should use:
- Dark glassmorphism cards
- Beautiful animations for each weather type
- A unique font pairing (not just Inter - maybe something more distinctive)
- Custom CSS animations for weather effects
- Smooth transitions

Let me design this with:
- Dark background with subtle gradient or texture
- 4 cards side by side (responsive)
- Each card showing a weather type with unique animations
- Toggle buttons to switch all cards to one weather type
- Premium feel with glass morphism, subtle borders, shadows

Let me create a sophisticated design:

**Wind**: Moving clouds, swaying elements, wind lines
**Rain**: Falling raindrops, ripple effects/puddles
**Sunny**: Shining rays, bright glow, lens flare
**Snow**: Falling snowflakes, accumulation effect

I'll use:
- A distinctive font like "Syne" or "DM Sans" for headings
- Glassmorphism cards on dark background
- CSS animations with keyframes
- Canvas or pure CSS for weather effects
- Particle effects for snow and rain
- Gradient overlays

Let me build this out. I'll make it really polished.

For the toggle mechanism, I'll create buttons at the top that can change all cards to display a single weather type, plus each card should also have its own default weather.

Actually, let me re-read the requirements:
- Cards side by side showing wind, rain, sunny, snow
- JavaScript toggle to switch between weather states
- Dark background

I think the best approach is:
- 4 cards displayed side by side
- Each card shows a different weather animation by default
- Buttons allow switching ALL cards to show the same weather type (demonstrating each animation on all cards)

Let me use a combination of CSS animations and JavaScript for the weather effects. I'll use canvas elements for rain and snow, and CSS for wind and sunny effects.

Actually, for better performance and quality, let me use pure CSS animations where possible and small canvas elements for particle effects.

Let me design this now:

Color palette for dark theme:
- Background: deep navy/indigo (#0a0e1a or similar)
- Cards: glassmorphism with subtle backdrop blur
- Accent colors per weather:
- Wind: teal/cyan
- Rain: blue/indigo
- Sunny: amber/gold
- Snow: white/ice blue

Font: Let me use "Plus Jakarta Sans" or "Outfit" - something modern but not overused.

Let me structure the HTML:

```
Container (dark bg)
Toggle buttons row
Cards row (4 cards)
Wind Card
Rain Card
Sunny Card
Snow Card
```

For each card, I'll create:
1. A weather icon area with animations
2. Temperature display
3. Weather description
4. Background effects specific to the weather

Let me write the full code now. I want to make this really premium.

For the weather animations:

**Wind**:
- Moving cloud shapes using CSS
- Swaying lines
- Floating leaves/debris particles

**Rain**:
- Falling raindrops (multiple divs with animation)
- Ripple effects at the bottom
- Darker card tint

**Sunny**:
- Rotating sun rays
- Glow effect with box-shadow
- Sparkle particles

**Snow**:
- Falling snowflakes (varied sizes and speeds)
- Gentle accumulation at the bottom
- Soft blue-white glow

I'll use CSS custom properties extensively for theming and make the JavaScript clean.

Let me also think about the toggle - maybe I should have a control panel at the top with weather icons/buttons, and when you click one, all cards transition to show that weather type.

Actually, re-reading: "JavaScript部分需包含切换不同天气状态的功能(例如通过函数或按钮组),以演示每种天气的动画效果。"

So I need functions or buttons to switch weather states. I think having a button group at the top that, when clicked, changes ALL cards to display that weather type would be great. And then maybe a "Show All" button to return to the default state with all 4 different weathers.

Let me code this now. I'll make it really polished.

For the cards, I'll use:
- Backdrop filter blur for glassmorphism
- Subtle border with gradient
- Inner card animations
- Smooth transitions when switching

Here's my plan for the complete implementation:

```html
<!DOCTYPE html>
<html>
...
</html>
```

Let me write the complete code now. I want to make the animations really sophisticated.

For the rain, I'll create multiple raindrop elements that fall at different speeds and delays. For snow, similar but with horizontal drift. For wind, I'll use animated cloud shapes and wind lines. For sunny, rotating rays with opacity animation.

I need to be careful about the dark background. Let me use a deep space-like gradient with subtle stars or dots.

Actually, for a truly premium $20/month feel, I should:
1. Use micro-interactions
2. Smooth easing curves
3. Subtle parallax effects
4. Glass morphism
5. Beautiful typography
6. Perfect spacing
7. Subtle shadows and glows

Let me also add some "extra" polish like:
- Card hover effects that lift and glow
- The active weather button has a nice indicator
- Smooth card content transitions
- Temperature readings with degree symbol styling

Now let me write the complete code. This will be substantial.

For the font, I'll go with "Outfit" for headings and "Plus Jakarta Sans" for body - both from Google Fonts and both modern and elegant.

Actually, let me use "DM Sans" for body and "Space Grotesk" for headings - wait, the instructions say to avoid overused fonts. Let me use "Outfit" paired with "Lexend" or something more unique. How about "Syne" for headings and "DM Sans" for body? That's a nice combo.

Actually, I'll use:
- "Outfit" for headings (clean, geometric, modern)
- "Quicksand" or "Nunito" for body

Hmm, let me just go with a solid choice. "Outfit" for headings and "Nunito Sans" for body text. Both are available on Google Fonts and look premium.

Wait, actually I want to be more unique. Let me use "Bricolage Grotesque" for headings - it's a distinctive, slightly quirky grotesque that feels premium, and "Work Sans" for body.

OK I'm overthinking fonts. Let me go with "Outfit" (headings) and "DM Sans" (body) - both available on Google Fonts and they look great together.

Now for colors:
- Background: #0a0e17 (deep blue-black)
- Wind card accent: #4ee6c8 (teal/mint)
- Rain card accent: #5b8def (blue)
- Sunny card accent: #f7b955 (gold/amber)
- Snow card accent: #c8d6e5 (ice blue)

For glassmorphism cards:
- bg: rgba(255,255,255,0.03) with backdrop-blur
- border: rgba(255,255,255,0.08)
- On hover: slightly more opaque

Let me now write the complete HTML file. This will be long but comprehensive.

I'll structure the weather effects using:
1. CSS keyframe animations for each weather type
2. Dynamically generated particles via JavaScript
3. Smooth transitions between states

For the snow and rain, I'll use JavaScript to create particle elements dynamically inside each card. For wind, I'll use CSS animations on cloud shapes. For sunny, I'll use CSS rotating rays and glow.

Actually, to keep it clean and performant, let me use a mix:
- Rain: Canvas or multiple animated divs
- Snow: Multiple animated divs
- Wind: CSS animated clouds and lines
- Sunny: CSS animated rays

I think using small animated divs (created via JS) for rain and snow will be most visually appealing. Let me create them within each card.

Here's my refined approach:

Each card has:
- A `.weather-visual` container (where the animation happens)
- Weather info (temperature, description)
- The visual container has weather-specific elements

When the toggle switches weather, I'll:
1. Add/remove CSS classes
2. Clear and regenerate particles as needed
3. Animate the transition

Let me write this out now. I want to make sure the code is well-organized.

For the wind animation, I'll create:
- Several cloud shapes (CSS drawn or simple divs with border-radius)
- Wind streak lines
- A subtle sway on elements in the card

For rain:
- 30-40 raindrop elements falling
- Small splash/ripple effects at the bottom
- The card gets a slightly darker, cooler tint

For sunny:
- Rotating rays emanating from a sun circle
- Sparkle effects
- Warm glow

For snow:
- 30-40 snowflake elements with varied sizes
- Gentle horizontal drifting
- Accumulation at the bottom of the card

OK, let me write the complete code now. I need to be very detailed.

I'll also add the toggle buttons at the top with nice styling. Each button will have an icon and label.

Let me finalize the structure and write it all out.

One more thing - the instructions say I must respond with the JSON format containing the HTML. Let me make sure the final output is correct.

Let me structure the code:

```
HTML:
- Main container with dark bg
- Control bar with toggle buttons
- Cards grid (4 columns on desktop, 2 on tablet, 1 on mobile)
- Each card with glassmorphism styling
- Weather visual area + info
- Scripts at the bottom

CSS:
- Custom properties for theming
- Glassmorphism card styles
- Weather-specific animation keyframes
- Responsive design

JS:
- Weather state management
- Particle generation for rain/snow
- Toggle functionality
- Smooth transitions
```

Now let me code this fully. I'm going to make it really beautiful.

Actually, for the particles (rain and snow), I think creating them dynamically with JavaScript and appending them to the card's visual container would be best. When switching weather, I can clear the old particles and create new ones.

Let me also add a subtle background animation - like slowly moving gradient or subtle particles in the overall dark background.

For the dark background, I'll use a subtle radial gradient with very faint animated "stars" or dots to give depth.

OK let me write this now.

One key decision: should I use canvas or DOM elements for particles? I think DOM elements with CSS animations will look better and be easier to control. For rain and snow, I'll create div elements with absolute positioning and CSS animations.

Let me also consider: each card should show its own weather by default, but the toggle should be able to set all 4 cards to the same weather. So the toggle has 5 states: "All", "Wind", "Rain", "Sunny", "Snow" (or just 4 weather buttons where clicking one sets all cards to that weather).

Actually, I'll make it so:
- Default: each card shows its own weather
- Click a weather button: all cards switch to that weather
- Click "Reset" or the same button again: back to default

Let me implement this with a clean state machine.

Now, for the particle animations, I need unique animation names to avoid conflicts. Each card's weather type will have its own set of particles.

Actually, let me simplify: I'll create particle containers within each card's visual area, and the JS will populate them based on the current weather state.

Let me write out the complete code now. This is going to be a substantial implementation.

I want to make sure the raindrops look really good. I'll style them as thin elongated ellipses with a slight gradient. Snowflakes will be small white circles with varying opacity and size. Wind will have cloud shapes and streak lines.

For the sun, I'll create a central circle with rays that rotate. The rays will have varying lengths and opacities.

Let me finalize everything and write the code.

Actually, I realize I should be strategic about the DOM structure. Let me create a reusable card structure where the `.weather-visual` area gets populated with weather-specific elements.

Here's my plan for each card's visual:

Wind visual:
- 3 crescent-shaped clouds at different positions
- 4-5 horizontal wind streak lines
- Clouds animate horizontally with slight vertical bounce

Rain visual:
- 25 small raindrop elements
- 3-4 puddle ripple circles at the bottom
- Dark overlay on the card

Sunny visual:
- Central sun circle with glow
- 8-10 rays rotating
- 3-4 sparkle elements

Snow visual:
- 25 snowflake elements (white circles)
- Varying sizes and fall speeds
- Slight horizontal drift
- Bottom accumulation (white gradient)

OK, I'm going to write this now. Let me make it really good.

For the card transitions, I'll use a fade/scale effect when switching weather types.

Let me also think about the glassmorphism effect more carefully. I want:
- Semi-transparent card backgrounds
- Backdrop blur
- Subtle border (maybe gradient border)
- Inner shadow for depth
- Hover: slight lift and increased glow

For the overall page background, I'll use:
- Deep radial gradient
- Very subtle animated particles (floating dots)
- Maybe a subtle grid pattern

Alright, writing the code now. I need to be comprehensive.

One more thing - the control buttons. I want them to look like pill-shaped toggles with icons. Font Awesome icons:
- Wind: fa-wind
- Rain: fa-cloud-rain
- Sunny: fa-sun
- Snow: fa-snowflake
- Show all: fa-table-cells or fa-grid

Let me code this now. I'll aim for ~500-700 lines of well-organized code.

Let me think about mobile responsiveness:
- Desktop: 4 columns
- Tablet: 2 columns
- Mobile: 1 column (stacked)

The cards should maintain their proportions and the animations should still work well on mobile.

Now, for the temperature display, I'll add realistic temperatures:
- Wind: 18°C
- Rain: 12°C
- Sunny: 28°C
- Snow: -3°C

And descriptions:
- Wind: 微风拂面 / Breezy
- Rain: 细雨绵绵 / Light Rain
- Sunny: 晴空万里 / Clear Sky
- Snow: 雪花纷飞 / Snowfall

Since the requirement says default Chinese text, I'll use Chinese for the main labels but might keep some English for stylistic purposes.

Actually, the instructions say "默认中文文案", so I should use Chinese.

Let me finalize and write the complete code now.

I need to be really careful about the JSON output format. The final output should be:
```json
{
"summary": "...",
"widget": {"type": "full-html", "html": "..."},
"next": [...],
"breakpoint": "none"
}
```

OK here we go.

One more consideration: I should use a texture from transparenttextures.com for the dark background to add depth. Let me use something like "dark-mosaic" or "carbon-fibre" or just rely on CSS-generated patterns.

Actually, for a premium feel, I'll use CSS-generated subtle noise/grain effect with SVG filters or a subtle CSS pattern. The transparent textures approach could work but might not align with the dark theme well. Let me use CSS for subtle background patterns.

I'll add a subtle noise overlay using CSS with a data URL or just use multiple gradient layers.

Alright, truly writing the code now. No more deliberation.

For the record, the key design elements:
1. Deep dark background (#080d18) with subtle radial gradient
2. Glassmorphism cards with backdrop-blur
3. Color-coded weather accents
4. Smooth CSS animations for weather effects
5. JS-powered particle systems for rain and snow
6. Toggle button group at top
7. Responsive grid layout
8. Premium typography (Outfit + Nunito Sans)

I think I also want to add a subtle "reflection" or "light bleed" effect on the cards based on their weather type - like the sunny card casting a warm glow, the snow card casting a cool blue glow, etc.

For the wind card, the clouds will be made using CSS with border-radius to create puffy cloud shapes. Let me use pseudo-elements or multiple divs for this.

Actually, for simplicity and cleanliness, I'll create the cloud shapes using divs with large border-radius values and background colors. A cloud is essentially several overlapping circles/ellipses.

Let me create a simplified but beautiful cloud shape using a div with border-radius and maybe a pseudo-element.

OK for real now, code time:

Actually, I want to also add a "current weather" indicator - like which button is active - using a nice underline or glow effect on the active toggle button.

And for the overall layout, I'll add:
- A title at the top: "天气动态展示" or "WeatherFlow"
- Subtitle
- Button group
- Cards

This will look like a proper demo/showcase page.

Let me also add subtle number animations - when switching weather, the temperature could animate to the new value. But that might be overcomplicating things. Let me keep the temperature static per weather type but have a nice transition when the card content changes.

OK writing now:

Actually one more thought on the cloud shape for the wind animation. I'll use this approach:
- A main cloud body using border-radius
- Multiple smaller circles positioned to create the puffy cloud silhouette
- The entire cloud group animates across the card

Here's a cloud shape using CSS:
```css
.cloud {
width: 60px;
height: 24px;
background: rgba(255,255,255,0.6);
border-radius: 24px;
position: relative;
}
.cloud::before {
content: '';
width: 30px;
height: 30px;
background: inherit;
border-radius: 50%;
position: absolute;
top: -15px;
left: 12px;
}
.cloud::after {
content: '';
width: 22px;
height: 22px;
background: inherit;
border-radius: 50%;
position: absolute;
top: -10px;
left: 32px;
}
```

This creates a nice puffy cloud shape.

For wind lines, I'll use thin elongated rectangles with tapered ends (using gradient or transform skew).

OK, truly writing the complete code now. I'll structure it clearly.

Let me also think about the card dimensions. For the weather visual area, I want it to be large enough to see the animations clearly. Maybe about 200-250px height for the visual area, with the info below.

Card structure:
- Visual area (tall, ~220px)
- Weather icon/info area (~80px)
- Temperature
- Description
- Additional details

Actually, let me keep it simpler:
- Visual area with weather animation (~180px)
- Temperature (large)
- Description
- "Feels like" or additional info

This keeps each card focused and visually impactful.

OK NOW writing the code. For real.

Let me also handle the edge case where all cards show the same weather - each card should independently run its animations so they don't look synchronized (for rain and snow, the particles should fall at different rates).

For the JavaScript, I'll use a class-based approach:
- WeatherCard class for each card
- Methods: setWeather(type), generateParticles(), clearParticles()
- Central controller for the toggle buttons

Alright, here's the complete implementation plan in pseudo:

```
State: {
activeMode: 'all' | 'wind' | 'rain' | 'sunny' | 'snow'
}

Cards: [
{ defaultWeather: 'wind', element: ..., visualContainer: ... },
{ defaultWeather: 'rain', element: ..., visualContainer: ... },
{ defaultWeather: 'sunny', element: ..., visualContainer: ... },
{ defaultWeather: 'snow', element: ..., visualContainer: ... },
]

When toggle clicked:
- Update activeMode
- For each card, set weather to activeMode (or card's default if 'all')
- Clear old particles, generate new ones
- Update card accent colors
- Update temperature/description
```

OK code time. Writing the complete HTML now.

I want to also add a subtle tilt effect on hover for the cards (3D perspective transform). This adds to the premium feel.

For the sunny card, I'll create a sun with CSS:
- Large circle with warm gradient
- Box-shadow for glow
- Rotating ray elements
- Maybe a subtle pulsing animation

For snowflakes, I'll use small white circles with varying opacity (0.3 to 0.9), size (2px to 6px), and animation duration (3s to 8s). They'll have a slight horizontal wobble.

For raindrops, I'll use thin lines (1px wide, 8-15px tall) with a gradient from transparent to light blue, falling at different speeds (0.5s to 1.5s).

Let me write the JavaScript particle generation carefully:

Rain particles:
```javascript
for (let i = 0; i < 40; i++) {
const drop = document.createElement('div');
drop.className = 'raindrop';
drop.style.left = Math.random() * 100 + '%';
drop.style.animationDuration = (0.5 + Math.random() * 1) + 's';
drop.style.animationDelay = Math.random() * 2 + 's';
drop.style.height = (8 + Math.random() * 15) + 'px';
container.appendChild(drop);
}
```

Snow particles:
```javascript
for (let i = 0; i < 35; i++) {
const flake = document.createElement('div');
flake.className = 'snowflake';
flake.style.left = Math.random() * 100 + '%';
flake.style.width = flake.style.height = (2 + Math.random() * 5) + 'px';
flake.style.animationDuration = (3 + Math.random() * 6) + 's';
flake.style.animationDelay = Math.random() * 8 + 's';
flake.style.opacity = 0.3 + Math.random() * 0.7;
container.appendChild(flake);
}
```

Wind clouds (these are CSS-based, not JS-generated):
I'll put 3 cloud elements in the wind visual with CSS animations.

Sun rays:
I'll put the ray elements in the sunny visual with CSS rotate animations.

Alright, truly truly writing the code now.

I realize I should also add sound-related or textual indicators. But since this is visual-only, I'll focus on the animations.

Let me also consider: when the user clicks a weather button to set all cards to that weather, should the cards instantly switch or animate? I think a quick fade transition (200-300ms) would be nice. The visual container fades out, clears, regenerates, then fades in.

For the implementation, I'll:
1. On weather change: add a 'transitioning' class
2. Fade out the visual container (opacity 0, 200ms)
3. Clear particles
4. Set new weather type
5. Generate new particles
6. Fade in (opacity 1, 200ms)

This creates a smooth, polished transition.

One more detail: the card's border/accent color should also transition smoothly when the weather changes. I'll use CSS transitions on the border-color and box-shadow properties.

OK I'm going to write the full HTML now. No more planning.

Let me also think about the card shadow/glow. Each weather type has its own accent color, and the card should have a subtle glow in that color:
- Wind: teal glow
- Rain: blue glow
- Sunny: amber/gold glow
- Snow: ice blue/white glow

This can be achieved with box-shadow using the accent color at low opacity.

Now, for real, the code:

Actually, one thing I want to add is a subtle "breathing" animation on the weather icon area - like the sun slightly pulsing, or the cloud slightly expanding/contracting. This adds life to the cards.

For the wind card, the clouds will gently bob up and down as they move across.

OK I'm just going to write the code. It's going to be long but beautiful.

Let me also handle the responsive design properly. On mobile (< 768px), the cards stack vertically. On tablet (768-1024px), they're in 2 columns. On desktop (> 1024px), they're in 4 columns.

For the toggle buttons, they should also wrap nicely on smaller screens.

Here's the final code structure:

```html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WeatherFlow - 天气动态展示</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
<link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;700&family=DM+Sans:wght@400;500&display=swap" rel="stylesheet">
<style>
/* All custom CSS here */
</style>
</head>
<body>
<!-- Main content -->
<script>
// All JavaScript here
</script>
</body>
</html>
```

Now I'm writing the actual complete HTML. Let me make every detail count.

For the background, I want a subtle starfield effect using small dots. I can achieve this with a CSS radial-gradient pattern or by generating small dots with box-shadow. Let me use a subtle approach with multiple background layers.

Background:
- Base: deep navy #070b15
- Radial gradient: slightly lighter at top center
- Very subtle dot pattern

For the glassmorphism cards:
```css
.card {
background: rgba(255, 255, 255, 0.03);
backdrop-filter: blur(20px);
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 24px;
}
```

Now for the sun animation, I'll create rays using thin rectangles that extend from the center sun circle. These rays will rotate continuously. I'll use 8 rays at 45-degree intervals.

Sun CSS:
- Center circle: warm gradient, ~40px diameter, with glow
- Rays: pseudo-elements or separate divs, rotating

Actually, for the sun rays, I can use a single rotating element with multiple box-shadows to create the ray effect. Or I can use conic-gradient. Let me use a simpler approach:

```css
.sun {
width: 50px;
height: 50px;
border-radius: 50%;
background: radial-gradient(circle, #ffd700, #ff8c00);
box-shadow: 0 0 30px rgba(255, 180, 50, 0.6), 0 0 60px rgba(255, 150, 30, 0.3);
}
.sun-rays {
/* Rotating container with ray lines */
animation: rotate 20s linear infinite;
}
```

And for the rays, I'll create them within the rotating container.

OK let me just write the full code now. I've been planning for way too long.

Final decisions:
- Font: Outfit (headings), DM Sans (body)
- Card size: ~280px wide on desktop
- Visual area: 200px tall
- Particles generated via JS
- Smooth transitions with opacity
- Glassmorphism cards
- Toggle buttons with active state styling

Here we go.
角色
assistant
content

```json
{
"summary": "打造了一款深色主题的精品天气动画展示页面,四张玻璃拟态卡片分别展示风、雨、晴、雪四种天气的精致动画效果。包含顶部切换按钮组,可一键将所有卡片切换为同一天气状态。使用Outfit字体搭配DM Sans,CSS自定义粒子系统实现雨滴、雪花、云朵飘动和阳光射线等动画,营造出价值感强烈的沉浸式天气体验。",
"widget": {
"type": "full-html",
"html": "<!DOCTYPE html>\n<html lang=\"zh-CN\">\n<head>\n <meta charset=\"UTF-8\">\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n <title>WeatherFlow - 天气动态展示</title>\n <script src=\"https://cdn.tailwindcss.com\"></script>\n <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.c…\">\n <link href=\"https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;500;600;70…\" rel=\"stylesheet\">\n <style>\n :root {\n --bg-deep: #060b14;\n --card-bg: rgba(255, 255, 255, 0.025);\n --card-border: rgba(255, 255, 255, 0.06);\n --card-hover-bg: rgba(255, 255, 255, 0.045);\n --accent-wind: #3ee8c7;\n --accent-rain: #5b9cf5;\n --accent-sunny: #f7b948;\n --accent-snow: #c8ddf8;\n --glow-wind: rgba(62, 232, 199, 0.25);\n --glow-rain: rgba(91, 156, 245, 0.25);\n --glow-sunny: rgba(247, 185, 72, 0.3);\n --glow-snow: rgba(200, 221, 248, 0.22);\n --font-display: 'Outfit', sans-serif;\n --font-body: 'DM Sans', sans-serif;\n --transition-smooth: 0.4s cubic-bezier(0.25, 0.1, 0.25, 1);\n }\n\n * {\n margin: 0;\n padding: 0;\n box-sizing: border-box;\n }\n\n body {\n font-family: var(--font-body);\n background: var(--bg-deep);\n min-height: 100vh;\n display: flex;\n align-items: center;\n justify-content: center;\n overflow-x: hidden;\n background-image:\n radial-gradient(ellipse 80% 50% at 50% -10%, rgba(56, 82, 140, 0.22), transparent),\n radial-gradient(ellipse 50% 40% at 20% 80%, rgba(62, 200, 180, 0.06), transparent),\n radial-gradient(ellipse 50% 40% at 80% 80%, rgba(140, 160, 220, 0.06), transparent),\n radial-gradient(circle at 50% 50%, rgba(255, 255, 255, 0.008) 0%, transparent 70%);\n background-attachment: fixed;\n }\n\n body::before {\n content: '';\n position: fixed;\n inset: 0;\n pointer-events: none;\n z-index: 0;\n background:\n radial-gradient(1px 1px at 12% 18%, rgba(255,255,255,0.35), transparent),\n radial-gradient(1px 1px at 28% 6%, rgba(255,255,255,0.3), transparent),\n radial-gradient(1.5px 1.5px at 44% 22%, rgba(255,255,255,0.25), transparent),\n radial-gradient(1px 1px at 62% 10%, rgba(255,255,255,0.3), transparent),\n radial-gradient(1.5px 1.5px at 75% 25%, rgba(255,255,255,0.2), transparent),\n radial-gradient(1px 1px at 88% 8%, rgba(255,255,255,0.3), transparent),\n radial-gradient(1px 1px at 8% 55%, rgba(255,255,255,0.25), transparent),\n radial-gradient(1.5px 1.5px at 33% 42%, rgba(255,255,255,0.2), transparent),\n radial-gradient(1px 1px at 55% 58%, rgba(255,255,255,0.3), transparent),\n radial-gradient(1px 1px at 70% 48%, rgba(255,255,255,0.2), transparent),\n radial-gradient(1.5px 1.5px at 92% 55%, rgba(255,255,255,0.25), transparent),\n radial-gradient(1px 1px at 18% 78%, rgba(255,255,255,0.2), transparent),\n radial-gradient(1px 1px at 42% 85%, rgba(255,255,255,0.25), transparent),\n radial-gradient(1px 1px at 65% 76%, rgba(255,255,255,0.2), transparent),\n radial-gradient(1.5px 1.5px at 82% 82%, rgba(255,255,255,0.2), transparent),\n radial-gradient(1px 1px at 50% 35%, rgba(255,255,255,0.15), transparent);\n }\n\n .main-container {\n position: relative;\n z-index: 1;\n width: 100%;\n max-width: 1300px;\n padding: 2rem 1.5rem;\n }\n\n .header {\n text-align: center;\n margin-bottom: 2.5rem;\n }\n\n .header .logo-text {\n font-family: var(--font-display);\n font-size: 2.2rem;\n font-weight: 700;\n letter-spacing: -0.02em;\n background: linear-gradient(135deg, #e8e8f0 0%, #a0b4d0 50%, #c8d8f0 100%);\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent;\n background-clip: text;\n margin-bottom: 0.3rem;\n }\n\n .header .subtitle {\n font-family: var(--font-body);\n font-size: 0.95rem;\n color: rgba(180, 190, 210, 0.7);\n font-weight: 400;\n letter-spacing: 0.04em;\n }\n\n .toggle-bar {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 0.5rem;\n flex-wrap: wrap;\n margin-bottom: 2.2rem;\n padding: 0.45rem;\n background: rgba(255, 255, 255, 0.03);\n border-radius: 50px;\n border: 1px solid rgba(255, 255, 255, 0.06);\n backdrop-filter: blur(16px);\n -webkit-backdrop-filter: blur(16px);\n max-width: fit-content;\n margin-left: auto;\n margin-right: auto;\n }\n\n .toggle-btn {\n font-family: var(--font-body);\n font-size: 0.82rem;\n font-weight: 500;\n padding: 0.55rem 1.2rem;\n border-radius: 50px;\n border: none;\n cursor: pointer;\n background: transparent;\n color: rgba(200, 210, 225, 0.65);\n transition: all var(--transition-smooth);\n letter-spacing: 0.02em;\n white-space: nowrap;\n position: relative;\n display: flex;\n align-items: center;\n gap: 0.4rem;\n }\n\n .toggle-btn i {\n font-size: 0.75rem;\n transition: transform var(--transition-smooth);\n }\n\n .toggle-btn:hover {\n color: rgba(220, 228, 245, 0.9);\n background: rgba(255, 255, 255, 0.04);\n }\n\n .toggle-btn:hover i {\n transform: scale(1.15);\n }\n\n .toggle-btn.active {\n background: rgba(255, 255, 255, 0.1);\n color: #fff;\n box-shadow: 0 2px 12px rgba(0, 0, 0, 0.3);\n font-weight: 600;\n }\n\n .toggle-btn.active i {\n transform: scale(1.1);\n }\n\n .toggle-btn.active[data-weather=\"wind\"] {\n background: rgba(62, 232, 199, 0.18);\n color: var(--accent-wind);\n box-shadow: 0 0 20px var(--glow-wind);\n }\n .toggle-btn.active[data-weather=\"rain\"] {\n background: rgba(91, 156, 245, 0.18);\n color: var(--accent-rain);\n box-shadow: 0 0 20px var(--glow-rain);\n }\n .toggle-btn.active[data-weather=\"sunny\"] {\n background: rgba(247, 185, 72, 0.18);\n color: var(--accent-sunny);\n box-shadow: 0 0 20px var(--glow-sunny);\n }\n .toggle-btn.active[data-weather=\"snow\"] {\n background: rgba(200, 221, 248, 0.16);\n color: var(--accent-snow);\n box-shadow: 0 0 20px var(--glow-snow);\n }\n\n .cards-grid {\n display: grid;\n grid-template-columns: repeat(4, 1fr);\n gap: 1.2rem;\n width: 100%;\n }\n\n @media (max-width: 1100px) {\n .cards-grid {\n grid-template-columns: repeat(2, 1fr);\n gap: 1rem;\n }\n }\n\n @media (max-width: 600px) {\n .cards-grid {\n grid-template-columns: 1fr;\n gap: 1rem;\n }\n .header .logo-text {\n font-size: 1.6rem;\n }\n .toggle-btn {\n font-size: 0.74rem;\n padding: 0.45rem 0.85rem;\n }\n .toggle-btn span.btn-label {\n display: none;\n }\n .toggle-btn i {\n font-size: 0.9rem;\n margin: 0;\n }\n .toggle-btn {\n padding: 0.5rem 0.7rem;\n }\n }\n\n .weather-card {\n position: relative;\n background: var(--card-bg);\n border: 1px solid var(--card-border);\n border-radius: 26px;\n padding: 1.5rem 1.3rem 1.3rem;\n backdrop-filter: blur(24px);\n -webkit-backdrop-filter: blur(24px);\n cursor: pointer;\n transition: all var(--transition-smooth);\n overflow: hidden;\n display: flex;\n flex-direction: column;\n gap: 0.8rem;\n min-height: 420px;\n }\n\n .weather-card::before {\n content: '';\n position: absolute;\n inset: 0;\n border-radius: 26px;\n opacity: 0;\n transition: opacity var(--transition-smooth);\n pointer-events: none;\n z-index: 0;\n background: radial-gradient(ellipse at 50% 0%, var(--card-glow, transparent) 0%, transparent 70%);\n }\n\n .weather-card:hover::before {\n opacity: 1;\n }\n\n .weather-card:hover {\n background: var(--card-hover-bg);\n border-color: rgba(255, 255, 255, 0.14);\n transform: translateY(-3px);\n box-shadow: 0 20px 50px rgba(0, 0, 0, 0.4), 0 0 0 1px rgba(255, 255, 255, 0.04) inset;\n }\n\n .weather-card[data-weather=\"wind\"] {\n --card-glow: var(--glow-wind);\n }\n .weather-card[data-weather=\"rain\"] {\n --card-glow: var(--glow-rain);\n }\n .weather-card[data-weather=\"sunny\"] {\n --card-glow: var(--glow-sunny);\n }\n .weather-card[data-weather=\"snow\"] {\n --card-glow: var(--glow-snow);\n }\n\n .weather-card[data-weather=\"wind\"]:hover {\n box-shadow: 0 20px 50px rgba(0,0,0,0.4), 0 0 60px rgba(62,232,199,0.12), 0 0 0 1px rgba(255,255,255,0.04) inset;\n }\n .weather-card[data-weather=\"rain\"]:hover {\n box-shadow: 0 20px 50px rgba(0,0,0,0.4), 0 0 60px rgba(91,156,245,0.12), 0 0 0 1px rgba(255,255,255,0.04) inset;\n }\n .weather-card[data-weather=\"sunny\"]:hover {\n box-shadow: 0 20px 50px rgba(0,0,0,0.4), 0 0 60px rgba(247,185,72,0.14), 0 0 0 1px rgba(255,255,255,0.04) inset;\n }\n .weather-card[data-weather=\"snow\"]:hover {\n box-shadow: 0 20px 50px rgba(0,0,0,0.4), 0 0 60px rgba(200,221,248,0.1), 0 0 0 1px rgba(255,255,255,0.04) inset;\n }\n\n .card-accent-line {\n position: absolute;\n top: 0;\n left: 20%;\n right: 20%;\n height: 1.5px;\n border-radius: 0 0 50% 50%;\n transition: all var(--transition-smooth);\n opacity: 0.5;\n }\n\n .weather-card[data-weather=\"wind\"] .card-accent-line { background: var(--accent-wind); box-shadow: 0 0 12px var(--glow-wind); }\n .weather-card[data-weather=\"rain\"] .card-accent-line { background: var(--accent-rain); box-shadow: 0 0 12px var(--glow-rain); }\n .weather-card[data-weather=\"sunny\"] .card-accent-line { background: var(--accent-sunny); box-shadow: 0 0 12px var(--glow-sunny); }\n .weather-card[data-weather=\"snow\"] .card-accent-line { background: var(--accent-snow); box-shadow: 0 0 12px var(--glow-snow); }\n\n .visual-container {\n position: relative;\n width: 100%;\n height: 200px;\n border-radius: 18px;\n overflow: hidden;\n z-index: 1;\n transition: opacity 0.35s ease;\n background: rgba(0, 0, 0, 0.2);\n }\n\n .visual-container.transitioning {\n opacity: 0.3;\n }\n\n .card-info {\n position: relative;\n z-index: 1;\n }\n\n .card-temp {\n font-family: var(--font-display);\n font-size: 3rem;\n font-weight: 700;\n letter-spacing: -0.03em;\n line-height: 1;\n transition: color var(--transition-smooth);\n }\n\n .weather-card[data-weather=\"wind\"] .card-temp { color: var(--accent-wind); }\n .weather-card[data-weather=\"rain\"] .card-temp { color: var(--accent-rain); }\n .weather-card[data-weather=\"sunny\"] .card-temp { color: var(--accent-sunny); }\n .weather-card[data-weather=\"snow\"] .card-temp { color: var(--accent-snow); }\n\n .card-desc {\n font-family: var(--font-body);\n font-size: 0.9rem;\n color: rgba(190, 200, 220, 0.7);\n font-weight: 500;\n letter-spacing: 0.03em;\n }\n\n .card-meta {\n display: flex;\n gap: 1rem;\n font-size: 0.75rem;\n color: rgba(160, 172, 195, 0.55);\n }\n\n .card-meta span {\n display: flex;\n align-items: center;\n gap: 0.3rem;\n }\n\n /* ============ WIND ANIMATIONS ============ */\n .cloud-group {\n position: absolute;\n animation: cloudDrift linear infinite;\n pointer-events: none;\n }\n\n .cloud-shape {\n position: relative;\n width: 55px;\n height: 20px;\n background: rgba(210, 225, 245, 0.5);\n border-radius: 20px;\n filter: blur(1px);\n }\n\n .cloud-shape::before {\n content: '';\n position: absolute;\n width: 26px;\n height: 26px;\n background: inherit;\n border-radius: 50%;\n top: -13px;\n left: 10px;\n }\n\n .cloud-shape::after {\n content: '';\n position: absolute;\n width: 20px;\n height: 20px;\n background: inherit;\n border-radius: 50%;\n top: -8px;\n left: 28px;\n }\n\n .cloud-group.cloud-1 {\n top: 15%;\n animation-duration: 7s;\n animation-delay: 0s;\n }\n .cloud-group.cloud-2 {\n top: 45%;\n animation-duration: 9.5s;\n animation-delay: -3s;\n }\n .cloud-group.cloud-2 .cloud-shape {\n width: 40px;\n height: 15px;\n border-radius: 15px;\n background: rgba(200, 215, 240, 0.4);\n }\n .cloud-group.cloud-2 .cloud-shape::before {\n width: 20px;\n height: 20px;\n top: -10px;\n left: 7px;\n }\n .cloud-group.cloud-2 .cloud-shape::after {\n width: 15px;\n height: 15px;\n top: -6px;\n left: 20px;\n }\n .cloud-group.cloud-3 {\n top: 68%;\n animation-duration: 8s;\n animation-delay: -5s;\n }\n .cloud-group.cloud-3 .cloud-shape {\n width: 48px;\n height: 18px;\n border-radius: 18px;\n background: rgba(190, 210, 238, 0.45);\n }\n .cloud-group.cloud-3 .cloud-shape::before {\n width: 22px;\n height: 22px;\n top: -11px;\n left: 9px;\n }\n .cloud-group.cloud-3 .cloud-shape::after {\n width: 17px;\n height: 17px;\n top: -7px;\n left: 24px;\n }\n\n @keyframes cloudDrift {\n 0% {\n transform: translateX(-80px);\n }\n 100% {\n transform: translateX(calc(100% + 80px));\n }\n }\n\n .wind-line {\n position: absolute;\n height: 1px;\n background: linear-gradient(90deg, transparent, rgba(180, 210, 240, 0.5), transparent);\n animation: windSweep linear infinite;\n pointer-events: none;\n }\n\n .wind-line.wl-1 {\n top: 28%;\n width: 45%;\n animation-duration: 2.5s;\n animation-delay: 0s;\n }\n .wind-line.wl-2 {\n top: 52%;\n width: 35%;\n animation-duration: 3.2s;\n animation-delay: -1s;\n }\n .wind-line.wl-3 {\n top: 74%;\n width: 50%;\n animation-duration: 2.8s;\n animation-delay: -1.8s;\n }\n .wind-line.wl-4 {\n top: 38%;\n width: 30%;\n animation-duration: 3.5s;\n animation-delay: -2.5s;\n }\n .wind-line.wl-5 {\n top: 62%;\n width: 40%;\n animation-duration: 2.9s;\n animation-delay: -0.6s;\n }\n\n @keyframes windSweep {\n 0% {\n transform: translateX(-100%);\n opacity: 0;\n }\n 15% {\n opacity: 0.8;\n }\n 85% {\n opacity: 0.8;\n }\n 100% {\n transform: translateX(calc(200% + 100px));\n opacity: 0;\n }\n }\n\n .sway-leaf {\n position: absolute;\n width: 6px;\n height: 3px;\n background: rgba(160, 200, 180, 0.5);\n border-radius: 50%;\n animation: leafSway ease-in-out infinite;\n pointer-events: none;\n }\n\n @keyframes leafSway {\n 0%, 100% {\n transform: translate(0, 0) rotate(0deg);\n }\n 25% {\n transform: translate(15px, -8px) rotate(20deg);\n }\n 50% {\n transform: translate(30px, 2px) rotate(-10deg);\n }\n 75% {\n transform: translate(15px, 6px) rotate(15deg);\n }\n }\n\n /* ============ RAIN ANIMATIONS ============ */\n .raindrop {\n position: absolute;\n width: 1.5px;\n border-radius: 0 0 2px 2px;\n background: linear-gradient(to bottom, transparent, rgba(140, 180, 230, 0.7));\n animation: rainFall linear infinite;\n pointer-events: none;\n top: -20px;\n }\n\n @keyframes rainFall {\n 0% {\n transform: translateY(-20px);\n opacity: 0;\n }\n 5% {\n opacity: 1;\n }\n 85% {\n opacity: 0.8;\n }\n 100% {\n transform: translateY(210px);\n opacity: 0;\n }\n }\n\n .puddle {\n position: absolute;\n bottom: 20px;\n border-radius: 50%;\n border: 0.5px solid rgba(140, 180, 230, 0.3);\n animation: puddleRipple ease-out infinite;\n pointer-events: none;\n }\n\n @keyframes puddleRipple {\n 0% {\n width: 0;\n height: 0;\n opacity: 0.6;\n }\n 100% {\n width: 50px;\n height: 12px;\n opacity: 0;\n }\n }\n\n /* ============ SUNNY ANIMATIONS ============ */\n .sun-core {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 52px;\n height: 52px;\n border-radius: 50%;\n background: radial-gradient(circle at 40% 35%, #ffe8a0, #f7b840 60%, #e89520 100%);\n box-shadow:\n 0 0 40px rgba(247, 185, 72, 0.5),\n 0 0 80px rgba(247, 160, 40, 0.25),\n 0 0 140px rgba(247, 140, 20, 0.12);\n animation: sunPulse 3s ease-in-out infinite;\n z-index: 2;\n }\n\n @keyframes sunPulse {\n 0%, 100% {\n box-shadow: 0 0 40px rgba(247, 185, 72, 0.5), 0 0 80px rgba(247, 160, 40, 0.25), 0 0 140px rgba(247, 140, 20, 0.12);\n }\n 50% {\n box-shadow: 0 0 55px rgba(247, 185, 72, 0.65), 0 0 100px rgba(247, 160, 40, 0.35), 0 0 160px rgba(247, 140, 20, 0.18);\n }\n }\n\n .sun-rays-container {\n position: absolute;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: 120px;\n height: 120px;\n animation: rayRotate 25s linear infinite;\n z-index: 1;\n }\n\n @keyframes rayRotate {\n 0% {\n transform: translate(-50%, -50%) rotate(0deg);\n }\n 100% {\n transform: translate(-50%, -50%) rotate(360deg);\n }\n }\n\n .sun-ray {\n position: absolute;\n top: 50%;\n left: 50%;\n height: 1.5px;\n background: linear-gradient(90deg, rgba(247, 185, 72, 0.5), transparent);\n transform-origin: left center;\n border-radius: 1px;\n }\n\n .sparkle {\n position: absolute;\n width: 3px;\n height: 3px;\n background: #fff;\n border-radius: 50%;\n animation: sparkleFlash ease-in-out infinite;\n pointer-events: none;\n z-index: 3;\n }\n\n @keyframes sparkleFlash {\n 0%, 100% {\n opacity: 0;\n transform: scale(0.3);\n }\n 30% {\n opacity: 1;\n transform: scale(1.3);\n }\n 60% {\n opacity: 0.5;\n transform: scale(0.7);\n }\n }\n\n /* ============ SNOW ANIMATIONS ============ */\n .snowflake {\n position: absolute;\n top: -10px;\n background: rgba(235, 242, 252, 0.85);\n border-radius: 50%;\n animation: snowFall linear infinite;\n pointer-events: none;\n box-shadow: 0 0 4px rgba(200, 220, 250, 0.3);\n }\n\n @keyframes snowFall {\n 0% {\n transform: translateY(-10px) translateX(0) rotate(0deg);\n opacity: 0;\n }\n 8% {\n opacity: 1;\n }\n 50% {\n transform: translateY(100px) translateX(12px) rotate(180deg);\n }\n 90% {\n opacity: 0.7;\n }\n 100% {\n transform: translateY(210px) translateX(-8px) rotate(360deg);\n opacity: 0;\n }\n }\n\n .snow-accumulate {\n position: absolute;\n bottom: 0;\n left: 0;\n right: 0;\n height: 25px;\n background: linear-gradient(to top, rgba(210, 225, 245, 0.25), transparent);\n border-radius: 0 0 18px 18px;\n pointer-events: none;\n }\n\n /* ============ BACKGROUND LENS FLARE (sunny card) ============ */\n .lens-flare {\n position: absolute;\n border-radius: 50%;\n pointer-events: none;\n animation: flareDrift 6s ease-in-out infinite;\n }\n\n @keyframes flareDrift {\n 0%, 100% {\n transform: translate(0, 0);\n opacity: 0.15;\n }\n 33% {\n transform: translate(-8px, 5px);\n opacity: 0.25;\n }\n 66% {\n transform: translate(5px, -3px);\n opacity: 0.18;\n }\n }\n </style>\n</head>\n<body>\n <div class=\"main-container\">\n <!-- Header -->\n <div class=\"header\">\n <div class=\"logo-text\">WeatherFlow</div>\n <div class=\"subtitle\">气象万千 · 动态呈现</div>\n </div>\n\n <!-- Toggle Bar -->\n <div class=\"toggle-bar\" id=\"toggleBar\">\n <button class=\"toggle-btn active\" data-weather=\"all\" aria-label=\"显示全部天气\">\n <i class=\"fa-solid fa-table-cells\"></i>\n <span class=\"btn-label\">全部展示</span>\n </button>\n <button class=\"toggle-btn\" data-weather=\"wind\" aria-label=\"切换为风力\">\n <i class=\"fa-solid fa-wind\"></i>\n <span class=\"btn-label\">风力</span>\n </button>\n <button class=\"toggle-btn\" data-weather=\"rain\" aria-label=\"切换为降雨\">\n <i class=\"fa-solid fa-cloud-rain\"></i>\n <span class=\"btn-label\">降雨</span>\n </button>\n <button class=\"toggle-btn\" data-weather=\"sunny\" aria-label=\"切换为晴天\">\n <i class=\"fa-solid fa-sun\"></i>\n <span class=\"btn-label\">晴天</span>\n </button>\n <button class=\"toggle-btn\" data-weather=\"snow\" aria-label=\"切换为下雪\">\n <i class=\"fa-regular fa-snowflake\"></i>\n <span class=\"btn-label\">下雪</span>\n </button>\n </div>\n\n <!-- Cards Grid -->\n <div class=\"cards-grid\" id=\"cardsGrid\">\n <!-- Wind Card -->\n <div class=\"weather-card\" data-weather=\"wind\" data-default=\"wind\" id=\"card-0\">\n <div class=\"card-accent-line\"></div>\n <div class=\"visual-container\" id=\"visual-0\"></div>\n <div class=\"card-info\">\n <div class=\"card-temp\">18°</div>\n <div class=\"card-desc\">清风徐来</div>\n <div class=\"card-meta\">\n <span><i class=\"fa-solid fa-wind\"></i> 15 km/h</span>\n <span><i class=\"fa-solid fa-droplet\"></i> 45%</span>\n </div>\n </div>\n </div>\n\n <!-- Rain Card -->\n <div class=\"weather-card\" data-weather=\"rain\" data-default=\"rain\" id=\"card-1\">\n <div class=\"card-accent-line\"></div>\n <div class=\"visual-container\" id=\"visual-1\"></div>\n <div class=\"card-info\">\n <div class=\"card-temp\">12°</div>\n <div class=\"card-desc\">细雨绵绵</div>\n <div class=\"card-meta\">\n <span><i class=\"fa-solid fa-cloud-rain\"></i> 85%</span>\n <span><i class=\"fa-solid fa-droplet\"></i> 92%</span>\n </div>\n </div>\n </div>\n\n <!-- Sunny Card -->\n <div class=\"weather-card\" data-weather=\"sunny\" data-default=\"sunny\" id=\"card-2\">\n <div class=\"card-accent-line\"></div>\n <div class=\"visual-container\" id=\"visual-2\"></div>\n <div class=\"card-info\">\n <div class=\"card-temp\">28°</div>\n <div class=\"card-desc\">晴空万里</div>\n <div class=\"card-meta\">\n <span><i class=\"fa-solid fa-sun\"></i> UV 6</span>\n <span><i class=\"fa-solid fa-droplet\"></i> 30%</span>\n </div>\n </div>\n </div>\n\n <!-- Snow Card -->\n <div class=\"weather-card\" data-weather=\"snow\" data-default=\"snow\" id=\"card-3\">\n <div class=\"card-accent-line\"></div>\n <div class=\"visual-container\" id=\"visual-3\"></div>\n <div class=\"card-info\">\n <div class=\"card-temp\">-3°</div>\n <div class=\"card-desc\">雪花纷飞</div>\n <div class=\"card-meta\">\n <span><i class=\"fa-regular fa-snowflake\"></i> 大雪</span>\n <span><i class=\"fa-solid fa-droplet\"></i> 78%</span>\n </div>\n </div>\n </div>\n </div>\n </div>\n\n <script>\n (function() {\n // ---- Configuration ----\n const weatherConfig = {\n wind: {\n temp: '18°',\n desc: '清风徐来',\n meta: '<span><i class=\"fa-solid fa-wind\"></i> 15 km/h</span><span><i class=\"fa-solid fa-droplet\"></i> 45%</span>',\n accent: 'var(--accent-wind)',\n },\n rain: {\n temp: '12°',\n desc: '细雨绵绵',\n meta: '<span><i class=\"fa-solid fa-cloud-rain\"></i> 85%</span><span><i class=\"fa-solid fa-droplet\"></i> 92%</span>',\n accent: 'var(--accent-rain)',\n },\n sunny: {\n temp: '28°',\n desc: '晴空万里',\n meta: '<span><i class=\"fa-solid fa-sun\"></i> UV 6</span><span><i class=\"fa-solid fa-droplet\"></i> 30%</span>',\n accent: 'var(--accent-sunny)',\n },\n snow: {\n temp: '-3°',\n desc: '雪花纷飞',\n meta: '<span><i class=\"fa-regular fa-snowflake\"></i> 大雪</span><span><i class=\"fa-solid fa-droplet\"></i> 78%</span>',\n accent: 'var(--accent-snow)',\n },\n };\n\n const cards = [\n { el: document.getElementById('card-0'), visual: document.getElementById('visual-0'), defaultW: 'wind' },\n { el: document.getElementById('card-1'), visual: document.getElementById('visual-1'), defaultW: 'rain' },\n { el: document.getElementById('card-2'), visual: document.getElementById('visual-2'), defaultW: 'sunny' },\n { el: document.getElementById('card-3'), visual: document.getElementById('visual-3'), defaultW: 'snow' },\n ];\n\n let currentMode = 'all';\n\n // ---- Helper: clear visual container ----\n function clearVisual(container) {\n while (container.firstChild) {\n container.removeChild(container.firstChild);\n }\n }\n\n // ---- Build WIND visual ----\n function buildWind(container) {\n // Clouds\n for (let i = 1; i <= 3; i++) {\n const cg = document.createElement('div');\n cg.className = 'cloud-group cloud-' + i;\n const cs = document.createElement('div');\n cs.className = 'cloud-shape';\n cg.appendChild(cs);\n container.appendChild(cg);\n }\n // Wind lines\n for (let i = 1; i <= 5; i++) {\n const wl = document.createElement('div');\n wl.className = 'wind-line wl-' + i;\n container.appendChild(wl);\n }\n // Swaying leaves\n const leafPositions = [\n { top: '22%', left: '18%', delay: '0s', dur: '3.5s' },\n { top: '48%', left: '55%', delay: '-1.2s', dur: '4s' },\n { top: '70%', left: '30%', delay: '-2.5s', dur: '3.8s' },\n { top: '35%', left: '72%', delay: '-0.7s', dur: '4.2s' },\n { top: '60%', left: '80%', delay: '-3s', dur: '3.3s' },\n ];\n leafPositions.forEach(lp => {\n const leaf = document.createElement('div');\n leaf.className = 'sway-leaf';\n leaf.style.top = lp.top;\n leaf.style.left = lp.left;\n leaf.style.animationDelay = lp.delay;\n leaf.style.animationDuration = lp.dur;\n container.appendChild(leaf);\n });\n }\n\n // ---- Build RAIN visual ----\n function buildRain(container) {\n for (let i = 0; i < 50; i++) {\n const drop = document.createElement('div');\n drop.className = 'raindrop';\n drop.style.left = (Math.random() * 94) + '%';\n drop.style.height = (7 + Math.random() * 16) + 'px';\n drop.style.animationDuration = (0.55 + Math.random() * 0.7) + 's';\n drop.style.animationDelay = (Math.random() * 1.8) + 's';\n drop.style.opacity = (0.4 + Math.random() * 0.5);\n container.appendChild(drop);\n }\n // Puddles\n for (let i = 0; i < 4; i++) {\n const puddle = document.createElement('div');\n puddle.className = 'puddle';\n puddle.style.left = (12 + Math.random() * 70) + '%';\n puddle.style.bottom = (8 + Math.random() * 20) + 'px';\n puddle.style.animationDuration = (2.5 + Math.random() * 3) + 's';\n puddle.style.animationDelay = (Math.random() * 3) + 's';\n container.appendChild(puddle);\n }\n // Subtle dark overlay\n const overlay = document.createElement('div');\n overlay.style.cssText =\n 'position:absolute;inset:0;background:linear-gradient(to bottom,rgba(20,40,70,0.3),rgba(10,20,40,0.5));pointer-events:none;border-radius:18px;';\n container.appendChild(overlay);\n }\n\n // ---- Build SUNNY visual ----\n function buildSunny(container) {\n // Lens flare bg\n const flare = document.createElement('div');\n flare.className = 'lens-flare';\n flare.style.cssText =\n 'width:90px;height:90px;top:10%;left:15%;background:radial-gradient(circle,rgba(255,220,150,0.35),transparent 70%);';\n container.appendChild(flare);\n\n const flare2 = document.createElement('div');\n flare2.className = 'lens-flare';\n flare2.style.cssText =\n 'width:50px;height:50px;top:55%;left:60%;background:radial-gradient(circle,rgba(255,200,120,0.25),transparent 70%);animation-delay:-3s;';\n container.appendChild(flare2);\n\n // Rays\n const raysContainer = document.createElement('div');\n raysContainer.className = 'sun-rays-container';\n for (let i = 0; i < 10; i++) {\n const ray = document.createElement('div');\n ray.className = 'sun-ray';\n const angle = (i * 36);\n ray.style.transform = 'rotate(' + angle + 'deg) translateY(-50%)';\n ray.style.width = (40 + Math.random() * 30) + 'px';\n ray.style.opacity = (0.3 + Math.random() * 0.4);\n raysContainer.appendChild(ray);\n }\n container.appendChild(raysContainer);\n\n // Sun core\n const sun = document.createElement('div');\n sun.className = 'sun-core';\n container.appendChild(sun);\n\n // Sparkles\n for (let i = 0; i < 8; i++) {\n const sp = document.createElement('div');\n sp.className = 'sparkle';\n sp.style.top = (15 + Math.random() * 65) + '%';\n sp.style.left = (10 + Math.random() * 75) + '%';\n sp.style.animationDuration = (2 + Math.random() * 3) + 's';\n sp.style.animationDelay = (Math.random() * 4) + 's';\n sp.style.width = sp.style.height = (2 + Math.random() * 3) + 'px';\n container.appendChild(sp);\n }\n\n // Warm glow overlay\n const glowOverlay = document.createElement('div');\n glowOverlay.style.cssText =\n 'position:absolute;inset:0;background:radial-gradient(ellipse at 50% 50%,rgba(255,180,60,0.12),transparent 65%);pointer-events:none;border-radius:18px;';\n container.appendChild(glowOverlay);\n }\n\n // ---- Build SNOW visual ----\n function buildSnow(container) {\n for (let i = 0; i < 45; i++) {\n const flake = document.createElement('div');\n flake.className = 'snowflake';\n const size = (2 + Math.random() * 5);\n flake.style.width = size + 'px';\n flake.style.height = size + 'px';\n flake.style.left = (Math.random() * 94) + '%';\n flake.style.animationDuration = (3.5 + Math.random() * 6) + 's';\n flake.style.animationDelay = (Math.random() * 8) + 's';\n flake.style.opacity = (0.35 + Math.random() * 0.55);\n container.appendChild(flake);\n }\n // Snow accumulation at bottom\n const accum = document.createElement('div');\n accum.className = 'snow-accumulate';\n container.appendChild(accum);\n // Cool overlay\n const coolOverlay = document.createElement('div');\n coolOverlay.style.cssText =\n 'position:absolute;inset:0;background:linear-gradient(to bottom,rgba(140,170,210,0.15),rgba(180,200,230,0.2));pointer-events:none;border-radius:18px;';\n container.appendChild(coolOverlay);\n }\n\n // ---- Build weather into container ----\n const builders = {\n wind: buildWind,\n rain: buildRain,\n sunny: buildSunny,\n snow: buildSnow,\n };\n\n function setCardWeather(cardData, weatherType) {\n const container = cardData.visual;\n const cardEl = cardData.el;\n\n // Transition out\n container.classList.add('transitioning');\n\n setTimeout(() => {\n clearVisual(container);\n if (builders[weatherType]) {\n builders[weatherType](container);\n }\n // Update card data attribute\n cardEl.setAttribute('data-weather', weatherType);\n // Update card info\n const config = weatherConfig[weatherType];\n cardEl.querySelector('.card-temp').textContent = config.temp;\n cardEl.querySelector('.card-desc').textContent = config.desc;\n cardEl.querySelector('.card-meta').innerHTML = config.meta;\n // Transition in\n requestAnimationFrame(() => {\n container.classList.remove('transitioning');\n });\n }, 300);\n }\n\n function applyMode(mode) {\n currentMode = mode;\n cards.forEach(card => {\n const targetWeather = (mode === 'all') ? card.defaultW : mode;\n setCardWeather(card, targetWeather);\n });\n // Update toggle buttons\n document.querySelectorAll('.toggle-btn').forEach(btn => {\n btn.classList.remove('active');\n if (btn.getAttribute('data-weather') === mode) {\n btn.classList.add('active');\n }\n });\n }\n\n // ---- Initialize all cards ----\n function initAll() {\n cards.forEach(card => {\n const container = card.visual;\n clearVisual(container);\n if (builders[card.defaultW]) {\n builders[card.defaultW](container);\n }\n });\n }\n\n initAll();\n\n // ---- Toggle button listeners ----\n const toggleBar = document.getElementById('toggleBar');\n toggleBar.addEventListener('click', function(e) {\n const btn = e.target.closest('.toggle-btn');\n if (!btn) return;\n const weather = btn.getAttribute('data-weather');\n if (weather === currentMode) return;\n applyMode(weather);\n });\n\n // ---- Optional: click card to reset to its default ----\n cards.forEach((card, idx) => {\n card.el.addEventListener('click', function(e) {\n // Don't trigger if clicking toggle buttons area (delegated)\n if (e.target.closest('.toggle-btn')) return;\n if (currentMode !== 'all') {\n applyMode('all');\n } else {\n // Reset just this card to its default\n setCardWeather(card, card.defaultW);\n }\n });\n });\n\n // ---- Keyboard accessibility for toggle buttons ----\n document.querySelectorAll('.toggle-btn').forEach(btn => {\n btn.addEventListener('keydown', function(e) {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n const weather = this.getAttribute('data-weather');\n if (weather !== currentMode) {\n applyMode(weather);\n }\n }\n });\n });\n\n console.log('WeatherFlow 已就绪 · 四种天气动画已加载');\n })();\n </script>\n</body>\n</html>"
},
"next": [
"增加逐小时天气预报时间轴",
"添加城市搜索与定位功能",
"加入体感温度与空气质量指标",
"制作天气预警通知卡片",
"整合未来7日天气预报图表"
],
"breakpoint": "none"
}
```

total_tokens
23193
summary
打造了一款深色主题的精品天气动画展示页面,四张玻璃拟态卡片分别展示风、雨、晴、雪四种天气的精致动画效果。包含顶部切换按钮组,可一键将所有卡片切换为同一天气状态。使用Outfit字体搭配DM Sans,CSS自定义粒子系统实现雨滴、雪花、云朵飘动和阳光射线等动画,营造出价值感强烈的沉浸式天气体验。
uiParsing
关闭