Section colour blend
The page background melts smoothly from one section’s colour into the next.
The sun arcs across a pinned sky and sets, the colours run from dawn to dusk, and stars come out.
One progress value does everything. It positions the sun on a half-circle with sin and cos, picks a pair of sky colours by interpolating between keyframes (dawn, noon, sunset, night), and fades in a layer of stars in the last third.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Day-to-night sky</title>
<style>
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; }
body { font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; -webkit-font-smoothing: antialiased; }
body { background: #000; color: #fff; }
.track { height: 400vh; }
.sky { position: sticky; top: 0; height: 100vh; overflow: hidden; }
.stars { position: absolute; inset: 0; opacity: 0;
background-image: radial-gradient(1.5px 1.5px at 10% 20%, #fff 50%, transparent), radial-gradient(1px 1px at 30% 10%, #fff 50%, transparent), radial-gradient(1.5px 1.5px at 55% 25%, #fff 50%, transparent), radial-gradient(1px 1px at 70% 8%, #fff 50%, transparent), radial-gradient(2px 2px at 85% 30%, #fff 50%, transparent), radial-gradient(1px 1px at 45% 40%, #fff 50%, transparent), radial-gradient(1.5px 1.5px at 20% 45%, #fff 50%, transparent); }
.sun { position: absolute; width: 16vmin; height: 16vmin; margin: -8vmin 0 0 -8vmin; border-radius: 50%; background: #fff4c2; box-shadow: 0 0 80px 20px rgba(255,220,120,.6); }
.hills { position: absolute; left: -5%; right: -5%; bottom: 0; height: 30vh; background: #1b4332; border-radius: 50% 50% 0 0 / 100% 100% 0 0; }
.clock { position: absolute; top: 20px; left: 24px; margin: 0; font: 700 1.4rem ui-monospace, monospace; }
</style>
</head>
<body>
<section class="track">
<div class="sky" id="sky">
<div class="stars" id="stars"></div>
<div class="sun" id="sun"></div>
<div class="hills"></div>
<p class="clock" id="clock">06:00</p>
</div>
</section>
<script>
const keys = [[0, '#ffb085', '#fde2c3'], [0.3, '#4ea8de', '#bde0fe'], [0.62, '#f07167', '#fed9b7'], [0.78, '#3c096c', '#ff9e00'], [1, '#03045e', '#0b090a']];
const hex = h => [1, 3, 5].map(i => parseInt(h.slice(i, i + 2), 16));
const mix = (a, b, t) => 'rgb(' + hex(a).map((v, i) => Math.round(v + (hex(b)[i] - v) * t)).join(',') + ')';
const sky = document.getElementById('sky'), sun = document.getElementById('sun'), stars = document.getElementById('stars'), clock = document.getElementById('clock');
const track = document.querySelector('.track');
function update() {
const r = track.getBoundingClientRect();
const p = Math.min(1, Math.max(0, -r.top / (r.height - innerHeight)));
let k = 0;
while (k < keys.length - 2 && p > keys[k + 1][0]) k++;
const [a0, top0, bot0] = keys[k], [a1, top1, bot1] = keys[k + 1];
const t = (p - a0) / (a1 - a0);
sky.style.background = 'linear-gradient(' + mix(top0, top1, t) + ',' + mix(bot0, bot1, t) + ')';
const ang = Math.PI * Math.min(1, p / 0.8);
sun.style.left = (50 - Math.cos(ang) * 40) + '%';
sun.style.top = (75 - Math.sin(ang) * 55) + '%';
stars.style.opacity = Math.max(0, (p - 0.65) / 0.35).toFixed(2);
const mins = Math.round(360 + p * 900);
clock.textContent = String(Math.floor(mins / 60) % 24).padStart(2, '0') + ':' + String(mins % 60).padStart(2, '0');
}
addEventListener('scroll', update, { passive: true });
update();
</script>
</body>
</html>