fixed carousel z-indexes issue

This commit is contained in:
Dmitri 2026-04-14 16:21:22 +04:00
parent fc624c0700
commit 4a61fd1a69
6 changed files with 68 additions and 58 deletions

View File

@ -35,7 +35,7 @@ const ConstructorControlsPanel: React.FC<ConstructorControlsPanelProps> = ({
}) => {
return (
<div
className='fixed z-40 w-[min(92vw,460px)] rounded-lg border border-gray-200 bg-white shadow-xl'
className='fixed z-[1000] w-[min(92vw,460px)] rounded-lg border border-gray-200 bg-white shadow-xl'
style={{
left: position.x,
top: position.y,

View File

@ -66,7 +66,7 @@ const ConstructorMenu = forwardRef<HTMLDivElement, ConstructorMenuProps>(
return (
<div
ref={ref}
className='fixed z-40 w-60 border border-gray-200 rounded-lg bg-white shadow-xl'
className='fixed z-[1000] w-60 border border-gray-200 rounded-lg bg-white shadow-xl'
style={{ left: position.x, top: position.y }}
>
<div

View File

@ -181,7 +181,7 @@ export function ElementEditorPanel({
return (
<div
ref={elementEditorRef}
className={`fixed z-40 ${isCollapsed ? 'w-[260px]' : 'w-[380px]'} max-h-[calc(100vh-2rem)] overflow-auto rounded-lg border border-gray-200 bg-white/95 p-3 shadow-xl`}
className={`fixed z-[1000] ${isCollapsed ? 'w-[260px]' : 'w-[380px]'} max-h-[calc(100vh-2rem)] overflow-auto rounded-lg border border-gray-200 bg-white/95 p-3 shadow-xl`}
style={{ left: position.x, top: position.y }}
>
<ElementEditorHeader

View File

@ -574,9 +574,10 @@ export default function RuntimePresentation({
{/* Outer container: full viewport with black background for letterbox bars */}
<div className='relative w-screen h-screen overflow-hidden bg-black'>
{/* Inner canvas: maintains aspect ratio centered in viewport */}
{/* Inner canvas: maintains aspect ratio centered in viewport.
z-[46] creates stacking context above carousel (z-10 bg, z-45 controls) portaled to body. */}
<div
className='overflow-hidden'
className='relative z-[46] overflow-hidden'
style={{
...cssVars,
...letterboxStyles,
@ -630,19 +631,17 @@ export default function RuntimePresentation({
/>
)}
{/* New page content wrapper - fades in for non-transition navigation.
z-1 ensures it's above previous backgrounds (z-0) during fade.
{/* Page background wrapper - z-5 keeps it BELOW carousel slide (z-10).
Fades in for non-transition navigation.
onAnimationEnd resets isFadingIn when CSS animation completes. */}
<div
data-testid='page-content-wrapper'
className={`absolute inset-0 z-1 ${isFadingIn ? 'animate-crossfade-in' : ''}`}
data-testid='page-background-wrapper'
className={`absolute inset-0 z-5 ${isFadingIn ? 'animate-crossfade-in' : ''}`}
onAnimationEnd={onFadeInAnimationEnd}
>
{/* Background image element - z-1 keeps it below backdrop blur (z-5).
CSS backgroundImage provides instant display.
Use native img for blob URLs to prevent repeated fetch requests from Next.js Image. */}
{/* Background image element */}
{backgroundImageUrl && !backgroundVideoUrl && (
<div className='absolute inset-0 z-1 pointer-events-none'>
<div className='absolute inset-0 pointer-events-none'>
{backgroundImageUrl.startsWith('blob:') ? (
// eslint-disable-next-line @next/next/no-img-element
<img
@ -682,12 +681,12 @@ export default function RuntimePresentation({
</div>
)}
{/* Background video - z-1 keeps it below backdrop blur (z-5) */}
{/* Background video */}
{backgroundVideoUrl && (
<video
ref={bgVideoRef}
key={backgroundVideoUrl}
className='absolute inset-0 z-1 h-full w-full object-contain'
className='absolute inset-0 h-full w-full object-contain'
src={backgroundVideoUrl}
autoPlay={videoAutoplay}
loop={useNativeLoop}
@ -695,23 +694,29 @@ export default function RuntimePresentation({
playsInline
/>
)}
{/* Page elements - z-40 ensures they appear above carousel background (z-10) and carousel controls (z-30) */}
<div className='absolute inset-0 z-40'>
{pageElements.map((element: CanvasElement) => (
<RuntimeElement
key={element.id}
element={element}
onClick={() => handleElementClick(element)}
resolveUrl={resolveUrlWithBlob}
onGalleryCardClick={(cardIndex) =>
handleGalleryCardClick(element, cardIndex)
}
/>
))}
</div>
</div>
{/* End new page content wrapper */}
{/* End page background wrapper */}
{/* Page elements wrapper - z-[46] keeps it ABOVE carousel slide (z-10) AND carousel controls (z-45).
UI controls (z-50) remain on top.
Fades in together with background. */}
<div
data-testid='page-elements-wrapper'
className={`absolute inset-0 z-[46] ${isFadingIn ? 'animate-crossfade-in' : ''}`}
>
{pageElements.map((element: CanvasElement) => (
<RuntimeElement
key={element.id}
element={element}
onClick={() => handleElementClick(element)}
resolveUrl={resolveUrlWithBlob}
onGalleryCardClick={(cardIndex) =>
handleGalleryCardClick(element, cardIndex)
}
/>
))}
</div>
{/* End page elements wrapper */}
{/* Controls: Offline toggle and Fullscreen button */}
<div className='absolute top-4 right-4 z-50 flex items-center gap-2'>

View File

@ -340,8 +340,8 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
};
// Full-width carousel - two layers:
// 1. Background image layer at z-10 (behind canvas elements)
// 2. Navigation/caption layer at z-30 (above everything for clickability)
// 1. Background image layer at z-10 (behind canvas z-[46])
// 2. Navigation/caption layer at z-[47] (above canvas z-[46], below UI controls z-50)
const fullWidthBackground = (
<div className='fixed inset-0 z-10 overflow-hidden bg-black pointer-events-none'>
{currentSlide?.imageUrl && (
@ -359,7 +359,7 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
const fullWidthControls = (
<div
ref={overlayRef}
className='fixed inset-0 z-30 pointer-events-none'
className='fixed inset-0 z-[47] pointer-events-none'
onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd}
>
@ -400,7 +400,7 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
);
// Full-width mode: use portal to render outside transform hierarchy
// Background at z-10, controls at z-30 for clickability
// Background at z-10, controls at z-[47] for clickability (above canvas z-[46])
if (isFullWidth) {
// SSR safety: only use portal when mounted in browser
if (!isMounted) {

View File

@ -1426,7 +1426,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
</title>
</Head>
<div className='relative w-screen h-screen bg-black overflow-hidden'>
<div className='absolute top-4 left-4 z-30 flex max-w-[80vw] flex-col gap-2'>
<div className='absolute top-4 left-4 z-[1000] flex max-w-[80vw] flex-col gap-2'>
<p className='text-xs font-semibold text-gray-700'>
{projectName || 'Loading project...'}
</p>
@ -1476,10 +1476,11 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
/>
)}
{/* Canvas container: z-[46] creates stacking context above carousel (z-10 bg, z-45 controls) portaled to body */}
<div
ref={canvasRef}
tabIndex={-1}
className={`z-20 overflow-clip ${hasFullWidthCarousel ? 'bg-transparent' : 'bg-black'}`}
className={`relative z-[46] overflow-clip ${hasFullWidthCarousel ? 'bg-transparent' : 'bg-black'}`}
style={{
...canvasCssVars,
...letterboxStyles,
@ -1487,29 +1488,33 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
}}
>
<BackdropPortalProvider>
<CanvasBackground
backgroundImageUrl={backgroundImageSrc}
backgroundVideoUrl={backgroundVideoSrc}
backgroundAudioUrl={backgroundAudioSrc}
previousBgImageUrl={pageSwitch.previousBgImageUrl}
previousBgVideoUrl={pageSwitch.previousBgVideoUrl}
isSwitching={pageSwitch.isSwitching}
isNewBgReady={pageSwitch.isNewBgReady}
isFadingIn={isFadingIn}
onBackgroundReady={() => {
pageSwitch.markBackgroundReady();
setIsBackgroundReady(true);
}}
videoAutoplay={backgroundVideoAutoplay}
videoLoop={backgroundVideoLoop}
videoMuted={backgroundVideoMuted}
videoStartTime={backgroundVideoStartTime}
videoEndTime={backgroundVideoEndTime}
/>
{/* Background wrapper - z-5 keeps it BELOW carousel slide (z-10) */}
<div className={`absolute inset-0 z-5 ${isFadingIn ? 'animate-crossfade-in' : ''}`}>
<CanvasBackground
backgroundImageUrl={backgroundImageSrc}
backgroundVideoUrl={backgroundVideoSrc}
backgroundAudioUrl={backgroundAudioSrc}
previousBgImageUrl={pageSwitch.previousBgImageUrl}
previousBgVideoUrl={pageSwitch.previousBgVideoUrl}
isSwitching={pageSwitch.isSwitching}
isNewBgReady={pageSwitch.isNewBgReady}
isFadingIn={isFadingIn}
onBackgroundReady={() => {
pageSwitch.markBackgroundReady();
setIsBackgroundReady(true);
}}
videoAutoplay={backgroundVideoAutoplay}
videoLoop={backgroundVideoLoop}
videoMuted={backgroundVideoMuted}
videoStartTime={backgroundVideoStartTime}
videoEndTime={backgroundVideoEndTime}
/>
</div>
{/* Elements container - z-10 ensures they appear above backdrop layer */}
{/* Elements container - z-[46] keeps it ABOVE carousel slide (z-10) AND carousel controls (z-45).
UI controls (z-50) remain on top. */}
<div
className={`absolute inset-0 z-10 ${isFadingIn ? 'animate-crossfade-in' : ''}`}
className={`absolute inset-0 z-[46] ${isFadingIn ? 'animate-crossfade-in' : ''}`}
>
{isLoading ? (
<div className='absolute inset-0 flex items-center justify-center'>