来一发qwen3.7max天气图,还不错

gally16 2026-05-19 20:31 1

你是一位就职于苹果公司的顶级前端工程师。请创建一个包含 CSS 和 JavaScript 的 HTML 文件,用于生成动画天气卡片。卡片需要以不同动画效果直观展示以下天气状况:
风力(如:飘动的云朵、摇曳的树木或风线)
降雨(如:下落的雨滴、形成的水洼)
晴天(如:闪耀的光线、明亮的背景)
下雪(如:飘落的雪花、积雪效果)

所有天气卡片需要并排显示,背景采用深色设计。所有 HTML、CSS 和 JavaScript 代码都需包含在这个单一文件中。JavaScript 部分需包含切换不同天气状态的功能(例如通过函数或按钮组),以演示每种天气的动画效果。

将前端显示效果优化得更精致流畅,打造出价值 2000 元 / 月的精品天气应用既视感。




源码

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Weather Atelier · 天气卡片</title>
<style>
/* ==========================================================
Base & Reset
========================================================== */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--bg-0: #05070d;
--bg-1: #0a0f1c;
--text-0: #f5f7fa;
--text-1: rgba(245, 247, 250, 0.7);
--text-2: rgba(245, 247, 250, 0.45);
--glass: rgba(255, 255, 255, 0.06);
--glass-border: rgba(255, 255, 255, 0.12);
--accent: #7aa2ff;
--radius-lg: 28px;
--radius-md: 18px;
--shadow-card: 0 30px 60px -20px rgba(0,0,0,0.6), 0 0 0 1px rgba(255,255,255,0.04) inset;
--ease: cubic-bezier(.2,.8,.2,1);
}
html, body { height: 100%; }
body {
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text",
"Helvetica Neue", "PingFang SC", "Microsoft YaHei", sans-serif;
color: var(--text-0);
background: var(--bg-0);
overflow-x: hidden;
min-height: 100vh;
letter-spacing: -0.01em;
-webkit-font-smoothing: antialiased;
display: flex;
flex-direction: column;
align-items: center;
padding: 48px 24px 64px;
position: relative;
}
/* Ambient background */
body::before {
content: "";
position: fixed; inset: -20%;
background:
radial-gradient(60% 50% at 20% 10%, rgba(122, 162, 255, 0.18), transparent 70%),
radial-gradient(50% 40% at 85% 90%, rgba(255, 130, 200, 0.12), transparent 70%),
radial-gradient(40% 40% at 50% 50%, rgba(120, 200, 255, 0.08), transparent 70%);
filter: blur(40px);
z-index: -2;
animation: ambientShift 20s ease-in-out infinite alternate;
}
@keyframes ambientShift {
0% { transform: translate3d(0,0,0) scale(1); }
100% { transform: translate3d(-2%, 1%, 0) scale(1.05); }
}

