DeepSeek v4 pro天气卡片测试,结果有点意外(附GPT5.5对比)

跑了五分多钟才出结果,现在并发可能太高了,有点慢。
用的提示词:

你是 Apple Inc 的顶级 UI 设计师,按 iOS 18 风格(毛玻璃、高斯模糊、动态渐变、细腻阴影)写一个单个HTML文件(包含完整CSS和JS)。做横版天气页面,要4个并排的动画天气卡片:

  • 晴天(太阳光线、动态光晕)
  • 大风(飘动的云、摇曳的树、风线)
  • 暴雨(下落的雨滴、形成水洼、闪电)
  • 暴雪(下落的雪花、堆积效果)
    卡片用深色背景,加按钮切换天气状态,交互要流畅,微动效做好。代码必须能直接跑,美观优先。

HTML 代码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>天气卡片 - iOS 18 风格</title>
    <style>
        :root {
            --bg-deep: #0a0a14;
            --card-bg: rgba(22, 22, 38, 0.55);
            --card-border: rgba(255, 255, 255, 0.12);
            --card-border-hover: rgba(255, 255, 255, 0.22);
            --text-primary: #ffffff;
            --text-secondary: rgba(255, 255, 255, 0.7);
            --text-tertiary: rgba(255, 255, 255, 0.45);
            --shadow-card: 0 2px 16px rgba(0, 0, 0, 0.35), 0 8px 40px rgba(0, 0, 0, 0.25);
            --shadow-card-hover: 0 4px 24px rgba(0, 0, 0, 0.45), 0 12px 56px rgba(0, 0, 0, 0.35),
                0 0 0 1px rgba(255, 255, 255, 0.08) inset;
            --radius-lg: 28px;
            --radius-xl: 32px;
            --transition-smooth: cubic-bezier(0.25, 0.1, 0.25, 1);
            --glass-blur: 24px;
        }

        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: -apple-system, BlinkMacSystemFont, 'SF Pro Display', 'SF Pro Text', 'Helvetica Neue', 'PingFang SC', sans-serif;
            background: #080810;
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            overflow-x: hidden;
            -webkit-font-smoothing: antialiased;
            -moz-osx-font-smoothing: grayscale;
            user-select: none;
            -webkit-user-select: none;
            -webkit-tap-highlight-color: transparent;
        }

        /* 动态渐变背景 */
        .bg-orchestra {
            position: fixed;
            inset: 0;
            z-index: 0;
            pointer-events: none;
            overflow: hidden;
        }
        .bg-orchestra .orb {
            position: absolute;
            border-radius: 50%;
            filter: blur(120px);
            opacity: 0.5;
            animation: orbDrift 18s ease-in-out infinite;
            will-change: transform;
        }
        .bg-orchestra .orb:nth-child(1) {
            width: 620px;
            height: 620px;
            background: radial-gradient(circle, rgba(90, 60, 200, 0.7) 0%, transparent 70%);
            top: -15%;
            left: -10%;
            animation-delay: 0s;
            animation-duration: 20s;
        }
        .bg-orchestra .orb:nth-child(2) {
            width: 500px;
            height: 500px;
            background: radial-gradient(circle, rgba(30, 120, 210, 0.65) 0%, transparent 70%);
            bottom: -18%;
            right: -8%;
            animation-delay: -7s;
            animation-duration: 22s;
        }
        .bg-orchestra .orb:nth-child(3) {
            width: 440px;
            height: 440px;
            background: radial-gradient(circle, rgba(180, 50, 120, 0.5) 0%, transparent 70%);
            top: 40%;
            left: 50%;
            animation-delay: -13s;
            animation-duration: 24s;
        }
        .bg-orchestra .orb:nth-child(4) {
            width: 350px;
            height: 350px;
            background: radial-gradient(circle, rgba(20, 160, 180, 0.55) 0%, transparent 70%);
            bottom: 25%;
            left: 20%;
            animation-delay: -4s;
            animation-duration: 19s;
        }
        @keyframes orbDrift {
            0%,
            100% {
                transform: translate(0, 0) scale(1);
            }
            25% {
                transform: translate(60px, -50px) scale(1.15);
            }
            50% {
                transform: translate(-30px, 40px) scale(0.9);
            }
            75% {
                transform: translate(-50px, -30px) scale(1.08);
            }
        }

        /* 主容器 */
        .main-container {
            position: relative;
            z-index: 1;
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 36px;
            padding: 28px 32px 36px;
            width: 100%;
            max-width: 1400px;
        }

        /* 标题 */
        .page-title {
            font-size: 1.6rem;
            font-weight: 500;
            letter-spacing: -0.02em;
            color: rgba(255, 255, 255, 0.85);
            text-align: center;
            text-shadow: 0 1px 3px rgba(0, 0, 0, 0.4);
        }
        .page-title span {
            font-weight: 300;
            color: rgba(255, 255, 255, 0.5);
        }

        /* 卡片行 */
        .cards-row {
            display: flex;
            gap: 20px;
            width: 100%;
            justify-content: center;
            flex-wrap: nowrap;
        }

        /* 单张卡片 */
        .weather-card {
            position: relative;
            flex: 1 1 260px;
            max-width: 300px;
            min-width: 240px;
            height: 460px;
            border-radius: var(--radius-xl);
            background: var(--card-bg);
            backdrop-filter: blur(var(--glass-blur));
            -webkit-backdrop-filter: blur(var(--glass-blur));
            border: 1px solid var(--card-border);
            box-shadow: var(--shadow-card);
            overflow: hidden;
            cursor: pointer;
            transition: all 0.5s var(--transition-smooth);
            isolation: isolate;
            -webkit-transform: translateZ(0);
            transform: translateZ(0);
        }
        .weather-card:hover {
            border-color: var(--card-border-hover);
            box-shadow: var(--shadow-card-hover);
            transform: translateY(-6px);
            transition: all 0.35s var(--transition-smooth);
        }
        .weather-card:active {
            transform: scale(0.975);
            transition: all 0.15s var(--transition-smooth);
        }

        /* 卡片内的光晕反射 (iOS风格顶部高光) */
        .weather-card::after {
            content: '';
            position: absolute;
            inset: 0;
            border-radius: var(--radius-xl);
            pointer-events: none;
            z-index: 10;
            background: linear-gradient(180deg,
                    rgba(255, 255, 255, 0.06) 0%,
                    rgba(255, 255, 255, 0.01) 30%,
                    transparent 55%,
                    rgba(0, 0, 0, 0.03) 100%);
            transition: opacity 0.5s;
        }
        .weather-card:hover::after {
            background: linear-gradient(180deg,
                    rgba(255, 255, 255, 0.09) 0%,
                    rgba(255, 255, 255, 0.02) 30%,
                    transparent 55%,
                    rgba(0, 0, 0, 0.02) 100%);
        }

        /* Canvas层 */
        .weather-card canvas {
            position: absolute;
            inset: 0;
            z-index: 2;
            border-radius: var(--radius-xl);
            pointer-events: none;
        }

        /* 卡片内容覆盖层 */
        .card-overlay {
            position: absolute;
            bottom: 0;
            left: 0;
            right: 0;
            z-index: 5;
            padding: 20px 22px 22px;
            display: flex;
            flex-direction: column;
            gap: 4px;
            pointer-events: none;
            background: linear-gradient(180deg, transparent 0%, rgba(0, 0, 0, 0.35) 55%, rgba(0, 0, 0, 0.6) 100%);
            border-radius: 0 0 var(--radius-xl) var(--radius-xl);
        }
        .card-overlay .temp {
            font-size: 3.4rem;
            font-weight: 200;
            letter-spacing: -0.04em;
            color: #fff;
            line-height: 1;
            text-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
        }
        .card-overlay .desc {
            font-size: 0.95rem;
            font-weight: 400;
            color: rgba(255, 255, 255, 0.8);
            letter-spacing: 0.01em;
        }
        .card-overlay .city {
            font-size: 0.78rem;
            font-weight: 400;
            color: rgba(255, 255, 255, 0.5);
            letter-spacing: 0.02em;
            text-transform: uppercase;
        }

        /* 选中高亮指示 */
        .weather-card.selected {
            border-color: rgba(255, 255, 255, 0.35);
            box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.18), 0 8px 40px rgba(0, 0, 0, 0.4),
                0 0 80px rgba(120, 140, 255, 0.2);
            transform: translateY(-4px);
        }
        .weather-card.selected::before {
            content: '';
            position: absolute;
            inset: -3px;
            border-radius: calc(var(--radius-xl) + 3px);
            z-index: -1;
            pointer-events: none;
            background: linear-gradient(135deg,
                    rgba(255, 255, 255, 0.25) 0%,
                    rgba(255, 255, 255, 0.05) 40%,
                    rgba(255, 255, 255, 0.15) 100%);
            animation: selectedGlow 3s ease-in-out infinite;
        }
        @keyframes selectedGlow {
            0%,
            100% {
                opacity: 0.6;
            }
            50% {
                opacity: 1;
            }
        }

        /* 底部控制栏 */
        .controls-bar {
            display: flex;
            align-items: center;
            gap: 10px;
            background: rgba(30, 30, 48, 0.5);
            backdrop-filter: blur(28px);
            -webkit-backdrop-filter: blur(28px);
            border: 1px solid rgba(255, 255, 255, 0.13);
            border-radius: 40px;
            padding: 6px;
            box-shadow: 0 4px 20px rgba(0, 0, 0, 0.35);
            z-index: 10;
        }
        .intensity-btn {
            padding: 10px 22px;
            border-radius: 34px;
            border: none;
            background: transparent;
            color: rgba(255, 255, 255, 0.65);
            font-size: 0.9rem;
            font-weight: 500;
            letter-spacing: 0.01em;
            cursor: pointer;
            transition: all 0.4s var(--transition-smooth);
            font-family: inherit;
            white-space: nowrap;
            position: relative;
            outline: none;
            -webkit-tap-highlight-color: transparent;
        }
        .intensity-btn:hover {
            color: rgba(255, 255, 255, 0.9);
            background: rgba(255, 255, 255, 0.06);
        }
        .intensity-btn.active {
            background: rgba(255, 255, 255, 0.15);
            color: #fff;
            font-weight: 600;
            box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3), 0 0 0 1px rgba(255, 255, 255, 0.2) inset;
            letter-spacing: 0.02em;
        }
        .intensity-btn:active {
            transform: scale(0.94);
            transition: all 0.12s var(--transition-smooth);
        }

        .controls-label {
            color: rgba(255, 255, 255, 0.4);
            font-size: 0.78rem;
            font-weight: 500;
            letter-spacing: 0.04em;
            padding: 0 8px;
            pointer-events: none;
            white-space: nowrap;
        }

        /* 响应式 */
        @media (max-width: 1100px) {
            .cards-row {
                flex-wrap: wrap;
                gap: 16px;
            }
            .weather-card {
                flex: 1 1 220px;
                max-width: 48%;
                min-width: 180px;
                height: 400px;
                border-radius: 24px;
            }
            .weather-card::after {
                border-radius: 24px;
            }
            .weather-card canvas {
                border-radius: 24px;
            }
            .card-overlay {
                border-radius: 0 0 24px 24px;
                padding: 16px 16px 18px;
            }
            .card-overlay .temp {
                font-size: 2.6rem;
            }
            .weather-card.selected::before {
                border-radius: 27px;
            }
        }
        @media (max-width: 640px) {
            .cards-row {
                flex-wrap: wrap;
                gap: 12px;
            }
            .weather-card {
                flex: 1 1 44%;
                max-width: 48%;
                min-width: 150px;
                height: 340px;
                border-radius: 20px;
            }
            .weather-card::after,
            .weather-card canvas {
                border-radius: 20px;
            }
            .card-overlay {
                border-radius: 0 0 20px 20px;
                padding: 12px 14px 14px;
            }
            .card-overlay .temp {
                font-size: 2rem;
            }
            .card-overlay .desc {
                font-size: 0.8rem;
            }
            .card-overlay .city {
                font-size: 0.7rem;
            }
            .weather-card.selected::before {
                border-radius: 23px;
            }
            .controls-bar {
                gap: 4px;
                padding: 4px;
                border-radius: 32px;
            }
            .intensity-btn {
                padding: 8px 14px;
                font-size: 0.78rem;
                border-radius: 26px;
            }
            .controls-label {
                font-size: 0.7rem;
                padding: 0 4px;
            }
            .page-title {
                font-size: 1.3rem;
            }
        }
    </style>
