, не
+ * перемонтируется) и держит один экземпляр BackgroundRenderer, пока есть хоть
+ * один потребитель. Страницы без AppShell (логин, QuickPurchase со своим
+ * экземпляром) фон, как и раньше, не получают.
+ */
+const useBackgroundConsumers = create<{ count: number }>(() => ({ count: 0 }));
+
+/** Вызывается из AppShell: «на этом роуте должен быть анимированный фон». */
+export function useBackgroundConsumer() {
+ useEffect(() => {
+ useBackgroundConsumers.setState((s) => ({ count: s.count + 1 }));
+ return () => useBackgroundConsumers.setState((s) => ({ count: s.count - 1 }));
+ }, []);
+}
+
+export function BackgroundHost() {
+ const active = useBackgroundConsumers((s) => s.count > 0);
+ const [mounted, setMounted] = useState(active);
+
+ useEffect(() => {
+ if (active) {
+ setMounted(true);
+ return;
+ }
+ // Грейс-период на случай, когда между размонтированием старого AppShell и
+ // маунтом нового есть зазор (StrictMode, Suspense): без него фон моргал бы
+ // и на таких переходах.
+ const timer = setTimeout(() => setMounted(false), 300);
+ return () => clearTimeout(timer);
+ }, [active]);
+
+ return mounted ? : null;
+}
diff --git a/src/components/layout/AppShell/AppShell.tsx b/src/components/layout/AppShell/AppShell.tsx
index 05d9e3c..18188cb 100644
--- a/src/components/layout/AppShell/AppShell.tsx
+++ b/src/components/layout/AppShell/AppShell.tsx
@@ -39,7 +39,7 @@ import {
import { MobileBottomNav } from './MobileBottomNav';
import { AppHeader } from './AppHeader';
-import { BackgroundRenderer } from '@/components/backgrounds/BackgroundRenderer';
+import { useBackgroundConsumer } from '@/components/backgrounds/BackgroundHost';
interface AppShellProps {
children: React.ReactNode;
@@ -60,6 +60,9 @@ export function AppShell({ children }: AppShellProps) {
const { appName, logoLetter, hasCustomLogo, logoUrl } = useBranding();
const { referralEnabled, wheelEnabled, hasContests, hasPolls, giftEnabled } = useFeatureFlags();
useScrollRestoration();
+ // Анимированный фон рендерит BackgroundHost в App (не перемонтируется при
+ // смене роута) — здесь только регистрируем, что на этом роуте он нужен.
+ useBackgroundConsumer();
// Theme toggle visibility
const { data: enabledThemes } = useQuery({
@@ -180,9 +183,6 @@ export function AppShell({ children }: AppShellProps) {
return (
- {/* Animated background renders via portal on document.body at z-index: -1 */}
-
-
{/* Global components */}