118 lines
6.1 KiB
TypeScript
118 lines
6.1 KiB
TypeScript
|
|
import { ChatBgVariants } from './types';
|
||
|
|
import { starDrift } from './animStars.css';
|
||
|
|
|
||
|
|
// animStars ("Star Drift") — a serene deep-space field slowly drifting, with
|
||
|
|
// genuine parallax between a near (brighter, faster) and a far (dim, slower)
|
||
|
|
// star layer, floated on a faint nebula wash and calmed by a center vignette.
|
||
|
|
//
|
||
|
|
// Concept: three tiling star layers at coprime-ish tile sizes (137/191/233 dark,
|
||
|
|
// 149/199/251 light) so their combined repeat is astronomically large and no
|
||
|
|
// seam is ever perceivable. The near layer is crisp and sparse; the far "dust"
|
||
|
|
// layer is dim and dense — the layer that gives depth. Beneath the stars sit a
|
||
|
|
// deep-blue -> violet nebula (two soft ellipses) and a center vignette that keeps
|
||
|
|
// the reading column the calmest, lowest-contrast area of the whole canvas.
|
||
|
|
//
|
||
|
|
// Layer stacking order (CSS paints image #1 on TOP):
|
||
|
|
// 1. near stars — brighter, largest visible drift (tile 137 / 149)
|
||
|
|
// 2. mid stars — softer, medium (tile 191 / 199)
|
||
|
|
// 3. far dust — dimmest, slowest, most-repeated (tile 233 / 251)
|
||
|
|
// 4. center vignette (100% 100%, static)
|
||
|
|
// 5. nebula wash A (100% 100%, static)
|
||
|
|
// 6. nebula wash B (100% 100%, static)
|
||
|
|
//
|
||
|
|
// Animation: `starDrift` (see animStars.css.ts) is a SLOW background-position PAN
|
||
|
|
// that translates each star layer by an exact integer number of its own tiles,
|
||
|
|
// so the loop is seamless AND the three layers drift at different apparent
|
||
|
|
// speeds (parallax). getChatBg adds willChange/contain for the animated case and
|
||
|
|
// STRIPS the `animation` for prefers-reduced-motion — at which point the static
|
||
|
|
// backgroundPosition below (identical to the keyframe's 0% frame) shows as a
|
||
|
|
// fully finished starfield on its own.
|
||
|
|
//
|
||
|
|
// Density is kept modest toward the center by the vignette + conservative dot
|
||
|
|
// sizes, and every star opacity stays low so text over the field always clears
|
||
|
|
// WCAG-AA in both themes.
|
||
|
|
|
||
|
|
export const animStars: ChatBgVariants = {
|
||
|
|
// Dark: cool white + faint blue stars on a near-black cosmos, lifted onto a
|
||
|
|
// deep-blue -> violet nebula with a soft vignette darkening the calm center.
|
||
|
|
dark: {
|
||
|
|
backgroundColor: 'oklch(0.15 0.03 275)',
|
||
|
|
backgroundImage: [
|
||
|
|
// 1. near stars — crisp cool-white, sparse, the "fast" parallax layer
|
||
|
|
'radial-gradient(circle at center, oklch(0.98 0.012 255 / 0.85) 0.6px, transparent 1.5px)',
|
||
|
|
// 2. mid stars — softer, a touch blue, more of them
|
||
|
|
'radial-gradient(circle at center, oklch(0.90 0.03 260 / 0.52) 0.6px, transparent 1.3px)',
|
||
|
|
// 3. far dust — faint blue haze, the slow depth layer (most repeats)
|
||
|
|
'radial-gradient(circle at center, oklch(0.78 0.06 255 / 0.28) 0.5px, transparent 1.1px)',
|
||
|
|
// 4. center vignette — keeps the reading column calmest / lowest-contrast
|
||
|
|
'radial-gradient(ellipse 120% 90% at 50% 42%, transparent 40%, oklch(0.09 0.03 270 / 0.58) 100%)',
|
||
|
|
// 5. nebula wash A — deep violet high-right
|
||
|
|
'radial-gradient(ellipse 140% 120% at 78% 10%, oklch(0.26 0.09 285 / 0.55) 0%, transparent 55%)',
|
||
|
|
// 6. nebula wash B — deep blue low-left
|
||
|
|
'radial-gradient(ellipse 130% 110% at 16% 94%, oklch(0.21 0.07 250 / 0.50) 0%, transparent 58%)',
|
||
|
|
].join(','),
|
||
|
|
backgroundSize: [
|
||
|
|
'137px 137px', // near stars
|
||
|
|
'191px 191px', // mid stars
|
||
|
|
'233px 233px', // far dust
|
||
|
|
'100% 100%', // vignette
|
||
|
|
'100% 100%', // nebula A
|
||
|
|
'100% 100%', // nebula B
|
||
|
|
].join(','),
|
||
|
|
// Must equal starDrift's 0% frame so reduced-motion shows this exact field.
|
||
|
|
backgroundPosition: [
|
||
|
|
'0 0', // near
|
||
|
|
'61px 43px', // mid (offset breaks tile alignment)
|
||
|
|
'113px 97px', // far (offset again)
|
||
|
|
'0 0', // vignette
|
||
|
|
'0 0', // nebula A
|
||
|
|
'0 0', // nebula B
|
||
|
|
].join(','),
|
||
|
|
animation: `${starDrift} 90s linear infinite`,
|
||
|
|
},
|
||
|
|
|
||
|
|
// Light: an airy pre-dawn sky. No literal white-on-white stars — instead very
|
||
|
|
// soft pale sparkles plus the merest cool speckles, floated on a gentle cool
|
||
|
|
// gradient. Reads as elegant atmosphere, never as noise over text.
|
||
|
|
light: {
|
||
|
|
backgroundColor: 'oklch(0.965 0.008 255)',
|
||
|
|
backgroundImage: [
|
||
|
|
// 1. near sparkles — a hair brighter/warmer than the sky
|
||
|
|
'radial-gradient(circle at center, oklch(0.995 0.015 90 / 0.50) 0.6px, transparent 1.5px)',
|
||
|
|
// 2. mid cool speckles — faintest hint of darkness for texture/contrast
|
||
|
|
'radial-gradient(circle at center, oklch(0.60 0.05 260 / 0.15) 0.5px, transparent 1.2px)',
|
||
|
|
// 3. far dust — very soft cool haze, the slow depth layer
|
||
|
|
'radial-gradient(circle at center, oklch(0.70 0.04 255 / 0.11) 0.5px, transparent 1.1px)',
|
||
|
|
// 4. center vignette — subtly brightens the calm reading center
|
||
|
|
'radial-gradient(ellipse 120% 90% at 50% 44%, oklch(1 0 0 / 0.45) 30%, transparent 100%)',
|
||
|
|
// 5. pre-dawn wash A — cool blue high-right
|
||
|
|
'radial-gradient(ellipse 150% 120% at 80% 6%, oklch(0.90 0.05 255 / 0.60) 0%, transparent 60%)',
|
||
|
|
// 6. pre-dawn wash B — warm blush low-left
|
||
|
|
'radial-gradient(ellipse 140% 120% at 14% 96%, oklch(0.93 0.04 40 / 0.42) 0%, transparent 62%)',
|
||
|
|
].join(','),
|
||
|
|
// Same tile sizes as dark (137/191/233). The shared starDrift keyframe pans
|
||
|
|
// each layer by an exact integer multiple of ITS tile (near 2x137, mid 1x191,
|
||
|
|
// far 1x233); reusing these tiles here guarantees the loop wraps seamlessly in
|
||
|
|
// light mode too, since one keyframe drives both themes. Coprime-ish sizes keep
|
||
|
|
// the combined repeat astronomically large so no seam is ever perceivable.
|
||
|
|
backgroundSize: [
|
||
|
|
'137px 137px', // near sparkles
|
||
|
|
'191px 191px', // mid speckles
|
||
|
|
'233px 233px', // far dust
|
||
|
|
'100% 100%', // vignette
|
||
|
|
'100% 100%', // wash A
|
||
|
|
'100% 100%', // wash B
|
||
|
|
].join(','),
|
||
|
|
// Positions mirror the keyframe 0% frame (== reduced-motion static field).
|
||
|
|
backgroundPosition: [
|
||
|
|
'0 0', // near
|
||
|
|
'61px 43px', // mid
|
||
|
|
'113px 97px', // far
|
||
|
|
'0 0', // vignette
|
||
|
|
'0 0', // wash A
|
||
|
|
'0 0', // wash B
|
||
|
|
].join(','),
|
||
|
|
animation: `${starDrift} 100s linear infinite`,
|
||
|
|
},
|
||
|
|
};
|