</head>
<body>

    <!-- 动态渐变背景 -->
    <div class="bg-orchestra">
        <div class="orb"></div>
        <div class="orb"></div>
        <div class="orb"></div>
        <div class="orb"></div>
    </div>

    <!-- 主容器 -->
    <div class="main-container">
        <div class="page-title">天 气 <span>· Weather</span></div>

        <!-- 卡片行 -->
        <div class="cards-row" id="cardsRow">
            <!-- 晴天卡片 -->
            <div class="weather-card" data-weather="sunny" data-index="0">
                <canvas id="canvasSunny" aria-hidden="true"></canvas>
                <div class="card-overlay">
                    <div class="temp">28°</div>
                    <div class="desc">晴空万里</div>
                    <div class="city">加利福尼亚 · 库比蒂诺</div>
                </div>
            </div>
            <!-- 大风卡片 -->
            <div class="weather-card" data-weather="windy" data-index="1">
                <canvas id="canvasWindy" aria-hidden="true"></canvas>
                <div class="card-overlay">
                    <div class="temp">19°</div>
                    <div class="desc">大风呼啸</div>
                    <div class="city">伊利诺伊 · 芝加哥</div>
                </div>
            </div>
            <!-- 暴雨卡片 -->
            <div class="weather-card" data-weather="rain" data-index="2">
                <canvas id="canvasRain" aria-hidden="true"></canvas>
                <div class="card-overlay">
                    <div class="temp">14°</div>
                    <div class="desc">暴雨倾盆</div>
                    <div class="city">华盛顿 · 西雅图</div>
                </div>
            </div>
            <!-- 暴雪卡片 -->
            <div class="weather-card" data-weather="snow" data-index="3">
                <canvas id="canvasSnow" aria-hidden="true"></canvas>
                <div class="card-overlay">
                    <div class="temp">-8°</div>
                    <div class="desc">暴雪纷飞</div>
                    <div class="city">纽约 · 布法罗</div>
                </div>
            </div>
        </div>

        <!-- 强度控制栏 -->
        <div class="controls-bar" id="controlsBar">
            <span class="controls-label">强度</span>
            <button class="intensity-btn" data-level="1" aria-label="轻度">🌤 柔和</button>
            <button class="intensity-btn active" data-level="2" aria-label="适中">⛅ 适中</button>
            <button class="intensity-btn" data-level="3" aria-label="强烈">🌩 强烈</button>
        </div>
    </div>

    <script>
        (function() {
            // ============ 全局状态 ============
            let globalIntensity = 2; // 1=柔和, 2=适中, 3=强烈
            let selectedCardIndex = -1;
            const cardElements = document.querySelectorAll('.weather-card');
            const intensityButtons = document.querySelectorAll('.intensity-btn');

            // ============ Canvas 初始化 ============
            const canvasIds = ['canvasSunny', 'canvasWindy', 'canvasRain', 'canvasSnow'];
            const weatherTypes = ['sunny', 'windy', 'rain', 'snow'];
            const canvases = {};
            const contexts = {};
            const cardRects = {}; // 存储每个卡片canvas的绘制尺寸

            function initCanvas(canvasId, cardEl) {
                const canvas = document.getElementById(canvasId);
                if (!canvas) return null;
                const dpr = Math.min(window.devicePixelRatio || 1, 2); // 限制像素比以保证性能
                const rect = cardEl.getBoundingClientRect();
                const displayW = rect.width;
                const displayH = rect.height;
                if (displayW <= 0 || displayH <= 0) {
                    // 使用默认尺寸
                    const defaultW = 280;
                    const defaultH = 460;
                    canvas.width = defaultW * dpr;
                    canvas.height = defaultH * dpr;
                    canvas.style.width = defaultW + 'px';
                    canvas.style.height = defaultH + 'px';
                    return { width: defaultW, height: defaultH, dpr };
                }
                canvas.width = displayW * dpr;
                canvas.height = displayH * dpr;
                canvas.style.width = displayW + 'px';
                canvas.style.height = displayH + 'px';
                return { width: displayW, height: displayH, dpr };
            }

            function updateAllCanvasSizes() {
                cardElements.forEach((cardEl, i) => {
                    const canvasId = canvasIds[i];
                    const info = initCanvas(canvasId, cardEl);
                    if (info) {
                        cardRects[canvasId] = info;
                        canvases[canvasId] = document.getElementById(canvasId);
                        contexts[canvasId] = canvases[canvasId]?.getContext('2d');
                    }
                });
            }

            updateAllCanvasSizes();

            // ============ 粒子系统 ============
            // 每个天气的粒子数据
            const particleData = {
                sunny: { particles: [], lastEmit: 0 },
                windy: { particles: [], clouds: [], windLines: [], treeSway: 0, lastEmit: 0 },
                rain: { drops: [], puddleRipples: [], lightningTimer: 0, lightningActive: false,
                lightningAlpha: 0, lastLightning: 0 },
                snow: { flakes: [], snowAccum: 0, lastEmit: 0 },
            };

            function resetParticles(weatherType) {
                const data = particleData[weatherType];
                if (!data) return;
                if (weatherType === 'sunny') {
                    data.particles = [];
                } else if (weatherType === 'windy') {
                    data.particles = [];
                    data.clouds = [];
                    data.windLines = [];
                    data.treeSway = 0;
                    // 初始化云朵
                    const w = cardRects['canvasWindy']?.width || 280;
                    const h = cardRects['canvasWindy']?.height || 460;
                    for (let i = 0; i < 5; i++) {
                        data.clouds.push({
                            x: Math.random() * w * 1.3 - w * 0.15,
                            y: 30 + Math.random() * h * 0.3,
                            width: 60 + Math.random() * 100,
                            height: 25 + Math.random() * 40,
                            speed: 0.3 + Math.random() * 0.8,
                            opacity: 0.5 + Math.random() * 0.4,
                        });
                    }
                    for (let i = 0; i < 12; i++) {
                        data.windLines.push({
                            x: Math.random() * w,
                            y: Math.random() * h,
                            length: 30 + Math.random() * 80,
                            speed: 1.5 + Math.random() * 4,
                            opacity: 0.2 + Math.random() * 0.4,
                            thickness: 0.5 + Math.random() * 1.5,
                        });
                    }
                } else if (weatherType === 'rain') {
                    data.drops = [];
                    data.puddleRipples = [];
                    data.lightningTimer = 0;
                    data.lightningActive = false;
                    data.lightningAlpha = 0;
                    data.lastLightning = performance.now();
                    const w = cardRects['canvasRain']?.width || 280;
                    const h = cardRects['canvasRain']?.height || 460;
                    // 预生成雨滴
                    const dropCount = 90;
                    for (let i = 0; i < dropCount; i++) {
                        data.drops.push({
                            x: Math.random() * w,
                            y: Math.random() * h,
                            speed: 5 + Math.random() * 10,
                            length: 10 + Math.random() * 22,
                            opacity: 0.35 + Math.random() * 0.5,
                            thickness: 1 + Math.random() * 1.8,
                        });
                    }
                    // 水洼位置
                    for (let i = 0; i < 4; i++) {
                        data.puddleRipples.push({
                            cx: 30 + Math.random() * (w - 60),
                            cy: h - 20 - Math.random() * 50,
                            radius: 15 + Math.random() * 30,
                            maxRadius: 35 + Math.random() * 50,
                            ripples: [],
                        });
                    }
                } else if (weatherType === 'snow') {
                    data.flakes = [];
                    data.snowAccum = 0;
                    const w = cardRects['canvasSnow']?.width || 280;
                    const h = cardRects['canvasSnow']?.height || 460;
                    const flakeCount = 100;
                    for (let i = 0; i < flakeCount; i++) {
                        data.flakes.push({
                            x: Math.random() * w,
                            y: Math.random() * h,
                            radius: 1.2 + Math.random() * 3.5,
                            speed: 0.4 + Math.random() * 1.6,
                            windDrift: -0.3 + Math.random() * 0.6,
                            opacity: 0.5 + Math.random() * 0.5,
                            wobbleAmp: 0.3 + Math.random() * 1.2,
                            wobbleSpeed: 0.01 + Math.random() * 0.03,
                            wobbleOffset: Math.random() * Math.PI * 2,
                        });
                    }
                }
            }

            // 初始化所有粒子
            weatherTypes.forEach(wt => resetParticles(wt));

            // ============ 绘制函数 ============

            // --- 晴天 ---
            function drawSunny(ctx, w, h, time, intensity) {
                ctx.clearRect(0, 0, w, h);
                const dpr = cardRects['canvasSunny']?.dpr || 2;

                // 天空渐变
                const skyGrad = ctx.createLinearGradient(0, 0, 0, h);
                const topColor = [135, 195, 235]; // 浅蓝
                const bottomColor = [245, 210, 140]; // 暖金
                const tR = topColor[0] - intensity * 12;
                const tG = topColor[1] - intensity * 10;
                const tB = topColor[2] + intensity * 5;
                const bR = bottomColor[0] + intensity * 8;
                const bG = bottomColor[1] - intensity * 15;
                const bB = bottomColor[2] - intensity * 20;
                skyGrad.addColorStop(0, `rgb(${Math.max(80,tR)},${Math.max(130,tG)},${Math.min(255,tB)})`);
                skyGrad.addColorStop(0.55, `rgb(${Math.max(160,bR)},${Math.max(170,bG)},${Math.max(100,bB)})`);
                skyGrad.addColorStop(1, `rgb(${Math.max(200,bR+20)},${Math.max(185,bG+10)},${Math.max(120,bB+15)})`);
                ctx.fillStyle = skyGrad;
                ctx.fillRect(0, 0, w, h);

                // 太阳
                const sunX = w * 0.72;
                const sunY = h * 0.22;
                const sunBaseRadius = 38 + intensity * 6;
                const glowPulse = 1 + Math.sin(time * 0.0018) * 0.08 + Math.sin(time * 0.0027) * 0.05;
                const sunRadius = sunBaseRadius * glowPulse;

                // 外层大光晕
                const outerGlow = ctx.createRadialGradient(sunX, sunY, sunRadius * 0.6, sunX, sunY, sunRadius * 3.5);
                outerGlow.addColorStop(0, 'rgba(255,220,130,0.55)');
                outerGlow.addColorStop(0.3, 'rgba(255,190,80,0.25)');
                outerGlow.addColorStop(0.6, 'rgba(255,150,40,0.06)');
                outerGlow.addColorStop(1, 'rgba(255,120,20,0)');
                ctx.fillStyle = outerGlow;
                ctx.beginPath();
                ctx.arc(sunX, sunY, sunRadius * 3.5, 0, Math.PI * 2);
                ctx.fill();

                // 中层光晕
                const midGlow = ctx.createRadialGradient(sunX, sunY, sunRadius * 0.4, sunX, sunY, sunRadius * 2);
                midGlow.addColorStop(0, 'rgba(255,240,180,0.8)');
                midGlow.addColorStop(0.5, 'rgba(255,200,100,0.35)');
                midGlow.addColorStop(1, 'rgba(255,160,50,0)');
                ctx.fillStyle = midGlow;
                ctx.beginPath();
                ctx.arc(sunX, sunY, sunRadius * 2, 0, Math.PI * 2);
                ctx.fill();

                // 太阳本体
                const sunBody = ctx.createRadialGradient(sunX - sunRadius * 0.15, sunY - sunRadius * 0.15, sunRadius * 0.05,
                    sunX, sunY, sunRadius);
                sunBody.addColorStop(0, 'rgba(255,255,245,1)');
                sunBody.addColorStop(0.35, 'rgba(255,240,200,0.95)');
                sunBody.addColorStop(0.7, 'rgba(255,200,100,0.7)');
                sunBody.addColorStop(1, 'rgba(255,150,40,0)');
                ctx.fillStyle = sunBody;
                ctx.beginPath();
                ctx.arc(sunX, sunY, sunRadius, 0, Math.PI * 2);
                ctx.fill();

                // 光线
                const rayCount = 16 + intensity * 4;
                const rayMaxLen = sunRadius * 2.2;
                for (let i = 0; i < rayCount; i++) {
                    const angle = (i / rayCount) * Math.PI * 2 + time * 0.0003;
                    const rayLen = rayMaxLen * (0.55 + 0.45 * Math.sin(time * 0.004 + i * 1.3));
                    const startR = sunRadius * 0.9;
                    const endR = startR + rayLen;
                    const sx = sunX + Math.cos(angle) * startR;
                    const sy = sunY + Math.sin(angle) * startR;
                    const ex = sunX + Math.cos(angle) * endR;
                    const ey = sunY + Math.sin(angle) * endR;
                    const rayGrad = ctx.createLinearGradient(sx, sy, ex, ey);
                    rayGrad.addColorStop(0, 'rgba(255,240,200,0.5)');
                    rayGrad.addColorStop(1, 'rgba(255,200,100,0)');
                    ctx.strokeStyle = rayGrad;
                    ctx.lineWidth = 1.5 + Math.random() * 0.5;
                    ctx.lineCap = 'round';
                    ctx.beginPath();
                    ctx.moveTo(sx, sy);
                    ctx.lineTo(ex, ey);
                    ctx.stroke();
                }

                // 微粒子(阳光尘埃)
                const data = particleData['sunny'];
                if (data.particles.length < 25) {
                    data.particles.push({
                        x: Math.random() * w,
                        y: Math.random() * h,
                        radius: 0.6 + Math.random() * 1.8,
                        speed: 0.15 + Math.random() * 0.5,
                        opacity: 0.25 + Math.random() * 0.45,
                        phase: Math.random() * Math.PI * 2,
                    });
                }
                for (let p of data.particles) {
                    p.y -= p.speed;
                    p.x += Math.sin(time * 0.002 + p.phase) * 0.3;
                    if (p.y < -10) { p.y = h + 10;
                        p.x = Math.random() * w; }
                    if (p.x < -10) p.x = w + 10;
                    if (p.x > w + 10) p.x = -10;
                    ctx.fillStyle = `rgba(255,240,200,${p.opacity})`;
                    ctx.beginPath();
                    ctx.arc(p.x, p.y, p.radius, 0, Math.PI * 2);
                    ctx.fill();
                }
                // 限制粒子数
                while (data.particles.length > 35) data.particles.shift();
            }

            // --- 大风 ---
            function drawWindy(ctx, w, h, time, intensity) {
                ctx.clearRect(0, 0, w, h);
                const dpr = cardRects['canvasWindy']?.dpr || 2;

                // 天空
                const skyGrad = ctx.createLinearGradient(0, 0, 0, h);
                skyGrad.addColorStop(0, 'rgb(110,130,155)');
                skyGrad.addColorStop(0.5, 'rgb(145,160,175)');
                skyGrad.addColorStop(1, 'rgb(170,180,190)');
                ctx.fillStyle = skyGrad;
                ctx.fillRect(0, 0, w, h);

                const data = particleData['windy'];
                const windMultiplier = 0.6 + intensity * 0.7;

                // 风线
                for (let wl of data.windLines) {
                    wl.x += wl.speed * windMultiplier;
                    if (wl.x > w + 40) { wl.x = -80;
                        wl.y = Math.random() * h; }
                    const grad = ctx.createLinearGradient(wl.x, wl.y, wl.x - wl.length, wl.y);
                    grad.addColorStop(0, `rgba(220,225,235,${wl.opacity})`);
                    grad.addColorStop(1, 'rgba(220,225,235,0)');
                    ctx.strokeStyle = grad;
                    ctx.lineWidth = wl.thickness;
                    ctx.lineCap = 'round';
                    ctx.beginPath();
                    ctx.moveTo(wl.x, wl.y);
                    ctx.lineTo(wl.x - wl.length, wl.y + Math.sin(time * 0.01 + wl.y * 0.05) * 3);
                    ctx.stroke();
                }

                // 云朵
                for (let cloud of data.clouds) {
                    cloud.x += cloud.speed * windMultiplier;
                    if (cloud.x > w + 120) { cloud.x = -120;
                        cloud.y = 20 + Math.random() * h * 0.35; }
                    drawCloud(ctx, cloud.x, cloud.y, cloud.width, cloud.height, cloud.opacity);
                }

                // 摇曳的树
                const treeBaseX = w * 0.35;
                const treeBaseY = h * 0.78;
                const swayAngle = Math.sin(time * 0.003) * (0.15 + intensity * 0.18) + Math.sin(time * 0.007) * 0.06;
                drawTree(ctx, treeBaseX, treeBaseY, 55, swayAngle, intensity);
                const tree2X = w * 0.62;
                const tree2Y = h * 0.82;
                const sway2 = Math.sin(time * 0.0035 + 1.5) * (0.12 + intensity * 0.16) + Math.sin(time * 0.006 + 2) * 0.05;
                drawTree(ctx, tree2X, tree2Y, 42, sway2, intensity);

                // 落叶
                if (data.particles.length < 8 + intensity * 4) {
                    data.particles.push({
                        x: Math.random() * w,
                        y: -10 - Math.random() * 60,
                        radius: 2 + Math.random() * 3.5,
                        speedX: 1 + Math.random() * 3 * windMultiplier,
                        speedY: 0.6 + Math.random() * 1.5,
                        rotation: Math.random() * Math.PI * 2,
                        rotSpeed: 0.02 + Math.random() * 0.08,
                        opacity: 0.5 + Math.random() * 0.4,
                        color: ['#c49040', '#d4a050', '#b87830', '#e0b860'][Math.floor(Math.random() * 4)],
                    });
                }
                for (let p of data.particles) {
                    p.x += p.speedX;
                    p.y += p.speedY;
                    p.rotation += p.rotSpeed;
                    if (p.y > h + 20) { p.y = -20;
                        p.x = Math.random() * w; }
                    if (p.x > w + 30) p.x = -30;
                    if (p.x < -30) p.x = w + 30;
                    ctx.save();
                    ctx.translate(p.x, p.y);
                    ctx.rotate(p.rotation);
                    ctx.fillStyle = p.color;
                    ctx.globalAlpha = p.opacity;
                    ctx.beginPath();
                    ctx.ellipse(0, 0, p.radius, p.radius * 0.55, 0, 0, Math.PI * 2);
                    ctx.fill();
                    ctx.restore();
                }
                while (data.particles.length > 16 + intensity * 6) data.particles.shift();
            }

            function drawCloud(ctx, x, y, w, h, opacity) {
                ctx.fillStyle = `rgba(210,215,225,${opacity})`;
                ctx.beginPath();
                const cx = x + w / 2;
                const cy = y + h / 2;
                ctx.arc(cx - w * 0.25, cy, h * 0.5, 0, Math.PI * 2);
                ctx.arc(cx + w * 0.1, cy - h * 0.15, h * 0.55, 0, Math.PI * 2);
                ctx.arc(cx + w * 0.3, cy, h * 0.45, 0, Math.PI * 2);
                ctx.arc(cx - w * 0.05, cy - h * 0.2, h * 0.5, 0, Math.PI * 2);
                ctx.arc(cx + w * 0.18, cy + h * 0.05, h * 0.4, 0, Math.PI * 2);
                ctx.fill();
            }

            function drawTree(ctx, baseX, baseY, height, swayAngle, intensity) {
                const trunkHeight = height * 0.45;
                const trunkTopX = baseX + Math.sin(swayAngle) * trunkHeight * 0.5;
                const trunkTopY = baseY - trunkHeight;
                // 树干
                ctx.strokeStyle = '#8B7355';
                ctx.lineWidth = 6;
                ctx.lineCap = 'round';
                ctx.beginPath();
                ctx.moveTo(baseX, baseY);
                ctx.lineTo(trunkTopX, trunkTopY);
                ctx.stroke();
                // 树枝和树冠
                const crownCenterX = trunkTopX + Math.sin(swayAngle * 1.6) * height * 0.3;
                const crownCenterY = trunkTopY - height * 0.3;
                const crownRadius = height * 0.38;
                // 多个圆形组成树冠
                const crownParts = [
                    { dx: 0, dy: 0, r: crownRadius },
                    { dx: crownRadius * 0.5, dy: -crownRadius * 0.2, r: crownRadius * 0.75 },
                    { dx: -crownRadius * 0.45, dy: -crownRadius * 0.1, r: crownRadius * 0.7 },
                    { dx: crownRadius * 0.15, dy: -crownRadius * 0.55, r: crownRadius * 0.65 },
                    { dx: -crownRadius * 0.15, dy: crownRadius * 0.3, r: crownRadius * 0.55 },
                ];
                const greenBase = [80, 140, 70];
                for (let part of crownParts) {
                    const shade = 0.75 + Math.random() * 0.25;
                    ctx.fillStyle =
                        `rgba(${Math.floor(greenBase[0]*shade)},${Math.floor(greenBase[1]*shade)},${Math.floor(greenBase[2]*shade)},0.85)`;
                    ctx.beginPath();
                    ctx.arc(crownCenterX + part.dx, crownCenterY + part.dy, part.r, 0, Math.PI * 2);
                    ctx.fill();
                }
                // 更亮的树冠高光
                const highlightGrad = ctx.createRadialGradient(crownCenterX - crownRadius * 0.2, crownCenterY - crownRadius *
                    0.25, crownRadius * 0.15, crownCenterX, crownCenterY, crownRadius);
                highlightGrad.addColorStop(0, 'rgba(160,210,130,0.45)');
                highlightGrad.addColorStop(1, 'rgba(80,140,70,0)');
                ctx.fillStyle = highlightGrad;
                ctx.beginPath();
                ctx.arc(crownCenterX, crownCenterY, crownRadius, 0, Math.PI * 2);
                ctx.fill();
            }

            // --- 暴雨 ---
            function drawRain(ctx, w, h, time, intensity) {
                ctx.clearRect(0, 0, w, h);
                const dpr = cardRects['canvasRain']?.dpr || 2;

                // 暗沉天空
                const skyGrad = ctx.createLinearGradient(0, 0, 0, h);
                skyGrad.addColorStop(0, 'rgb(30,35,50)');
                skyGrad.addColorStop(0.4, 'rgb(45,50,65)');
                skyGrad.addColorStop(0.75, 'rgb(55,58,70)');
                skyGrad.addColorStop(1, 'rgb(65,68,78)');
                ctx.fillStyle = skyGrad;
                ctx.fillRect(0, 0, w, h);

                const data = particleData['rain'];
                const dropMultiplier = 0.5 + intensity * 0.5;
                const now = performance.now();

                // 闪电
                if (!data.lightningActive && now - data.lastLightning > 2500 - intensity * 600) {
                    if (Math.random() < 0.25 + intensity * 0.2) {
                        data.lightningActive = true;
                        data.lightningTimer = 0;
                        data.lightningAlpha = 1;
                        data.lastLightning = now;
                        data.lightningPath = generateLightningPath(w, h);
                    }
                }
                if (data.lightningActive) {
                    data.lightningTimer += 16;
                    if (data.lightningTimer < 60) {
                        data.lightningAlpha = 1;
                    } else if (data.lightningTimer < 180) {
                        data.lightningAlpha = Math.max(0, 1 - (data.lightningTimer - 60) / 120);
                    } else {
                        data.lightningActive = false;
                        data.lightningAlpha = 0;
                    }
                    // 绘制闪电闪光
                    if (data.lightningAlpha > 0.01) {
                        const flashGrad = ctx.createRadialGradient(w * 0.5, h * 0.35, 0, w * 0.5, h * 0.35, Math.max(w, h) * 0.8);
                        flashGrad.addColorStop(0, `rgba(255,255,240,${data.lightningAlpha*0.6})`);
                        flashGrad.addColorStop(0.3, `rgba(220,220,255,${data.lightningAlpha*0.3})`);
                        flashGrad.addColorStop(1, `rgba(200,200,240,0)`);
                        ctx.fillStyle = flashGrad;
                        ctx.fillRect(0, 0, w, h);
                        // 闪电线条
                        if (data.lightningPath && data.lightningAlpha > 0.3) {
                            ctx.strokeStyle = `rgba(255,255,240,${data.lightningAlpha})`;
                            ctx.lineWidth = 2.5;
                            ctx.lineCap = 'round';
                            ctx.lineJoin = 'round';
                            ctx.shadowColor = 'rgba(255,255,240,0.9)';
                            ctx.shadowBlur = 18;
                            ctx.beginPath();
                            ctx.moveTo(data.lightningPath[0].x, data.lightningPath[0].y);
                            for (let i = 1; i < data.lightningPath.length; i++) {
                                ctx.lineTo(data.lightningPath[i].x, data.lightningPath[i].y);
                            }
                            ctx.stroke();
                            ctx.shadowBlur = 0;
                            // 细分支
                            ctx.strokeStyle = `rgba(255,255,240,${data.lightningAlpha*0.55})`;
                            ctx.lineWidth = 1;
                            ctx.beginPath();
                            const mainPath = data.lightningPath;
                            const midIdx = Math.floor(mainPath.length / 2);
                            ctx.moveTo(mainPath[midIdx].x, mainPath[midIdx].y);
                            ctx.lineTo(mainPath[midIdx].x + 25, mainPath[midIdx].y + 30);
                            ctx.moveTo(mainPath[Math.floor(midIdx * 0.7)].x, mainPath[Math.floor(midIdx * 0.7)].y);
                            ctx.lineTo(mainPath[Math.floor(midIdx * 0.7)].x - 18, mainPath[Math.floor(midIdx * 0.7)].y + 22);
                            ctx.stroke();
                        }
                    }
                }

                // 雨滴
                const targetDropCount = Math.floor((60 + intensity * 50) * dropMultiplier);
                while (data.drops.length < targetDropCount) {
                    data.drops.push({
                        x: Math.random() * w * 1.15 - w * 0.075,
                        y: -Math.random() * h,
                        speed: 6 + Math.random() * 12 * dropMultiplier,
                        length: 10 + Math.random() * 24,
                        opacity: 0.3 + Math.random() * 0.55,
                        thickness: 1 + Math.random() * 2,
                    });
                }
                while (data.drops.length > targetDropCount + 10) data.drops.shift();

                for (let drop of data.drops) {
                    drop.y += drop.speed;
                    if (drop.y > h + 20) {
                        drop.y = -15 - Math.random() * 30;
                        drop.x = Math.random() * w * 1.15 - w * 0.075;
                        // 溅起水花(水洼波纹)
                        if (drop.y > h - 60 && Math.random() < 0.25) {
                            const puddle = data.puddleRipples[Math.floor(Math.random() * data.puddleRipples.length)];
                            if (puddle) {
                                puddle.ripples.push({
                                    radius: 2,
                                    maxRadius: 14 + Math.random() * 24,
                                    alpha: 0.7,
                                    speed: 0.8 + Math.random() * 1.5,
                                });
                            }
                        }
                    }
                    ctx.strokeStyle = `rgba(180,195,220,${drop.opacity})`;
                    ctx.lineWidth = drop.thickness;
                    ctx.lineCap = 'round';
                    ctx.beginPath();
                    ctx.moveTo(drop.x, drop.y);
                    ctx.lineTo(drop.x - 1.5, drop.y + drop.length);
                    ctx.stroke();
                }

                // 水洼与波纹
                for (let puddle of data.puddleRipples) {
                    // 水洼本体
                    const puddleGrad = ctx.createRadialGradient(puddle.cx, puddle.cy, puddle.radius * 0.3, puddle.cx, puddle.cy,
                        puddle.radius);
                    puddleGrad.addColorStop(0, 'rgba(140,155,180,0.5)');
                    puddleGrad.addColorStop(0.7, 'rgba(100,115,140,0.3)');
                    puddleGrad.addColorStop(1, 'rgba(80,95,120,0)');
                    ctx.fillStyle = puddleGrad;
                    ctx.beginPath();
                    ctx.ellipse(puddle.cx, puddle.cy, puddle.radius, puddle.radius * 0.45, 0, 0, Math.PI * 2);
                    ctx.fill();

                    // 波纹
                    for (let i = puddle.ripples.length - 1; i >= 0; i--) {
                        const rp = puddle.ripples[i];
                        rp.radius += rp.speed;
                        rp.alpha -= 0.025;
                        if (rp.alpha <= 0 || rp.radius > rp.maxRadius) {
                            puddle.ripples.splice(i, 1);
                            continue;
                        }
                        ctx.strokeStyle = `rgba(200,210,225,${rp.alpha})`;
                        ctx.lineWidth = 1;
                        ctx.beginPath();
                        ctx.ellipse(puddle.cx, puddle.cy, rp.radius, rp.radius * 0.45, 0, 0, Math.PI * 2);
                        ctx.stroke();
                    }
                    // 限制波纹数
                    while (puddle.ripples.length > 8) puddle.ripples.shift();
                }
            }

            function generateLightningPath(w, h) {
                const startX = w * 0.4 + Math.random() * w * 0.2;
                const startY = h * 0.05;
                const endX = w * 0.25 + Math.random() * w * 0.5;
                const endY = h * 0.5 + Math.random() * h * 0.3;
                const points = [{ x: startX, y: startY }];
                const segments = 6 + Math.floor(Math.random() * 8);
                const dx = (endX - startX) / segments;
                const dy = (endY - startY) / segments;
                for (let i = 1; i <= segments; i++) {
                    const jitterX = (Math.random() - 0.5) * w * 0.14;
                    const jitterY = (Math.random() - 0.5) * h * 0.08;
                    points.push({
                        x: startX + dx * i + jitterX,
                        y: startY + dy * i + jitterY,
                    });
                }
                points.push({ x: endX, y: endY });
                return points;
            }

            // --- 暴雪 ---
            function drawSnow(ctx, w, h, time, intensity) {
                ctx.clearRect(0, 0, w, h);
                const dpr = cardRects['canvasSnow']?.dpr || 2;

                // 冷色调天空
                const skyGrad = ctx.createLinearGradient(0, 0, 0, h);
                skyGrad.addColorStop(0, 'rgb(140,155,175)');
                skyGrad.addColorStop(0.5, 'rgb(160,172,190)');
                skyGrad.addColorStop(1, 'rgb(185,195,205)');
                ctx.fillStyle = skyGrad;
                ctx.fillRect(0, 0, w, h);

                const data = particleData['snow'];
                const snowMultiplier = 0.5 + intensity * 0.5;
                const targetFlakeCount = Math.floor((70 + intensity * 60) * snowMultiplier);

                while (data.flakes.length < targetFlakeCount) {
                    data.flakes.push({
                        x: Math.random() * w,
                        y: -Math.random() * h * 0.6,
                        radius: 1.2 + Math.random() * 3.5,
                        speed: 0.4 + Math.random() * 1.6 * snowMultiplier,
                        windDrift: -0.3 + Math.random() * 0.6 + (intensity - 2) * 0.3,
                        opacity: 0.5 + Math.random() * 0.5,
                        wobbleAmp: 0.3 + Math.random() * 1.4,
                        wobbleSpeed: 0.01 + Math.random() * 0.03,
                        wobbleOffset: Math.random() * Math.PI * 2,
                    });
                }
                while (data.flakes.length > targetFlakeCount + 15) data.flakes.shift();

                for (let flake of data.flakes) {
                    flake.y += flake.speed;
                    flake.x += flake.windDrift + Math.sin(time * flake.wobbleSpeed + flake.wobbleOffset) * flake.wobbleAmp;
                    if (flake.y > h + 8) {
                        flake.y = -8 - Math.random() * 20;
                        flake.x = Math.random() * w;
                        // 积雪增长
                        data.snowAccum += 0.015 * snowMultiplier;
                    }
                    if (flake.x > w + 10) flake.x = -10;
                    if (flake.x < -10) flake.x = w + 10;
                    ctx.fillStyle = `rgba(240,245,255,${flake.opacity})`;
                    ctx.beginPath();
                    ctx.arc(flake.x, flake.y, flake.radius, 0, Math.PI * 2);
                    ctx.fill();
                }

                // 积雪堆积
                const maxAccum = 18 + intensity * 22;
                data.snowAccum = Math.min(data.snowAccum, maxAccum);
                if (data.snowAccum > 0.3) {
                    const accumHeight = data.snowAccum;
                    // 起伏的积雪
                    ctx.fillStyle = 'rgba(235,240,248,0.85)';
                    ctx.beginPath();
                    ctx.moveTo(0, h);
                    for (let x = 0; x <= w; x += 8) {
                        const yOffset = Math.sin(x * 0.04 + time * 0.0004) * 5 + Math.sin(x * 0.09) * 3;
                        ctx.lineTo(x, h - accumHeight + yOffset);
                    }
                    ctx.lineTo(w, h);
                    ctx.closePath();
                    ctx.fill();

                    // 积雪高光
                    const snowHighlight = ctx.createLinearGradient(0, h - accumHeight - 6, 0, h);
                    snowHighlight.addColorStop(0, 'rgba(255,255,255,0.55)');
                    snowHighlight.addColorStop(0.4, 'rgba(240,245,255,0.3)');
                    snowHighlight.addColorStop(1, 'rgba(220,230,240,0)');
                    ctx.fillStyle = snowHighlight;
                    ctx.beginPath();
                    ctx.moveTo(0, h);
                    for (let x = 0; x <= w; x += 8) {
                        const yOffset = Math.sin(x * 0.04 + time * 0.0004) * 5 + Math.sin(x * 0.09) * 3;
                        ctx.lineTo(x, h - accumHeight + yOffset);
                    }
                    ctx.lineTo(w, h);
                    ctx.closePath();
                    ctx.fill();
                }

                // 重置积雪(定期融化一些)
                if (data.snowAccum > maxAccum * 0.9) {
                    data.snowAccum *= 0.998;
                }
            }

            // ============ 动画循环 ============
            let animationId;
            let lastTime = performance.now();

            function animate(timestamp) {
                const dt = Math.min(timestamp - lastTime, 50);
                lastTime = timestamp;

                weatherTypes.forEach((wt, i) => {
                    const canvasId = canvasIds[i];
                    const ctx = contexts[canvasId];
                    const rect = cardRects[canvasId];
                    if (!ctx || !rect || rect.width <= 0 || rect.height <= 0) return;
                    const w = rect.width;
                    const h = rect.height;

                    ctx.save();
                    // 根据天气类型绘制
                    switch (wt) {
                        case 'sunny':
                            drawSunny(ctx, w, h, timestamp, globalIntensity);
                            break;
                        case 'windy':
                            drawWindy(ctx, w, h, timestamp, globalIntensity);
                            break;
                        case 'rain':
                            drawRain(ctx, w, h, timestamp, globalIntensity);
                            break;
                        case 'snow':
                            drawSnow(ctx, w, h, timestamp, globalIntensity);
                            break;
                    }
                    ctx.restore();
                });

                animationId = requestAnimationFrame(animate);
            }

            // ============ 强度切换 ============
            function setIntensity(level) {
                globalIntensity = level;
                // 更新按钮状态
                intensityButtons.forEach(btn => {
                    const btnLevel = parseInt(btn.dataset.level);
                    if (btnLevel === level) {
                        btn.classList.add('active');
                    } else {
                        btn.classList.remove('active');
                    }
                });
                // 重置粒子系统以适应新强度
                weatherTypes.forEach(wt => {
                    resetParticles(wt);
                });
                // 更新卡片描述文字
                updateCardDescriptions(level);
            }

            function updateCardDescriptions(level) {
                const overlays = document.querySelectorAll('.card-overlay .desc');
                const sunnyDescs = ['晴空柔和', '晴空万里', '烈日当空'];
                const windyDescs = ['微风轻拂', '大风呼啸', '狂风怒号'];
                const rainDescs = ['细雨绵绵', '暴雨倾盆', '特大暴雨'];
                const snowDescs = ['小雪飘落', '暴雪纷飞', '暴风雪肆虐'];
                const allDescs = [sunnyDescs, windyDescs, rainDescs, snowDescs];
                const tempAdjust = [
                    [27, 28, 30],
                    [20, 19, 17],
                    [16, 14, 11],
                    [-5, -8, -13]
                ];

                overlays.forEach((descEl, i) => {
                    if (allDescs[i]) {
                        descEl.textContent = allDescs[i][level - 1] || allDescs[i][1];
                    }
                });
                // 更新温度
                const tempEls = document.querySelectorAll('.card-overlay .temp');
                tempEls.forEach((tempEl, i) => {
                    if (tempAdjust[i]) {
                        const newTemp = tempAdjust[i][level - 1] || tempAdjust[i][1];
                        tempEl.textContent = newTemp + '°';
                    }
                });
            }

            intensityButtons.forEach(btn => {
                btn.addEventListener('click', () => {
                    const level = parseInt(btn.dataset.level);
                    if (level === globalIntensity) return;
                    setIntensity(level);
                    // 按钮微动效
                    btn.style.transform = 'scale(0.9)';
                    setTimeout(() => { btn.style.transform = ''; }, 150);
                });
            });

            // ============ 卡片点击选中 ============
            cardElements.forEach(card => {
                card.addEventListener('click', () => {
                    const index = parseInt(card.dataset.index);
                    if (selectedCardIndex === index) {
                        // 取消选中
                        card.classList.remove('selected');
                        selectedCardIndex = -1;
                    } else {
                        // 取消其他选中
                        cardElements.forEach(c => c.classList.remove('selected'));
                        card.classList.add('selected');
                        selectedCardIndex = index;
                    }
                });
            });

            // ============ 窗口大小调整 ============
            let resizeTimeout;
            function handleResize() {
                clearTimeout(resizeTimeout);
                resizeTimeout = setTimeout(() => {
                    updateAllCanvasSizes();
                    weatherTypes.forEach(wt => resetParticles(wt));
                }, 300);
            }
            window.addEventListener('resize', handleResize);
            window.addEventListener('orientationchange', () => {
                setTimeout(handleResize, 500);
            });

            // ============ 启动 ============
            function startAnimation() {
                // 确保所有canvas尺寸正确
                updateAllCanvasSizes();
                weatherTypes.forEach(wt => resetParticles(wt));
                updateCardDescriptions(globalIntensity);
                // 设置初始活跃按钮
                intensityButtons.forEach(btn => {
                    if (parseInt(btn.dataset.level) === globalIntensity) {
                        btn.classList.add('active');
                    }
                });
                // 启动动画循环
                if (animationId) cancelAnimationFrame(animationId);
                lastTime = performance.now();
                animationId = requestAnimationFrame(animate);
            }

            // 页面加载完成后启动
            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', startAnimation);
            } else {
                startAnimation();
            }

            // ============ 键盘导航 ============
            document.addEventListener('keydown', (e) => {
                if (e.key === '1' || e.key === '2' || e.key === '3') {
                    const level = parseInt(e.key);
                    setIntensity(level);
                }
                if (e.key === 'ArrowRight' || e.key === 'ArrowLeft') {
                    const dir = e.key === 'ArrowRight' ? 1 : -1;
                    let newIndex = selectedCardIndex + dir;
                    if (newIndex < 0) newIndex = cardElements.length - 1;
                    if (newIndex >= cardElements.length) newIndex = 0;
                    cardElements.forEach(c => c.classList.remove('selected'));
                    if (newIndex >= 0 && newIndex < cardElements.length) {
                        cardElements[newIndex].classList.add('selected');
                        cardElements[newIndex].scrollIntoView({ behavior: 'smooth', block: 'nearest', inline: 'center' });
                        selectedCardIndex = newIndex;
                    }
                }
                if (e.key === 'Escape') {
                    cardElements.forEach(c => c.classList.remove('selected'));
                    selectedCardIndex = -1;
                }
            });

            console.log('%c🌤 天气卡片已就绪 %c| %c强度等级: ' + globalIntensity +
                ' %c| %c键盘快捷键: 1-3切换强度, ←→切换卡片, Esc取消选中',
                'font-size:1.1em;', '', 'font-weight:bold;color:#ffcc80;', '',
                'color:rgba(255,255,255,0.5);');
            console.log('%c💡 点击卡片可选中高亮,底部按钮切换天气强度', 'color:rgba(255,255,255,0.6);font-style:italic;');
        })();
    </script>
