labs / static /css /scene.css
3v324v23's picture
deploy: unified router + dreamy website (2026-06-16T09:46:52Z)
c1a683f
Raw
History Blame Contribute Delete
6.6 kB
/* =========================================================================
scene.css — the 6-layer parallax cloudscape
.scene is fixed behind content; each .layer translates on scroll + mouse.
Depths (back → front):
0 sky (static gradient, lives on body)
1 far clouds (data-speed slow)
2 moon (fades in on scroll)
3 mid clouds (data-speed medium)
3 whale (owned by whale.js — scroll-mapped)
4 bubbles (data-speed fast, ambient rise)
5 content (normal document flow, z-index 1)
========================================================================= */
.scene {
position: fixed;
inset: 0;
z-index: 0;
pointer-events: none; /* never block content clicks */
overflow: hidden;
transform: translateZ(0);
will-change: transform;
/* NO gradient here anymore. The sky now lives ONLY on <body> (see
base.css). Having two gradients — a fixed .scene one AND a scrolling
body one — caused the strip: they're identical at the top of the
page but diverge as you scroll (fixed stays locked, body shifts to
peach), so any momentary gap in .scene revealed a MISMATCHED body
colour = a visible band. One sky on <body> (which scrolls with the
content and cannot gap) eliminates the mismatch entirely. .scene is
now just a transparent holder for the parallax clouds/moon. */
background: transparent;
}
.layer {
position: absolute;
inset: 0;
will-change: transform; /* promote to its own GPU layer */
}
/* Top layers float ABOVE the content. They are children of <body>, not
.scene, so they escape the scene's stacking context (z-index 0) and
render above <main> (z-index 1). Whale = 2, bubbles = 3 (above all). */
.layer-top {
position: fixed;
inset: 0;
z-index: 2;
pointer-events: none;
overflow: hidden;
will-change: transform;
}
.layer-top.layer-bubbles { z-index: 3; } /* bubbles above the whale */
/* Each layer seeded taller than the viewport so parallax never reveals
an empty edge. The JS reads data-speed and data-mouse to translate. */
.layer-far-clouds { height: 200%; top: -50%; }
.layer-moon { height: 160%; top: -30%; }
.layer-mid-clouds { height: 180%; top: -40%; }
/* --- Far clouds (layer 1) --------------------------------------------- */
.cloud-far {
position: absolute;
width: 340px;
opacity: 0.5;
filter: blur(6px);
color: var(--cloud); /* SVG fill uses currentColor */
}
.cloud-far.c1 { top: 12%; left: 8%; }
.cloud-far.c2 { top: 6%; left: 62%; transform: scale(1.3); }
.cloud-far.c3 { top: 38%; left: 78%; transform: scale(0.85); }
.cloud-far.c4 { top: 24%; left: 40%; transform: scale(1.1); opacity: 0.4; }
/* slow horizontal drift */
@keyframes drift-far {
0%, 100% { transform: translateX(0) scale(var(--s, 1)); }
50% { transform: translateX(40px) scale(var(--s, 1)); }
}
.cloud-far { animation: drift-far 18s var(--ease-gentle) infinite; }
.cloud-far.c2 { --s: 1.3; animation-duration: 24s; }
.cloud-far.c3 { --s: 0.85; animation-duration: 20s; animation-delay: -6s; }
.cloud-far.c4 { --s: 1.1; animation-duration: 22s; animation-delay: -10s; }
/* --- Moon (layer 2) --------------------------------------------------- */
/* A dreamy crescent moon: no glow, a translucent body. The crescent is
carved with a CSS mask for the clean silhouette. */
.moon {
position: absolute;
top: 8%;
right: 9%;
width: 96px;
height: 96px;
border-radius: 50%;
opacity: 0; /* fades in as user scrolls */
transition: opacity 1.2s var(--ease-soft);
}
.moon.is-visible { opacity: 0.9; }
.moon-disc {
position: absolute;
inset: 0;
border-radius: 50%;
/* warm translucent body — a bit less see-through than before */
background: radial-gradient(circle at 34% 30%,
rgba(255, 251, 234, 0.78) 0%,
rgba(255, 233, 77, 0.66) 50%,
rgba(245, 178, 46, 0.58) 100%);
/* crescent flipped top↔bottom: bite now sits upper-left, opening down */
-webkit-mask: radial-gradient(circle 50px at 30% 28%, transparent 96%, #000 100%);
mask: radial-gradient(circle 50px at 30% 28%, transparent 96%, #000 100%);
}
/* --- Mid clouds (layer 3) --------------------------------------------- */
.cloud-mid {
position: absolute;
width: 260px;
opacity: 0.78;
filter: blur(2px);
color: var(--cloud);
}
.cloud-mid.m1 { top: 22%; left: -10%; animation: drift-mid 38s linear infinite; }
.cloud-mid.m2 { top: 58%; left: 70%; animation: drift-mid 52s linear infinite; animation-delay: -18s; }
.cloud-mid.m3 { top: 44%; left: 30%; animation: drift-mid 46s linear infinite; animation-delay: -30s; transform: scale(0.7); }
@keyframes drift-mid {
from { transform: translateX(-30vw); }
to { transform: translateX(130vw); }
}
/* --- Bubbles (layer 4) ------------------------------------------------ */
.bubbles {
/* container; individual bubbles injected by main.js */
}
.bubble {
position: absolute;
bottom: -110px; /* start further down (bigger now) */
border-radius: 50%;
/* glass body: mostly transparent, faint wash toward the light source */
background: radial-gradient(circle at 30% 28%,
rgba(255, 255, 255, 0.5) 0%,
rgba(255, 255, 255, 0.12) 42%,
rgba(255, 143, 171, 0.06) 72%,
rgba(255, 143, 171, 0.02) 100%);
/* the bright soap-film rim + inner spherical depth */
border: 1px solid rgba(255, 255, 255, 0.42);
box-shadow:
inset -5px -7px 12px rgba(157, 170, 242, 0.22), /* bottom-inner shade */
inset 4px 5px 10px rgba(255, 255, 255, 0.45), /* top-inner glow */
0 2px 10px rgba(157, 170, 242, 0.1); /* soft contact shadow*/
animation: rise linear infinite;
will-change: transform, opacity;
}
/* the crisp glassy highlight on each bubble */
.bubble::after {
content: "";
position: absolute;
top: 15%;
left: 19%;
width: 32%;
height: 32%;
border-radius: 50%;
background: radial-gradient(circle, rgba(255,255,255,0.95) 0%, rgba(255,255,255,0.25) 55%, transparent 75%);
}
@keyframes rise {
0% { transform: translate(0, 0) rotate(0deg); opacity: 0; }
8% { opacity: 0.75; }
92% { opacity: 0.75; }
100% { transform: translate(var(--sway, 24px), -115vh) rotate(var(--spin, 40deg)); opacity: 0; }
}
/* --- Content sits above the whole scene --- */
main {
position: relative;
z-index: 1;
}