Models 44

GLM 5.3 Flash

by Z.aiReasoningClean
Open in new window

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 &amp; 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 &amp; 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.