</body>
</html>

用同样的提示词跑了下GPT5.5做个对比,它的 thinking 思考进阶程度:

代码没贴全啊,后面的CSS和JS呢?想复现都看不了。尤其是那个“毛玻璃”效果的具体实现,我看你前面只定义了–glass-blur变量,但没看到backdrop-filter或者filter: blur()的实际应用位置。还有动态渐变背景的代码被截断了,能补一下吗?

这东西就那样

是不是所有天气卡片都是用CSS动画做的?我不太确定JS在里面起了多大作用。还有那个“堆积效果”的暴雪,是用的Canvas还是纯CSS啊?小白问一下。

哇,这个帖子来得正好!我上周也试过让Claude写一个类似的天气组件,不过我要的是竖排的。我的经历是,它代码虽然能跑,但动画卡得不行,尤其是雨滴下落数量一多就掉帧。后来我手动加了个will-change: transform和用requestAnimationFrame重写了JS部分才好点。感觉现在这些大模型写前端动效,样子是有了,性能优化还得自己来。

又来这种帖子了,贴个半截代码吊人胃口,然后说结果意外。意外啥啊?是不是DeepSeek生成了个不能动的静态图,还是GPT5.5直接给你写了个用Three.js的版本,让你电脑风扇狂转?

先别管模型了,这个需求本身前端实现起来就挺有意思的。我的做法一般是:1. 容器用flex布局,四个卡片等宽,加gap。2. 每个卡片用独立的div,背景色用rgba加透明度。3. 动效部分,像雨滴雪花这种大量重复元素,用CSS伪元素生成,通过animation-delay错开时间。4. 按钮切换天气状态,其实就是用JS切换卡片上的一个data-attribute,然后用CSS选择器触发不同的动画。性能关键点是尽量用transform和opacity。

楼主估计只贴了关键定义,建议直接发个code pen可复现的版本

雨滴一多就掉帧的问题改用Canvas渲染就行,纯CSS动画做粒子效果对GPU不友好

五分钟出结果在天气卡片这种纯前端任务里太长了,prompt再细分一下能快不少

雨滴用canvas画帧率会稳很多

五分钟才出来这速度有点拉跨

楼主估计就贴了变量定义,完整CSS和JS没看到