feat(chat-bg): redesign 19 chat backgrounds as modular per-pattern files
Same treatment as the seasonal themes: split the 502-line chatBackground.ts Record into one premium module per background under lotus/backgrounds/ (each exposes a tuned dark + light ChatBgVariants), one Opus agent per background against a shared brief. chatBackground.ts now assembles DARK/LIGHT from the modules; getChatBg is unchanged. Carbon + Aurora are kept inline as-is (user favorites); none stays the empty layer. Every redesign: layered oklch palettes, seamless tiling with worked-out tile math (integer-multiple periods; edge-wrapping inline-SVG data-URIs for circuit/hexgrid/waves/herringbone/chevron/tactical), independently-tuned dark+light (not a recolor), and low "felt-not-read" opacity so chat text stays WCAG-AA legible. The 5 animated backgrounds (rain, star drift, grid pulse, aurora flow, fireflies) each colocate a vanilla-extract keyframe .css.ts, animate only background-position for a jump-free loop, and — since getChatBg strips animation for reduced-motion — render a finished static frame too. Redesigned: blueprint, stars, topographic, herringbone, crosshatch, chevron, polka, triangles, plaid, tactical, circuit, hexgrid, waves, neon, anim-rain, anim-stars, anim-pulse, anim-aurora, anim-fireflies. Gates: tsc clean, ESLint clean, Prettier clean, build OK, 551 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,123 @@
|
||||
import { ChatBgVariants } from './types';
|
||||
import { rainFall } from './animRain.css';
|
||||
|
||||
// anim-rain — "Digital Rain" — a premium take on the Matrix code-rain motif.
|
||||
//
|
||||
// Concept: sparse vertical columns of falling glyph-streaks. Each streak is a
|
||||
// soft vertical gradient that fades from a brighter LEADING glyph (the drop's
|
||||
// head) up into a dim trailing tail, punctuated by a scatter of faint monospace
|
||||
// glyph marks so it reads as CODE rather than plain stripes. It floats over a
|
||||
// near-black base carrying a subtle green phosphor cast and a gentle vignette.
|
||||
// Columns are deliberately sparse (only a handful across the 260px-wide tile)
|
||||
// so the reading area breathes and text always wins the contrast fight.
|
||||
//
|
||||
// SEAMLESS TILING + PAN — the streak SVG tile is 260×200. Its content is
|
||||
// authored to wrap top↔bottom: each streak's gradient and glyphs are placed so
|
||||
// the tile is vertically continuous, and the animation (see animRain.css.ts)
|
||||
// pans this first layer down by EXACTLY one tile height (200px) per cycle, so
|
||||
// the "fall" loops with no seam. The base / vignette layers are 100% 100% and
|
||||
// stay fixed (the keyframe holds them at '0 0').
|
||||
//
|
||||
// ANIMATION-STRIP SAFETY — getChatBg removes `animation` for reduced-motion /
|
||||
// pause-animations users, so the non-animation properties below already read as
|
||||
// a finished, gorgeous STATIC rain: a frozen frame of streaks over the base.
|
||||
//
|
||||
// CSP / Tauri-safe: inline SVG via encodeURIComponent (NOT base64). oklch used
|
||||
// throughout; alphas kept low so both themes stay WCAG-AA-friendly for text.
|
||||
|
||||
// One vertical streak-column, colour-parameterised. Placed at x within a
|
||||
// 260-wide tile. `head` is the bright leading-glyph colour, `tail` the dim
|
||||
// trailing colour, `glyph` the colour of the riding monospace glyph ticks.
|
||||
const streak = (
|
||||
x: number,
|
||||
headY: number, // y of the leading glyph (drop head)
|
||||
len: number, // trailing tail length upward
|
||||
head: string,
|
||||
tail: string,
|
||||
glyph: string,
|
||||
): string => {
|
||||
const topY = headY - len;
|
||||
const id = `g${x}_${headY}`; // unique even when two columns share an x
|
||||
// Vertical fade: transparent at the tail top → tail colour → bright head.
|
||||
const grad = `
|
||||
<linearGradient id='${id}' x1='0' y1='${topY}' x2='0' y2='${headY}' gradientUnits='userSpaceOnUse'>
|
||||
<stop offset='0' stop-color='${tail}' stop-opacity='0'/>
|
||||
<stop offset='0.55' stop-color='${tail}'/>
|
||||
<stop offset='1' stop-color='${head}'/>
|
||||
</linearGradient>`;
|
||||
// The streak body is a soft, slightly-blurred vertical bar.
|
||||
const bar = `<rect x='${x - 3}' y='${topY}' width='6' height='${len}' rx='3' fill='url(#${id})'/>`;
|
||||
// A few monospace glyph ticks riding the column (short horizontal dashes).
|
||||
const ticks = [0.22, 0.45, 0.68, 0.86]
|
||||
.map((f, i) => {
|
||||
const gy = Math.round(topY + len * f);
|
||||
const gw = i % 2 === 0 ? 5 : 3;
|
||||
const op = i === 3 ? '0.9' : '0.5';
|
||||
return `<rect x='${x - gw / 2}' y='${gy}' width='${gw}' height='1.4' rx='0.7' fill='${glyph}' fill-opacity='${op}'/>`;
|
||||
})
|
||||
.join('');
|
||||
// The leading glyph: a brighter small square cap at the head.
|
||||
const cap = `<rect x='${x - 2.5}' y='${headY - 3}' width='5' height='5' rx='1' fill='${head}'/>`;
|
||||
return grad + bar + ticks + cap;
|
||||
};
|
||||
|
||||
// Full 260×200 tile. Columns are wrapped vertically: a column whose head sits
|
||||
// low in the tile has its tail running off the top, and a companion column
|
||||
// re-enters that space, so panning by one tile height reads as continuous fall.
|
||||
const tile = (head: string, tail: string, glyph: string): string => {
|
||||
const cols = [
|
||||
streak(24, 150, 140, head, tail, glyph),
|
||||
streak(78, 60, 120, head, tail, glyph),
|
||||
streak(122, 196, 160, head, tail, glyph), // head near bottom → tail wraps up
|
||||
streak(122, 40, 160, head, tail, glyph), // partner near top completes the wrap
|
||||
streak(178, 110, 100, head, tail, glyph),
|
||||
streak(232, 176, 130, head, tail, glyph),
|
||||
].join('');
|
||||
const svg = `<svg xmlns='http://www.w3.org/2000/svg' width='260' height='200' viewBox='0 0 260 200'><defs></defs>${cols}</svg>`;
|
||||
return `url("data:image/svg+xml,${encodeURIComponent(svg)}")`;
|
||||
};
|
||||
|
||||
export const animRain: ChatBgVariants = {
|
||||
// Dark: phosphor-green streaks on deep near-black with a faint green cast.
|
||||
dark: {
|
||||
backgroundColor: 'oklch(0.16 0.02 150)',
|
||||
backgroundImage: [
|
||||
// 1) the falling streak columns (this is the panned layer)
|
||||
tile(
|
||||
'oklch(0.75 0.14 150 / 0.5)', // head — bright phosphor glyph
|
||||
'oklch(0.68 0.12 150 / 0.28)', // tail — dim phosphor
|
||||
'oklch(0.82 0.1 150 / 0.5)', // glyph ticks — brightest
|
||||
),
|
||||
// 2) soft top-down phosphor haze so the rain has atmosphere
|
||||
'linear-gradient(180deg, oklch(0.24 0.04 150 / 0.55) 0%, transparent 40%)',
|
||||
// 3) subtle green cast pooling toward the bottom
|
||||
'radial-gradient(120% 90% at 50% 100%, oklch(0.28 0.05 150 / 0.45) 0%, transparent 60%)',
|
||||
// 4) vignette — quiet the corners so the reading column stays clean
|
||||
'radial-gradient(140% 140% at 50% 45%, transparent 60%, oklch(0.1 0.02 150 / 0.6) 100%)',
|
||||
].join(','),
|
||||
backgroundSize: ['260px 200px', '100% 100%', '100% 100%', '100% 100%'].join(','),
|
||||
backgroundPosition: ['0 0', '0 0', '0 0', '0 0'].join(','),
|
||||
animation: `${rainFall} 12s linear infinite`,
|
||||
},
|
||||
|
||||
// Light: soft teal-grey streaks on a pale cool base — elegant, never neon.
|
||||
light: {
|
||||
backgroundColor: 'oklch(0.97 0.008 165)',
|
||||
backgroundImage: [
|
||||
tile(
|
||||
'oklch(0.55 0.07 165 / 0.4)', // head — soft teal-grey drop
|
||||
'oklch(0.62 0.05 165 / 0.22)', // tail — faint teal-grey
|
||||
'oklch(0.5 0.06 165 / 0.42)', // glyph ticks
|
||||
),
|
||||
// gentle cool wash from the top
|
||||
'linear-gradient(180deg, oklch(0.94 0.015 175 / 0.6) 0%, transparent 42%)',
|
||||
// faint teal pooling at the bottom edge
|
||||
'radial-gradient(120% 90% at 50% 100%, oklch(0.9 0.02 170 / 0.5) 0%, transparent 60%)',
|
||||
// soft vignette in cool grey
|
||||
'radial-gradient(140% 140% at 50% 45%, transparent 62%, oklch(0.88 0.02 165 / 0.5) 100%)',
|
||||
].join(','),
|
||||
backgroundSize: ['260px 200px', '100% 100%', '100% 100%', '100% 100%'].join(','),
|
||||
backgroundPosition: ['0 0', '0 0', '0 0', '0 0'].join(','),
|
||||
animation: `${rainFall} 12s linear infinite`,
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user