122 lines
6.2 KiB
TypeScript
122 lines
6.2 KiB
TypeScript
|
|
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`,
|
||
|
|
},
|
||
|
|
};
|