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`, }, };