diff --git a/src/app/components/seasonal/SeasonalEffect.tsx b/src/app/components/seasonal/SeasonalEffect.tsx
index caf40d9ff..1d570fe66 100644
--- a/src/app/components/seasonal/SeasonalEffect.tsx
+++ b/src/app/components/seasonal/SeasonalEffect.tsx
@@ -1,21 +1,49 @@
-import React, { useEffect, useMemo, useState } from 'react';
+import React, { Suspense, useEffect, useMemo, useState } from 'react';
import { useAtomValue } from 'jotai';
import { settingsAtom } from '../../state/settings';
import { useReducedMotion } from '../../hooks/useReducedMotion';
import { zIndices } from '../../styles/zIndex';
import { SeasonTheme } from './types';
import { resolveSeasonTheme } from './seasonSchedule';
-import { HalloweenOverlay } from './themes/Halloween';
-import { ChristmasOverlay } from './themes/Christmas';
-import { NewYearOverlay } from './themes/NewYear';
-import { AutumnOverlay } from './themes/Autumn';
-import { AprilFoolsOverlay } from './themes/AprilFools';
-import { LunarNewYearOverlay } from './themes/LunarNewYear';
-import { ValentinesOverlay } from './themes/Valentines';
-import { StPatricksOverlay } from './themes/StPatricks';
-import { EarthDayOverlay } from './themes/EarthDay';
-import { DeepSpaceOverlay } from './themes/DeepSpace';
-import { ArcadeOverlay } from './themes/Arcade';
+
+// [Gitea #167] Each overlay (its particles + keyframes) is its own chunk,
+// fetched only when that season is active or previewed in Settings — the
+// date/override decision stays in the main bundle.
+const lazyOverlay = (load: () => Promise<{ default: React.ComponentType<{ reduced: boolean }> }>) =>
+ React.lazy(load);
+const HalloweenOverlay = lazyOverlay(() =>
+ import('./themes/Halloween').then((m) => ({ default: m.HalloweenOverlay })),
+);
+const ChristmasOverlay = lazyOverlay(() =>
+ import('./themes/Christmas').then((m) => ({ default: m.ChristmasOverlay })),
+);
+const NewYearOverlay = lazyOverlay(() =>
+ import('./themes/NewYear').then((m) => ({ default: m.NewYearOverlay })),
+);
+const AutumnOverlay = lazyOverlay(() =>
+ import('./themes/Autumn').then((m) => ({ default: m.AutumnOverlay })),
+);
+const AprilFoolsOverlay = lazyOverlay(() =>
+ import('./themes/AprilFools').then((m) => ({ default: m.AprilFoolsOverlay })),
+);
+const LunarNewYearOverlay = lazyOverlay(() =>
+ import('./themes/LunarNewYear').then((m) => ({ default: m.LunarNewYearOverlay })),
+);
+const ValentinesOverlay = lazyOverlay(() =>
+ import('./themes/Valentines').then((m) => ({ default: m.ValentinesOverlay })),
+);
+const StPatricksOverlay = lazyOverlay(() =>
+ import('./themes/StPatricks').then((m) => ({ default: m.StPatricksOverlay })),
+);
+const EarthDayOverlay = lazyOverlay(() =>
+ import('./themes/EarthDay').then((m) => ({ default: m.EarthDayOverlay })),
+);
+const DeepSpaceOverlay = lazyOverlay(() =>
+ import('./themes/DeepSpace').then((m) => ({ default: m.DeepSpaceOverlay })),
+);
+const ArcadeOverlay = lazyOverlay(() =>
+ import('./themes/Arcade').then((m) => ({ default: m.ArcadeOverlay })),
+);
// SeasonTheme + the date-window logic now live in leaf modules (single source
// of truth, shared with the settings UI). Re-exported here for existing
@@ -69,7 +97,7 @@ function SeasonalOverlay({ theme, reduced }: { theme: SeasonTheme; reduced: bool
overflow: 'hidden',
}}
>
- {buildOverlayContent(theme, reduced)}
+ {buildOverlayContent(theme, reduced)}
);
}
@@ -94,7 +122,7 @@ export function SeasonalPreview({ theme }: { theme: SeasonTheme }) {
containerType: 'inline-size',
}}
>
- {buildOverlayContent(theme, true)}
+ {buildOverlayContent(theme, true)}
);
}