/* ==========================================================
Header
========================================================== */
header {
width: 100%;
max-width: 1400px;
display: flex;
flex-wrap: wrap;
align-items: center;
justify-content: space-between;
gap: 24px;
margin-bottom: 40px;
}
.brand { display: flex; align-items: baseline; gap: 14px; }
.brand h1 {
font-size: 28px;
font-weight: 700;
letter-spacing: -0.02em;
background: linear-gradient(120deg, #fff 0%, #a9c3ff 100%);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
}
.brand .dot {
width: 6px; height: 6px; border-radius: 50%;
background: var(--accent);
box-shadow: 0 0 12px var(--accent);
align-self: center;
}
.brand .sub {
font-size: 14px;
color: var(--text-2);
font-weight: 500;
letter-spacing: 0.02em;
}

/* Button group */
.controls {
display: inline-flex;
padding: 6px;
background: var(--glass);
border: 1px solid var(--glass-border);
border-radius: 999px;
backdrop-filter: blur(20px);
-webkit-backdrop-filter: blur(20px);
}
.controls button {
appearance: none; border: 0; cursor: pointer;
padding: 9px 18px;
font: inherit;
font-size: 13px;
font-weight: 500;
color: var(--text-1);
background: transparent;
border-radius: 999px;
transition: all .35s var(--ease);
display: inline-flex; align-items: center; gap: 6px;
letter-spacing: 0.01em;
}
.controls button:hover { color: var(--text-0); }
.controls button.active {
color: #fff;
background: linear-gradient(135deg, rgba(255,255,255,0.18), rgba(255,255,255,0.06));
box-shadow: 0 4px 14px rgba(0,0,0,0.3), 0 0 0 1px rgba(255,255,255,0.12) inset;
}
.controls button .ic { font-size: 14px; }

/* ==========================================================
Card grid
========================================================== */
.grid {
width: 100%;
max-width: 1400px;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
}
@media (max-width: 1200px) { .grid { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 640px) { .grid { grid-template-columns: 1fr; } }

/* ==========================================================
Card base
========================================================== */
.card {
position: relative;
aspect-ratio: 3 / 4;
min-height: 460px;
border-radius: var(--radius-lg);
overflow: hidden;
padding: 24px;
display: flex;
flex-direction: column;
justify-content: space-between;
box-shadow: var(--shadow-card);
border: 1px solid var(--glass-border);
transition: transform .6s var(--ease), box-shadow .6s var(--ease);
cursor: pointer;
isolation: isolate;
}
.card::before {
content: "";
position: absolute; inset: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(160deg, rgba(255,255,255,0.25), rgba(255,255,255,0) 40%);
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor;
mask-composite: exclude;
pointer-events: none;
z-index: 3;
}
.card:hover { transform: translateY(-6px); }
.card:hover { box-shadow: 0 40px 80px -25px rgba(0,0,0,0.7), 0 0 0 1px rgba(255,255,255,0.08) inset; }
.card.dim { filter: saturate(.6) brightness(.6); transform: scale(.97); }

.card .scene {
position: absolute; inset: 0;
z-index: 0;
overflow: hidden;
pointer-events: none;
}
.card canvas {
position: absolute; inset: 0;
width: 100%; height: 100%;
display: block;
}

.card .meta, .card .body {
position: relative; z-index: 2;
}
.card .meta {
display: flex; justify-content: space-between; align-items: flex-start;
font-size: 13px; color: var(--text-1);
}
.card .meta .city { font-weight: 600; color: #fff; font-size: 15px; letter-spacing: 0.01em; }
.card .meta .loc { color: var(--text-2); margin-top: 3px; font-size: 12px; }
.card .meta .time { font-variant-numeric: tabular-nums; font-size: 12px; color: var(--text-2); }

.card .body .temp {
font-size: 76px;
font-weight: 200;
line-height: 1;
letter-spacing: -0.04em;
color: #fff;
text-shadow: 0 4px 30px rgba(0,0,0,0.25);
}
.card .body .temp sup {
font-size: 32px;
font-weight: 300;
vertical-align: super;
margin-left: 2px;
opacity: .8;
}
.card .body .label {
margin-top: 8px;
font-size: 17px;
font-weight: 500;
color: #fff;
}
.card .body .range {
margin-top: 4px;
font-size: 13px;
color: var(--text-1);
}
.card .footer {
position: relative; z-index: 2;
display: flex; justify-content: space-between;
padding: 14px 16px;
border-radius: var(--radius-md);
background: rgba(0,0,0,0.25);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border: 1px solid rgba(255,255,255,0.08);
font-size: 12px;
color: var(--text-1);
}
.card .footer div { display: flex; flex-direction: column; align-items: center; gap: 2px; }
.card .footer .v { color: #fff; font-weight: 600; font-variant-numeric: tabular-nums; }
.card .footer .k { color: var(--text-2); font-size: 11px; }

/* ==========================================================
Weather-specific backgrounds
========================================================== */
.card.sunny { background: linear-gradient(160deg, #ff9a3c 0%, #ff6a88 40%, #5c2e91 100%); }
.card.windy { background: linear-gradient(160deg, #4a6a8a 0%, #2d4259 60%, #161c2a 100%); }
.card.rainy { background: linear-gradient(160deg, #1e3a5f 0%, #14213d 55%, #0a1020 100%); }
.card.snowy { background: linear-gradient(160deg, #3a4a6b 0%, #1c2541 55%, #0b1022 100%); }

/* ==========================================================
SUNNY
========================================================== */
.sunny .sun {
position: absolute;
top: 18%; right: -12%;
width: 260px; height: 260px;
border-radius: 50%;
background: radial-gradient(circle at 50% 50%, #fff6d6 0%, #ffcf6b 40%, #ff8f3a 70%, rgba(255,143,58,0) 72%);
box-shadow:
0 0 60px rgba(255, 200, 120, 0.7),
0 0 140px rgba(255, 140, 80, 0.5),
0 0 220px rgba(255, 100, 100, 0.3);
animation: sunPulse 5s ease-in-out infinite;
}
@keyframes sunPulse {
0%, 100% { transform: scale(1); filter: brightness(1); }
50% { transform: scale(1.06); filter: brightness(1.15); }
}
.sunny .rays {
position: absolute;
top: 18%; right: -12%;
width: 260px; height: 260px;
animation: spin 40s linear infinite;
opacity: 0.7;
mix-blend-mode: screen;
}
.sunny .rays::before, .sunny .rays::after {
content: "";
position: absolute; inset: -20%;
background:
conic-gradient(from 0deg,
transparent 0deg, rgba(255,230,180,0.35) 10deg, transparent 20deg,
transparent 40deg, rgba(255,220,170,0.25) 50deg, transparent 60deg,
transparent 80deg, rgba(255,230,180,0.35) 90deg, transparent 100deg,
transparent 120deg, rgba(255,220,170,0.25) 130deg, transparent 140deg,
transparent 160deg, rgba(255,230,180,0.35) 170deg, transparent 180deg,
transparent 200deg, rgba(255,220,170,0.25) 210deg, transparent 220deg,
transparent 240deg, rgba(255,230,180,0.35) 250deg, transparent 260deg,
transparent 280deg, rgba(255,220,170,0.25) 290deg, transparent 300deg,
transparent 320deg, rgba(255,230,180,0.35) 330deg, transparent 340deg);
filter: blur(2px);
}
@keyframes spin { to { transform: rotate(360deg); } }
.sunny .haze {
position: absolute; bottom: 0; left: 0; right: 0; height: 40%;
background: linear-gradient(to top, rgba(255,140,100,0.25), transparent);
}

/* ==========================================================
WINDY
========================================================== */
.windy .cloud {
position: absolute;
background: rgba(255,255,255,0.85);
border-radius: 100px;
filter: blur(0.5px);
box-shadow: 0 10px 40px rgba(0,0,0,0.25);
}
.windy .cloud::before, .windy .cloud::after {
content: ""; position: absolute; background: inherit; border-radius: 50%;
}
.windy .c1 { top: 16%; left: -30%; width: 140px; height: 36px; animation: drift 14s linear infinite; }
.windy .c1::before { width: 58px; height: 58px; top: -26px; left: 18px; }
.windy .c1::after { width: 42px; height: 42px; top: -16px; left: 70px; }

.windy .c2 { top: 32%; left: -40%; width: 180px; height: 44px; opacity: .7; animation: drift 20s linear infinite; animation-delay: -6s; }
.windy .c2::before { width: 70px; height: 70px; top: -32px; left: 22px; }
.windy .c2::after { width: 54px; height: 54px; top: -22px; left: 90px; }

.windy .c3 { top: 50%; left: -30%; width: 110px; height: 30px; opacity: .55; animation: drift 11s linear infinite; animation-delay: -3s; }
.windy .c3::before { width: 46px; height: 46px; top: -22px; left: 12px; }
.windy .c3::after { width: 34px; height: 34px; top: -14px; left: 54px; }

@keyframes drift {
from { transform: translateX(0); }
to { transform: translateX(520px); }
}

.windy .line {
position: absolute;
height: 1px;
background: linear-gradient(90deg, transparent, rgba(255,255,255,0.6), transparent);
animation: windLine 2.6s linear infinite;
}
@keyframes windLine {
0% { transform: translateX(-120%); opacity: 0; }
20% { opacity: 1; }
100% { transform: translateX(260%); opacity: 0; }
}

/* Tree */
.windy .tree {
position: absolute;
bottom: 6%;
left: 14%;
width: 80px; height: 140px;
transform-origin: bottom center;
animation: sway 3.6s ease-in-out infinite;
}
.windy .tree .trunk {
position: absolute; bottom: 0; left: 50%; transform: translateX(-50%);
width: 8px; height: 60px;
background: linear-gradient(to right, #3a2414, #5a3a22, #3a2414);
border-radius: 2px;
}
.windy .tree .leaves {
position: absolute; bottom: 40px; left: 50%; transform: translateX(-50%);
width: 80px; height: 100px;
background: radial-gradient(ellipse at 50% 60%, #2d5a3d 0%, #1d3a28 70%, rgba(29,58,40,0) 100%);
border-radius: 50% 50% 45% 45%;
filter: blur(0.5px);
}
@keyframes sway {
0%, 100% { transform: rotate(-3deg); }
50% { transform: rotate(4deg); }
}

/* ==========================================================
RAINY — canvas + lightning + puddle
========================================================== */
.rainy .flash {
position: absolute; inset: 0;
background: rgba(200, 220, 255, 0);
transition: background 80ms linear;
pointer-events: none;
z-index: 1;
}
.rainy .puddle {
position: absolute;
bottom: 0; left: 0; right: 0;
height: 22%;
background: linear-gradient(to top, rgba(180,200,230,0.12), transparent);
backdrop-filter: blur(2px);
overflow: hidden;
}
.rainy .ripple {
position: absolute;
bottom: 4%;
width: 10px; height: 3px;
border-radius: 50%;
border: 1px solid rgba(255,255,255,0.4);
animation: ripple 2.2s ease-out infinite;
}
@keyframes ripple {
0% { transform: scale(0.3); opacity: 0.9; }
100% { transform: scale(8); opacity: 0; }
}

/* ==========================================================
SNOWY — canvas + accumulation
========================================================== */
.snowy .snow-ground {
position: absolute;
bottom: 0; left: 0; right: 0;
height: 14%;
background: linear-gradient(to top, rgba(255,255,255,0.9), rgba(255,255,255,0.3) 70%, transparent);
border-top: 1px solid rgba(255,255,255,0.35);
box-shadow: 0 -4px 16px rgba(255,255,255,0.15);
}
.snowy .moon {
position: absolute;
top: 12%; right: 12%;
width: 80px; height: 80px;
border-radius: 50%;
background: radial-gradient(circle at 35% 35%, #fff 0%, #e8edff 50%, #b8c4e6 100%);
box-shadow:
0 0 40px rgba(220,230,255,0.5),
0 0 100px rgba(180,200,255,0.3);
}
.snowy .mist {
position: absolute;
inset: 0;
background:
radial-gradient(60% 30% at 30% 80%, rgba(200,220,255,0.15), transparent 70%),
radial-gradient(60% 30% at 70% 85%, rgba(200,220,255,0.12), transparent 70%);
}

/* ==========================================================
Footer note
========================================================== */
.footer-note {
margin-top: 48px;
text-align: center;
font-size: 12px;
color: var(--text-2);
letter-spacing: 0.02em;
}
</style>
</head>
<body>

<header>
<div class="brand">
<span class="dot"></span>
<h1>Weather Atelier</h1>
<span class="sub">· 天气工坊</span>
</div>
<div class="controls" id="controls">
<button data-target="all" class="active"><span class="ic">◉</span> 全部</button>
<button data-target="sunny"><span class="ic">☀</span> 晴天</button>
<button data-target="windy"><span class="ic">🜨</span> 大风</button>
<button data-target="rainy"><span class="ic">☂</span> 降雨</button>
<button data-target="snowy"><span class="ic">❄</span> 飘雪</button>
</div>
</header>

<div class="grid" id="grid">
<!-- ================= Sunny ================= -->
<div class="card sunny" data-weather="sunny">
<div class="scene">
<div class="sun"></div>
<div class="rays"></div>
<div class="haze"></div>
</div>
<div class="meta">
<div>
<div class="city">马里布</div>
<div class="loc">Malibu, CA</div>
</div>
<div class="time">14:32</div>
</div>
<div class="body">
<div class="temp">28<sup>°</sup></div>
<div class="label">晴朗 · Sunny</div>
<div class="range">最高 30° · 最低 22°</div>
</div>
<div class="footer">
<div><span class="v">UV 9</span><span class="k">紫外线</span></div>
<div><span class="v">42%</span><span class="k">湿度</span></div>
<div><span class="v">10km</span><span class="k">能见度</span></div>
</div>
</div>

<!-- ================= Windy ================= -->
<div class="card windy" data-weather="windy">
<div class="scene">
<div class="cloud c1"></div>
<div class="cloud c2"></div>
<div class="cloud c3"></div>
<div class="line" style="top:22%; left:10%; width:80px;"></div>
<div class="line" style="top:40%; left:30%; width:120px; animation-delay:-0.8s;"></div>
<div class="line" style="top:58%; left:5%; width:60px; animation-delay:-1.6s;"></div>
<div class="line" style="top:70%; left:45%; width:100px; animation-delay:-0.4s;"></div>
<div class="line" style="top:80%; left:20%; width:90px; animation-delay:-1.2s;"></div>
<div class="tree">
<div class="leaves"></div>
<div class="trunk"></div>
</div>
</div>
<div class="meta">
<div>
<div class="city">青岛</div>
<div class="loc">Qingdao, CN</div>
</div>
<div class="time">14:32</div>
</div>
<div class="body">
<div class="temp">16<sup>°</sup></div>
<div class="label">大风 · Windy</div>
<div class="range">最高 19° · 最低 12°</div>
</div>
<div class="footer">
<div><span class="v">38</span><span class="k">km/h</span></div>
<div><span class="v">NE</span><span class="k">风向</span></div>
<div><span class="v">6级</span><span class="k">阵风</span></div>
</div>
</div>

<!-- ================= Rainy ================= -->
<div class="card rainy" data-weather="rainy">
<div class="scene">
<canvas id="rain-canvas"></canvas>
<div class="flash" id="rain-flash"></div>
<div class="puddle">
<div class="ripple" style="left: 18%; animation-delay: 0s;"></div>
<div class="ripple" style="left: 42%; animation-delay: -0.7s;"></div>
<div class="ripple" style="left: 66%; animation-delay: -1.3s;"></div>
<div class="ripple" style="left: 84%; animation-delay: -0.3s;"></div>
</div>
</div>
<div class="meta">
<div>
<div class="city">上海</div>
<div class="loc">Shanghai, CN</div>
</div>
<div class="time">14:32</div>
</div>
<div class="body">
<div class="temp">19<sup>°</sup></div>
<div class="label">暴雨 · Heavy Rain</div>
<div class="range">最高 22° · 最低 17°</div>
</div>
<div class="footer">
<div><span class="v">18mm</span><span class="k">降水量</span></div>
<div><span class="v">92%</span><span class="k">湿度</span></div>
<div><span class="v">3km</span><span class="k">能见度</span></div>
</div>
</div>

<!-- ================= Snowy ================= -->
<div class="card snowy" data-weather="snowy">
<div class="scene">
<div class="mist"></div>
<div class="moon"></div>
<canvas id="snow-canvas"></canvas>
<div class="snow-ground"></div>
</div>
<div class="meta">
<div>
<div class="city">札幌</div>
<div class="loc">Sapporo, JP</div>
</div>
<div class="time">14:32</div>
</div>
<div class="body">
<div class="temp">-4<sup>°</sup></div>
<div class="label">飘雪 · Snowing</div>
<div class="range">最高 -1° · 最低 -8°</div>
</div>
<div class="footer">
<div><span class="v">6cm</span><span class="k">积雪</span></div>
<div><span class="v">78%</span><span class="k">湿度</span></div>
<div><span class="v">2km</span><span class="k">能见度</span></div>
</div>
</div>
</div>

<div class="footer-note">Designed with care · Tap a card or use the controls to focus</div>

<script>
/* ============================================================
Utility: high-DPI canvas resize
============================================================ */
function fitCanvas(canvas) {
const dpr = window.devicePixelRatio || 1;
const rect = canvas.getBoundingClientRect();
canvas.width = Math.floor(rect.width * dpr);
canvas.height = Math.floor(rect.height * dpr);
const ctx = canvas.getContext("2d");
ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
return { ctx, w: rect.width, h: rect.height };
}

/* ============================================================
Clock — update every card's time
============================================================ */
function tickClock() {
const now = new Date();
const hh = String(now.getHours()).padStart(2,"0");
const mm = String(now.getMinutes()).padStart(2,"0");
document.querySelectorAll(".card .time").forEach(el => el.textContent = `${hh}:${mm}`);
}
setInterval(tickClock, 30000); tickClock();

/* ============================================================
RAIN — Canvas particles
============================================================ */
(function initRain() {
const canvas = document.getElementById("rain-canvas");
if (!canvas) return;
let { ctx, w, h } = fitCanvas(canvas);

const COUNT = 160;
const drops = Array.from({ length: COUNT }, () => ({
x: Math.random() * w,
y: Math.random() * h,
l: 10 + Math.random() * 18,
v: 9 + Math.random() * 7,
o: 0.25 + Math.random() * 0.55,
}));

function step() {
ctx.clearRect(0, 0, w, h);
ctx.lineCap = "round";
for (const d of drops) {
ctx.beginPath();
ctx.strokeStyle = `rgba(180, 210, 255, ${d.o})`;
ctx.lineWidth = 1.2;
ctx.moveTo(d.x, d.y);
ctx.lineTo(d.x - 2, d.y + d.l);
ctx.stroke();

d.x -= 1.2;
d.y += d.v;
if (d.y > h) {
d.y = -d.l - Math.random() * 40;
d.x = Math.random() * w + 20;
}
}
requestAnimationFrame(step);
}
step();

window.addEventListener("resize", () => {
({ ctx, w, h } = fitCanvas(canvas));
});

/* Lightning flashes */
const flash = document.getElementById("rain-flash");
function lightning() {
if (Math.random() < 0.5) {
flash.style.background = "rgba(210, 225, 255, 0.35)";
setTimeout(() => flash.style.background = "rgba(255,255,255,0)", 90);
setTimeout(() => {
flash.style.background = "rgba(210, 225, 255, 0.18)";
setTimeout(() => flash.style.background = "rgba(255,255,255,0)", 80);
}, 180);
}
setTimeout(lightning, 3500 + Math.random() * 5500);
}
lightning();
})();

/* ============================================================
SNOW — Canvas particles (parallax + spin)
============================================================ */
(function initSnow() {
const canvas = document.getElementById("snow-canvas");
if (!canvas) return;
let { ctx, w, h } = fitCanvas(canvas);

const COUNT = 90;
const flakes = Array.from({ length: COUNT }, () => ({
x: Math.random() * w,
y: Math.random() * h,
r: 1 + Math.random() * 3.2,
vy: 0.3 + Math.random() * 1.1,
vx: (Math.random() - 0.5) * 0.6,
a: 0.4 + Math.random() * 0.6,
phase: Math.random() * Math.PI * 2,
amp: 0.3 + Math.random() * 0.8,
}));

let t = 0;
function step() {
ctx.clearRect(0, 0, w, h);
t += 0.01;
for (const f of flakes) {
const sway = Math.sin(t * 1.5 + f.phase) * f.amp;
ctx.beginPath();
const grad = ctx.createRadialGradient(f.x, f.y, 0, f.x, f.y, f.r * 2);
grad.addColorStop(0, `rgba(255,255,255,${f.a})`);
grad.addColorStop(1, "rgba(255,255,255,0)");
ctx.fillStyle = grad;
ctx.arc(f.x + sway, f.y, f.r, 0, Math.PI * 2);
ctx.fill();

f.y += f.vy;
f.x += f.vx + Math.sin(t + f.phase) * 0.1;
if (f.y > h + 5) {
f.y = -5;
f.x = Math.random() * w;
}
if (f.x > w + 5) f.x = -5;
if (f.x < -5) f.x = w + 5;
}
requestAnimationFrame(step);
}
step();

window.addEventListener("resize", () => {
({ ctx, w, h } = fitCanvas(canvas));
});
})();

/* ============================================================
Controls — focus / dim cards
============================================================ */
(function initControls() {
const buttons = document.querySelectorAll("#controls button");
const cards = document.querySelectorAll(".card");

function focus(target) {
cards.forEach(c => {
if (target === "all" || c.dataset.weather === target) {
c.classList.remove("dim");
} else {
c.classList.add("dim");
}
});
}

buttons.forEach(btn => {
btn.addEventListener("click", () => {
buttons.forEach(b => b.classList.remove("active"));
btn.classList.add("active");
focus(btn.dataset.target);
});
});

/* Card click: quick focus toggle */
cards.forEach(c => {
c.addEventListener("click", () => {
const t = c.dataset.weather;
const anyDim = [...cards].some(x => x.classList.contains("dim"));
if (anyDim && !c.classList.contains("dim")) {
focus("all");
buttons.forEach(b => b.classList.toggle("active", b.dataset.target === "all"));
} else {
focus(t);
buttons.forEach(b => b.classList.toggle("active", b.dataset.target === t));
}
});
});
})();
</script>
</body>
</html>

最新回复 (12)
  • bobo0915 05-19 21:08
    1

    这样看来怎么感觉还是差点意思呢?

  • yangs329 05-19 21:31
    2

    比deepseek-v4-pro好看。deepseek-v4-pro生成居然用了6毛

    今天搭建的Claude Code加deepseek.试试效果。

    哎,放弃了,还是用codex吧。

  • Charles 05-19 21:33
    3

    6毛直接打造2000块的效果,这是赚大了啊 ^-^



    将前端显示效果优化得更精致流畅,打造出价值 2000 元 / 月的精品天气应用既视感


  • gally16 楼主 05-19 21:45
    4


    提示词改成6毛后 ^-^

  • yangs329 05-19 22:16
    5

    看起来效果挺棒的啊。

    qwen3.7max消耗的费用咋样,你是在claude code里面生成的?

  • gally16 楼主 05-19 22:59
    6

    qwen code里弄的,但是速度很慢 ^-^

  • EVELance 05-19 23:22
    7

    这个太阳看起来得有40度往上了吧。

  • Kevvvin Yao 05-20 02:04
    8

    贡献一个kimi的,在claude code先开plan mode然后直接接受修改:

  • Nuyoah_sleep 05-20 08:31
    9

    效果看着确实不错呀,这6毛绝对赚大了 ^-^

  • luckyBear 05-20 09:11
    10

    这很不错了,同样的提示词我让gemini 3.1 pro生成的效果图



    代码如下:



    Premium Weather Experience

    /* =========================================
    基础重置与全局设定 (Apple Typography)
    ========================================= */
    :root {
    --bg-dark: #0a0a0c;
    --glass-bg: rgba(255, 255, 255, 0.03);
    --glass-border: rgba(255, 255, 255, 0.08);
    --text-main: #ffffff;
    --text-sub: rgba(255, 255, 255, 0.5);
    /* 氛围光晕颜色 */
    --glow-sunny: rgba(255, 170, 0, 0.15);
    --glow-wind: rgba(150, 180, 200, 0.15);
    --glow-rain: rgba(0, 120, 255, 0.15);
    --glow-snow: rgba(200, 230, 255, 0.15);
    --glow-all: rgba(255, 255, 255, 0.05);
    }
        * {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
    }

    body {
    font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
    background-color: var(--bg-dark);
    color: var(--text-main);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    overflow: hidden;
    transition: background-color 1s cubic-bezier(0.25, 1, 0.5, 1);
    }

    /* 动态全屏氛围背景光 */
    .ambient-light {
    position: absolute;
    top: 50%;
    left: 50%;
    width: 80vw;
    height: 80vw;
    transform: translate(-50%, -50%);
    background: radial-gradient(circle, var(--glow-all) 0%, transparent 70%);
    z-index: 0;
    transition: background 1.5s ease-in-out;
    pointer-events: none;
    }

    /* =========================================
    卡片布局与玻璃拟物化 (Glassmorphism)
    ========================================= */
    .cards-container {
    display: flex;
    gap: 24px;
    z-index: 1;
    padding: 40px;
    flex-wrap: wrap;
    justify-content: center;
    }

    .weather-card {
    width: 260px;
    height: 380px;
    background: var(--glass-bg);
    backdrop-filter: blur(30px);
    -webkit-backdrop-filter: blur(30px);
    border: 1px solid var(--glass-border);
    border-radius: 32px;
    box-shadow: 0 30px 60px rgba(0, 0, 0, 0.4),
    inset 0 1px 0 rgba(255, 255, 255, 0.1);
    position: relative;
    overflow: hidden;
    display: flex;
    flex-direction: column;
    transition: all 0.6s cubic-bezier(0.2, 0.8, 0.2, 1);
    cursor: pointer;
    }

    .weather-card:hover {
    transform: translateY(-10px) scale(1.02);
    box-shadow: 0 40px 80px rgba(0, 0, 0, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.2);
    border-color: rgba(255, 255, 255, 0.15);
    }

    /* 未激活状态的暗化处理 */
    .cards-container.focus-mode .weather-card.inactive {
    opacity: 0.4;
    transform: scale(0.95);
    filter: grayscale(0.5);
    }

    .cards-container.focus-mode .weather-card.active {
    transform: translateY(-15px) scale(1.05);
    box-shadow: 0 50px 100px rgba(0, 0, 0, 0.8),
    inset 0 1px 0 rgba(255, 255, 255, 0.3);
    border-color: rgba(255, 255, 255, 0.3);
    z-index: 10;
    }

    /* 卡片文本内容 */
    .card-content {
    position: absolute;
    bottom: 30px;
    left: 30px;
    z-index: 10;
    }

    .temperature {
    font-size: 3.5rem;
    font-weight: 300;
    letter-spacing: -2px;
    margin-bottom: 5px;
    }

    .condition {
    font-size: 1.2rem;
    font-weight: 500;
    margin-bottom: 2px;
    }

    .location {
    font-size: 0.9rem;
    color: var(--text-sub);
    font-weight: 400;
    }

    /* 动画视口 */
    .animation-viewport {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 60%;
    overflow: hidden;
    border-radius: 32px 32px 0 0;
    }

    /* =========================================
    具体天气动画组件 (仅使用 GPU 友好属性)
    ========================================= */

    /* --- 1. 晴天 (Sunny) --- */
    .sun-container {
    position: absolute;
    top: 40px;
    right: 40px;
    }
    .sun {
    width: 60px;
    height: 60px;
    background: linear-gradient(135deg, #FFD700, #FF8C00);
    border-radius: 50%;
    box-shadow: 0 0 40px rgba(255, 140, 0, 0.6);
    position: absolute;
    top: 0;
    right: 0;
    animation: pulseGlow 4s infinite alternate ease-in-out;
    }
    .sun-rays {
    width: 120px;
    height: 120px;
    position: absolute;
    top: -30px;
    right: -30px;
    background: radial-gradient(circle, rgba(255,170,0,0.2) 0%, transparent 60%);
    animation: spin 20s linear infinite;
    }
    @keyframes pulseGlow {
    0% { transform: scale(1); box-shadow: 0 0 30px rgba(255, 140, 0, 0.4); }
    100% { transform: scale(1.05); box-shadow: 0 0 60px rgba(255, 170, 0, 0.8); }
    }
    @keyframes spin {
    100% { transform: rotate(360deg); }
    }

    /* --- 2. 风力 (Wind) --- */
    .cloud {
    background: rgba(255, 255, 255, 0.8);
    border-radius: 50px;
    position: absolute;
    box-shadow: 0 10px 20px rgba(0,0,0,0.1);
    }
    .cloud::before, .cloud::after {
    content: '';
    position: absolute;
    background: rgba(255, 255, 255, 0.8);
    border-radius: 50%;
    }
    .wind-cloud-1 {
    width: 100px; height: 35px; top: 50px; left: -100px;
    animation: floatClouds 12s linear infinite;
    }
    .wind-cloud-1::before { width: 40px; height: 40px; top: -20px; left: 15px; }
    .wind-cloud-1::after { width: 50px; height: 50px; top: -25px; right: 15px; }

    .wind-cloud-2 {
    width: 70px; height: 25px; top: 90px; left: -80px;
    opacity: 0.6;
    animation: floatClouds 8s linear infinite 3s;
    }
    .wind-cloud-2::before { width: 30px; height: 30px; top: -15px; left: 10px; }
    .wind-cloud-2::after { width: 35px; height: 35px; top: -18px; right: 10px; }

    .wind-line {
    position: absolute;
    background: linear-gradient(90deg, transparent, rgba(255,255,255,0.4), transparent);
    height: 2px;
    border-radius: 2px;
    animation: blowWind linear infinite;
    }

    @keyframes floatClouds {
    0% { transform: translateX(0); }
    100% { transform: translateX(400px); }
    }
    @keyframes blowWind {
    0% { transform: translateX(-50px); opacity: 0; }
    10% { opacity: 1; }
    90% { opacity: 1; }
    100% { transform: translateX(300px); opacity: 0; }
    }

    /* --- 3. 降雨 (Rain) --- */
    .rain-cloud {
    width: 140px; height: 45px; top: -10px; left: 60px;
    background: #4a5a6a; /* 暗沉乌云 */
    animation: hoverCloud 4s ease-in-out infinite alternate;
    }
    .rain-cloud::before { width: 60px; height: 60px; top: -30px; left: 20px; background: #4a5a6a; }
    .rain-cloud::after { width: 70px; height: 70px; top: -35px; right: 20px; background: #4a5a6a; }

    .raindrop {
    position: absolute;
    background: linear-gradient(to bottom, transparent, rgba(255,255,255,0.8));
    width: 2px;
    border-radius: 2px;
    animation: fallRain linear infinite;
    }
    .splash {
    position: absolute;
    border: 1px solid rgba(255,255,255,0.5);
    border-radius: 50%;
    transform: scale(0);
    animation: splashDrop ease-out infinite;
    }

    @keyframes hoverCloud {
    0% { transform: translateY(0); }
    100% { transform: translateY(10px); }
    }
    @keyframes fallRain {
    0% { transform: translateY(-20px) scaleY(1); opacity: 0; }
    10% { opacity: 1; }
    80% { opacity: 1; }
    100% { transform: translateY(200px) scaleY(1.5); opacity: 0; }
    }
    @keyframes splashDrop {
    0% { transform: scale(0) scaleY(0.3); opacity: 1; }
    100% { transform: scale(1.5) scaleY(0.3); opacity: 0; }
    }

    /* --- 4. 下雪 (Snow) --- */
    .snow-cloud {
    width: 120px; height: 40px; top: 0px; left: 70px;
    background: #d8e2eb;
    animation: hoverCloud 5s ease-in-out infinite alternate-reverse;
    }
    .snow-cloud::before { width: 50px; height: 50px; top: -25px; left: 15px; background: #d8e2eb; }
    .snow-cloud::after { width: 60px; height: 60px; top: -30px; right: 15px; background: #d8e2eb; }

    .snowflake {
    position: absolute;
    background: #fff;
    border-radius: 50%;
    filter: blur(1px);
    animation: fallSnow linear infinite;
    }

    /* 底部积雪 */
    .snow-accumulation {
    position: absolute;
    bottom: 0;
    left: -10%;
    width: 120%;
    height: 20px;
    background: #fff;
    border-radius: 50% 50% 0 0;
    filter: blur(2px);
    opacity: 0.8;
    transform: translateY(10px);
    transition: transform 2s ease;
    }
    .weather-card.snow:hover .snow-accumulation {
    transform: translateY(0);
    }

    @keyframes fallSnow {
    0% { transform: translate(0, -20px) rotate(0deg); opacity: 0; }
    10% { opacity: 0.8; }
    90% { opacity: 0.8; }
    100% { transform: translate(var(--drift), 200px) rotate(360deg); opacity: 0; }
    }

    /* =========================================
    控制面版 (Apple Segmented Control 风格)
    ========================================= */
    .controls {
    display: flex;
    background: rgba(255, 255, 255, 0.05);
    backdrop-filter: blur(20px);
    -webkit-backdrop-filter: blur(20px);
    padding: 6px;
    border-radius: 100px;
    margin-bottom: 40px;
    z-index: 10;
    border: 1px solid rgba(255, 255, 255, 0.1);
    }

    .control-btn {
    background: transparent;
    border: none;
    color: var(--text-sub);
    padding: 10px 24px;
    font-size: 1rem;
    font-weight: 500;
    border-radius: 100px;
    cursor: pointer;
    transition: all 0.3s ease;
    position: relative;
    outline: none;
    }

    .control-btn:hover {
    color: var(--text-main);
    }

    .control-btn.active {
    color: #000;
    background: #fff;
    box-shadow: 0 4px 15px rgba(255, 255, 255, 0.2);
    }

    .header-title {
    text-align: center;
    font-size: 2.5rem;
    font-weight: 700;
    letter-spacing: -1px;
    margin-bottom: 20px;
    z-index: 10;
    }

    </style>


    <!-- 动态背景光 -->
    <div class="ambient-light" id="ambientLight"></div>

    <h1 class="header-title">Weather</h1>

    <!-- 控制面板组 -->
    <div class="controls">
    <button class="control-btn active" onclick="toggleWeather('all', this)">全部</button>
    <button class="control-btn" onclick="toggleWeather('sunny', this)">晴朗</button>
    <button class="control-btn" onclick="toggleWeather('wind', this)">大风</button>
    <button class="control-btn" onclick="toggleWeather('rain', this)">阵雨</button>
    <button class="control-btn" onclick="toggleWeather('snow', this)">降雪</button>
    </div>

    <!-- 卡片容器 -->
    <div class="cards-container" id="cardsContainer">

    <!-- 1. 晴天卡片 -->
    <div class="weather-card sunny" data-type="sunny">
    <div class="animation-viewport">
    <div class="sun-container">
    <div class="sun-rays"></div>
    <div class="sun"></div>
    </div>
    </div>
    <div class="card-content">
    <div class="temperature">28°</div>
    <div class="condition">晴朗无云</div>
    <div class="location">加利福尼亚, 库比蒂诺</div>
    </div>
    </div>

    <!-- 2. 大风卡片 -->
    <div class="weather-card wind" data-type="wind">
    <div class="animation-viewport" id="windViewport">
    <div class="cloud wind-cloud-1"></div>
    <div class="cloud wind-cloud-2"></div>
    <!-- 动态生成的风线 -->
    </div>
    <div class="card-content">
    <div class="temperature">18°</div>
    <div class="condition">疾风交加</div>
    <div class="location">伊利诺伊, 芝加哥</div>
    </div>
    </div>

    <!-- 3. 降雨卡片 -->
    <div class="weather-card rain" data-type="rain">
    <div class="animation-viewport" id="rainViewport">
    <div class="cloud rain-cloud"></div>
    <!-- 动态生成的雨滴和水花 -->
    </div>
    <div class="card-content">
    <div class="temperature">14°</div>
    <div class="condition">中到阵雨</div>
    <div class="location">华盛顿, 西雅图</div>
    </div>
    </div>

    <!-- 4. 下雪卡片 -->
    <div class="weather-card snow" data-type="snow">
    <div class="animation-viewport" id="snowViewport">
    <div class="cloud snow-cloud"></div>
    <div class="snow-accumulation"></div>
    <!-- 动态生成的雪花 -->
    </div>
    <div class="card-content">
    <div class="temperature">-2°</div>
    <div class="condition">漫天飞雪</div>
    <div class="location">北海道, 札幌</div>
    </div>
    </div>

    </div>

    <script>
    /**
    * 动态粒子引擎:利用纯 JS 实例化具有不同物理参数的 DOM 元素,
    * 实现自然随机的动画表现,这是高端 UI 区别于死板 CSS 的核心。
    */

    // 1. 生成大风风线
    function generateWindLines() {
    const viewport = document.getElementById('windViewport');
    for (let i = 0; i < 8; i++) {
    const line = document.createElement('div');
    line.classList.add('wind-line');
    // 随机属性:垂直位置、长度、动画时间、延迟
    const top = Math.random() * 80 + 10;
    const width = Math.random() * 60 + 40;
    const duration = Math.random() * 1.5 + 1;
    const delay = Math.random() * 2;

    line.style.top = `${top}px`;
    line.style.width = `${width}px`;
    line.style.animationDuration = `${duration}s`;
    line.style.animationDelay = `${delay}s`;
    viewport.appendChild(line);
    }
    }

    // 2. 生成降雨雨滴与水坑涟漪
    function generateRain() {
    const viewport = document.getElementById('rainViewport');
    for (let i = 0; i < 30; i++) {
    // 雨滴
    const drop = document.createElement('div');
    drop.classList.add('raindrop');
    const left = Math.random() * 260;
    const duration = Math.random() * 0.5 + 0.5;
    const delay = Math.random() * 2;
    const height = Math.random() * 15 + 10;

    drop.style.left = `${left}px`;
    drop.style.height = `${height}px`;
    drop.style.animationDuration = `${duration}s`;
    drop.style.animationDelay = `${delay}s`;
    viewport.appendChild(drop);

    // 水滴溅起的水花 (低频出现,增强真实感)
    if (i % 3 === 0) {
    const splash = document.createElement('div');
    splash.classList.add('splash');
    splash.style.left = `${left - 5}px`;
    splash.style.bottom = '10px';
    splash.style.width = '10px';
    splash.style.height = '10px';
    splash.style.animationDuration = `${duration}s`;
    splash.style.animationDelay = `${delay + duration * 0.8}s`; // 落在底部后溅起
    viewport.appendChild(splash);
    }
    }
    }

    // 3. 生成漫天飞雪
    function generateSnow() {
    const viewport = document.getElementById('snowViewport');
    for (let i = 0; i < 40; i++) {
    const flake = document.createElement('div');
    flake.classList.add('snowflake');
    const left = Math.random() * 260;
    const size = Math.random() * 4 + 2;
    const duration = Math.random() * 3 + 2;
    const delay = Math.random() * 3;
    // 利用 CSS 变量传递横向飘动偏量
    const drift = (Math.random() - 0.5) * 60;

    flake.style.left = `${left}px`;
    flake.style.width = `${size}px`;
    flake.style.height = `${size}px`;
    flake.style.setProperty('--drift', `${drift}px`);
    flake.style.animationDuration = `${duration}s`;
    flake.style.animationDelay = `${delay}s`;
    viewport.appendChild(flake);
    }
    }

    // 初始化所有天气粒子引擎
    generateWindLines();
    generateRain();
    generateSnow();

    // 交互逻辑:切换不同天气的沉浸状态
    function toggleWeather(type, btnElement) {
    // 1. 更新按钮 UI 状态
    document.querySelectorAll('.control-btn').forEach(btn => btn.classList.remove('active'));
    btnElement.classList.add('active');

    const container = document.getElementById('cardsContainer');
    const ambient = document.getElementById('ambientLight');
    const cards = document.querySelectorAll('.weather-card');

    if (type === 'all') {
    // 恢复默认视图
    container.classList.remove('focus-mode');
    cards.forEach(card => {
    card.classList.remove('active', 'inactive');
    });
    ambient.style.background = 'radial-gradient(circle, var(--glow-all) 0%, transparent 70%)';
    } else {
    // 开启 Focus 沉浸模式:暗化其他卡片,提亮选中卡片
    container.classList.add('focus-mode');
    cards.forEach(card => {
    if (card.getAttribute('data-type') === type) {
    card.classList.remove('inactive');
    card.classList.add('active');
    } else {
    card.classList.remove('active');
    card.classList.add('inactive');
    }
    });

    // 2. 变换背景氛围灯以匹配天气情感色彩
    const glowMap = {
    'sunny': 'var(--glow-sunny)',
    'wind': 'var(--glow-wind)',
    'rain': 'var(--glow-rain)',
    'snow': 'var(--glow-snow)'
    };
    ambient.style.background = `radial-gradient(circle, ${glowMap[type]} 0%, transparent 70%)`;
    }
    }

    // 允许直接点击卡片进行切换
    document.querySelectorAll('.weather-card').forEach(card => {
    card.addEventListener('click', function() {
    const type = this.getAttribute('data-type');
    // 找到对应的按钮
    const targetBtn = Array.from(document.querySelectorAll('.control-btn')).find(btn => {
    // 通过按钮内部文本或绑定逻辑查找,这里简化为索引映射
    const textMap = {'sunny':'晴朗', 'wind':'大风', 'rain':'阵雨', 'snow':'降雪'};
    return btn.innerText === textMap[type];
    });
    if(targetBtn) toggleWeather(type, targetBtn);
    });
    });
    </script>
  • anthoney 05-20 12:19
    11

    对比下grok


  • jamliang 06-06 11:16
    12

    感觉3.7比3.6强不少呀,现在的3.6纯粹用不成,要不是有多模态,狗都不用

* 帖子来源Linux.do
返回