/* ========================================================================= 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 (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 (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 , not .scene, so they escape the scene's stacking context (z-index 0) and render above
(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; }