Glassy weather widget with sun/cloud/rain morph and temperature count.
Edit these placeholders inline or pass --var NAME=value to hyperframes render.
| Variable | Type | Default | Range |
|---|---|---|---|
{{$CITY}}City | text | San Francisco | — |
{{$TEMP}}Temperature | number | 68 | -40 → 130 step 1 |
{{$SKY_TOP}}Sky top | color | #3a8fd6 | — |
{{$SKY_BOT}}Sky bottom | color | #9bc7f0 | — |
<!doctype html>
<html data-duration="8" data-aspect="16:9"><head><style>
:root {
--cream:#f6f5f1; --cream-2:#efece4; --ink:#0a0a0a; --mute:#6b6862;
--line:#e3dfd3; --signal:#ff3b1f; --signal-2:#ff6a4a; --frame:#ffb800;
--green:#1f8a5b; --blue:#2b66ff;
}
* { box-sizing: border-box; }
body { margin:0; }
body { background: linear-gradient(160deg, {{$SKY_TOP}}, {{$SKY_BOT}});
color: white; height: 100vh; overflow: hidden;
display: grid; place-items: center; font-family: ui-sans-serif, system-ui; }
.card { width: 760px; padding: 56px 64px; border-radius: 36px;
background: rgba(255,255,255,.10); backdrop-filter: blur(20px);
border: 1px solid rgba(255,255,255,.18);
box-shadow: 0 40px 100px -40px rgba(0,0,0,.4); }
.loc { font-size: 14px; letter-spacing: .3em; text-transform: uppercase; opacity: .7; }
.row { display: flex; align-items: center; justify-content: space-between; margin-top: 12px; }
.t { font-size: 200px; font-weight: 300; letter-spacing: -.05em; line-height: .9;
font-variant-numeric: tabular-nums; }
.t sup { font-size: 56px; font-weight: 400; vertical-align: top; margin-left: 6px; opacity: .8; }
.icon { width: 200px; height: 200px; position: relative; }
.sun { position: absolute; inset: 30px; border-radius: 50%; background: #ffd56a;
box-shadow: 0 0 80px 10px rgba(255,213,106,.55); transition: transform .4s; }
.cloud { position: absolute; left: 10px; top: 80px; width: 180px; height: 60px;
background: #fff; border-radius: 30px; opacity: 0;
box-shadow: 30px 18px 0 -6px #fff, -30px 18px 0 -10px #fff; transition: opacity .4s, transform .4s; }
.rain i { position: absolute; top: 150px; width: 3px; height: 14px; background: #cfe8ff;
border-radius: 2px; opacity: 0; }
.cond { font-size: 28px; font-weight: 500; opacity: .92; margin-top: 18px; }
.meta { display: flex; gap: 28px; margin-top: 24px; font-size: 16px; opacity: .8; }
.meta span b { font-weight: 700; }
</style></head><body>
<div class="card">
<div class="loc">{{$CITY}}</div>
<div class="row">
<div>
<div class="t" id="tp">{{$TEMP}}<sup>°</sup></div>
<div class="cond" id="cd">Sunny</div>
<div class="meta">
<span>wind <b id="wn">6</b> mph</span>
<span>humidity <b id="hu">38</b>%</span>
</div>
</div>
<div class="icon">
<div class="sun" id="sun"></div>
<div class="cloud" id="cld"></div>
<div class="rain">
<i id="r1" style="left:50px"></i><i id="r2" style="left:90px"></i>
<i id="r3" style="left:130px"></i><i id="r4" style="left:70px"></i>
<i id="r5" style="left:110px"></i>
</div>
</div>
</div>
</div>
<script>
var sun = document.getElementById('sun');
var cld = document.getElementById('cld');
var cd = document.getElementById('cd');
var tp = document.getElementById('tp');
var TARGET = parseFloat('{{$TEMP}}');
var drops = [document.getElementById('r1'),document.getElementById('r2'),document.getElementById('r3'),document.getElementById('r4'),document.getElementById('r5')];
function render(t){
// Phase 1 (0-3s): sunny. Phase 2 (3-6s): cloudy. Phase 3 (6-8s): rainy.
var cloudy = Math.max(0, Math.min(1, (t - 2.4) / 0.8));
var rainy = Math.max(0, Math.min(1, (t - 5.4) / 0.8));
sun.style.transform = 'scale(' + (1 - cloudy * 0.4).toFixed(2) + ') translateX(' + (-cloudy * 40) + 'px)';
cld.style.opacity = cloudy.toFixed(2);
cld.style.transform = 'translateX(' + ((1 - cloudy) * -40) + 'px)';
cd.textContent = rainy > 0.5 ? 'Light rain' : (cloudy > 0.5 ? 'Cloudy' : 'Sunny');
drops.forEach(function(d, i){
var phase = (t * 2 + i * 0.18) % 1;
d.style.opacity = (rainy * (1 - phase)).toFixed(2);
d.style.transform = 'translateY(' + (phase * 40).toFixed(1) + 'px)';
});
var u = Math.min(1, t / 1.4);
var e = 1 - Math.pow(1 - u, 3);
tp.firstChild.nodeValue = Math.round(TARGET * e);
}
addEventListener('hf-seek', function(e){ render(e.detail.time); });
render(0);
</script>
</body></html>