DeepSeek V4 Pro 前端测试——天气卡片

2026-04-29 10:172阅读0评论SEO基础
  • 内容介绍
  • 文章标签
  • 相关推荐
问题描述:

工具: Claude Code
api 来源: DeepSeek 开放平台官网https://platform.deepseek.com

提示词:

以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。

效果如下:

PixPin2026-04-2414-33-472940×1464 283 KB
PixPin2026-04-2414-34-152940×1464 307 KB
PixPin2026-04-2414-34-292940×1464 243 KB
PixPin2026-04-2414-35-132940×1464 193 KB
代码:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>天气</title> <style> *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } @font-face { font-family: 'SF Pro'; src: local('SF Pro Display'), local('SF Pro Text'), local('Helvetica Neue'), local('PingFang SC'); } body { font-family: 'SF Pro', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #1c1c1e; overflow-x: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Dynamic background that changes based on selected card */ .bg-layer { position: fixed; inset: 0; transition: background 1.2s cubic-bezier(0.25, 0.1, 0.25, 1); z-index: 0; } /* Frosted overlay for iOS depth effect */ .frost-overlay { position: fixed; inset: 0; backdrop-filter: blur(80px) saturate(140%); -webkit-backdrop-filter: blur(80px) saturate(140%); z-index: 1; pointer-events: none; } .app-container { position: relative; z-index: 2; width: 100%; max-width: 960px; padding: 32px 24px; } /* Header */ .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 28px; padding: 0 8px; } .header-location { display: flex; align-items: center; gap: 6px; } .location-icon { width: 20px; height: 20px; opacity: 0.8; } .location-text { font-size: 20px; font-weight: 600; color: #fff; letter-spacing: -0.3px; } .location-chevron { width: 14px; height: 14px; opacity: 0.6; margin-left: -2px; } .header-time { font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.7); letter-spacing: -0.2px; } /* Cards row */ .cards-row { display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; padding: 8px 8px 16px; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .cards-row::-webkit-scrollbar { display: none; } /* Individual card */ .weather-card { flex: 1 0 0; min-width: 195px; max-width: 240px; border-radius: 28px; padding: 24px 20px 20px; cursor: pointer; position: relative; overflow: hidden; transition: all 0.55s cubic-bezier(0.22, 0.9, 0.36, 1); background: rgba(255,255,255,0.12); backdrop-filter: blur(24px) saturate(160%); -webkit-backdrop-filter: blur(24px) saturate(160%); border: 1px solid rgba(255,255,255,0.16); box-shadow: 0 4px 24px rgba(0,0,0,0.12), 0 1px 4px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.1); user-select: none; -webkit-tap-highlight-color: transparent; scroll-snap-align: start; } .weather-card:hover { transform: translateY(-6px); box-shadow: 0 12px 40px rgba(0,0,0,0.22), 0 2px 8px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.24); } .weather-card:active { transform: scale(0.97); transition: transform 0.2s cubic-bezier(0.22, 0.9, 0.36, 1); } .weather-card.expanded { flex: 2.5 0 0; max-width: 400px; min-width: 260px; background: rgba(255,255,255,0.16); border-color: rgba(255,255,255,0.28); box-shadow: 0 16px 48px rgba(0,0,0,0.28), 0 2px 8px rgba(0,0,0,0.14), inset 0 1px 0 rgba(255,255,255,0.16); } /* Canvas layer for weather animations */ .card-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; border-radius: 28px; } .card-content { position: relative; z-index: 1; } /* Weather icon area */ .card-icon-row { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 8px; } .weather-icon-wrap { width: 52px; height: 52px; border-radius: 16px; display: flex; align-items: center; justify-content: center; font-size: 28px; transition: transform 0.5s cubic-bezier(0.22, 0.9, 0.36, 1); } .weather-card:hover .weather-icon-wrap { transform: scale(1.08); } .card-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.2px; opacity: 0.5; color: #fff; } .card-temp { font-size: 48px; font-weight: 200; line-height: 1; letter-spacing: -2px; margin-bottom: 2px; color: #fff; } .card-desc { font-size: 15px; font-weight: 500; opacity: 0.75; margin-bottom: 16px; color: #fff; letter-spacing: -0.2px; } /* Detail rows (shown more in expanded) */ .card-details { display: flex; flex-direction: column; gap: 8px; opacity: 0.55; transition: opacity 0.5s ease; } .weather-card.expanded .card-details { opacity: 0.85; } .detail-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #fff; letter-spacing: -0.1px; } .detail-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } .detail-value { font-weight: 600; margin-left: auto; } /* Hourly strip (expanded) */ .hourly-strip { display: flex; gap: 10px; margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.1); opacity: 0; transform: translateY(8px); transition: all 0.45s cubic-bezier(0.22, 0.9, 0.36, 1); max-height: 0; overflow: hidden; } .weather-card.expanded .hourly-strip { opacity: 1; transform: translateY(0); max-height: 80px; } .hourly-item { display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; color: rgba(255,255,255,0.7); flex: 1; } .hourly-icon { font-size: 18px; } .hourly-temp { font-weight: 600; color: #fff; } /* SVG icons */ .icon-svg { width: 36px; height: 36px; } /* Responsive */ @media (max-width: 860px) { .cards-row { gap: 10px; padding: 4px 4px 12px; } .weather-card { min-width: 150px; padding: 18px 14px 16px; border-radius: 22px; } .weather-card.expanded { min-width: 200px; max-width: 320px; } .card-temp { font-size: 36px; } .card-canvas { border-radius: 22px; } .header { margin-bottom: 16px; } } @media (max-width: 600px) { .cards-row { flex-wrap: wrap; gap: 10px; } .weather-card { flex: 1 0 calc(50% - 5px); min-width: 0; max-width: none; } .weather-card.expanded { flex: 1 0 100%; max-width: none; } } </style> </head> <body> <div class="bg-layer" id="bgLayer"></div> <div class="frost-overlay"></div> <div class="app-container"> <div class="header"> <div class="header-location"> <svg class="location-icon" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round"> <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/> <circle cx="12" cy="9" r="2.5"/> </svg> <span class="location-text">北京</span> <svg class="location-chevron" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round"> <polyline points="6 9 12 15 18 9"/> </svg> </div> <span class="header-time" id="headerTime"></span> </div> <div class="cards-row" id="cardsRow"> <!-- Cards injected by JS --> </div> </div> <script> (function() { // ── Config ────────────────────────────────────────────── const weatherData = [ { id: 'sunny', label: '晴天', icon: '☀️', temp: 28, desc: '晴朗无云', hi: 31, lo: 22, humidity: 35, wind: 8, uv: 6, aqi: 42, hourly: [ { time: '现在', icon: '☀️', temp: 28 }, { time: '14时', icon: '☀️', temp: 30 }, { time: '16时', icon: '🌤️', temp: 29 }, { time: '18时', icon: '🌤️', temp: 26 }, { time: '20时', icon: '🌙', temp: 24 }, { time: '22时', icon: '🌙', temp: 23 }, ], gradient: 'linear-gradient(165deg, #4a90d9 0%, #63b8ff 25%, #87ceeb 50%, #f0c060 85%, #e8a840 100%)', accent: 'rgba(255,184,60,0.35)', dotColor: '#fbbf24', }, { id: 'windy', label: '大风', icon: '💨', temp: 18, desc: '大风蓝色预警', hi: 22, lo: 14, humidity: 45, wind: 42, uv: 3, aqi: 28, hourly: [ { time: '现在', icon: '💨', temp: 18 }, { time: '14时', icon: '💨', temp: 20 }, { time: '16时', icon: '🌬️', temp: 21 }, { time: '18时', icon: '🌬️', temp: 19 }, { time: '20时', icon: '🌙', temp: 16 }, { time: '22时', icon: '🌙', temp: 15 }, ], gradient: 'linear-gradient(165deg, #2d5f7c 0%, #3a7ca5 30%, #5699c0 60%, #7fb8d0 100%)', accent: 'rgba(150,210,240,0.3)', dotColor: '#7dd3fc', }, { id: 'rain', label: '暴雨', icon: '🌧️', temp: 20, desc: '暴雨黄色预警', hi: 23, lo: 18, humidity: 92, wind: 28, uv: 1, aqi: 18, hourly: [ { time: '现在', icon: '🌧️', temp: 20 }, { time: '14时', icon: '⛈️', temp: 21 }, { time: '16时', icon: '🌧️', temp: 22 }, { time: '18时', icon: '🌧️', temp: 21 }, { time: '20时', icon: '🌧️', temp: 20 }, { time: '22时', icon: '🌙', temp: 19 }, ], gradient: 'linear-gradient(165deg, #1a2a3a 0%, #2c4053 30%, #4a6278 60%, #6b8299 100%)', accent: 'rgba(130,170,200,0.3)', dotColor: '#94a3b8', }, { id: 'snow', label: '暴雪', icon: '❄️', temp: -8, desc: '暴雪橙色预警', hi: -5, lo: -12, humidity: 78, wind: 35, uv: 1, aqi: 15, hourly: [ { time: '现在', icon: '❄️', temp: -8 }, { time: '14时', icon: '❄️', temp: -7 }, { time: '16时', icon: '🌨️', temp: -9 }, { time: '18时', icon: '🌨️', temp: -10 }, { time: '20时', icon: '❄️', temp: -11 }, { time: '22时', icon: '❄️', temp: -12 }, ], gradient: 'linear-gradient(165deg, #8399ad 0%, #a8bfcf 25%, #c5d5e0 50%, #dce6ed 80%, #e8eef3 100%)', accent: 'rgba(220,235,250,0.4)', dotColor: '#e2e8f0', }, ]; const cardsRow = document.getElementById('cardsRow'); const bgLayer = document.getElementById('bgLayer'); const headerTime = document.getElementById('headerTime'); // ── Update time ───────────────────────────────────────── function updateTime() { const now = new Date(); headerTime.textContent = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }); } updateTime(); setInterval(updateTime, 10000); // ── Set background ────────────────────────────────────── function setBackground(gradient) { bgLayer.style.background = gradient; } // Init with sunny setBackground(weatherData[0].gradient); // ── Card click expand logic ───────────────────────────── function handleCardClick(card, data) { const wasExpanded = card.classList.contains('expanded'); // Collapse all document.querySelectorAll('.weather-card').forEach(c => c.classList.remove('expanded')); if (!wasExpanded) { card.classList.add('expanded'); setBackground(data.gradient); } else { setBackground(weatherData[0].gradient); } } // ── Build cards ───────────────────────────────────────── weatherData.forEach((data, index) => { const card = document.createElement('div'); card.className = 'weather-card'; if (index === 0) card.classList.add('expanded'); card.setAttribute('data-id', data.id); const detailsHTML = ` <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> 湿度 <span class="detail-value">${data.humidity}%</span> </div> <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> 风速 <span class="detail-value">${data.wind} km/h</span> </div> <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> UV 指数 <span class="detail-value">${data.uv}</span> </div> <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> 空气质量 <span class="detail-value">${data.aqi} 优</span> </div> `; const hourlyHTML = data.hourly.map(h => ` <div class="hourly-item"> <span>${h.time}</span> <span class="hourly-icon">${h.icon}</span> <span class="hourly-temp">${h.temp}°</span> </div> `).join(''); card.innerHTML = ` <canvas class="card-canvas" id="canvas-${data.id}"></canvas> <div class="card-content"> <div class="card-icon-row"> <div class="weather-icon-wrap" style="background:${data.accent}"> <span style="line-height:1">${data.icon}</span> </div> <span class="card-label">${data.label}</span> </div> <div class="card-temp">${data.temp}°</div> <div class="card-desc">${data.desc}</div> <div class="card-details">${detailsHTML}</div> <div class="hourly-strip">${hourlyHTML}</div> </div> `; card.addEventListener('click', () => handleCardClick(card, data)); cardsRow.appendChild(card); }); // ── Weather Animation Canvases ────────────────────────── function initCanvas(canvasId, type) { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); let particles = []; let animId; function resize() { const rect = canvas.parentElement.getBoundingClientRect(); canvas.width = rect.width * devicePixelRatio; canvas.height = rect.height * devicePixelRatio; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.scale(devicePixelRatio, devicePixelRatio); } resize(); const observer = new ResizeObserver(resize); observer.observe(canvas.parentElement); const W = () => canvas.width / devicePixelRatio; const H = () => canvas.height / devicePixelRatio; // ── Particle types ────────────────────────────────── function createSunRay() { const cx = W() * 0.35; const cy = H() * 0.32; const angle = Math.random() * Math.PI * 2; const dist = 18 + Math.random() * 22; return { x: cx + Math.cos(angle) * 18, y: cy + Math.sin(angle) * 18, targetX: cx + Math.cos(angle) * dist, targetY: cy + Math.sin(angle) * dist, progress: 0, speed: 0.008 + Math.random() * 0.015, alpha: 0.7 + Math.random() * 0.3, size: 1.2 + Math.random() * 1.8, life: 1, type: 'ray', }; } function createWindLine() { return { x: -20, y: Math.random() * H(), length: 30 + Math.random() * 60, speed: 2.5 + Math.random() * 4, alpha: 0.15 + Math.random() * 0.25, thickness: 0.6 + Math.random() * 1.2, life: 1, type: 'wind', }; } function createRaindrop() { return { x: Math.random() * W(), y: -10 - Math.random() * 60, speed: 8 + Math.random() * 14, length: 8 + Math.random() * 16, alpha: 0.25 + Math.random() * 0.35, thickness: 0.8 + Math.random() * 0.6, life: 1, type: 'rain', wind: 1.5 + Math.random() * 3, }; } function createSnowflake() { const size = 1.5 + Math.random() * 3.5; return { x: Math.random() * W(), y: -10 - Math.random() * 20, size, speedY: 0.3 + Math.random() * 1.2, speedX: -0.3 + Math.random() * 0.6, wobble: Math.random() * Math.PI * 2, wobbleSpeed: 0.01 + Math.random() * 0.03, alpha: 0.5 + Math.random() * 0.5, life: 1, type: 'snow', }; } // ── Init particles ────────────────────────────────── function initParticles() { particles = []; if (type === 'sunny') { for (let i = 0; i < 18; i++) particles.push(createSunRay()); } else if (type === 'windy') { for (let i = 0; i < 12; i++) { const p = createWindLine(); p.x = Math.random() * W(); particles.push(p); } } else if (type === 'rain') { for (let i = 0; i < 50; i++) { const p = createRaindrop(); p.y = Math.random() * H(); particles.push(p); } } else if (type === 'snow') { for (let i = 0; i < 45; i++) { const p = createSnowflake(); p.y = Math.random() * H(); particles.push(p); } } } initParticles(); // ── Draw functions ────────────────────────────────── function drawSun(cx, cy) { // Glow layers for (let i = 3; i >= 0; i--) { const r = 24 + i * 8; const grad = ctx.createRadialGradient(cx, cy, r * 0.3, cx, cy, r); grad.addColorStop(0, `rgba(255,220,140,${0.25 - i * 0.05})`); grad.addColorStop(0.5, `rgba(255,200,100,${0.12 - i * 0.03})`); grad.addColorStop(1, 'rgba(255,180,60,0)'); ctx.fillStyle = grad; ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.fill(); } // Core const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, 10); core.addColorStop(0, 'rgba(255,245,220,0.95)'); core.addColorStop(1, 'rgba(255,200,100,0.6)'); ctx.fillStyle = core; ctx.beginPath(); ctx.arc(cx, cy, 10, 0, Math.PI * 2); ctx.fill(); } function drawCloud(cx, cy, scale, alpha) { ctx.fillStyle = `rgba(255,255,255,${alpha})`; ctx.beginPath(); const s = scale; ctx.arc(cx, cy, 10 * s, 0, Math.PI * 2); ctx.arc(cx + 9 * s, cy - 3 * s, 8 * s, 0, Math.PI * 2); ctx.arc(cx - 8 * s, cy - 1 * s, 7 * s, 0, Math.PI * 2); ctx.arc(cx + 16 * s, cy + 1 * s, 6 * s, 0, Math.PI * 2); ctx.arc(cx - 5 * s, cy + 4 * s, 5 * s, 0, Math.PI * 2); ctx.fill(); } // ── Animate ───────────────────────────────────────── let frameCount = 0; function animate() { ctx.clearRect(0, 0, W(), H()); frameCount++; const sunCx = W() * 0.35; const sunCy = H() * 0.32; if (type === 'sunny') { drawSun(sunCx, sunCy); // Draw rays particles.forEach(p => { p.progress += p.speed; if (p.progress >= 1) { // Reset ray const angle = Math.random() * Math.PI * 2; const dist = 18 + Math.random() * 22; p.x = sunCx + Math.cos(angle) * 14; p.y = sunCy + Math.sin(angle) * 14; p.targetX = sunCx + Math.cos(angle) * dist; p.targetY = sunCy + Math.sin(angle) * dist; p.progress = 0; p.alpha = 0.5 + Math.random() * 0.35; } const ease = Math.sin(p.progress * Math.PI * 0.5); const rx = p.x + (p.targetX - p.x) * ease; const ry = p.y + (p.targetY - p.y) * ease; const alpha = p.alpha * (1 - ease * 0.8); ctx.strokeStyle = `rgba(255,220,150,${alpha})`; ctx.lineWidth = p.size; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(rx, ry); ctx.stroke(); }); // Spawn new rays if (frameCount % 3 === 0 && particles.length < 24) { particles.push(createSunRay()); } // Small clouds const t = Date.now() * 0.0002; drawCloud(W() * 0.68 + Math.sin(t) * 4, H() * 0.28, 0.5, 0.25); drawCloud(W() * 0.55 + Math.cos(t * 1.3) * 3, H() * 0.48, 0.4, 0.18); } else if (type === 'windy') { // Draw some curved wind lines particles.forEach(p => { p.x += p.speed; if (p.x > W() + 40) { p.x = -40; p.y = Math.random() * H(); p.alpha = 0.15 + Math.random() * 0.25; } const midX = p.x - p.length * 0.4; const midY = p.y + Math.sin(p.x * 0.03 + frameCount * 0.02) * 15; ctx.strokeStyle = `rgba(200,225,255,${p.alpha})`; ctx.lineWidth = p.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p.x - p.length, p.y); ctx.quadraticCurveTo(midX, midY, p.x, p.y); ctx.stroke(); // Tiny streak near end ctx.strokeStyle = `rgba(220,240,255,${p.alpha * 0.7})`; ctx.lineWidth = p.thickness * 0.5; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(p.x - 6, p.y - 1); ctx.stroke(); }); if (frameCount % 8 === 0 && particles.length < 18) { particles.push(createWindLine()); } // Subtle cloud drawCloud(W() * 0.4, H() * 0.35, 0.7, 0.2); } else if (type === 'rain') { // Dark cloud at top drawCloud(W() * 0.3, H() * 0.18, 0.9, 0.3); drawCloud(W() * 0.6, H() * 0.16, 0.8, 0.25); particles.forEach(p => { p.y += p.speed; p.x += p.wind; if (p.y > H() + 10) { p.y = -15; p.x = Math.random() * (W() + 30) - 15; } ctx.strokeStyle = `rgba(160,200,230,${p.alpha})`; ctx.lineWidth = p.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(p.x - p.wind * 0.4, p.y - p.length); ctx.stroke(); // Splash at bottom if (p.y > H() * 0.88 && Math.random() < 0.3) { ctx.fillStyle = `rgba(160,200,230,${p.alpha * 0.3})`; ctx.beginPath(); ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2); ctx.fill(); } }); if (frameCount % 2 === 0 && particles.length < 60) { particles.push(createRaindrop()); } } else if (type === 'snow') { // Soft cloud at top drawCloud(W() * 0.35, H() * 0.16, 1.0, 0.35); drawCloud(W() * 0.6, H() * 0.14, 0.85, 0.28); particles.forEach(p => { p.wobble += p.wobbleSpeed; p.y += p.speedY; p.x += p.speedX + Math.sin(p.wobble) * 0.4; if (p.y > H() + 10) { p.y = -10; p.x = Math.random() * (W() + 20) - 10; } if (p.x < -10) p.x = W() + 10; if (p.x > W() + 10) p.x = -10; // Draw snowflake (small circle with glow) const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size); grad.addColorStop(0, `rgba(255,255,255,${p.alpha})`); grad.addColorStop(1, `rgba(220,235,255,0)`); ctx.fillStyle = grad; ctx.beginPath(); ctx.arc(p.x, p.y, p.size * 1.6, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = `rgba(255,255,255,${p.alpha * 0.9})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.size * 0.6, 0, Math.PI * 2); ctx.fill(); }); if (frameCount % 3 === 0 && particles.length < 55) { particles.push(createSnowflake()); } // Ground snow accumulation ctx.fillStyle = 'rgba(240,245,250,0.25)'; ctx.beginPath(); ctx.moveTo(0, H()); for (let x = 0; x <= W(); x += 3) { ctx.lineTo(x, H() - 4 - Math.sin(x * 0.05 + frameCount * 0.002) * 3); } ctx.lineTo(W(), H()); ctx.closePath(); ctx.fill(); } animId = requestAnimationFrame(animate); } animate(); // Cleanup return () => { cancelAnimationFrame(animId); observer.disconnect(); }; } // Init all canvases after DOM ready const cleaners = []; setTimeout(() => { weatherData.forEach(data => { const clean = initCanvas(`canvas-${data.id}`, data.id); if (clean) cleaners.push(clean); }); }, 100); // Cleanup on page unload window.addEventListener('beforeunload', () => cleaners.forEach(fn => fn())); // ── Keyboard navigation ──────────────────────────────── document.addEventListener('keydown', (e) => { const cards = [...document.querySelectorAll('.weather-card')]; const current = cards.findIndex(c => c.classList.contains('expanded')); if (e.key === 'ArrowRight' && current < cards.length - 1) { cards[current + 1].click(); cards[current + 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } else if (e.key === 'ArrowLeft' && current > 0) { cards[current - 1].click(); cards[current - 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }); })(); </script> </body> </html> 网友解答:


--【壹】--:

哇,这个手绘风的天气的图标很亮眼啊,佬这个是用了什么 skill 吗还是直出的啊,我用 2.5pro 出的质量差好多


--【贰】--:

是啊,5.4是真的前端苦手, 组件叠组件叠半天出来一坨大的


--【叁】--:

image1920×947 87.1 KB
aistudio.google 生成的 ,带有动效


--【肆】--:

感觉这模型挺依赖抽卡的,看另一个帖子里和gpt5.5的天气卡片对比,v4pro做出来感觉就很一般


--【伍】--:

image1920×901 93.9 KB

小米mimo-2.5pro搞的,我感觉蛮不错的


--【陆】--:

岂可修,难道是 opencode 的问题 ,opencode 里面的 mimo-2.5pro出来的是这样的,难绷的很
PixPin2026-04-2417-25-052940×1470 283 KB


--【柒】--:

我这里 GPT 5.5 自动调用了 skill 和 Chrome Devtools,做出来是这样的(GPT做前端还是喜欢堆组件嵌套)
PixPin2026-04-2415-34-432940×1470 294 KB


--【捌】--:

好像是有点抽卡的意味,我看有的佬发的效果很差,我的这次就还行


--【玖】--:

GPT5.5做的
d35c7be2ea9a6f701340×880 130 KB


--【拾】--:

配色风格比gpt-5.4弄出来的好看太多了 也有质感了


--【拾壹】--:

有佬友说v4pro后训练可能有点问题,导致现在回答还不要稳定,但现在毕竟还是测试版嘛


--【拾贰】--:

这就充值个10块钱, 然后导入CPA, 然后alias gpt-5.3-codex看看效果


--【拾叁】--:

我是直接cc里直接出的,刚买了coding plan就看到佬的帖子就试了下,没想到挺不错的

image1113×626 33.5 KB

问题描述:

工具: Claude Code
api 来源: DeepSeek 开放平台官网https://platform.deepseek.com

提示词:

以 iOS 18 的设计风格做一个带有动画效果的天气卡片,要求是使用 HTML、CSS 和基础 JavaScript,使用横板天气页面(拥有 4 个天气卡片 (晴天,大风,暴雨,暴雪))。应足够美观,实现一定的交互效果。

效果如下:

PixPin2026-04-2414-33-472940×1464 283 KB
PixPin2026-04-2414-34-152940×1464 307 KB
PixPin2026-04-2414-34-292940×1464 243 KB
PixPin2026-04-2414-35-132940×1464 193 KB
代码:

<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>天气</title> <style> *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } @font-face { font-family: 'SF Pro'; src: local('SF Pro Display'), local('SF Pro Text'), local('Helvetica Neue'), local('PingFang SC'); } body { font-family: 'SF Pro', -apple-system, BlinkMacSystemFont, 'PingFang SC', 'Helvetica Neue', sans-serif; min-height: 100vh; display: flex; align-items: center; justify-content: center; background: #1c1c1e; overflow-x: hidden; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } /* Dynamic background that changes based on selected card */ .bg-layer { position: fixed; inset: 0; transition: background 1.2s cubic-bezier(0.25, 0.1, 0.25, 1); z-index: 0; } /* Frosted overlay for iOS depth effect */ .frost-overlay { position: fixed; inset: 0; backdrop-filter: blur(80px) saturate(140%); -webkit-backdrop-filter: blur(80px) saturate(140%); z-index: 1; pointer-events: none; } .app-container { position: relative; z-index: 2; width: 100%; max-width: 960px; padding: 32px 24px; } /* Header */ .header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 28px; padding: 0 8px; } .header-location { display: flex; align-items: center; gap: 6px; } .location-icon { width: 20px; height: 20px; opacity: 0.8; } .location-text { font-size: 20px; font-weight: 600; color: #fff; letter-spacing: -0.3px; } .location-chevron { width: 14px; height: 14px; opacity: 0.6; margin-left: -2px; } .header-time { font-size: 15px; font-weight: 500; color: rgba(255,255,255,0.7); letter-spacing: -0.2px; } /* Cards row */ .cards-row { display: flex; gap: 14px; overflow-x: auto; scroll-snap-type: x mandatory; scroll-behavior: smooth; padding: 8px 8px 16px; -webkit-overflow-scrolling: touch; scrollbar-width: none; } .cards-row::-webkit-scrollbar { display: none; } /* Individual card */ .weather-card { flex: 1 0 0; min-width: 195px; max-width: 240px; border-radius: 28px; padding: 24px 20px 20px; cursor: pointer; position: relative; overflow: hidden; transition: all 0.55s cubic-bezier(0.22, 0.9, 0.36, 1); background: rgba(255,255,255,0.12); backdrop-filter: blur(24px) saturate(160%); -webkit-backdrop-filter: blur(24px) saturate(160%); border: 1px solid rgba(255,255,255,0.16); box-shadow: 0 4px 24px rgba(0,0,0,0.12), 0 1px 4px rgba(0,0,0,0.08), inset 0 1px 0 rgba(255,255,255,0.1); user-select: none; -webkit-tap-highlight-color: transparent; scroll-snap-align: start; } .weather-card:hover { transform: translateY(-6px); box-shadow: 0 12px 40px rgba(0,0,0,0.22), 0 2px 8px rgba(0,0,0,0.12), inset 0 1px 0 rgba(255,255,255,0.14); border-color: rgba(255,255,255,0.24); } .weather-card:active { transform: scale(0.97); transition: transform 0.2s cubic-bezier(0.22, 0.9, 0.36, 1); } .weather-card.expanded { flex: 2.5 0 0; max-width: 400px; min-width: 260px; background: rgba(255,255,255,0.16); border-color: rgba(255,255,255,0.28); box-shadow: 0 16px 48px rgba(0,0,0,0.28), 0 2px 8px rgba(0,0,0,0.14), inset 0 1px 0 rgba(255,255,255,0.16); } /* Canvas layer for weather animations */ .card-canvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 0; border-radius: 28px; } .card-content { position: relative; z-index: 1; } /* Weather icon area */ .card-icon-row { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 8px; } .weather-icon-wrap { width: 52px; height: 52px; border-radius: 16px; display: flex; align-items: center; justify-content: center; font-size: 28px; transition: transform 0.5s cubic-bezier(0.22, 0.9, 0.36, 1); } .weather-card:hover .weather-icon-wrap { transform: scale(1.08); } .card-label { font-size: 11px; font-weight: 600; text-transform: uppercase; letter-spacing: 1.2px; opacity: 0.5; color: #fff; } .card-temp { font-size: 48px; font-weight: 200; line-height: 1; letter-spacing: -2px; margin-bottom: 2px; color: #fff; } .card-desc { font-size: 15px; font-weight: 500; opacity: 0.75; margin-bottom: 16px; color: #fff; letter-spacing: -0.2px; } /* Detail rows (shown more in expanded) */ .card-details { display: flex; flex-direction: column; gap: 8px; opacity: 0.55; transition: opacity 0.5s ease; } .weather-card.expanded .card-details { opacity: 0.85; } .detail-row { display: flex; align-items: center; gap: 8px; font-size: 13px; color: #fff; letter-spacing: -0.1px; } .detail-dot { width: 6px; height: 6px; border-radius: 50%; flex-shrink: 0; } .detail-value { font-weight: 600; margin-left: auto; } /* Hourly strip (expanded) */ .hourly-strip { display: flex; gap: 10px; margin-top: 16px; padding-top: 14px; border-top: 1px solid rgba(255,255,255,0.1); opacity: 0; transform: translateY(8px); transition: all 0.45s cubic-bezier(0.22, 0.9, 0.36, 1); max-height: 0; overflow: hidden; } .weather-card.expanded .hourly-strip { opacity: 1; transform: translateY(0); max-height: 80px; } .hourly-item { display: flex; flex-direction: column; align-items: center; gap: 4px; font-size: 11px; color: rgba(255,255,255,0.7); flex: 1; } .hourly-icon { font-size: 18px; } .hourly-temp { font-weight: 600; color: #fff; } /* SVG icons */ .icon-svg { width: 36px; height: 36px; } /* Responsive */ @media (max-width: 860px) { .cards-row { gap: 10px; padding: 4px 4px 12px; } .weather-card { min-width: 150px; padding: 18px 14px 16px; border-radius: 22px; } .weather-card.expanded { min-width: 200px; max-width: 320px; } .card-temp { font-size: 36px; } .card-canvas { border-radius: 22px; } .header { margin-bottom: 16px; } } @media (max-width: 600px) { .cards-row { flex-wrap: wrap; gap: 10px; } .weather-card { flex: 1 0 calc(50% - 5px); min-width: 0; max-width: none; } .weather-card.expanded { flex: 1 0 100%; max-width: none; } } </style> </head> <body> <div class="bg-layer" id="bgLayer"></div> <div class="frost-overlay"></div> <div class="app-container"> <div class="header"> <div class="header-location"> <svg class="location-icon" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.2" stroke-linecap="round"> <path d="M12 2C8.13 2 5 5.13 5 9c0 5.25 7 13 7 13s7-7.75 7-13c0-3.87-3.13-7-7-7z"/> <circle cx="12" cy="9" r="2.5"/> </svg> <span class="location-text">北京</span> <svg class="location-chevron" viewBox="0 0 24 24" fill="none" stroke="white" stroke-width="2.5" stroke-linecap="round"> <polyline points="6 9 12 15 18 9"/> </svg> </div> <span class="header-time" id="headerTime"></span> </div> <div class="cards-row" id="cardsRow"> <!-- Cards injected by JS --> </div> </div> <script> (function() { // ── Config ────────────────────────────────────────────── const weatherData = [ { id: 'sunny', label: '晴天', icon: '☀️', temp: 28, desc: '晴朗无云', hi: 31, lo: 22, humidity: 35, wind: 8, uv: 6, aqi: 42, hourly: [ { time: '现在', icon: '☀️', temp: 28 }, { time: '14时', icon: '☀️', temp: 30 }, { time: '16时', icon: '🌤️', temp: 29 }, { time: '18时', icon: '🌤️', temp: 26 }, { time: '20时', icon: '🌙', temp: 24 }, { time: '22时', icon: '🌙', temp: 23 }, ], gradient: 'linear-gradient(165deg, #4a90d9 0%, #63b8ff 25%, #87ceeb 50%, #f0c060 85%, #e8a840 100%)', accent: 'rgba(255,184,60,0.35)', dotColor: '#fbbf24', }, { id: 'windy', label: '大风', icon: '💨', temp: 18, desc: '大风蓝色预警', hi: 22, lo: 14, humidity: 45, wind: 42, uv: 3, aqi: 28, hourly: [ { time: '现在', icon: '💨', temp: 18 }, { time: '14时', icon: '💨', temp: 20 }, { time: '16时', icon: '🌬️', temp: 21 }, { time: '18时', icon: '🌬️', temp: 19 }, { time: '20时', icon: '🌙', temp: 16 }, { time: '22时', icon: '🌙', temp: 15 }, ], gradient: 'linear-gradient(165deg, #2d5f7c 0%, #3a7ca5 30%, #5699c0 60%, #7fb8d0 100%)', accent: 'rgba(150,210,240,0.3)', dotColor: '#7dd3fc', }, { id: 'rain', label: '暴雨', icon: '🌧️', temp: 20, desc: '暴雨黄色预警', hi: 23, lo: 18, humidity: 92, wind: 28, uv: 1, aqi: 18, hourly: [ { time: '现在', icon: '🌧️', temp: 20 }, { time: '14时', icon: '⛈️', temp: 21 }, { time: '16时', icon: '🌧️', temp: 22 }, { time: '18时', icon: '🌧️', temp: 21 }, { time: '20时', icon: '🌧️', temp: 20 }, { time: '22时', icon: '🌙', temp: 19 }, ], gradient: 'linear-gradient(165deg, #1a2a3a 0%, #2c4053 30%, #4a6278 60%, #6b8299 100%)', accent: 'rgba(130,170,200,0.3)', dotColor: '#94a3b8', }, { id: 'snow', label: '暴雪', icon: '❄️', temp: -8, desc: '暴雪橙色预警', hi: -5, lo: -12, humidity: 78, wind: 35, uv: 1, aqi: 15, hourly: [ { time: '现在', icon: '❄️', temp: -8 }, { time: '14时', icon: '❄️', temp: -7 }, { time: '16时', icon: '🌨️', temp: -9 }, { time: '18时', icon: '🌨️', temp: -10 }, { time: '20时', icon: '❄️', temp: -11 }, { time: '22时', icon: '❄️', temp: -12 }, ], gradient: 'linear-gradient(165deg, #8399ad 0%, #a8bfcf 25%, #c5d5e0 50%, #dce6ed 80%, #e8eef3 100%)', accent: 'rgba(220,235,250,0.4)', dotColor: '#e2e8f0', }, ]; const cardsRow = document.getElementById('cardsRow'); const bgLayer = document.getElementById('bgLayer'); const headerTime = document.getElementById('headerTime'); // ── Update time ───────────────────────────────────────── function updateTime() { const now = new Date(); headerTime.textContent = now.toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit', hour12: false }); } updateTime(); setInterval(updateTime, 10000); // ── Set background ────────────────────────────────────── function setBackground(gradient) { bgLayer.style.background = gradient; } // Init with sunny setBackground(weatherData[0].gradient); // ── Card click expand logic ───────────────────────────── function handleCardClick(card, data) { const wasExpanded = card.classList.contains('expanded'); // Collapse all document.querySelectorAll('.weather-card').forEach(c => c.classList.remove('expanded')); if (!wasExpanded) { card.classList.add('expanded'); setBackground(data.gradient); } else { setBackground(weatherData[0].gradient); } } // ── Build cards ───────────────────────────────────────── weatherData.forEach((data, index) => { const card = document.createElement('div'); card.className = 'weather-card'; if (index === 0) card.classList.add('expanded'); card.setAttribute('data-id', data.id); const detailsHTML = ` <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> 湿度 <span class="detail-value">${data.humidity}%</span> </div> <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> 风速 <span class="detail-value">${data.wind} km/h</span> </div> <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> UV 指数 <span class="detail-value">${data.uv}</span> </div> <div class="detail-row"> <span class="detail-dot" style="background:${data.dotColor}"></span> 空气质量 <span class="detail-value">${data.aqi} 优</span> </div> `; const hourlyHTML = data.hourly.map(h => ` <div class="hourly-item"> <span>${h.time}</span> <span class="hourly-icon">${h.icon}</span> <span class="hourly-temp">${h.temp}°</span> </div> `).join(''); card.innerHTML = ` <canvas class="card-canvas" id="canvas-${data.id}"></canvas> <div class="card-content"> <div class="card-icon-row"> <div class="weather-icon-wrap" style="background:${data.accent}"> <span style="line-height:1">${data.icon}</span> </div> <span class="card-label">${data.label}</span> </div> <div class="card-temp">${data.temp}°</div> <div class="card-desc">${data.desc}</div> <div class="card-details">${detailsHTML}</div> <div class="hourly-strip">${hourlyHTML}</div> </div> `; card.addEventListener('click', () => handleCardClick(card, data)); cardsRow.appendChild(card); }); // ── Weather Animation Canvases ────────────────────────── function initCanvas(canvasId, type) { const canvas = document.getElementById(canvasId); if (!canvas) return; const ctx = canvas.getContext('2d'); let particles = []; let animId; function resize() { const rect = canvas.parentElement.getBoundingClientRect(); canvas.width = rect.width * devicePixelRatio; canvas.height = rect.height * devicePixelRatio; ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.scale(devicePixelRatio, devicePixelRatio); } resize(); const observer = new ResizeObserver(resize); observer.observe(canvas.parentElement); const W = () => canvas.width / devicePixelRatio; const H = () => canvas.height / devicePixelRatio; // ── Particle types ────────────────────────────────── function createSunRay() { const cx = W() * 0.35; const cy = H() * 0.32; const angle = Math.random() * Math.PI * 2; const dist = 18 + Math.random() * 22; return { x: cx + Math.cos(angle) * 18, y: cy + Math.sin(angle) * 18, targetX: cx + Math.cos(angle) * dist, targetY: cy + Math.sin(angle) * dist, progress: 0, speed: 0.008 + Math.random() * 0.015, alpha: 0.7 + Math.random() * 0.3, size: 1.2 + Math.random() * 1.8, life: 1, type: 'ray', }; } function createWindLine() { return { x: -20, y: Math.random() * H(), length: 30 + Math.random() * 60, speed: 2.5 + Math.random() * 4, alpha: 0.15 + Math.random() * 0.25, thickness: 0.6 + Math.random() * 1.2, life: 1, type: 'wind', }; } function createRaindrop() { return { x: Math.random() * W(), y: -10 - Math.random() * 60, speed: 8 + Math.random() * 14, length: 8 + Math.random() * 16, alpha: 0.25 + Math.random() * 0.35, thickness: 0.8 + Math.random() * 0.6, life: 1, type: 'rain', wind: 1.5 + Math.random() * 3, }; } function createSnowflake() { const size = 1.5 + Math.random() * 3.5; return { x: Math.random() * W(), y: -10 - Math.random() * 20, size, speedY: 0.3 + Math.random() * 1.2, speedX: -0.3 + Math.random() * 0.6, wobble: Math.random() * Math.PI * 2, wobbleSpeed: 0.01 + Math.random() * 0.03, alpha: 0.5 + Math.random() * 0.5, life: 1, type: 'snow', }; } // ── Init particles ────────────────────────────────── function initParticles() { particles = []; if (type === 'sunny') { for (let i = 0; i < 18; i++) particles.push(createSunRay()); } else if (type === 'windy') { for (let i = 0; i < 12; i++) { const p = createWindLine(); p.x = Math.random() * W(); particles.push(p); } } else if (type === 'rain') { for (let i = 0; i < 50; i++) { const p = createRaindrop(); p.y = Math.random() * H(); particles.push(p); } } else if (type === 'snow') { for (let i = 0; i < 45; i++) { const p = createSnowflake(); p.y = Math.random() * H(); particles.push(p); } } } initParticles(); // ── Draw functions ────────────────────────────────── function drawSun(cx, cy) { // Glow layers for (let i = 3; i >= 0; i--) { const r = 24 + i * 8; const grad = ctx.createRadialGradient(cx, cy, r * 0.3, cx, cy, r); grad.addColorStop(0, `rgba(255,220,140,${0.25 - i * 0.05})`); grad.addColorStop(0.5, `rgba(255,200,100,${0.12 - i * 0.03})`); grad.addColorStop(1, 'rgba(255,180,60,0)'); ctx.fillStyle = grad; ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2); ctx.fill(); } // Core const core = ctx.createRadialGradient(cx, cy, 0, cx, cy, 10); core.addColorStop(0, 'rgba(255,245,220,0.95)'); core.addColorStop(1, 'rgba(255,200,100,0.6)'); ctx.fillStyle = core; ctx.beginPath(); ctx.arc(cx, cy, 10, 0, Math.PI * 2); ctx.fill(); } function drawCloud(cx, cy, scale, alpha) { ctx.fillStyle = `rgba(255,255,255,${alpha})`; ctx.beginPath(); const s = scale; ctx.arc(cx, cy, 10 * s, 0, Math.PI * 2); ctx.arc(cx + 9 * s, cy - 3 * s, 8 * s, 0, Math.PI * 2); ctx.arc(cx - 8 * s, cy - 1 * s, 7 * s, 0, Math.PI * 2); ctx.arc(cx + 16 * s, cy + 1 * s, 6 * s, 0, Math.PI * 2); ctx.arc(cx - 5 * s, cy + 4 * s, 5 * s, 0, Math.PI * 2); ctx.fill(); } // ── Animate ───────────────────────────────────────── let frameCount = 0; function animate() { ctx.clearRect(0, 0, W(), H()); frameCount++; const sunCx = W() * 0.35; const sunCy = H() * 0.32; if (type === 'sunny') { drawSun(sunCx, sunCy); // Draw rays particles.forEach(p => { p.progress += p.speed; if (p.progress >= 1) { // Reset ray const angle = Math.random() * Math.PI * 2; const dist = 18 + Math.random() * 22; p.x = sunCx + Math.cos(angle) * 14; p.y = sunCy + Math.sin(angle) * 14; p.targetX = sunCx + Math.cos(angle) * dist; p.targetY = sunCy + Math.sin(angle) * dist; p.progress = 0; p.alpha = 0.5 + Math.random() * 0.35; } const ease = Math.sin(p.progress * Math.PI * 0.5); const rx = p.x + (p.targetX - p.x) * ease; const ry = p.y + (p.targetY - p.y) * ease; const alpha = p.alpha * (1 - ease * 0.8); ctx.strokeStyle = `rgba(255,220,150,${alpha})`; ctx.lineWidth = p.size; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(rx, ry); ctx.stroke(); }); // Spawn new rays if (frameCount % 3 === 0 && particles.length < 24) { particles.push(createSunRay()); } // Small clouds const t = Date.now() * 0.0002; drawCloud(W() * 0.68 + Math.sin(t) * 4, H() * 0.28, 0.5, 0.25); drawCloud(W() * 0.55 + Math.cos(t * 1.3) * 3, H() * 0.48, 0.4, 0.18); } else if (type === 'windy') { // Draw some curved wind lines particles.forEach(p => { p.x += p.speed; if (p.x > W() + 40) { p.x = -40; p.y = Math.random() * H(); p.alpha = 0.15 + Math.random() * 0.25; } const midX = p.x - p.length * 0.4; const midY = p.y + Math.sin(p.x * 0.03 + frameCount * 0.02) * 15; ctx.strokeStyle = `rgba(200,225,255,${p.alpha})`; ctx.lineWidth = p.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p.x - p.length, p.y); ctx.quadraticCurveTo(midX, midY, p.x, p.y); ctx.stroke(); // Tiny streak near end ctx.strokeStyle = `rgba(220,240,255,${p.alpha * 0.7})`; ctx.lineWidth = p.thickness * 0.5; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(p.x - 6, p.y - 1); ctx.stroke(); }); if (frameCount % 8 === 0 && particles.length < 18) { particles.push(createWindLine()); } // Subtle cloud drawCloud(W() * 0.4, H() * 0.35, 0.7, 0.2); } else if (type === 'rain') { // Dark cloud at top drawCloud(W() * 0.3, H() * 0.18, 0.9, 0.3); drawCloud(W() * 0.6, H() * 0.16, 0.8, 0.25); particles.forEach(p => { p.y += p.speed; p.x += p.wind; if (p.y > H() + 10) { p.y = -15; p.x = Math.random() * (W() + 30) - 15; } ctx.strokeStyle = `rgba(160,200,230,${p.alpha})`; ctx.lineWidth = p.thickness; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(p.x, p.y); ctx.lineTo(p.x - p.wind * 0.4, p.y - p.length); ctx.stroke(); // Splash at bottom if (p.y > H() * 0.88 && Math.random() < 0.3) { ctx.fillStyle = `rgba(160,200,230,${p.alpha * 0.3})`; ctx.beginPath(); ctx.arc(p.x, p.y, 1.5, 0, Math.PI * 2); ctx.fill(); } }); if (frameCount % 2 === 0 && particles.length < 60) { particles.push(createRaindrop()); } } else if (type === 'snow') { // Soft cloud at top drawCloud(W() * 0.35, H() * 0.16, 1.0, 0.35); drawCloud(W() * 0.6, H() * 0.14, 0.85, 0.28); particles.forEach(p => { p.wobble += p.wobbleSpeed; p.y += p.speedY; p.x += p.speedX + Math.sin(p.wobble) * 0.4; if (p.y > H() + 10) { p.y = -10; p.x = Math.random() * (W() + 20) - 10; } if (p.x < -10) p.x = W() + 10; if (p.x > W() + 10) p.x = -10; // Draw snowflake (small circle with glow) const grad = ctx.createRadialGradient(p.x, p.y, 0, p.x, p.y, p.size); grad.addColorStop(0, `rgba(255,255,255,${p.alpha})`); grad.addColorStop(1, `rgba(220,235,255,0)`); ctx.fillStyle = grad; ctx.beginPath(); ctx.arc(p.x, p.y, p.size * 1.6, 0, Math.PI * 2); ctx.fill(); ctx.fillStyle = `rgba(255,255,255,${p.alpha * 0.9})`; ctx.beginPath(); ctx.arc(p.x, p.y, p.size * 0.6, 0, Math.PI * 2); ctx.fill(); }); if (frameCount % 3 === 0 && particles.length < 55) { particles.push(createSnowflake()); } // Ground snow accumulation ctx.fillStyle = 'rgba(240,245,250,0.25)'; ctx.beginPath(); ctx.moveTo(0, H()); for (let x = 0; x <= W(); x += 3) { ctx.lineTo(x, H() - 4 - Math.sin(x * 0.05 + frameCount * 0.002) * 3); } ctx.lineTo(W(), H()); ctx.closePath(); ctx.fill(); } animId = requestAnimationFrame(animate); } animate(); // Cleanup return () => { cancelAnimationFrame(animId); observer.disconnect(); }; } // Init all canvases after DOM ready const cleaners = []; setTimeout(() => { weatherData.forEach(data => { const clean = initCanvas(`canvas-${data.id}`, data.id); if (clean) cleaners.push(clean); }); }, 100); // Cleanup on page unload window.addEventListener('beforeunload', () => cleaners.forEach(fn => fn())); // ── Keyboard navigation ──────────────────────────────── document.addEventListener('keydown', (e) => { const cards = [...document.querySelectorAll('.weather-card')]; const current = cards.findIndex(c => c.classList.contains('expanded')); if (e.key === 'ArrowRight' && current < cards.length - 1) { cards[current + 1].click(); cards[current + 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } else if (e.key === 'ArrowLeft' && current > 0) { cards[current - 1].click(); cards[current - 1].scrollIntoView({ behavior: 'smooth', block: 'nearest' }); } }); })(); </script> </body> </html> 网友解答:


--【壹】--:

哇,这个手绘风的天气的图标很亮眼啊,佬这个是用了什么 skill 吗还是直出的啊,我用 2.5pro 出的质量差好多


--【贰】--:

是啊,5.4是真的前端苦手, 组件叠组件叠半天出来一坨大的


--【叁】--:

image1920×947 87.1 KB
aistudio.google 生成的 ,带有动效


--【肆】--:

感觉这模型挺依赖抽卡的,看另一个帖子里和gpt5.5的天气卡片对比,v4pro做出来感觉就很一般


--【伍】--:

image1920×901 93.9 KB

小米mimo-2.5pro搞的,我感觉蛮不错的


--【陆】--:

岂可修,难道是 opencode 的问题 ,opencode 里面的 mimo-2.5pro出来的是这样的,难绷的很
PixPin2026-04-2417-25-052940×1470 283 KB


--【柒】--:

我这里 GPT 5.5 自动调用了 skill 和 Chrome Devtools,做出来是这样的(GPT做前端还是喜欢堆组件嵌套)
PixPin2026-04-2415-34-432940×1470 294 KB


--【捌】--:

好像是有点抽卡的意味,我看有的佬发的效果很差,我的这次就还行


--【玖】--:

GPT5.5做的
d35c7be2ea9a6f701340×880 130 KB


--【拾】--:

配色风格比gpt-5.4弄出来的好看太多了 也有质感了


--【拾壹】--:

有佬友说v4pro后训练可能有点问题,导致现在回答还不要稳定,但现在毕竟还是测试版嘛


--【拾贰】--:

这就充值个10块钱, 然后导入CPA, 然后alias gpt-5.3-codex看看效果


--【拾叁】--:

我是直接cc里直接出的,刚买了coding plan就看到佬的帖子就试了下,没想到挺不错的

image1113×626 33.5 KB