Files
cinny/src/app/pages/App.tsx
T
jared 6db07f1371 feat: seasonal theme overlays + improved animated chat backgrounds
Adds 11 CSS-only seasonal overlays (Halloween, Christmas, New Year, Autumn,
April Fool's, Lunar New Year, Valentine's Day, St. Patrick's Day, Earth Day,
Deep Space, Retro Arcade) with date-based auto-detection and a manual override
dropdown in Settings → Appearance → Seasonal Theme. All themes respect
prefers-reduced-motion. SeasonalEffect mounts at z-index 9997 in App.tsx.

Also rewrites all 5 animated chat background keyframes for smoother, more
organic motion: Digital Rain gains a phosphor glow flicker; Star Drift now
loops each layer by exactly its own tile size (no more seam); Grid Pulse adds
an independent brightness oscillation at a prime period; Aurora Flow drives
all four gradient layers through distinct paths; Fireflies adds glow-pulse and
opacity-blink animations at prime periods for unsynchronised bioluminescence.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-14 00:33:04 -04:00

162 lines
5.8 KiB
TypeScript

import React, { useEffect } from 'react';
import * as Sentry from '@sentry/react';
import { Provider as JotaiProvider, useAtomValue } from 'jotai';
import { OverlayContainerProvider, PopOutContainerProvider, TooltipContainerProvider } from 'folds';
import { RouterProvider } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { ReactQueryDevtools } from '@tanstack/react-query-devtools';
import { ClientConfigLoader } from '../components/ClientConfigLoader';
import { ClientConfigProvider } from '../hooks/useClientConfig';
import { ConfigConfigError, ConfigConfigLoading } from './ConfigConfig';
import { FeatureCheck } from './FeatureCheck';
import { createRouter } from './Router';
import { ScreenSizeProvider, useScreenSize } from '../hooks/useScreenSize';
import { useCompositionEndTracking } from '../hooks/useComposingCheck';
import { settingsAtom } from '../state/settings';
import { LotusToastContainer } from '../features/toast/LotusToastContainer';
import { useTauriNotificationBadge } from '../hooks/useTauriNotificationBadge';
import { SeasonalEffect } from '../components/seasonal/SeasonalEffect';
const FONT_MAP: Record<string, string> = {
system: "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif",
inter: "'InterVariable', sans-serif",
'jetbrains-mono': "'JetBrains Mono', monospace",
'fira-code': "'Fira Code', monospace",
};
function AppearanceEffects() {
const settings = useAtomValue(settingsAtom);
useEffect(() => {
const color = settings.mentionHighlightColor;
if (color) {
document.body.style.setProperty('--mention-highlight-bg', color);
// compute black or white text based on hex luminance
const r = parseInt(color.slice(1, 3), 16);
const g = parseInt(color.slice(3, 5), 16);
const b = parseInt(color.slice(5, 7), 16);
const lum = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
document.body.style.setProperty('--mention-highlight-text', lum > 0.5 ? '#000' : '#fff');
document.body.style.setProperty('--mention-highlight-border', color);
} else {
document.body.style.removeProperty('--mention-highlight-bg');
document.body.style.removeProperty('--mention-highlight-text');
document.body.style.removeProperty('--mention-highlight-border');
}
}, [settings.mentionHighlightColor]);
useEffect(() => {
const font = FONT_MAP[settings.fontFamily ?? 'inter'] ?? FONT_MAP.inter;
document.body.style.setProperty('--font-secondary', font);
}, [settings.fontFamily]);
return null;
}
function TauriEffects() {
useTauriNotificationBadge();
return null;
}
function NightLightOverlay() {
const settings = useAtomValue(settingsAtom);
if (!settings.nightLightEnabled) return null;
return (
<div
aria-hidden="true"
style={{
position: 'fixed',
inset: 0,
pointerEvents: 'none',
zIndex: 9998,
backgroundColor: `rgba(255, 140, 0, ${(settings.nightLightOpacity ?? 30) / 100})`,
}}
/>
);
}
const queryClient = new QueryClient();
function App() {
const screenSize = useScreenSize();
useCompositionEndTracking();
const portalContainer = document.getElementById('portalContainer') ?? undefined;
return (
<Sentry.ErrorBoundary
fallback={({ error, resetError }) => (
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
height: '100vh',
gap: '16px',
fontFamily: 'sans-serif',
padding: '24px',
textAlign: 'center',
}}
>
<h2 style={{ margin: 0 }}>Something went wrong</h2>
<p style={{ margin: 0, color: '#666', maxWidth: '400px' }}>
{error instanceof Error ? error.message : 'An unexpected error occurred.'}
</p>
<button
type="button"
onClick={resetError}
style={{
padding: '8px 20px',
borderRadius: '6px',
border: 'none',
background: '#5865f2',
color: '#fff',
cursor: 'pointer',
fontSize: '14px',
}}
>
Try again
</button>
</div>
)}
>
<TooltipContainerProvider value={portalContainer}>
<PopOutContainerProvider value={portalContainer}>
<OverlayContainerProvider value={portalContainer}>
<ScreenSizeProvider value={screenSize}>
<FeatureCheck>
<ClientConfigLoader
fallback={() => <ConfigConfigLoading />}
error={(err, retry, ignore) => (
<ConfigConfigError error={err} retry={retry} ignore={ignore} />
)}
>
{(clientConfig) => (
<ClientConfigProvider value={clientConfig}>
<QueryClientProvider client={queryClient}>
<JotaiProvider>
<AppearanceEffects />
<TauriEffects />
<RouterProvider router={createRouter(clientConfig, screenSize)} />
<SeasonalEffect />
<NightLightOverlay />
<LotusToastContainer />
</JotaiProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
</ClientConfigProvider>
)}
</ClientConfigLoader>
</FeatureCheck>
</ScreenSizeProvider>
</OverlayContainerProvider>
</PopOutContainerProvider>
</TooltipContainerProvider>
</Sentry.ErrorBoundary>
);
}
export default App;