GLM 5.3 Flash
by Z.aiReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 60 s
- Cost
- $0.012
- Output tokens
- 24,021
- Spent thinking
- 19,590 tokens
- Speed
- 134.1 tok/s
- Console errors
- 0
- First token after
- 643 ms
- First visible text after
- 2 min 39 s
- Prompt tokens
- 499
- Code size
- 12.6 KB
- Finish reason
- stop
- Served by
- Particle AI
- Model id
z-ai/glm-5.3-flash- Reasoning sent
{"effort":"max"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 2026
What it was asked
The prompt
Create a set of four animated weather cards (sunny, rainy, snowy, stormy), made with HTML and CSS only. Requirements: - Absolutely no JavaScript: the file must not contain any <script> tag or inline event handler. - No images, no SVG, no icon fonts: every weather icon is built from HTML elements and pseudo-elements styled with CSS. - Each card shows an animated icon (rotating sun rays, falling rain drops, drifting snowflakes, flashing lightning), a city name, a temperature and a short description. - Each card has its own background gradient matching the weather. - The cards are displayed in a responsive grid (4 per row on desktop, stacked on mobile) and lift up on hover. - Smooth infinite animations using @keyframes, and respect prefers-reduced-motion.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task. Output rules: - Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block. - Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task. - Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw. - The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar. - Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction. - Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session. - Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. - The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. - The code must run with zero errors in the console of the latest Chrome.
Code run-1.html · 373 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animated Weather Cards — Pure CSS</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
color: #eaf1fb;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 42px 22px;
background:
radial-gradient(1100px 560px at 72% -12%, rgba(92, 134, 224, .20), transparent 62%),
radial-gradient(900px 520px at 8% 112%, rgba(64, 168, 205, .14), transparent 62%),
#0e1420;
}
.page { width: 100%; max-width: 1120px; }
/* ---------- header ---------- */
.top { text-align: center; margin-bottom: 36px; }
.top h1 {
font-size: 2rem;
font-weight: 800;
letter-spacing: .01em;
background: linear-gradient(92deg, #9ecbff, #ffe3a1 70%, #ffb9a1);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.top p { margin-top: 9px; font-size: .95rem; color: rgba(234, 241, 251, .55); }
/* ---------- grid ---------- */
.grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 22px;
}
@media (max-width: 980px) {
.grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 520px) {
.grid { grid-template-columns: 1fr; gap: 18px; }
.top { margin-bottom: 26px; }
}
/* ---------- card ---------- */
.card {
position: relative;
overflow: hidden;
display: flex;
flex-direction: column;
border-radius: 20px;
border: 1px solid rgba(255, 255, 255, .16);
padding: 24px 22px 20px;
box-shadow: 0 12px 26px rgba(2, 8, 20, .35);
text-shadow: 0 1px 3px rgba(10, 30, 50, .25);
transition: transform .28s ease, box-shadow .28s ease;
}
.card:hover {
transform: translateY(-10px);
box-shadow: 0 26px 46px rgba(2, 8, 20, .5);
}
.card > * { position: relative; z-index: 1; }
/* glass sheen over the top of every card */
.card::after {
content: "";
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
border-radius: inherit;
background: linear-gradient(180deg, rgba(255, 255, 255, .16), rgba(255, 255, 255, 0) 46%);
}
/* per-card sky gradients */
.sunny { background: linear-gradient(170deg, #2e86de 0%, #5fb8ef 55%, #ffe0a3 135%); }
.rainy { background: linear-gradient(170deg, #4a6482 0%, #7fa2c0 100%); }
.snowy { background: linear-gradient(170deg, #6a97ba 0%, #b8d6ea 100%); }
.stormy { background: linear-gradient(170deg, #252d49 0%, #4e5878 100%); }
/* ambient flash behind the storm content (synced with the bolt) */
.stormy::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
background: radial-gradient(130px 95px at 50% 34%, rgba(255, 238, 170, .5), transparent 70%);
opacity: 0;
animation: skyflash 2.6s linear infinite;
}
/* ---------- card text ---------- */
.icon { position: relative; height: 124px; margin-bottom: 14px; flex-shrink: 0; }
.city {
font-size: .76rem;
font-weight: 600;
letter-spacing: .22em;
text-transform: uppercase;
opacity: .88;
}
.temp { font-size: 3.1rem; font-weight: 800; line-height: 1.05; margin: 6px 0 4px; }
.desc { font-size: .92rem; line-height: 1.45; opacity: .88; }
.meta {
margin-top: auto;
padding-top: 10px;
border-top: 1px solid rgba(255, 255, 255, .22);
font-size: .76rem;
letter-spacing: .03em;
opacity: .72;
}
/* ================= SUN ================= */
.sun {
position: absolute;
top: 10px; left: 50%;
width: 104px; height: 104px;
margin-left: -52px;
animation: spin 18s linear infinite;
}
.core {
position: absolute;
left: 50%; top: 50%;
width: 46px; height: 46px;
margin: -23px 0 0 -23px;
border-radius: 50%;
background: radial-gradient(circle at 34% 32%, #fff6c9, #ffd44d 58%, #ff9d2f);
animation: pulse 3.2s ease-in-out infinite;
}
.ray {
position: absolute;
left: 50%; top: 50%;
width: 5px; height: 15px;
margin: -7.5px 0 0 -2.5px;
border-radius: 4px;
background: linear-gradient(180deg, #ffe9a8, #ffbe45);
transform: rotate(var(--a)) translateY(-37px);
}
.r1 { --a: 0deg; } .r2 { --a: 45deg; } .r3 { --a: 90deg; } .r4 { --a: 135deg; }
.r5 { --a: 180deg; } .r6 { --a: 225deg; } .r7 { --a: 270deg; } .r8 { --a: 315deg; }
/* ================= CLOUD (shared) ================= */
.cloud {
position: absolute;
top: 26px; left: 50%;
width: 92px; height: 30px;
margin-left: -46px;
border-radius: 30px;
animation: bob 3.6s ease-in-out infinite alternate;
}
.cloud::before,
.cloud::after { content: ""; position: absolute; border-radius: 50%; background: inherit; }
.cloud::before { top: -24px; left: 16px; width: 42px; height: 42px; }
.cloud::after { top: -15px; right: 15px; width: 30px; height: 30px; }
.rainy .cloud { background: #f2f7fc; filter: drop-shadow(0 4px 6px rgba(15, 35, 60, .22)); }
.snowy .cloud { background: #f8fbff; filter: drop-shadow(0 4px 6px rgba(30, 60, 90, .20)); }
.stormy .cloud { background: #414d72; filter: drop-shadow(0 5px 8px rgba(0, 0, 0, .35)); animation-duration: 2.4s; }
/* ================= RAIN ================= */
.drops { position: absolute; inset: 0; }
.drops span {
position: absolute;
top: 60px;
width: 4px; height: 13px;
border-radius: 4px;
background: linear-gradient(180deg, rgba(255, 255, 255, .95), rgba(126, 196, 255, .85));
opacity: 0;
animation: rainfall 1.3s linear infinite;
}
.rainy .drops span:nth-child(1) { left: 28%; animation-delay: -.15s; }
.rainy .drops span:nth-child(2) { left: 37%; animation-delay: -.60s; }
.rainy .drops span:nth-child(3) { left: 46%; animation-delay: -1.05s; }
.rainy .drops span:nth-child(4) { left: 55%; animation-delay: -.35s; }
.rainy .drops span:nth-child(5) { left: 64%; animation-delay: -.80s; }
.rainy .drops span:nth-child(6) { left: 73%; animation-delay: -1.20s; }
/* storm rain: faster, pushed to the sides of the bolt */
.stormy .drops span {
width: 3px; height: 12px;
background: linear-gradient(180deg, rgba(214, 232, 255, .95), rgba(140, 180, 255, .8));
animation-duration: .8s;
}
.stormy .drops span:nth-child(1) { left: 18%; animation-delay: -.10s; }
.stormy .drops span:nth-child(2) { left: 32%; animation-delay: -.45s; }
.stormy .drops span:nth-child(3) { left: 68%; animation-delay: -.20s; }
.stormy .drops span:nth-child(4) { left: 82%; animation-delay: -.60s; }
/* ================= SNOW ================= */
.flakes { position: absolute; inset: 0; }
.flake {
position: absolute;
top: 56px;
width: var(--s, 12px);
height: var(--s, 12px);
opacity: 0;
animation: snowfall 4s linear infinite;
}
.flake i { position: absolute; inset: 0; display: block; }
.flake::before, .flake::after,
.flake i::before, .flake i::after {
content: "";
position: absolute;
left: 50%; top: 50%;
background: #fff;
border-radius: 2px;
}
.flake::before { width: 2px; height: var(--s, 12px); transform: translate(-50%, -50%); }
.flake::after { width: var(--s, 12px); height: 2px; transform: translate(-50%, -50%); }
.flake i::before { width: 2px; height: calc(var(--s, 12px) * .72); transform: translate(-50%, -50%) rotate(45deg); }
.flake i::after { width: calc(var(--s, 12px) * .72); height: 2px; transform: translate(-50%, -50%) rotate(45deg); }
.snowy .flake:nth-child(1) { --s: 13px; left: 22%; animation-duration: 3.8s; animation-delay: -.4s; }
.snowy .flake:nth-child(2) { --s: 9px; left: 36%; animation-duration: 4.6s; animation-delay: -2.1s; }
.snowy .flake:nth-child(3) { --s: 12px; left: 47%; animation-duration: 3.4s; animation-delay: -1.3s; }
.snowy .flake:nth-child(4) { --s: 10px; left: 58%; animation-duration: 4.2s; animation-delay: -3.0s; }
.snowy .flake:nth-child(5) { --s: 13px; left: 69%; animation-duration: 3.6s; animation-delay: -2.5s; }
.snowy .flake:nth-child(6) { --s: 9px; left: 80%; animation-duration: 4.4s; animation-delay: -1.0s; }
/* ================= LIGHTNING ================= */
.bolt {
position: absolute;
top: 56px; left: 50%;
width: 34px; height: 50px;
margin-left: -17px;
background: linear-gradient(180deg, #ffe873, #ffb62e);
clip-path: polygon(58% 0%, 18% 55%, 45% 55%, 32% 100%, 85% 38%, 52% 38%);
filter: drop-shadow(0 0 10px rgba(255, 214, 90, .55));
opacity: .1;
transform: scale(1);
animation: flash 2.6s linear infinite;
}
/* ================= keyframes ================= */
@keyframes spin { to { transform: rotate(360deg); } }
@keyframes pulse {
0%, 100% { transform: scale(1); box-shadow: 0 0 22px rgba(255, 205, 90, .55); }
50% { transform: scale(1.09); box-shadow: 0 0 36px rgba(255, 205, 90, .85); }
}
@keyframes bob {
from { transform: translateY(0); }
to { transform: translateY(6px); }
}
@keyframes rainfall {
0% { transform: translateY(-4px); opacity: 0; }
12% { opacity: .95; }
70% { opacity: .95; }
100% { transform: translateY(40px); opacity: 0; }
}
@keyframes snowfall {
0% { transform: translate(0, -4px) rotate(0deg); opacity: 0; }
10% { opacity: .95; }
50% { transform: translate(5px, 22px) rotate(160deg); }
88% { opacity: .9; }
100% { transform: translate(-3px, 46px) rotate(320deg); opacity: 0; }
}
@keyframes flash {
0%, 100% { opacity: .1; transform: scale(1); }
5% { opacity: 1; transform: scale(1.06); }
9% { opacity: .25; transform: scale(1); }
13% { opacity: .95; transform: scale(1.08); }
20% { opacity: .55; transform: scale(1); }
27% { opacity: .1; }
}
@keyframes skyflash {
0%, 100% { opacity: 0; }
5% { opacity: .8; }
9% { opacity: .15; }
13% { opacity: .7; }
22% { opacity: 0; }
}
/* ================= reduced motion ================= */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
}
</style>
</head>
<body>
<main class="page">
<header class="top">
<h1>Today's Forecast</h1>
<p>Four skies, animated entirely with HTML & CSS — no JavaScript, no images.</p>
</header>
<section class="grid">
<!-- SUNNY -->
<article class="card sunny">
<div class="icon">
<div class="sun">
<span class="core"></span>
<span class="ray r1"></span>
<span class="ray r2"></span>
<span class="ray r3"></span>
<span class="ray r4"></span>
<span class="ray r5"></span>
<span class="ray r6"></span>
<span class="ray r7"></span>
<span class="ray r8"></span>
</div>
</div>
<h2 class="city">Phoenix</h2>
<div class="temp">31°</div>
<p class="desc">Clear skies and warm sunshine all afternoon.</p>
<div class="meta">H 34° · L 22° · UV high</div>
</article>
<!-- RAINY -->
<article class="card rainy">
<div class="icon">
<div class="cloud"></div>
<div class="drops">
<span></span><span></span><span></span>
<span></span><span></span><span></span>
</div>
</div>
<h2 class="city">London</h2>
<div class="temp">11°</div>
<p class="desc">Light rain drifting in from the coast until evening.</p>
<div class="meta">H 14° · L 8° · Humidity 84%</div>
</article>
<!-- SNOWY -->
<article class="card snowy">
<div class="icon">
<div class="cloud"></div>
<div class="flakes">
<span class="flake"><i></i></span>
<span class="flake"><i></i></span>
<span class="flake"><i></i></span>
<span class="flake"><i></i></span>
<span class="flake"><i></i></span>
<span class="flake"><i></i></span>
</div>
</div>
<h2 class="city">Oslo</h2>
<div class="temp">−3°</div>
<p class="desc">Gentle snow showers settling over the city tonight.</p>
<div class="meta">H −1° · L −7° · Wind 14 km/h</div>
</article>
<!-- STORMY -->
<article class="card stormy">
<div class="icon">
<div class="cloud"></div>
<div class="bolt"></div>
<div class="drops">
<span></span><span></span><span></span><span></span>
</div>
</div>
<h2 class="city">Manila</h2>
<div class="temp">26°</div>
<p class="desc">Thunderstorms building through the afternoon.</p>
<div class="meta">H 28° · L 24° · Storm risk 90%</div>
</article>
</section>
</main>
</body>
</html>Reasoning trace (19,590 tokens)
The task: four animated weather cards, HTML + CSS only, no JavaScript, no script tags, no images/SVG/icon fonts. Icons built from HTML elements and pseudo-elements. Each card: animated icon (rotating sun rays, falling raindrops, drifting snowflakes, flashing lightning), city name, temperature, short description. Each card has its own background gradient. Responsive grid: 4 per row desktop, stacked on mobile. Lift on hover. Smooth infinite animations with @keyframes, respect prefers-reduced-motion.
Note the general system prompt says games need self-playing demo mode etc., but this is a static CSS demo, so first paint will show everything. No JS allowed — the task explicitly overrides. The system prompt says output exactly one fenced code block with the complete HTML file, no text before/after.
Also "no external files, images, fonts or network requests" — fine, pure CSS.
Let me design:
Layout: a page with a header maybe, then a grid of 4 cards. Actually keep it focused: a small title area and the grid. 1280×800 viewport should show all four cards nicely.
Card design: rounded cards, gradient backgrounds, big icon area, city name, temperature large, description.
Icons:
1. Sunny: a circle (sun core) with rotating rays. Rays can be made from a set of spans or pseudo-elements. Since pseudo-elements give only 2 per element, I can use multiple child divs for rays. E.g., `.sun` with 8 ray divs, each rotated 45deg apart, each ray a thin rounded bar with ::before/::after? Simpler: each ray is a child div with a bar shape, positioned via transform rotate around center. Animate the container rotating slowly (rotate 360 over ~20s). Also maybe a subtle pulsing glow.
2. Rainy: a cloud (built from a rounded rectangle plus two pseudo-element circles for puffs) plus rain drops — several small line drops falling with animation, staggered delays. Drops can be child spans positioned under the cloud, animated translateY + opacity fade.
3. Snowy: similar cloud (different tint) plus snowflakes drifting down — flakes as small circles or plus-shaped flakes? Could make flakes as small dots that drift with slight horizontal sway, rotate. Maybe make each flake a tiny "✱"-like shape via pseudo elements? Keep simple: small white circles with sway animation using translateX oscillation. Could also build a six-armed flake using a span with ::before and ::after crossing lines, rotated — that's nicer. But many flakes... Use a mix: maybe 6 flakes, each a tiny dot with glow. To make it look like snowflakes, I can do little crosses: each flake element has ::before (vertical bar) and ::after (horizontal bar) forming a plus, and rotate the whole element. That reads as a snowflake-ish asterisk. Good.
4. Stormy: dark cloud + lightning bolt. Bolt built from a div with clip-path polygon (clip-path is CSS, allowed). Flashing animation: opacity flicker + maybe a background flash on the card? The requirement says "flashing lightning" — animate bolt opacity flicker with steps. Could also add a subtle card glow pulse via animation on a pseudo overlay. Also maybe rain drops too for storm. Keep bolt flashing with occasional double-flash.
Cloud construction:
```
.cloud {
position: relative;
width: 90px; height: 32px;
background: #fff;
border-radius: 20px;
}
.cloud::before, .cloud::after {
content:"";
position:absolute;
background: inherit; (or same color)
border-radius: 50%;
}
.cloud::before { width: 40px; height: 40px; top: -22px; left: 14px; }
.cloud::after { width: 30px; height: 30px; top: -14px; right: 14px; }
```
That makes a puffy cloud.
For rainy cloud color: light gray-white; stormy: dark slate.
Rays for sun:
```
.rays span { position:absolute; left:50%; top:50%; width:4px; height:14px; ... }
```
Better: 8 spans, each `transform: rotate(deg) translateY(-radius)`. Set transform-origin center. Position each span centered then rotate around center: `transform: rotate(var(--a)) translate(0, -34px)`. Each span is a bar with rounded ends. The whole `.rays` container spins slowly. Alternate ray lengths maybe.
Sun core: circle with radial gradient, pulsing scale animation subtle.
Rain drops: spans below cloud: `width:3px; height:12px; border-radius:3px; background: linear-gradient(...)` or just light blue rounded bars. Animate translateY(0→ 40px) with opacity 0→1→0, staggered delays via nth-child. Loop with animation-timing linear, infinite. To make it seamless, use keyframes where drop starts above (hidden) and falls down fading out.
Snowflakes: spans with plus shape:
```
.flake { position:absolute; width:10px; height:10px; }
.flake::before, .flake::after { content:""; position:absolute; background:#fff; border-radius:2px; }
.flake::before { left:4px; top:0; width:2px; height:10px; }
.flake::after { top:4px; left:0; height:2px; width:10px; }
```
Plus rotate the flake slowly and another pair at 45deg? A plus with rotation looks like an asterisk when spinning. Animate fall + sway: use two nested? Can't nest easily with spans... Actually I can: flake span falls (translateY), and its rotation animation on the element itself. Sway horizontal could be combined in keyframes: translateX oscillation inside the fall keyframes. Fine.
Lightning bolt: use clip-path:
```
.bolt {
width: 34px; height: 56px;
background: linear-gradient(#ffe066, #ffb703);
clip-path: polygon(55% 0, 15% 52%, 42% 52%, 30% 100%, 85% 40%, 55% 40%);
}
```
Classic bolt shape. Flash animation: opacity 1 → 0.2 quick flicker with steps or multi-stop keyframes: 0%,100% opacity .15; 8% 1; 12% .3; 16% 1; then long off. Also add drop-shadow glow via filter? filter: drop-shadow is fine CSS.
Also storm cloud darker, maybe slight bobbing animation.
Card hover lift: `transform: translateY(-8px)` with box-shadow increase, transition 0.25s. Also maybe icon speeds up? Keep simple.
Backgrounds:
- Sunny: warm gradient: #ff9a3d → #ffd86b? Actually sunny sky: #4aa8ff to #ffe29a? Let's do a cheerful sky: linear-gradient(160deg, #3fa7ff, #7fd0ff) hmm. Sunny typically blue sky with warm sun. Let's pick: `linear-gradient(165deg, #2f8fe6, #8ed0ff)`? But then sun white-ish. Or warm: `linear-gradient(160deg,#f7b733,#fc4a1a)` too harsh. I'll do sunny as golden: `linear-gradient(160deg, #ffcf5c, #ff9d3c)`? The sun icon would blend. Let me choose distinct palettes:
- Sunny: sky blue to warm: `linear-gradient(165deg, #38a1f0 0%, #7fd4ff 55%, #ffe9a8 100%)` — sun core golden stands out on blue.
- Rainy: gray-blue: `linear-gradient(165deg, #5b7c9e, #8fb0c9)`? Maybe `linear-gradient(165deg,#4b647d,#7e9bb5)`.
- Snowy: icy: `linear-gradient(165deg,#7fb8d8, #cfe9f7)`? or lavender-blue `#8ea8c8` to `#e6f2fb`.
- Stormy: dark: `linear-gradient(165deg,#232a3d,#4a5470)` with maybe purple tint.
Text color white with slight shadow works on all.
Typography: system font stack (no external fonts). Big temperature, small uppercase city? Typically city name then temp. Add small label chip maybe. Keep tasteful: city name in caps letter-spaced small, temp huge (like 3rem, font-weight 700), description below in lighter opacity.
Icon area: fixed height container (e.g., 110px) with elements centered.
Prefers-reduced-motion:
```
@media (prefers-reduced-motion: reduce) {
* { animation: none !important; transition: none !important; }
}
```
Maybe keep hover transition? "respect prefers-reduced-motion" — disable animations; transitions on transform for hover are fine but safer to shorten. I'll disable animations and keep transitions minimal or none. I'll do: animations: none; and transitions: none for safety? Hover lift is fine without motion concerns, but to fully respect, I'll disable animations only and keep the hover transition (it's interaction feedback). Actually reduce-motion guidance targets large movement; hover lift is small. I'll disable infinite animations and keep a short fade-free hover (just shadow + translate via transition — fine). I'll keep transition but that's OK. Simpler: set `animation-duration: 0.01ms; animation-iteration-count: 1;` pattern. I'll just do `* { animation: none !important; }` and leave hover transitions.
Responsive grid:
```
.grid { display:grid; grid-template-columns: repeat(4, 1fr); gap: 20px; }
@media (max-width: 980px) { 2 columns }
@media (max-width: 560px) { 1 column }
```
Requirement: "4 per row on desktop, stacked on mobile" — 2-col intermediate is fine and graceful; the requirement mainly says stacked on mobile. I'll include 2-col tablet as graceful adaptation.
Page: dark page background (like deep slate) so colorful cards pop. Header: title "Weather" small. Maybe center content vertically. Height 800: cards ~ maybe 300px tall. Fine.
Ensure no horizontal scrollbar: use box-sizing border-box, width constraints, padding with minmax(0,1fr)? Use `max-width: 1100px; margin: auto;`.
First paint visible: everything visible immediately, animations running. Good. No "click to start" needed since no JS/game.
Let me now write careful CSS for each icon.
HTML structure:
```html
<main class="page">
<header class="top">
<h1>Weather</h1>
<p>Four skies, one forecast — pure CSS</p>
</header>
<section class="grid">
<article class="card sunny">
<div class="icon">
<div class="sun">
<span class="core"></span>
<span class="ray r1"></span> ... r8
</div>
</div>
<h2 class="city">Phoenix</h2>
<div class="temp">31°</div>
<p class="desc">Clear skies all day</p>
</article>
...
</section>
</main>
```
Sun rays: 8 spans. Using nth-child to set rotation angle:
```css
.ray {
position: absolute;
left: 50%; top: 50%;
width: 5px; height: 16px;
margin: -8px 0 0 -2.5px;
border-radius: 4px;
background: #ffd75e;
transform: rotate(var(--a)) translateY(-38px);
}
```
Set --a per ray: 0,45,...315deg. transform-origin default center of the element itself — rotate then translateY: rotate happens around element's own center, then translate along rotated axis, which effectively places it around a circle. Yes: `transform: rotate(45deg) translateY(-38px)` moves the element 38px along the direction rotated 45° from up. That's the standard trick. Good.
Sun container spins: `.sun { animation: spin 24s linear infinite; }` — rotating the whole sun including core (core is a circle, rotation invisible on it, fine). But rays with rounded ends rotating is nice. Also add pulse on core: `animation: pulse 3s ease-in-out infinite` scaling 1↔1.06 and glow via box-shadow.
Wait: if .sun spins and core has pulse, both fine (nested animations on different elements).
Cloud (rain):
```css
.cloud { position: relative; width: 92px; height: 30px; background: #f4f8fb; border-radius: 30px; }
.cloud::before { content:""; position:absolute; width: 42px; height:42px; border-radius:50%; background:#f4f8fb; top:-24px; left:16px; }
.cloud::after { content:""; position:absolute; width: 30px; height:30px; border-radius:50%; background:#f4f8fb; top:-16px; right:14px; }
```
Cloud floats: bob animation `translateY(0 → 4px)` 4s ease-in-out infinite alternate. But careful: animating transform on .cloud — the drops positioned relative to the icon container, so they won't move with the cloud. That's fine (drops fall independently). Or wrap cloud in .cloudWrap that bobs. Simpler: put bob on cloud, drops separate.
Rain drops: 6 spans inside .drops container under cloud:
```css
.drops span {
position:absolute;
top: 46px;
width: 4px; height: 14px;
border-radius: 4px;
background: linear-gradient(#bfe3ff, #6ec1ff); /* on gray bg use lighter */
animation: fall 1.3s linear infinite;
opacity: 0;
}
```
Keyframes fall: 0% translateY(-6px) opacity 0; 15% opacity 1; 80% opacity 1; 100% translateY(34px) opacity 0. Stagger with nth-child left positions and delays. Positions left: 18%, 32%, 46%, 60%, 74%, 86%? With negative delays so they're mid-fall at first paint: use `animation-delay: -0.2s` etc. Negative delays make them appear immediately mid-cycle.
Actually position drops by left within .drops container spanning the cloud width, centered.
Snow: cloud tinted #eaf4fd; flakes: 5-6 spans:
```css
.flake {
position:absolute;
width: 12px; height: 12px;
animation: snowfall 3.6s linear infinite;
opacity: 0;
}
.flake::before, .flake::after { content:""; position:absolute; background:#fff; border-radius:3px; }
.flake::before { left:5px; top:0; width:2px; height:12px; }
.flake::after { top:5px; left:0; height:2px; width:12px; }
```
Hmm plus shape only has 4 arms; add rotation during fall so it looks like spinning flake. Also could add a second cross rotated 45° — that would need another element. Could give flake a child? Spans can't have children easily... Actually spans can contain pseudo-elements only (2). To get 8 arms, use two overlapping backgrounds? Alternative: use `background: conic/linear` ... Simplest visual: small white dots of varying sizes look like snow from distance; but "snowflakes" plural with plus-shape reads well. I'll do plus shape + rotate animation; the rotation makes it look like a 6/8-armed flake while spinning. Or add another pair via box-shadow? Pseudo-element with box-shadow duplicating shape rotated? Can't rotate box-shadows.
Alternative: give the flake an inner span? The HTML can be `<span class="flake"><i></i></span>` — allowed, HTML elements. Then .flake::before/::after = one cross, .flake i::before/::after = second cross rotated 45°. That gives an 8-armed asterisk. Nice but adds markup; fine. Actually simpler: keep plus + spin. Hmm, 8-arm asterisk looks distinctly snowflake-like. Let's do:
```html
<span class="flake"><i></i></span>
```
```css
.flake::before, .flake::after, .flake i::before, .flake i::after { content:""; position:absolute; left:50%; top:50%; background:#fff; border-radius:2px; }
.flake::before { width:2px; height:12px; transform:translate(-50%,-50%); }
.flake::after { width:12px; height:2px; transform:translate(-50%,-50%); }
.flake i::before { width:2px; height:9px; transform:translate(-50%,-50%) rotate(45deg); }
.flake i::after { width:9px; height:2px; transform:translate(-50%,-50%) rotate(45deg); }
```
i positioned absolute inset 0. Good — 8-arm flake.
Snowfall keyframes: translateY from -8 to 44 with translateX sway: keyframes can include intermediate steps: 0%: translate(0,-10px) rotate(0); 25%: translate(4px, 6px) rotate(90deg); 50%: translate(-2px, 22px) rotate(180deg); 75%: translate(3px, 34px) rotate(270deg); 100%: translate(0,46px) rotate(360deg); opacity fade in/out at ends. Stagger delays negative.
Storm: dark cloud (color #3a4258 / #2f3648) + bolt + maybe 2-3 drops. Bolt flash keyframes:
```css
@keyframes flash {
0%, 100% { opacity: 0.12; filter: drop-shadow(0 0 0 rgba(255,224,102,0)); }
6% { opacity: 1; }
9% { opacity: 0.25; }
12% { opacity: 1; filter: drop-shadow(0 0 12px rgba(255,224,102,.9)); }
18% { opacity: 0.7; }
24% { opacity: 0.12; }
}
```
Duration ~2.8s infinite. Also card-level subtle flash? Could animate a ::after overlay on storm card: white radial glow pulse synced (same 2.8s). Nice touch: `.card.stormy::after` overlay animating opacity. But card::after might conflict with hover shadow? Hover uses box-shadow on card itself; ::after overlay is separate — fine. But overlay covering content must be pointer-events none and behind text: z-index issue — put overlay with mix or low opacity white, above background but below content? Simpler: animate the card's box-shadow? Hmm. Alternative: put a radial glow div behind icon. Or skip; bolt glow is enough. Maybe add a faint flicker on the background gradient via a ::before with light gradient and animation opacity — positioned absolute inset 0, z-index 0, and card content z-index 1. That's doable:
```css
.card { position: relative; overflow: hidden; }
.card::before { content:""; position:absolute; inset:0; background: radial-gradient(...); opacity:0; }
```
But ::before on card for all cards? Only storm uses it; others don't define ::before. But I might want decorative texture for others... keep ::before only for storm flash. Set `.card > * { position: relative; z-index: 1; }` to layer content above. Careful: .icon children etc. `.card > *` selector covers direct children (icon, h2, temp, desc). Good.
Storm flash overlay:
```css
.stormy::before {
background: radial-gradient(circle at 50% 30%, rgba(255,240,180,.35), transparent 60%);
animation: skyflash 2.8s infinite;
}
@keyframes skyflash { 0%,100%{opacity:0} 6%{opacity:1} 10%{opacity:.2} 14%{opacity:.8} 20%{opacity:0} }
```
Sync with bolt flash (same duration/delay).
Storm drops: a couple of fast rain lines optional. Add 4 drops like rain but faster/diagonal? Keep vertical, shorter duration 0.9s. Nice detail. Maybe skip to reduce complexity? It adds richness; I'll add 4 drops with bluish color on dark bg (light #9fc7ff).
Also storm cloud could bob faster (agitated) — small shake? Add subtle `animation: bob 3s` same as others.
Hover lift:
```css
.card { transition: transform .3s ease, box-shadow .3s ease; }
.card:hover { transform: translateY(-10px); box-shadow: 0 22px 40px rgba(0,0,0,.35); }
```
Also maybe icon scale slightly on hover: `.card:hover .sun { ... }` not necessary.
Page background: `#0f1626` with subtle radial vignette. Header text light.
Card content styling:
- .city: font-size 0.8rem, letter-spacing .18em, uppercase, opacity .85.
- .temp: font-size 3rem, weight 800, line-height 1.
- .desc: 0.9rem, opacity .8.
Add a small divider or spacing. Card padding 26px 22px; min-height maybe 300px; icon container height ~110px, margin-bottom.
Icon centering: `.icon { position: relative; height: 112px; display:flex; align-items:flex-start; justify-content:center; }` Actually absolute positioning inside a relatively positioned wrapper of fixed size; center elements with left:50%; transform translateX(-50%) for cloud. But clouds also bob via transform — conflict: transform used both for centering and animation. Solution: position cloud with left:50%; margin-left:-46px (half width) instead of translate. Or wrap: `.cloudWrap { position:absolute; left:50%; transform:translateX(-50%); }` then animate inner .cloud. I'll use wrapper approach for cloud (bob on inner), sun centered via wrapper too.
Structure per card icon:
Sunny:
```html
<div class="icon">
<div class="sun">
<span class="core"></span>
<span class="ray" style="--a:0deg"></span> ...
</div>
</div>
```
Inline style with custom property — is inline style allowed? It's not JS; fine. But maybe cleaner with nth-of-type CSS: `.ray:nth-of-type(2){--a:45deg}` etc. Wait core is first span, rays are spans 2..9. Use classes r1..r8 to be explicit. I'll use nth-child within .sun: `.sun .ray:nth-child(2)`... Let me just give each ray a class r1–r8 and define --a. Cleaner to read.
Sun sizing: core 46px diameter, rays at radius ~34px (translateY(-34px)), ray height 14px width 5px. Sun container size 100x100 centered.
Ray gradient: linear-gradient(#ffe38a, #ffb340). Core: radial-gradient(circle at 35% 35%, #fff3b0, #ffcf4d 60%, #ff9d2e); box-shadow: 0 0 24px rgba(255,200,80,.55).
Rain icon arrangement: wrapper .cloudWrap at top center (top ~14px), drops below. Cloud white/light gray #f2f6fa with slight gradient? Use background #eef4f9 and pseudo same color via background: inherit — inherit takes computed background of parent (which includes gradient? inherit inherits the value, gradient inherits fine). Set `background: inherit` on pseudos — but pseudo's parent is .cloud, so inherit = cloud's background. Works.
Rain drop color: rgba white-blue: `linear-gradient(180deg, rgba(255,255,255,.9), rgba(140,200,255,.9))`. On gray-blue bg, visible.
Rain card gradient: `linear-gradient(165deg, #54718c, #7fa3bf)`? Slightly muted. Let me refine palettes:
- sunny: `linear-gradient(165deg, #2e8fdd 0%, #66c1f5 55%, #ffe1a6 130%)` hmm gradient stops beyond 100 fine. Simpler: `linear-gradient(165deg,#2f8fdd,#8fd3ff)`.
- rainy: `linear-gradient(165deg,#4f6d8c,#88a7c4)`.
- snowy: `linear-gradient(165deg,#6ea6c9,#cfe6f4)`? Might be light for white text — description white on light bg issue. Snow bg: keep mid-tone: `linear-gradient(165deg,#7aa7cc,#b9d7ea)`; text white with text-shadow ok. Hmm contrast. Alternatively snowy: `linear-gradient(165deg,#5f87ad,#9fc3dd)`. White text readable-ish with shadow. Let me add subtle text-shadow: 0 1px 2px rgba(0,30,60,.25) on card text for lighter cards.
- stormy: `linear-gradient(165deg,#222b40,#4b5370)` maybe with purple: `#2b2d4a → #555a80`.
Temps/desc:
- Phoenix — 32° — "Clear and sunny" / "Bright sunshine all day"
- Seattle/London — 12° — "Light rain showers"
- Oslo/Reykjavík — -4° — "Snow showers this evening"
- Miami/Manila storm — 24° — "Thunderstorms likely"
Cities: Phoenix, London, Oslo, Singapore? Stormy: "Manila". Fine: Phoenix 31°, London 11°, Oslo −3°, Manila 26°.
Descriptions short: "Clear skies all day", "Light rain until evening", "Snow showers tonight", "Thunderstorms developing".
Icon detail placement inside 110px area:
Sunny: sun centered slightly. Cloud cards: cloud at top (~10px), precipitation below occupying rest.
Let me define `.icon { position:relative; width:100%; height:120px; margin-bottom: 8px; }` with children absolutely positioned.
Cloud wrapper: `position:absolute; top:12px; left:50%; margin-left:-46px;` (cloud width 92). Bob animation on .cloud itself (transform translateY), wrapper static.
Drops container: `position:absolute; inset:0;` drops absolute with left % and top 52px. Drop height 14, fall to ~ 56px total: keyframes translateY(0→40px). Top 52 + 40 = 92 + 14 height = 106 < 120 good. Actually drops should start under cloud bottom (~ top 12 + cloud height 30 + puffs above... cloud total height with puffs = 30+24 = 54; bottom of cloud bar = 12+30=42). Start drops top: 46px. Fall distance 44px → ends 90px+14=104. Good.
Flakes similar: top 46, fall to ~46+50.
For sunny, center sun: `.sun { position:absolute; top:50%; left:50%; margin:-50px 0 0 -50px; width:100px; height:100px; animation: spin 22s linear infinite; }` core centered: `position:absolute; left:50%; top:50%; width:46px; height:46px; margin:-23px 0 0 -23px; border-radius:50%;`. Rays: absolute at center with rotate/translate as described.
Bolt position: under storm cloud: `.bolt { position:absolute; top:44px; left:50%; margin-left:-17px; width:34px; height:52px; clip-path: polygon(...); background: linear-gradient(180deg,#ffe066,#ffb020); animation: flash 2.6s infinite; }`
Check clip-path polygon for a bolt: points: (60% 0), (20% 55%), (45% 55%), (35% 100%), (85% 38%), (55% 38%). Let me think: classic bolt: start top-right-ish, zigzag. Coordinates (x,y):
- (58% 0%) top point slightly right
- (18% 55%) down-left
- (45% 55%) small horizontal right
- (32% 100%) bottom-left tail down
- (85% 38%) up-right long
- (52% 38%) left small
back to start. That forms a bolt. I'll test mentally: The polygon: (58,0) → (18,55): left slant down. → (45,55): right. → (32,100): down-left slightly. → (85,38): up-right. → (52,38): left. → close to (58,0): up-right slightly. Yes that's a lightning bolt silhouette.
Storm cloud color: `#39415c` with darker puffs; on dark bg need slight lighter than bg or darker? Make cloud a lighter slate `#aab4cf`? Dark storm clouds read as dark gray-blue: use `#454f6b` against bg `#222b40→#4b5370` — cloud would blend. Use lighter `#8d99b8`? Stormy cards often show near-black cloud against mid-dark sky. Let me make cloud `#2f3852` with a subtle lighter rim? Simpler: cloud slightly darker than bg top: bg top #222b40... Use `#1f2740` cloud with border? Hmm. I'll pick cloud color `#3d4666` plus drop-shadow to separate, or make cloud lighter: `#66708f`. Let me go: storm cloud gradient? Pseudo background:inherit trick keeps consistent. I'll set storm cloud color `#525d7d` and give the cloud a `filter: drop-shadow(0 4px 6px rgba(0,0,0,.3))` for separation. Looks fine.
Storm bolt color yellow `#ffd94d`.
Also for rain drops on storm: color `#a9c6ff` at 0.9 opacity.
Bob animation:
```css
@keyframes bob { from { transform: translateY(0); } to { transform: translateY(5px); } }
.cloud { animation: bob 3.4s ease-in-out infinite alternate; }
```
Storm bob faster: `animation: bob 2.2s ease-in-out infinite alternate;` fine, same keyframes.
Spin: `@keyframes spin { to { transform: rotate(360deg); } }` with from implicit (0). Applied to .sun which is positioned with margin trick — rotation around its center, fine.
Pulse core: `@keyframes pulse { 0%,100% { transform: scale(1); } 50% { transform: scale(1.08); } }` core also has glow shadow constant.
Now hover: maybe also raise shadow color per card? Keep single.
Reduced motion:
```css
@media (prefers-reduced-motion: reduce) {
.sun, .core, .cloud, .drops span, .flake, .bolt, .stormy::before { animation: none; }
}
```
Or blanket `* { animation: none !important; }` — blanket is fine but kills nothing else since only animations used. I'll blanket but keep transitions for hover (they're fine). Actually to be safe and explicit, blanket `*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}`? The requirement just says respect it. I'll do:
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation: none !important;
transition-duration: 0.01ms !important;
}
}
```
Good.
Grid breakpoints:
```css
.grid { display:grid; grid-template-columns: repeat(4, minmax(0,1fr)); gap: 22px; }
@media (max-width: 1000px) { .grid { grid-template-columns: repeat(2, minmax(0,1fr)); } }
@media (max-width: 540px) { .grid { grid-template-columns: 1fr; } }
```
Page layout: `.page { max-width: 1140px; margin: 0 auto; padding: 48px 24px; }` Body min-height 100vh, flex center? For 800px height with header + cards (~360px cards) total ~ 48+70+360+48 ≈ 526 — fits. Use flex column center to vertically center. Actually center vertically: body { min-height:100vh; display:flex; align-items:center; justify-content:center; } with page inside. On small screens content grows; fine with padding.
Body background: deep navy `#101623` plus radial highlight. Text color #e8eef7.
Header: h1 1.6rem weight 700, subtitle .95rem opacity .6. Maybe a small live dot? No JS anyway. Keep minimal, maybe an "Updated just now" static? Skip gimmicks.
Small detail: card top accent? Each card could have a tiny label chip like "SUNNY". The description covers it. Add small weather word above city? Might clutter. I'll add small tag: e.g., `.tag` uppercase tiny with translucent white bg. Actually city name + temp + desc is required; tag optional. I'll include a subtle condition tag for polish? Requirement lists: city, temp, short description. I'll keep those three + icon. Maybe also a details row like "H 34° · L 22°"? Extra polish nice: small meta line. Adds realism. Keep description then meta line small: e.g., "H:34° L:22°". Hmm might add clutter on 375px? It's fine, cards stack full width.
Let me include meta: `.meta { font-size: .78rem; opacity:.75; }` e.g. "H 34° · L 21° · Wind 8 km/h". Nice.
Card border-radius 20px; box-shadow default `0 10px 24px rgba(0,0,0,.25)`; hover deeper + translateY(-10px). Also add `will-change` not needed.
Add slight inner top highlight: `.card::after`? Storm uses ::before for flash; others could use ::after for gloss: `linear-gradient(180deg, rgba(255,255,255,.18), transparent 40%)`. But .card::after on all cards is fine (storm too). Wait storm uses ::before for flash overlay; gloss uses ::after — no conflict. But gloss over content? Put ::after as decorative overlay pointer-events none; it's a subtle gradient at top, above content slightly — acceptable since it's translucent and top area is icon. Actually overlay above text could wash it; limit to top 45% and low opacity. Also need z-index: ::after default stacks above static children; make children z-index 1? If children have z-index they'd be above the gloss — then gloss only covers background. For a top sheen, having it above icon slightly is fine visually even over text? It would lighten text top area. Cards' text starts below icon mostly; city name under icon. Sheen covering icon area top — fine, looks like glass. I'll set `.card::after { content:""; position:absolute; inset:0; border-radius:inherit; background: linear-gradient(180deg, rgba(255,255,255,.14), rgba(255,255,255,0) 42%); pointer-events:none; }` and keep content z-index below it? Children with z-index:1 would sit above sheen; then sheen only on background. Hmm choose: give sheen z-index:2 above all, pointer-events none — glassy look over icon; text mostly lower half unaffected. I'll set z-index 2 on ::after, content z-index 1, storm ::before z-index 0.
Wait storm ::before flash should be above background but below content: z-index 0 with content z-index 1. But ::before with no z-index and children static — painting order: ::before is below children anyway if children positioned with z-index 1. OK.
Card overflow hidden for sheen radius: yes `overflow:hidden`.
But bolt flash glow uses drop-shadow filter — inside overflow hidden card fine.
Font stack: `font-family: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;`
Temperature with degree symbol: use `31°`.
Now, negative delays for continuous motion at first paint:
Rain drops: 6 drops, animation 1.4s, delays: 0, -0.35, -0.7, -1.05, -0.5, -0.9 — varied so not uniform. Assign per nth-child: left: 12%, 26%, 41%, 56%, 70%, 84%; delays: -0.1s, -0.6s, -1.1s, -0.35s, -0.85s, -1.3s.
Snowflakes: 6 flakes: left 10%, 25%, 42%, 58%, 74%, 88%; delays various; durations 3.2–4.2s vary per nth for organic feel. Sizes vary: scale via width/height per nth (12, 9, 14, 10, 12, 9). Since pseudo shapes use fixed px inside, size variation via transform: scale(...) on flake. But transform is used by animation... animation sets transform entirely, overriding scale. So bake size into width/height + pseudo dims per nth-child. That's several rules; or use a CSS var --s per flake: `.flake { width: var(--s); height: var(--s); }` and pseudo sizes derived: `.flake::before { width: 2px; height: var(--s); ... }`. With inline style="--s:12px" per flake? Inline styles fine. Or define vars in CSS per nth-child: `.flake:nth-child(1){ --s:12px; left:10%; animation-delay:-.4s; animation-duration:3.6s; }`. That's clean. Pseudo uses var(--s) inherited — custom properties inherit into pseudos.
Flake keyframes with sway:
```css
@keyframes snowfall {
0% { transform: translate(0, -12px) rotate(0deg); opacity: 0; }
10% { opacity: 1; }
50% { transform: translate(6px, 20px) rotate(180deg); }
90% { opacity: 1; }
100% { transform: translate(-3px, 52px) rotate(360deg); opacity: 0; }
}
```
Hmm opacity between 50 and 90 stays 1 (interpolated from neighbors: 10%→1, then holds until 90% since next specified is 90%:1). Yes.
Flakes start above cloud? Start at top 40 (under cloud) translateY -12 → 28 initially... starts at top:44 with -12 offset = 32 (still under cloud bottom ~42; hidden behind cloud? Cloud is above in DOM? Stacking: flakes after cloud in DOM → flakes on top of cloud. Starting under the cloud bottom edge is fine (they emerge from cloud). top:44 minus 12 = 32 which is inside cloud bar area (cloud bar occupies 12–42). Flakes would appear over cloud briefly. To avoid, start opacity 0 until 10% (already fading in) — at 10% of 3.8s = 0.38s, translate ≈ -12 + 0.1*64 ≈ -5.6 → still near cloud. Slight overlap acceptable since fade-in masks it, or start offset -4. I'll set keyframes 0% translate(0,-6px). Fall total ~58px: 0:-6 → 100%: 52. With top:46 → ends at 46+52+size≈ 110 ok within 120.
Rain fall keyframes:
```css
@keyframes rainfall {
0% { transform: translateY(-4px); opacity: 0; }
12% { opacity: .95; }
75% { opacity: .95; }
100% { transform: translateY(46px); opacity: 0; }
}
```
top 46, end 46+46+14 = 106 ✓.
Storm drops: 4 drops, duration 0.85s, similar keyframes (reuse rainfall). Lefts: 26%, 42%, 58%, 72%; delays: -.1, -.4, -.6, -.25.
Bolt keyframes as above with duration 2.6s. skyflash same duration 2.6s and same delay (0). Sync since both start at load with same period.
Also storm cloud slight darker + maybe an extra small dark puff. Fine.
Sun ray color alternates: even rays slightly longer: `.r2, .r4, .r6, .r8 { height: 18px; }`? Since rays positioned by rotate/translate with margin centering, differing heights fine — but the transform translateY(-34px) positions the center... Let me recompute: ray element default position: left:50%, top:50%, then margin-left:-2.5px; margin-top: -8px (half height 16). For height 18 → margin-top:-9px. Different margins per class complicates. Alternative approach: place ray with `transform-origin: 50% 50%` and use `top:50%; left:50%; translate then rotate`? Order: `transform: translateY(-34px) rotate(var(--a))` — translate first moves up 34, then rotate around element center — the ray would rotate in place around its own center at the top position — wrong. The correct pattern: `rotate(a) translateY(-34)` — rotation of coordinate system then translate along rotated Y — element's own center stays origin of transform... Actually transform functions compose around transform-origin (element center by default, but element center is at left:50%/top:50% point? The element's box is positioned with its top-left at (50%,50%) of parent minus margins... hmm with margins -half it's centered. transform-origin = center of element box. rotate(a) rotates element about its center; then translateY(-34) translates along the *rotated* local Y axis (since transforms compose in local coordinates post-rotation). The final position: element center moves to 34px along direction (a measured from up? rotate(0) → translateY negative = up). rotate(45deg) rotates clockwise; local Y-axis points up rotated 45° clockwise → direction up-right. So rays distribute clockwise starting at top.
For varying lengths, adjust translateY per class: long rays translateY(-36px), short -33px, with margin-top adjusted. Meh — simpler: keep all rays same size, alternate opacity or color. I'll keep uniform 16px rays but alternate color slightly (lighter/darker) for visual rhythm: even rays `background:#ffe9a8`. Actually simplest pleasing: uniform. Add second detail: tiny dots at ray tips? Over-engineering; skip.
Ray count 8 at 45° steps: r1 --a:0deg; r2 45; r3 90; r4 135; r5 180; r6 225; r7 270; r8 315.
Sun spin 22s might be slow-ish; 16s nice. Also rays could counter... fine.
Additional flourish: sun glow ring? The core's box-shadow glow suffices.
Now card content ordering: icon top, then city, temp, desc, meta. Vertical spacing: icon 120px, then 6px, city, 6px, temp, 6px, desc, 10px, meta. Card padding 24px.
Card min-height: let content define (~340px). At 1280 wide, grid width 1140-48=1092; each card ~ (1092-66)/4 ≈ 256px wide. Fine.
Temp display font-size 3.2rem maybe too big for 256 width? "31°" fits easily. Use clamp(2.6rem, 6vw, 3.4rem)? At mobile full width fine. I'll use 3rem fixed, fine everywhere.
Meta line: e.g., "H 34° · L 24°". plus wind: "Wind 12 km/h". Keep "H 34° · L 24° · Wind 10 km/h" might wrap on 256px? At .78rem ~ 12.5px, that string ~ 30 chars ≈ 220px — fits within 208px content width? 256-48 padding = 208. 30 chars * ~6.5px = 195. OK borderline; use shorter: "H 34° · L 24°" for all, plus maybe humidity varies. Keep "H 34° · L 24°" style. Good.
City names: Phoenix, London, Oslo, Manila.
Header copy: h1 "Around the Globe" or "Today's Forecast". Subtitle: "Pure HTML & CSS — no JavaScript, no images." Nice meta note showing requirement fulfillment. Maybe small badge "CSS only". I'll add subtitle: "Four animated forecasts built with nothing but HTML & CSS."
Footer? Skip.
Storm card maybe slightly different accent: add subtle vignette. Skip.
Now write the code carefully, checking every animation & layout.
One more consideration: `background: inherit` on cloud pseudos — with .cloud having background color, inherit copies color. OK.
Bolt flash: also slight scale flicker? Add transform: scale in keyframes? Bolt positioned via top/left + margins (not transform), so transform free for animation: keyframes could include scale(1.05) at peaks. But combining with translate? Not needed. I'll add subtle scale at bright frames: 12% { opacity:1; transform: scale(1.08); } requires transform in 0%/100% baseline scale(1). Fine.
Actually the bolt is a child of .icon; its glow via filter drop-shadow — animating filter also possible; keep filter constant for perf: `filter: drop-shadow(0 0 10px rgba(255,210,80,.55));` constant; flash only opacity/scale. And storm::before flash overlay gives ambient.
Also add to storm a second darker cloud puff offset behind? Skip.
Snow cloud: slightly bluish white `#f0f7fd`. Rain cloud `#f4f8fb`. On rainy mid-blue bg, white cloud pops. Snow bg lighter icy — cloud white still visible with drop-shadow. Add `.cloud { filter: drop-shadow(0 3px 4px rgba(20,40,60,.18)); }` for rain/snow clouds.
Let me also double check no horizontal overflow: body padding, max-width, border-box. Add `*{box-sizing:border-box;margin:0}` reset.
Title area maybe with small gradient text? Keep simple: h1 white, subtitle muted.
Vertical centering: body flex; on short screens content scrolls fine (min-height).
Let me write the final HTML.
Also honor: "Everything must be visible on first paint" — yes.
Edge: pseudo-element content on spans requires display not inline: set `display:block` or absolute (absolute makes block). All pseudos absolute — fine.
Flake `i` element: `position:absolute; inset:0;` then its pseudos centered via left/top 50% translate(-50%,-50%) rotate... For i::before (vertical bar rotated 45°): width 2px height var(--s) rotate(45deg) translate(-50%,-50%) — order matters: transform: translate(-50%,-50%) rotate(45deg). Rotation after translate happens around element center (post-translate position center matches flake center) — rotating a centered bar 45° keeps centered. Good.
Wait: flake::before vertical bar: width:2px; height:var(--s); left:50%; top:50%; transform: translate(-50%,-50%). Yes.
Snowfall animation on .flake uses transform (translate+rotate) — flake's own box (width var(--s), height var(--s)) rotated; arms are pseudos of flake so they rotate together.
Also flakes i::before/after rotate(45deg) static — with flake rotating, 8 arms spin.
Storm cloud: same structure class .cloud with modifier .dark? I'll give cloud a color via card-scoped rule: `.rainy .cloud, .rainy .cloud::before, .rainy .cloud::after { background:#f2f7fb; }` Using background:inherit on pseudos means setting only .cloud background suffices! Pseudos inherit. So: `.rainy .cloud { background:#f2f7fb; }` done. `.snowy .cloud { background:#f7fbff; }`. `.stormy .cloud { background:#4d5878; }`.
But wait, I planned `background: inherit` — write pseudo rule: `.cloud::before, .cloud::after { background: inherit; }` — inherit resolves to parent's background (the .cloud). But the parent of the pseudo is .cloud itself, so inherit = .cloud's background. Correct.
Also cloud border-radius: main bar radius 30px/ (height 30 → fully rounded ends). Puffs circles.
Now precise cloud geometry: width 92, height 30, top puff 42px diameter at left 16 top -24 → covers 16..58 horizontally, -24..18 vertically. Right puff 30px at right 14 (i.e., x 48..78), top -16 → -16..14. Combined silhouette decent. Maybe add a middle-left smaller puff? Two puffs + bar is classic fine.
Cloud total height = 30 + 24 = 54 above? Puff top at -24 relative to cloud top. Cloud placed top:12 in icon → puff top at -12? Wait top:12 + (-24) = -12 → clipped by card overflow? Icon at card top with padding 24 → absolute within .icon (position relative). Negative top of puff extends above .icon bounds but overflow hidden is on .card — puff at icon top 12 - 24 = -12 relative to icon; icon sits after card padding-top 24 → absolute y = 24-12 = 12 ≥ 0, inside card. OK not clipped. But drops container top etc fine.
Hmm, actually let me reconsider: I want icon area to have some top padding so puffs don't touch card edge: cloud wrapper top: 26px instead of 12. Then cloud bar bottom = 26+30 = 56; drops start 58; fall 44 → 102 (+14 height=116) within 120. For flakes similar: start top 56, keyframe -6 → 50... end 56+52=108 +12 size = 120. Tight; reduce fall end to 48: end 56+48+12=116. OK adjust: snowfall 100% translateY 48px, start -4px.
Let me set icon height 124px for breathing room. Card total ≈ 24+124+content(~150)+24 ≈ 320. Good.
Recompute drops: start top 60 (just below cloud bottom 56), translateY to 40px → bottom edge 60+40+14=114 <124 ✓. Rain keyframes: 0% translateY(-4) (starts 56 hidden behind cloud? cloud bottom 56, drop top 60-4=56 exactly at edge, fading in) good; 100% translateY(40) opacity 0.
Storm: cloud same geometry; bolt top 58, height 50 → bottom 108. Storm drops top 60 similar but would overlap bolt — bolt center; drops at left 26/72% avoid center bolt mostly (26% of icon width ~ within card icon width 208 → 54px; bolt spans center 87..121px). Drops at 30% & 70%: x≈62 & 146 — clear of bolt (87–121). OK choose lefts: 28%, 44%, 58%, 72%? 44%→92 overlaps bolt left edge (87). Use 24%, 40%(83 borderline)... Let me just use 20%, 36%, 64%, 80% → x 42,75,133,166 — bolt 87..121 clear.
Bolt width 34 centered: margin-left -17 → x from (iconWidth/2 -17). Icon width = card width - 48 padding = 208 → center 104, bolt 87..123. Yes.
Rain drops lefts across cloud width: cloud spans x 58..150 (92 wide centered at 104). Drops between 64..144: lefts in %: 30,40,50,60,70,80 → 62,83,104,125,146,166? 80% = 166 beyond cloud right edge 150. Use 30..78: 30,39,48,57,66,75 → x 62,81,100,119,137,156? 75% = 156 > 150 slightly. Use 28,37,46,55,64,72 → 58,77,96,114,133,150. Edge ones align to cloud edges — good spread under cloud.
Fine — precise pixel perfection isn't critical; plausible values OK.
Snow flake lefts: similar spread: 26%, 38%, 50%, 62%, 74% (5 flakes) plus maybe 32%, 68% for 7? 5–6 flakes fine: use 6: 24,35,46,57,68,79 → x 50,73,96,119,141,164. Cloud 58..150 — 24% (50) slightly left of cloud; acceptable (drift). Keep.
Delays/durations:
- flakes: durations 3.4,4.1,3.7,4.4,3.9,4.6? Hmm random-ish; delays negative: -.2,-1.6,-2.9,-.9,-2.2,-3.4.
- sizes --s: 12,9,13,10,12,9.
Rain drops: duration 1.3s all; delays: -.15,-.55,-.95,-.35,-.75,-1.15. lefts as above.
Storm drops duration .8s; delays -.1,-.45,-.2,-.6.
Now the spin of sun: also rays length alternation skip.
Sun core pulse: transform scale on core — core positioned via margins; transform-origin center default. OK.
Let me also add a subtle secondary animation for sun: a slow rotating glow? skip.
Compose keyframes list:
1. spin (rays)
2. pulse (core)
3. bob (clouds)
4. rainfall (drops)
5. snowfall (flakes)
6. flash (bolt)
7. skyflash (storm card ::before)
Hover: also lift icon? e.g., `.card:hover .icon { transform: translateY(2px) }`? Not needed.
Write CSS variables for card text shadow.
Accessibility: role/aria not required; add alt-ish semantics: h2 for city, p for desc.
Also add `lang="en"`, meta viewport, title.
Let me draft final code:
```html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Animated Weather Cards — Pure CSS</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
background:
radial-gradient(1200px 600px at 70% -10%, rgba(90,130,220,.18), transparent 60%),
radial-gradient(900px 500px at 10% 110%, rgba(60,160,200,.12), transparent 60%),
#0e1420;
color: #eaf1fb;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 40px 22px;
}
.page { width: 100%; max-width: 1120px; }
.top { text-align:center; margin-bottom: 34px; }
.top h1 { font-size: 2rem; font-weight: 700; letter-spacing: .01em; }
.top h1 span { ... }
.top p { margin-top: 8px; color: rgba(234,241,251,.55); font-size: .95rem; }
.grid {
display: grid;
grid-template-columns: repeat(4, minmax(0, 1fr));
gap: 22px;
}
.card {
position: relative;
overflow: hidden;
border-radius: 20px;
padding: 24px 22px 22px;
box-shadow: 0 12px 26px rgba(2, 8, 20, .35);
transition: transform .28s ease, box-shadow .28s ease;
}
.card:hover {
transform: translateY(-10px);
box-shadow: 0 26px 46px rgba(2, 8, 20, .5);
}
.card > * { position: relative; z-index: 1; }
.card::after { /* glass sheen */
content: "";
position: absolute;
inset: 0;
z-index: 2;
pointer-events: none;
background: linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,0) 46%);
border-radius: inherit;
}
/* gradients */
.sunny { background: linear-gradient(168deg, #2f8fdd 0%, #63bdf2 55%, #ffd98f 130%); }
.rainy { background: linear-gradient(168deg, #46617f 0%, #7d9cbb 100%); }
.snowy { background: linear-gradient(168deg, #6493b8 0%, #a9cbe4 100%); }
.stormy { background: linear-gradient(168deg, #232b44 0%, #4c5578 100%); }
.stormy::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
background: radial-gradient(120px 90px at 50% 34%, rgba(255,238,170,.5), transparent 70%);
opacity: 0;
animation: skyflash 2.6s linear infinite;
}
.icon { position: relative; height: 124px; margin-bottom: 14px; }
/* ===== text ===== */
.city { font-size: .78rem; font-weight: 600; letter-spacing: .22em; text-transform: uppercase; opacity: .85; }
.temp { font-size: 3.1rem; font-weight: 750?; }
```
font-weight 750 might not map; use 700/800. `.temp { font-size: 3.1rem; font-weight: 800; line-height: 1.05; margin: 6px 0 4px; }` `.desc { font-size: .92rem; opacity: .85; }` `.meta { margin-top: 12px; font-size: .78rem; opacity: .6; border-top: 1px solid rgba(255,255,255,.18); padding-top: 10px; }`
Hmm border-top on meta adds structure. Good.
Text shadow: `.card { text-shadow: 0 1px 3px rgba(10,30,50,.25); }` for light cards readability.
Now icons.
```css
/* ===== SUN ===== */
.sun {
position: absolute;
top: 6px; left: 50%;
width: 104px; height: 104px;
margin-left: -52px;
animation: spin 18s linear infinite;
}
.core {
position: absolute;
left: 50%; top: 50%;
width: 46px; height: 46px;
margin: -23px 0 0 -23px;
border-radius: 50%;
background: radial-gradient(circle at 34% 32%, #fff6c9, #ffd44d 58%, #ff9d2f);
box-shadow: 0 0 26px rgba(255, 205, 90, .6);
animation: pulse 3.2s ease-in-out infinite;
}
.ray {
position: absolute;
left: 50%; top: 50%;
width: 5px; height: 15px;
margin: -7.5px 0 0 -2.5px;
border-radius: 4px;
background: linear-gradient(180deg, #ffe9a8, #ffbe45);
transform: rotate(var(--a)) translateY(-37px);
}
.r1{--a:0deg} .r2{--a:45deg} ...
```
Margin top -7.5px: half of 15 = 7.5 fine.
Sun vertical: sun box top 6, center at 6+52=58 in icon of 124 — visually slightly above center, good since sun alone; maybe center it: top:10 → center 62 (icon center 62). Set top:10px.
/* CLOUD */
```css
.cloud {
position: absolute;
top: 26px; left: 50%;
width: 92px; height: 30px;
margin-left: -46px;
border-radius: 30px;
animation: bob 3.6s ease-in-out infinite alternate;
}
.cloud::before {
content: "";
position: absolute;
top: -24px; left: 16px;
width: 42px; height: 42px;
border-radius: 50%;
background: inherit;
}
.cloud::after {
content: "";
position: absolute;
top: -15px; right: 15px;
width: 30px; height: 30px;
border-radius: 50%;
background: inherit;
}
.rainy .cloud { background: #f2f7fc; filter: drop-shadow(0 4px 6px rgba(15,35,60,.2)); }
.snowy .cloud { background: #f8fbff; filter: drop-shadow(0 4px 6px rgba(30,60,90,.18)); }
.stormy .cloud { background: #3f4a6e; filter: drop-shadow(0 5px 8px rgba(0,0,0,.35)); animation-duration: 2.4s; }
```
Wait drop-shadow on .cloud also shadows pseudos? filter applies to element including pseudos as one unit — yes, drop-shadow captures the rendered element incl. children/pseudos. Good.
/* drops */
```css
.drops span {
position: absolute;
top: 60px;
width: 4px; height: 13px;
border-radius: 4px;
background: linear-gradient(180deg, rgba(255,255,255,.95), rgba(126,196,255,.85));
opacity: 0;
animation: rainfall 1.3s linear infinite;
}
.rainy .drops span:nth-child(1) { left: 28%; animation-delay: -.15s; }
... etc
.stormy .drops span {
background: linear-gradient(180deg, rgba(210,230,255,.95), rgba(140,180,255,.8));
animation-duration: .8s;
height: 12px;
}
.stormy .drops span:nth-child(1) { left: 20%; animation-delay: -.1s; }
...
```
/* flakes */
```css
.flake {
position: absolute;
top: 56px;
width: var(--s, 12px);
height: var(--s, 12px);
opacity: 0;
animation: snowfall 4s linear infinite;
}
.flake i { position: absolute; inset: 0; display: block; }
.flake::before, .flake::after,
.flake i::before, .flake i::after {
content: "";
position: absolute;
left: 50%; top: 50%;
background: #fff;
border-radius: 2px;
}
.flake::before { width: 2px; height: var(--s,12px); transform: translate(-50%, -50%); }
.flake::after { width: var(--s,12px); height: 2px; transform: translate(-50%, -50%); }
.flake i::before { width: 2px; height: calc(var(--s,12px) * .72); transform: translate(-50%, -50%) rotate(45deg); }
.flake i::after { width: calc(var(--s,12px) * .72); height: 2px; transform: translate(-50%, -50%) rotate(45deg); }
.snowy .flake:nth-child(1) { --s: 13px; left: 24%; animation-duration: 3.6s; animation-delay: -.4s; }
...
```
Wait: .flake children — .icon contains cloud + .flakes container? I said `.drops` container spanning icon; similarly `.flakes` container. If flakes are children of `.flakes` (position absolute inset 0), nth-child indexes within it. Yes:
```html
<div class="flakes">
<span class="flake"><i></i></span> ×6
</div>
```
And rain: `<div class="drops"><span></span>×6</div>`.
Storm icon: cloud + bolt + drops(4). Structure:
```html
<div class="icon">
<div class="cloud"></div>
<div class="bolt"></div>
<div class="drops"><span></span>...</div>
</div>
```
Bolt:
```css
.bolt {
position: absolute;
top: 56px; left: 50%;
width: 34px; height: 50px;
margin-left: -17px;
background: linear-gradient(180deg, #ffe873, #ffb62e);
clip-path: polygon(58% 0%, 18% 55%, 45% 55%, 32% 100%, 85% 38%, 52% 38%);
filter: drop-shadow(0 0 10px rgba(255, 214, 90, .55));
opacity: .12;
transform: scale(1);
animation: flash 2.6s linear infinite;
}
```
Hmm filter drop-shadow with clip-path: drop-shadow applies after clipping, following the clipped shape — yes, drop-shadow works with clip-path (filter applied to element's rendered alpha). Good.
flash keyframes:
```css
@keyframes flash {
0%, 100% { opacity: .1; transform: scale(1); }
5% { opacity: 1; transform: scale(1.06); }
9% { opacity: .25; transform: scale(1); }
13% { opacity: .95; transform: scale(1.08); }
20% { opacity: .55; }
27% { opacity: .1; }
}
```
Between 20% and 27% opacity interpolates .55→.1, transform stays scale(1.08) until? transform only specified at 0,5,9,13 — from 13% to 100% interpolates 1.08→1 slowly. Slight; acceptable. Add transform: scale(1) at 20%? Add at 20% { transform: scale(1); }. Then 27% default continues scale(1). OK.
skyflash keyframes:
```css
@keyframes skyflash {
0%, 100% { opacity: 0; }
5% { opacity: .8; }
9% { opacity: .15; }
13% { opacity: .7; }
22% { opacity: 0; }
}
```
Bob:
```css
@keyframes bob { from { transform: translateY(0); } to { transform: translateY(6px); } }
```
Spin: `@keyframes spin { to { transform: rotate(1turn); } }` — from defaults to current (none=0). OK.
Pulse: `@keyframes pulse { 0%,100% { transform: scale(1); box-shadow: 0 0 22px rgba(255,205,90,.55);} 50% { transform: scale(1.09); box-shadow: 0 0 34px rgba(255,205,90,.8);} }` — animating box-shadow fine.
Rainfall:
```css
@keyframes rainfall {
0% { transform: translateY(-4px); opacity: 0; }
12% { opacity: .95; }
70% { opacity: .95; }
100% { transform: translateY(40px); opacity: 0; }
}
```
Snowfall:
```css
@keyframes snowfall {
0% { transform: translate(0, -4px) rotate(0deg); opacity: 0; }
10% { opacity: .95; }
50% { transform: translate(5px, 22px) rotate(160deg); }
88% { opacity: .9; }
100% { transform: translate(-3px, 46px) rotate(320deg); opacity: 0; }
}
```
rotate 320 (not full 360) to avoid a visible snap on loop? On loop, rotation jumps from 320 to 0 — visually a spin back; with opacity 0 at boundary it's invisible. OK.
Storm drops reuse rainfall.
Reduced motion block at end.
Media queries at end (before or after reduced-motion; order matters only for conflicts).
Hover on touch? fine.
One more check: `.card > * { position: relative; z-index: 1; }` — .icon is child; its absolutely positioned children (.sun etc.) are positioned relative to .icon (nearest positioned ancestor = .icon since it's position:relative now). Good — originally I set .icon position:relative explicitly anyway. The `> *` rule gives .icon relative + z-index 1 — fine.
::before of stormy card: z-index 0, but card's background is on the card itself (background paints below all children regardless). ::before with z-index 0 vs children z-index 1 — children above. ::after sheen z-index 2 above children — includes above meta text? Sheen gradient only top 46%, fades before text. OK.
Hmm: `.card > *` sets position:relative on h2/p — fine.
grid responsive: also center cards? 1fr each fills.
At 375px: padding 22px each side → card width ~331. Fine.
Check horizontal scrollbar: body padding + max-width; ok.
Meta line content:
- Phoenix: "H 34° · L 22°"
- London: "H 14° · L 8°"
- Oslo: "H -1° · L -7°"
- Manila: "H 28° · L 24°"
Descriptions:
- "Clear skies and warm sunshine all day."
- "Light rain expected until this evening."
- "Gentle snow showers drifting through tonight."
- "Thunderstorms building through the afternoon."
Keep shortish; on card width 208px, desc wraps 2 lines fine.
City label maybe with small dot? fine plain.
Title: "Today's Forecast" h1; subtitle "Four skies, animated entirely with HTML & CSS."
Hmm em-dash fine.
Also maybe add small badge row under subtitle? skip.
Double-check no `<script`, no on* attributes, no external anything. Fonts system. Good.
prefers-reduced-motion:
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation: none !important;
transition: none !important;
}
}
```
This also kills hover lift transition — acceptable ("respect reduced motion"). Cards still change shadow on hover instantly. Fine.
Also `.temp` degree formatting: "31°".
Now nth-child rules for drops (rainy card): 6 spans:
```css
.rainy .drops span:nth-child(1) { left: 26%; animation-delay: -.15s; }
.rainy .drops span:nth-child(2) { left: 37%; animation-delay: -.6s; }
.rainy .drops span:nth-child(3) { left: 48%; animation-delay: -1.05s; }
.rainy .drops span:nth-child(4) { left: 59%; animation-delay: -.35s; }
.rainy .drops span:nth-child(5) { left: 70%; animation-delay: -.8s; }
.rainy .drops span:nth-child(6) { left: 81%; animation-delay: -1.2s; }
```
Hmm 81% of 208 = 168; cloud right edge 150. Adjust to 28..74: 28,38,48,58,68,78 → 58..162? 78% = 162 still beyond. The cloud occupies from x=58 to 150 in 208 width → 28%..72%. Use 28,37,46,55,64,73. 73% = 152 ≈ edge. Good.
But wait — .drops container: is it inset:0 of .icon (width 208)? Yes make `.drops, .flakes { position:absolute; inset:0; }`.
Storm drops: 4 spans: lefts 30,46,62,78? avoid bolt 42%..59% (87..123 of 208). 46% = 96 inside bolt zone; but bolt only occupies top 56..106 and drops start at 60 top... bolt region x 87–123; drop at 96 falls through bolt visually — overlapping bolt looks odd (drop in front of bolt). Drops z-order: drops after bolt in DOM → in front. Move storm drops to sides: lefts 18%, 32%, 68%, 82% → 37, 67, 141, 171. Good.
Also storm: maybe reduce drop count visible & fast — 0.8s duration.
Flakes nth rules (6):
```css
.snowy .flake:nth-child(1) { --s: 13px; left: 22%; animation-duration: 3.8s; animation-delay: -.4s; }
.snowy .flake:nth-child(2) { --s: 9px; left: 36%; animation-duration: 4.6s; animation-delay: -2.1s; }
.snowy .flake:nth-child(3) { --s: 12px; left: 47%; animation-duration: 3.4s; animation-delay: -1.3s; }
.snowy .flake:nth-child(4) { --s: 10px; left: 58%; animation-duration: 4.2s; animation-delay: -3.0s; }
.snowy .flake:nth-child(5) { --s: 13px; left: 69%; animation-duration: 3.6s; animation-delay: -2.5s; }
.snowy .flake:nth-child(6) { --s: 9px; left: 80%; animation-duration: 4.4s; animation-delay: -1.0s; }
```
Also flakes color slightly translucent? White with opacity via keyframes .95 — good. Add slight blur? skip.
Cloud for snowy tinted; flakes white pop.
Also maybe snow ground accumulation? skip.
Now double-check the sun ray transform with margin centering: ray positioned left:50% top:50% of .sun (104px box). With margin -2.5/-7.5, center of ray at sun center. transform rotate(a) translateY(-37px): ray's box center moves 37px outward direction a. transform-origin is ray's center — rotation around own center then translate in rotated frame — final center at distance 37 from sun center along direction. Ray length 15 → inner tip at 37-7.5=29.5, outer at 44.5. Core radius 23. Gap 6.5px between core edge and ray inner tip. Good look.
Sun box 104 fits rays (44.5 < 52). Good.
Icon height 124, sun top 10 → occupies 10..114.
For rainy icon: cloud wrapper is .cloud itself absolute top 26 margin-left -46. Bob animates transform translateY — fine (position via top/left/margin untouched).
Everything checks. One more requirement recheck:
- four cards ✓ (sunny, rainy, snowy, stormy)
- no script ✓ no handlers ✓
- no images/SVG/icon fonts ✓ (clip-path & CSS shapes only)
- animated icons: rotating rays ✓, falling drops ✓, drifting flakes ✓, flashing lightning ✓
- city, temp, description ✓
- per-card gradients ✓
- responsive grid 4/row desktop, stacked mobile ✓ (with 2-col tablet)
- hover lift ✓
- keyframes infinite ✓, prefers-reduced-motion ✓
- visible at first paint ✓ (animations with negative delays start mid-motion)
Also title/heading polish. Let me also add `user-select` nothing? unnecessary.
Let me also consider `.top h1` gradient text for signature? Could do background-clip text with warm gradient — nice small touch. h1 { background: linear-gradient(90deg,#9ecbff,#ffe3a1); -webkit-background-clip: text; background-clip:text; color:transparent; } That's a nice tasteful accent. Ensure -webkit-background-clip works in Chrome — yes.
Font sizes h1 2rem.
Also maybe add small "live" pulse dot? skip.
Write final code now. Also include comments modestly.
Check clip-path polygon string syntax: `polygon(58% 0%, 18% 55%, 45% 55%, 32% 100%, 85% 38%, 52% 38%)`. Verify shape closes: last (52,38) connects to first (58,0) — edge from (52,38) to (58,0): up-right slight. The bolt: top tip at 58,0; left slant to 18,55; horizontal to 45,55; down to 32,100 (bottom tip); then up-right to 85,38; left to 52,38; close. Classic zigzag bolt ✓.
Potential issue: `margin: -7.5px 0 0 -2.5px;` fine.
Body flex centering with tall content on mobile: align-items:center could clip top when content taller than viewport? With min-height and padding, flex center + overflow: content taller than viewport → centered but top cut off? In flex, if child taller than container, it overflows both ways but page scroll shows? body min-height 100vh, content taller → body grows to fit content (height auto min 100vh) — align-items center irrelevant once body height = content height. Fine.
Add `overflow-x: hidden` on body as safety? Not needed; but safe: html,body { overflow-x: hidden }? Hmm could interfere with nothing. I'll skip; widths are controlled.
One more: the stormy ::before radial uses fixed px — fine.
Also `transition` for card only transform/box-shadow.
Meta uses `·` char — ensure charset utf-8 ✓.
Degree signs fine.
Let me also give each card a subtle inner border: `border: 1px solid rgba(255,255,255,.14)` — adds definition on dark page. Yes.
Write it.
Also check `.flake i` — `i` is inline; position absolute makes it block-level. inset:0 supported.
For the sheen ::after over icon: pointer-events none set.
Final check on `.card > *` z-index vs ::before storm flash (z-index 0): ::before is not matched by `> *` (it's a pseudo on card). Card children have z-index 1 → above ::before. ✓.
Edge case: `.drops span` base rule includes animation & delay defaults; stormy overrides duration via more specific selector `.stormy .drops span` — specificity (0,2,1) vs base (0,2,1)? base is `.drops span` = (0,1,1). stormy override (0,2,1) wins ✓. nth-child rules `.rainy .drops span:nth-child(1)` (0,3,1) fine.
Rainy drops color: on rainy bg (#46617f→#7d9cbb) light blue drops visible ✓.
Stormy drops lighter blue on dark ✓.
Also the temp for Oslo uses minus sign "−3°" (U+2212) nicer typographically; use "-3°" simple hyphen? Use −.
Now produce final answer: single fenced code block, no surrounding text.
Let me write out completely and carefully.
Additional small nicety: `::selection` styling? skip.
Count keyframes names unique.
For hover, maybe also slightly brighten card: filter? skip.
Card content vertical: after icon margin-bottom 14, city, temp margin 6px 0 2px, desc, meta margin-top auto? Cards equal height via grid stretch; meta at bottom with margin-top:auto requires card display:flex column. Let me do `.card { display:flex; flex-direction:column; }` then `.meta { margin-top: auto; }` so meta pins to bottom on equal-height rows. Nice. Add `padding-bottom:20px`.
Icon flex-shrink 0.
OK writing final.
```css
.grid { ... }
@media (max-width: 980px) { .grid { grid-template-columns: repeat(2, minmax(0,1fr)); } .page? }
@media (max-width: 520px) { .grid { grid-template-columns: 1fr; } }
```
Also on mobile reduce card padding? fine as is.
Header margin-bottom 34px; on mobile 24px via media query maybe. Minor.
Double-check: h1 gradient text needs fallback color if unsupported — Chrome fine.
Let me write the whole file now, mentally compiling for syntax errors.
Also note: `.stormy .cloud { animation-duration: 2.4s; }` — .cloud base has animation shorthand with duration 3.6s; override duration separately keeps name ✓ but must come after base rule ✓.
Storm bolt: also small second bolt? skip.
I think there might be an issue with `.card > *` giving position:relative to .icon — .icon already relative; fine.
For the sunny card, should the sun icon be vertically centered nicely: sun top 10, height 104 → bottom 114, icon 124 — near-centered ✓.
Let me finalize colors:
Sunny gradient: `linear-gradient(170deg, #2e86de 0%, #5fb8ef 55%, #ffe0a3 135%)` — stop 135% valid.
Ray gradient endpoints fine.
Rainy: `linear-gradient(170deg, #4a6482 0%, #7fa2c0 100%)`.
Snowy: `linear-gradient(170deg, #6a97ba 0%, #b8d6ea 100%)` — bottom light; white text with text-shadow okay; desc opacity .9. Meta border white .25. Should be fine.
Stormy: `linear-gradient(170deg, #252d49 0%, #4e5878 100%)`.
Card text default color white #fff.
Page subtitle color rgba(233,240,250,.55).
Now assemble HTML body:
```html
<main class="page">
<header class="top">
<h1>Today's Forecast</h1>
<p>Four skies, animated entirely with HTML & CSS — no JavaScript, no images.</p>
</header>
<section class="grid">
<article class="card sunny">
<div class="icon">
<div class="sun">
<span class="core"></span>
<span class="ray r1"></span>… r8
</div>
</div>
<h2 class="city">Phoenix</h2>
<div class="temp">31°</div>
<p class="desc">Clear skies and warm sunshine all afternoon.</p>
<div class="meta">H 34° · L 22° · UV high</div>
</article>
<article class="card rainy">
<div class="icon">
<div class="cloud"></div>
<div class="drops"><span></span>×6</div>
</div>
<h2 class="city">London</h2>
<div class="temp">11°</div>
<p class="desc">Light rain drifting in from the coast until evening.</p>
<div class="meta">H 14° · L 8° · Humidity 84%</div>
</article>
<article class="card snowy">
<div class="icon">
<div class="cloud"></div>
<div class="flakes">
<span class="flake"><i></i></span> ×6
</div>
</div>
<h2 class="city">Oslo</h2>
<div class="temp">−3°</div>
<p class="desc">Gentle snow showers settling over the city tonight.</p>
<div class="meta">H −1° · L −7° · Wind 14 km/h</div>
</article>
<article class="card stormy">
<div class="icon">
<div class="cloud"></div>
<div class="bolt"></div>
<div class="drops"><span></span>×4</div>
</div>
<h2 class="city">Manila</h2>
<div class="temp">26°</div>
<p class="desc">Thunderstorms building through the afternoon.</p>
<div class="meta">H 28° · L 24° · Storm risk 90%</div>
</article>
</section>
</main>
```
Meta lines might be long for 208px at .78rem: "H 14° · L 8° · Humidity 84%" ~27 chars ≈ 170px ok. "H −1° · L −7° · Wind 14 km/h" ~28 chars ok. Good.
Now finalize CSS ordering. Also make sure `.ray` uses var --a defined by classes r1..r8.
I'm fairly confident. Also ensure `@keyframes spin { to { transform: rotate(360deg); } }` — .sun base transform none; animating to rotate ✓.
Also for reduced motion, blanket kill. Also add `scroll-behavior` irrelevant.
Write final file.
One more consideration on `.cloud::after` right puff overlapping before puff — same color, union shape fine.
Also give `.cloud` slight inner shading? Could add gradient: `background: linear-gradient(180deg, #fbfdff, #e7eef6);` then pseudos `background: inherit` inherit the gradient value — but gradient is sized to each pseudo box individually (puff circles get their own full gradient) → slight mismatch but same colors, subtle. Actually inherit copies the declared value; each box renders gradient across own box — circles would look slightly different shading. To keep flat consistency, use solid colors. Solid chosen ✓.
Also add tiny second puff left: skip.
Final code below. Ensure no stray `<script`. Also no `on...=` attributes. ✓
Also check: transition none in reduced-motion kills hover lift animation — cards still get shadow change instantly. Acceptable.
Also verify no `position: fixed` weirdness. fine.
Grid gap 22; on mobile gap 18.
Write it.