mirror of
https://github.com/chillpadclub/bedolaga-cabinet.git
synced 2026-07-28 09:33:46 +00:00
fix: isolate content layer from animated background to eliminate flickering
Root cause: backdrop-filter on header/nav resamples the animated background 60fps, causing GPU compositing flicker (Chromium bug). - Add isolation: isolate wrapper in AppShell to prevent backdrop-filter from seeing through to the animated background layer - Add will-change: transform + translateZ(0) to BackgroundRenderer for stable GPU layer - Replace contain: layout style with contain: content on main - Replace transition-all with specific properties in Card component
This commit is contained in:
@@ -90,6 +90,8 @@ export function BackgroundRenderer() {
|
||||
opacity: effectiveConfig.opacity,
|
||||
filter: effectiveConfig.blur > 0 ? `blur(${effectiveConfig.blur}px)` : undefined,
|
||||
contain: 'strict',
|
||||
willChange: 'transform',
|
||||
transform: 'translateZ(0)',
|
||||
}}
|
||||
>
|
||||
<Suspense fallback={null}>
|
||||
|
||||
@@ -11,7 +11,7 @@ const cardVariants = cva(
|
||||
'relative overflow-hidden',
|
||||
'border border-dark-700/40 bg-dark-900/70',
|
||||
'rounded-[var(--bento-radius)]',
|
||||
'transition-all duration-200',
|
||||
'transition-[border-color,background-color,box-shadow,transform,opacity] duration-200',
|
||||
// GPU acceleration
|
||||
'transform-gpu',
|
||||
// Glass border inset
|
||||
|
||||
@@ -280,158 +280,162 @@ export function AppShell({ children }: AppShellProps) {
|
||||
|
||||
return (
|
||||
<div className="min-h-screen">
|
||||
{/* Animated background */}
|
||||
{/* Animated background — on its own GPU layer, below everything */}
|
||||
<BackgroundRenderer />
|
||||
|
||||
{/* Global components */}
|
||||
<WebSocketNotifications />
|
||||
<CampaignBonusNotifier />
|
||||
<SuccessNotificationModal />
|
||||
{/* Content layer — isolated from background to prevent backdrop-filter
|
||||
from resampling the animated background every frame (Chromium bug) */}
|
||||
<div className="relative z-10" style={{ isolation: 'isolate' }}>
|
||||
{/* Global components */}
|
||||
<WebSocketNotifications />
|
||||
<CampaignBonusNotifier />
|
||||
<SuccessNotificationModal />
|
||||
|
||||
{/* Desktop Header */}
|
||||
<header className="fixed left-0 right-0 top-0 z-50 hidden border-b border-dark-800/50 bg-dark-950/80 backdrop-blur-xl lg:block">
|
||||
<div className="mx-auto grid h-14 max-w-6xl grid-cols-[auto_1fr_auto] items-center gap-4 px-6">
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center gap-2.5" onClick={handleNavClick}>
|
||||
<div className="relative flex h-8 w-8 flex-shrink-0 items-center justify-center overflow-hidden rounded-lg bg-dark-800">
|
||||
<span
|
||||
className={cn(
|
||||
'absolute text-sm font-bold text-accent-400 transition-opacity duration-200',
|
||||
hasCustomLogo && isLogoPreloaded() ? 'opacity-0' : 'opacity-100',
|
||||
)}
|
||||
>
|
||||
{logoLetter}
|
||||
</span>
|
||||
{hasCustomLogo && logoUrl && (
|
||||
<img
|
||||
src={logoUrl}
|
||||
alt={appName || 'Logo'}
|
||||
{/* Desktop Header */}
|
||||
<header className="fixed left-0 right-0 top-0 z-50 hidden border-b border-dark-800/50 bg-dark-950/80 backdrop-blur-xl lg:block">
|
||||
<div className="mx-auto grid h-14 max-w-6xl grid-cols-[auto_1fr_auto] items-center gap-4 px-6">
|
||||
{/* Logo */}
|
||||
<Link to="/" className="flex items-center gap-2.5" onClick={handleNavClick}>
|
||||
<div className="relative flex h-8 w-8 flex-shrink-0 items-center justify-center overflow-hidden rounded-lg bg-dark-800">
|
||||
<span
|
||||
className={cn(
|
||||
'absolute h-full w-full object-contain transition-opacity duration-200',
|
||||
isLogoPreloaded() ? 'opacity-100' : 'opacity-0',
|
||||
'absolute text-sm font-bold text-accent-400 transition-opacity duration-200',
|
||||
hasCustomLogo && isLogoPreloaded() ? 'opacity-0' : 'opacity-100',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-base font-semibold text-dark-100">{appName}</span>
|
||||
</Link>
|
||||
>
|
||||
{logoLetter}
|
||||
</span>
|
||||
{hasCustomLogo && logoUrl && (
|
||||
<img
|
||||
src={logoUrl}
|
||||
alt={appName || 'Logo'}
|
||||
className={cn(
|
||||
'absolute h-full w-full object-contain transition-opacity duration-200',
|
||||
isLogoPreloaded() ? 'opacity-100' : 'opacity-0',
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
<span className="text-base font-semibold text-dark-100">{appName}</span>
|
||||
</Link>
|
||||
|
||||
{/* Center Navigation */}
|
||||
<nav className="flex items-center justify-center gap-1">
|
||||
{desktopNavItems.map((item) => (
|
||||
<Link
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
onClick={handleNavClick}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive(item.path)
|
||||
? 'bg-dark-800 text-dark-50'
|
||||
: 'text-dark-400 hover:bg-dark-800/50 hover:text-dark-200',
|
||||
)}
|
||||
>
|
||||
<item.icon className="h-4 w-4" />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
{referralEnabled && (
|
||||
<Link
|
||||
to="/referral"
|
||||
onClick={handleNavClick}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive('/referral')
|
||||
? 'bg-dark-800 text-dark-50'
|
||||
: 'text-dark-400 hover:bg-dark-800/50 hover:text-dark-200',
|
||||
)}
|
||||
>
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
<span>{t('nav.referral')}</span>
|
||||
</Link>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<>
|
||||
{/* Separator before admin */}
|
||||
<div className="mx-2 h-5 w-px bg-dark-700" />
|
||||
{/* Center Navigation */}
|
||||
<nav className="flex items-center justify-center gap-1">
|
||||
{desktopNavItems.map((item) => (
|
||||
<Link
|
||||
to="/admin"
|
||||
key={item.path}
|
||||
to={item.path}
|
||||
onClick={handleNavClick}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
location.pathname.startsWith('/admin')
|
||||
? 'bg-warning-500/10 text-warning-400'
|
||||
: 'text-warning-500/70 hover:bg-warning-500/10 hover:text-warning-400',
|
||||
isActive(item.path)
|
||||
? 'bg-dark-800 text-dark-50'
|
||||
: 'text-dark-400 hover:bg-dark-800/50 hover:text-dark-200',
|
||||
)}
|
||||
>
|
||||
<ShieldIcon className="h-4 w-4" />
|
||||
<span>{t('admin.nav.title')}</span>
|
||||
<item.icon className="h-4 w-4" />
|
||||
<span>{item.label}</span>
|
||||
</Link>
|
||||
))}
|
||||
{referralEnabled && (
|
||||
<Link
|
||||
to="/referral"
|
||||
onClick={handleNavClick}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
isActive('/referral')
|
||||
? 'bg-dark-800 text-dark-50'
|
||||
: 'text-dark-400 hover:bg-dark-800/50 hover:text-dark-200',
|
||||
)}
|
||||
>
|
||||
<UsersIcon className="h-4 w-4" />
|
||||
<span>{t('nav.referral')}</span>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
haptic.impact('light');
|
||||
toggleTheme();
|
||||
}}
|
||||
className={cn(
|
||||
'rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-all duration-200 hover:bg-dark-700 hover:text-accent-400',
|
||||
!canToggleTheme && 'pointer-events-none invisible',
|
||||
)}
|
||||
title={isDark ? t('theme.light') || 'Light mode' : t('theme.dark') || 'Dark mode'}
|
||||
>
|
||||
{isDark ? <MoonIcon className="h-5 w-5" /> : <SunIcon className="h-5 w-5" />}
|
||||
</button>
|
||||
<TicketNotificationBell isAdmin={location.pathname.startsWith('/admin')} />
|
||||
<LanguageSwitcher />
|
||||
<button
|
||||
onClick={() => {
|
||||
haptic.impact('light');
|
||||
logout();
|
||||
}}
|
||||
className="rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-all duration-200 hover:bg-dark-700 hover:text-accent-400"
|
||||
title={t('nav.logout')}
|
||||
>
|
||||
<LogoutIcon className="h-5 w-5" />
|
||||
</button>
|
||||
{isAdmin && (
|
||||
<>
|
||||
{/* Separator before admin */}
|
||||
<div className="mx-2 h-5 w-px bg-dark-700" />
|
||||
<Link
|
||||
to="/admin"
|
||||
onClick={handleNavClick}
|
||||
className={cn(
|
||||
'flex items-center gap-2 rounded-lg px-3 py-2 text-sm font-medium transition-colors',
|
||||
location.pathname.startsWith('/admin')
|
||||
? 'bg-warning-500/10 text-warning-400'
|
||||
: 'text-warning-500/70 hover:bg-warning-500/10 hover:text-warning-400',
|
||||
)}
|
||||
>
|
||||
<ShieldIcon className="h-4 w-4" />
|
||||
<span>{t('admin.nav.title')}</span>
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
|
||||
{/* Right side actions */}
|
||||
<div className="flex items-center justify-end gap-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
haptic.impact('light');
|
||||
toggleTheme();
|
||||
}}
|
||||
className={cn(
|
||||
'rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-all duration-200 hover:bg-dark-700 hover:text-accent-400',
|
||||
!canToggleTheme && 'pointer-events-none invisible',
|
||||
)}
|
||||
title={isDark ? t('theme.light') || 'Light mode' : t('theme.dark') || 'Dark mode'}
|
||||
>
|
||||
{isDark ? <MoonIcon className="h-5 w-5" /> : <SunIcon className="h-5 w-5" />}
|
||||
</button>
|
||||
<TicketNotificationBell isAdmin={location.pathname.startsWith('/admin')} />
|
||||
<LanguageSwitcher />
|
||||
<button
|
||||
onClick={() => {
|
||||
haptic.impact('light');
|
||||
logout();
|
||||
}}
|
||||
className="rounded-xl border border-dark-700/50 bg-dark-800/50 p-2 text-dark-400 transition-all duration-200 hover:bg-dark-700 hover:text-accent-400"
|
||||
title={t('nav.logout')}
|
||||
>
|
||||
<LogoutIcon className="h-5 w-5" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
</header>
|
||||
|
||||
{/* Mobile Header */}
|
||||
<AppHeader
|
||||
mobileMenuOpen={mobileMenuOpen}
|
||||
setMobileMenuOpen={setMobileMenuOpen}
|
||||
onCommandPaletteOpen={() => {}}
|
||||
headerHeight={headerHeight}
|
||||
isFullscreen={isMobileFullscreen}
|
||||
safeAreaInset={safeAreaInset}
|
||||
contentSafeAreaInset={contentSafeAreaInset}
|
||||
telegramPlatform={platform}
|
||||
wheelEnabled={wheelEnabled}
|
||||
referralEnabled={referralEnabled}
|
||||
hasContests={hasContests}
|
||||
hasPolls={hasPolls}
|
||||
/>
|
||||
{/* Mobile Header */}
|
||||
<AppHeader
|
||||
mobileMenuOpen={mobileMenuOpen}
|
||||
setMobileMenuOpen={setMobileMenuOpen}
|
||||
onCommandPaletteOpen={() => {}}
|
||||
headerHeight={headerHeight}
|
||||
isFullscreen={isMobileFullscreen}
|
||||
safeAreaInset={safeAreaInset}
|
||||
contentSafeAreaInset={contentSafeAreaInset}
|
||||
telegramPlatform={platform}
|
||||
wheelEnabled={wheelEnabled}
|
||||
referralEnabled={referralEnabled}
|
||||
hasContests={hasContests}
|
||||
hasPolls={hasPolls}
|
||||
/>
|
||||
|
||||
{/* Desktop spacer */}
|
||||
<div className="hidden h-14 lg:block" />
|
||||
{/* Desktop spacer */}
|
||||
<div className="hidden h-14 lg:block" />
|
||||
|
||||
{/* Mobile spacer */}
|
||||
<div className="lg:hidden" style={{ height: headerHeight }} />
|
||||
{/* Mobile spacer */}
|
||||
<div className="lg:hidden" style={{ height: headerHeight }} />
|
||||
|
||||
{/* Main content */}
|
||||
<main className="mx-auto max-w-6xl px-4 py-6 pb-28 lg:px-6 lg:pb-8">{children}</main>
|
||||
{/* Main content */}
|
||||
<main className="mx-auto max-w-6xl px-4 py-6 pb-28 lg:px-6 lg:pb-8">{children}</main>
|
||||
|
||||
{/* Mobile Bottom Navigation */}
|
||||
<MobileBottomNav
|
||||
isKeyboardOpen={isKeyboardOpen}
|
||||
referralEnabled={referralEnabled}
|
||||
wheelEnabled={wheelEnabled}
|
||||
/>
|
||||
{/* Mobile Bottom Navigation */}
|
||||
<MobileBottomNav
|
||||
isKeyboardOpen={isKeyboardOpen}
|
||||
referralEnabled={referralEnabled}
|
||||
wheelEnabled={wheelEnabled}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -268,10 +268,10 @@
|
||||
}
|
||||
}
|
||||
|
||||
/* Optimize main scrollable content */
|
||||
/* Main content — containment removed to avoid GPU layer conflicts
|
||||
with isolation: isolate on content wrapper */
|
||||
main {
|
||||
/* Prevent layout shifts during scroll */
|
||||
contain: layout style;
|
||||
contain: content;
|
||||
}
|
||||
|
||||
/* Light theme - Champagne */
|
||||
|
||||
Reference in New Issue
Block a user