Files
cinny/src/app/features/lotus/backgrounds/animPulse.ts
T

122 lines
6.2 KiB
TypeScript
Raw Normal View History

import { ChatBgVariants } from './types';
import { gridPulse } from './animPulse.css';
// Grid Pulse (anim-pulse) — a refined sci-fi grid with a slow energy pulse.
//
// Concept: a crisp thin tech grid over which a single soft radial glow drifts
// diagonally, so the lines it crosses seem to charge and settle — a hypnotic
// travelling pulse rather than a strobing brightness flash. Three ingredients,
// exactly per the quality bar:
// 1. a crisp thin grid — two hairline linear layers (V + H) at a 120px module
// plus a fainter 60px sub-grid, so the mesh reads as fine machined lattice;
// 2. a soft bloom layer — one wide, very-low-opacity radial that TRAVELS across
// the grid (the pulse), authored to tile so the loop is seamless;
// 3. a radial vignette — keeps the reading centre calm (dark theme darkens it,
// light theme brightens it) so text always sits on the quietest region.
//
// Animation approach & why it's subtle: only ONE layer moves — the bloom — and
// it moves by pure background-position (the property getChatBg hints via
// willChange). No line ever shifts, no global brightness flicker, so text never
// wobbles. The glow itself is barely-there (opacity well under the neon bloom),
// so the "pulse" is felt as a slow wash of light passing behind the words. 22s
// per cycle makes it meditative, not busy.
//
// Seamless loop: the bloom's backgroundSize is 480px — an exact 4x multiple of
// the 120px grid module (and 8x of the 60px sub-grid). The keyframe pans it by
// exactly one 480px tile on both axes, so it wraps onto an identical tile with
// no visible seam (see animPulse.css.ts).
//
// Reduced-motion fallback: getChatBg strips `animation`, leaving the bloom at
// its authored static position — parked slightly above-centre so the finished
// frame reads as a deliberately-lit, gently glowing grid rather than a frozen
// mid-sweep. The grid, wash and vignette are all static regardless, so the
// still image is already a complete, premium background.
//
// Dark vs light: dark is a cool cyan lattice glowing on deep blue-black with a
// dim bloom and a centre-darkening vignette. Light is a soft slate-blue lattice
// on pale cool-white with a whisper-faint bloom and a centre-BRIGHTENING
// vignette, so the reading column lifts toward white. Both keep line + glow
// opacity low for WCAG-AA legibility in either app theme.
export const animPulse: ChatBgVariants = {
// Dark: cyan grid on deep blue-black, a dim energy bloom sweeping through.
dark: {
backgroundColor: 'oklch(0.16 0.03 240)',
backgroundImage: [
// 0. grid core — vertical hairlines (cool cyan)
'linear-gradient(90deg, oklch(0.75 0.11 200 / 0.14) 0 1px, transparent 1px)',
// 1. grid core — horizontal hairlines
'linear-gradient(0deg, oklch(0.75 0.11 200 / 0.14) 0 1px, transparent 1px)',
// 2. fine sub-grid — vertical (fainter, half module)
'linear-gradient(90deg, oklch(0.75 0.11 200 / 0.05) 0 1px, transparent 1px)',
// 3. fine sub-grid — horizontal
'linear-gradient(0deg, oklch(0.75 0.11 200 / 0.05) 0 1px, transparent 1px)',
// 4. TRAVELLING BLOOM — the pulse: a wide soft cyan glow that drifts
'radial-gradient(circle at 50% 50%, oklch(0.8 0.12 200 / 0.16) 0%, oklch(0.75 0.11 205 / 0.06) 26%, transparent 55%)',
// 5. base wash — a faint steady centre glow so the grid never looks flat
'radial-gradient(ellipse 120% 100% at 50% 42%, oklch(0.42 0.07 235 / 0.28) 0%, transparent 62%)',
// 6. vignette — darken the edges, keep the reading centre calm & dark
'radial-gradient(ellipse 130% 100% at 50% 46%, transparent 34%, oklch(0.11 0.02 245 / 0.72) 100%)',
].join(','),
backgroundSize: [
'120px 120px', // grid core V
'120px 120px', // grid core H
'60px 60px', // sub-grid V (exact 1/2 divisor — re-registers)
'60px 60px', // sub-grid H
'480px 480px', // bloom (4x module — pans one whole tile, seamless)
'100% 100%', // base wash
'100% 100%', // vignette
].join(','),
backgroundPosition: [
'0 0', // grid core V
'0 0', // grid core H
'0 0', // sub-grid V
'0 0', // sub-grid H
'120px 40px', // bloom static (reduced-motion) — parked above-centre
'0 0', // base wash
'0 0', // vignette
].join(','),
animation: `${gridPulse} 22s ease-in-out infinite`,
},
// Light: soft slate-blue grid on pale cool-white, a gentle luminance breathe.
light: {
backgroundColor: 'oklch(0.975 0.006 235)',
backgroundImage: [
// 0. grid core — vertical hairlines (soft slate-blue)
'linear-gradient(90deg, oklch(0.55 0.08 245 / 0.15) 0 1px, transparent 1px)',
// 1. grid core — horizontal hairlines
'linear-gradient(0deg, oklch(0.55 0.08 245 / 0.15) 0 1px, transparent 1px)',
// 2. fine sub-grid — vertical (fainter, half module)
'linear-gradient(90deg, oklch(0.55 0.08 245 / 0.055) 0 1px, transparent 1px)',
// 3. fine sub-grid — horizontal
'linear-gradient(0deg, oklch(0.55 0.08 245 / 0.055) 0 1px, transparent 1px)',
// 4. TRAVELLING BLOOM — a whisper of slate-blue light drifting through
'radial-gradient(circle at 50% 50%, oklch(0.6 0.09 240 / 0.09) 0%, oklch(0.62 0.08 245 / 0.035) 26%, transparent 55%)',
// 5. base wash — the faintest cool tint so the grid sits on soft light
'radial-gradient(ellipse 120% 100% at 50% 42%, oklch(0.86 0.03 235 / 0.30) 0%, transparent 62%)',
// 6. vignette — brighten the calm reading centre toward white for legibility
'radial-gradient(ellipse 130% 100% at 50% 46%, oklch(1 0 0 / 0.5) 30%, transparent 100%)',
].join(','),
backgroundSize: [
'120px 120px', // grid core V
'120px 120px', // grid core H
'60px 60px', // sub-grid V
'60px 60px', // sub-grid H
'480px 480px', // bloom (4x module — seamless one-tile pan)
'100% 100%', // base wash
'100% 100%', // vignette
].join(','),
backgroundPosition: [
'0 0', // grid core V
'0 0', // grid core H
'0 0', // sub-grid V
'0 0', // sub-grid H
'120px 40px', // bloom static (reduced-motion) — parked above-centre
'0 0', // base wash
'0 0', // vignette
].join(','),
animation: `${gridPulse} 22s ease-in-out infinite`,
},
};