From 4bea48959e3cd1f0b9f9eed87be22be304f438fb Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 12 Sep 2026 01:29:33 -0400 Subject: [PATCH] fix(seasonal): keep Arcade + Deep Space overlays out of the chat text MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Arcade: - perspective grid is masked through the central timeline column so it frames the chat instead of striping messages; lower opacity, thinner non-scaling strokes (also fixes the grid vanishing in the 76px swatch) - neon horizon line is broken through the middle so it never underlines a message; softer sun glow - "1UP" score + "INSERT COIN" moved from top-left/bottom-centre (space bar / composer) to a single bottom-right HUD stack; font-size clamps to 0 via cqw inside the settings swatch so no more clipped glyphs - scanlines: 4px pitch at lower opacity (light theme was fully striped); ambient wash slightly lighter Deep Space: - starfield 28 โ†’ 62 stars across the two parallax layers - base violet wash slightly lighter for light themes SeasonalPreview wrapper is now a size container (container-type) so overlays can scale/hide fixed-px details with cqw. LOTUS_FEATURES.md theme table now describes what both overlays actually render. Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01PPmy3tPq869XDW4njjVaKA --- LOTUS_FEATURES.md | 26 +-- .../components/seasonal/SeasonalEffect.tsx | 10 +- .../components/seasonal/themes/Arcade.css.ts | 12 +- src/app/components/seasonal/themes/Arcade.tsx | 172 +++++++++++------- .../components/seasonal/themes/DeepSpace.tsx | 6 +- 5 files changed, 134 insertions(+), 92 deletions(-) diff --git a/LOTUS_FEATURES.md b/LOTUS_FEATURES.md index f3c4253e2..6411e424d 100644 --- a/LOTUS_FEATURES.md +++ b/LOTUS_FEATURES.md @@ -175,19 +175,19 @@ Decorative CSS-only overlays that activate automatically on holidays and events. ### Themes -| Theme | Window | Effect | -| -------------------- | ------------- | -------------------------------------------------------------------------------------------------- | -| ๐ŸŽ† New Year | Dec 31โ€“Jan 2 | Radial firework bursts in gold, red, cyan, purple; gold shimmer sweep | -| ๐Ÿฎ Lunar New Year | Jan 22โ€“Feb 5 | Floating paper lanterns bobbing; silk texture; gold shimmer accent | -| ๐Ÿ’– Valentine's Day | Feb 10โ€“15 | โ™ฅ hearts floating upward; soft pink ambient glow | -| ๐Ÿ€ St. Patrick's Day | Mar 15โ€“18 | โ˜˜ clovers drifting down; gold metallic shimmer top border | -| ๐Ÿƒ April Fool's | Apr 1 | Glitch overlay: RGB channel separation, hue-rotate spikes, scanline sweep, "SIGNAL LOST" watermark | -| ๐ŸŒฑ Earth Day | Apr 20โ€“23 | ๐ŸŒฟ๐Ÿƒ leaf emoji drift; sage green ambient tint; vine accent on left edge | -| ๐Ÿ‚ Autumn | Sep 21โ€“Oct 31 | Warm orange/amber leaf shapes rotating and falling | -| ๐Ÿ‘พ Arcade Day | Sep 12 | CRT scanlines; blinking pixel corner decorations; "INSERT COIN" prompt | -| ๐Ÿš€ Deep Space Week | Oct 4โ€“10 | Warp-speed star streaks radiating from screen centre; nebula purple/blue ambient | -| ๐ŸŽƒ Halloween | Oct 15โ€“Nov 1 | Purple and orange glowing particles; SVG spider web in top-left corner; dark purple tint | -| โ„๏ธ Christmas | Dec 10โ€“Jan 2 | White dot snowfall in multiple layers at varied speeds | +| Theme | Window | Effect | +| -------------------- | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| ๐ŸŽ† New Year | Dec 31โ€“Jan 2 | Radial firework bursts in gold, red, cyan, purple; gold shimmer sweep | +| ๐Ÿฎ Lunar New Year | Jan 22โ€“Feb 5 | Floating paper lanterns bobbing; silk texture; gold shimmer accent | +| ๐Ÿ’– Valentine's Day | Feb 10โ€“15 | โ™ฅ hearts floating upward; soft pink ambient glow | +| ๐Ÿ€ St. Patrick's Day | Mar 15โ€“18 | โ˜˜ clovers drifting down; gold metallic shimmer top border | +| ๐Ÿƒ April Fool's | Apr 1 | Glitch overlay: RGB channel separation, hue-rotate spikes, scanline sweep, "SIGNAL LOST" watermark | +| ๐ŸŒฑ Earth Day | Apr 20โ€“23 | ๐ŸŒฟ๐Ÿƒ leaf emoji drift; sage green ambient tint; vine accent on left edge | +| ๐Ÿ‚ Autumn | Sep 21โ€“Oct 31 | Warm orange/amber leaf shapes rotating and falling | +| ๐Ÿ‘พ Arcade Day | Sep 12 | Synthwave CRT: neon perspective grid framing the timeline (faded through the chat column), broken horizon line, rolling scanlines, pixel sparkles, bottom-right "1UP / INSERT COIN" HUD | +| ๐Ÿš€ Deep Space Week | Oct 4โ€“10 | Violet void with drifting magenta/cyan nebula clouds, two-depth parallax starfield (~60 twinkling stars + 6 hero gleams), slow galaxy spiral, occasional comet streaks | +| ๐ŸŽƒ Halloween | Oct 15โ€“Nov 1 | Purple and orange glowing particles; SVG spider web in top-left corner; dark purple tint | +| โ„๏ธ Christmas | Dec 10โ€“Jan 2 | White dot snowfall in multiple layers at varied speeds | ### Implementation diff --git a/src/app/components/seasonal/SeasonalEffect.tsx b/src/app/components/seasonal/SeasonalEffect.tsx index 82b4f2e34..caf40d9ff 100644 --- a/src/app/components/seasonal/SeasonalEffect.tsx +++ b/src/app/components/seasonal/SeasonalEffect.tsx @@ -84,7 +84,15 @@ export function SeasonalPreview({ theme }: { theme: SeasonTheme }) { return ( diff --git a/src/app/components/seasonal/themes/Arcade.css.ts b/src/app/components/seasonal/themes/Arcade.css.ts index 83f77a572..570cdd5e1 100644 --- a/src/app/components/seasonal/themes/Arcade.css.ts +++ b/src/app/components/seasonal/themes/Arcade.css.ts @@ -102,12 +102,12 @@ export const animSparkleTwinkle = keyframes({ * Opacity + a hair of scale for a CRT bloom feel. */ export const animCoinBlink = keyframes({ - '0%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' }, - '6%': { opacity: '1', transform: 'translateX(-50%) scale(1.015)' }, - '12%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' }, - '49%': { opacity: '0.85', transform: 'translateX(-50%) scale(1)' }, - '50%': { opacity: '0', transform: 'translateX(-50%) scale(1)' }, - '100%': { opacity: '0', transform: 'translateX(-50%) scale(1)' }, + '0%': { opacity: '0.85', transform: 'scale(1)' }, + '6%': { opacity: '1', transform: 'scale(1.015)' }, + '12%': { opacity: '0.85', transform: 'scale(1)' }, + '49%': { opacity: '0.85', transform: 'scale(1)' }, + '50%': { opacity: '0', transform: 'scale(1)' }, + '100%': { opacity: '0', transform: 'scale(1)' }, }); /** diff --git a/src/app/components/seasonal/themes/Arcade.tsx b/src/app/components/seasonal/themes/Arcade.tsx index 95f4471dc..c5c1d0652 100644 --- a/src/app/components/seasonal/themes/Arcade.tsx +++ b/src/app/components/seasonal/themes/Arcade.tsx @@ -46,6 +46,10 @@ const NEON_CYAN = 'oklch(0.80 0.15 200)'; const GRID_PURPLE = 'oklch(0.45 0.18 300)'; // The receding grid as an inline SVG data-URI (CSP-safe, no external assets). +// Strokes use vector-effect=non-scaling-stroke so a line is ~1px whether the +// tile is stretched across a 2000px plane (preserveAspectRatio=none would +// otherwise fatten the verticals ~4x) or squeezed into the 76px settings +// swatch (where scaled strokes disappeared entirely). // It is a 1x2 vertical tile of horizontal rule lines + a single set of vertical // lines fanning toward a top-center vanishing point. The plane is then placed // under a CSS `perspective` rotateX so the lines genuinely recede. Scrolling the @@ -58,7 +62,7 @@ function gridDataUri(): string { rows.forEach((y) => { lines.push( ``, + `stroke-width='1.2' stroke-opacity='0.9' vector-effect='non-scaling-stroke'/>`, ); }); // Vertical lines fanning out from the top-center vanishing point. @@ -67,7 +71,7 @@ function gridDataUri(): string { const botX = 300 + i * 95; // wide at the foreground lines.push( ``, + `stroke-width='1.2' stroke-opacity='0.8' vector-effect='non-scaling-stroke'/>`, ); } const svg = @@ -105,6 +109,13 @@ const RESTING_SPARKLES: ReadonlyArray<{ const GRID_URI = gridDataUri(); +// HUD text size: 11px on any real viewport, 0px (invisible) inside anything +// narrower than ~330px. `cqw` resolves against the nearest size container โ€” +// the settings swatch (`SeasonalPreview` sets container-type) โ€” and falls back +// to the viewport width when there is no container, i.e. the full-screen +// overlay. clamp(0, 100cqw - 320px, 11px) โ†’ 76px swatch: 0px; 1440px app: 11px. +const HUD_FONT_SIZE = 'clamp(0px, calc(100cqw - 320px), 11px)'; + export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) { // Deterministic sparkle field, computed ONCE. No per-frame state. const sparkles = useMemo(() => { @@ -134,9 +145,9 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) { position: 'absolute', inset: 0, backgroundImage: [ - 'radial-gradient(140% 80% at 50% -8%, oklch(0.65 0.25 350 / 0.16) 0%, transparent 55%)', - 'radial-gradient(120% 70% at 50% 112%, oklch(0.45 0.18 300 / 0.20) 0%, transparent 60%)', - 'linear-gradient(180deg, oklch(0.12 0.05 300 / 0.10) 0%, transparent 38%, oklch(0.10 0.06 310 / 0.16) 100%)', + 'radial-gradient(140% 80% at 50% -8%, oklch(0.65 0.25 350 / 0.12) 0%, transparent 55%)', + 'radial-gradient(120% 70% at 50% 112%, oklch(0.45 0.18 300 / 0.16) 0%, transparent 60%)', + 'linear-gradient(180deg, oklch(0.12 0.05 300 / 0.08) 0%, transparent 38%, oklch(0.10 0.06 310 / 0.12) 100%)', ].join(','), contain: 'layout paint style', }} @@ -147,41 +158,59 @@ export function ArcadeOverlay({ reduced }: SeasonalOverlayProps) { a vanishing point at the top (the horizon). It lives in the lower half of the screen โ€” the "floor". The inner plane scrolls upward by one tile via transform translateY, which reads as the grid flowing - toward the viewer. Pure transform; never background-position. */} + toward the viewer. Pure transform; never background-position. + + Two masks are nested (multiple mask-images on one element union by + default, and `mask-composite: intersect` isn't universal yet): the + outer wrapper fades the lattice through the central column where the + message timeline lives, so it frames the chat instead of striping + the text; the inner box fades it in from the horizon. */}