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 ( return (
<div <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={{ style={{
left: position.x, left: position.x,
top: position.y, top: position.y,

View File

@ -66,7 +66,7 @@ const ConstructorMenu = forwardRef<HTMLDivElement, ConstructorMenuProps>(
return ( return (
<div <div
ref={ref} 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 }} style={{ left: position.x, top: position.y }}
> >
<div <div

View File

@ -181,7 +181,7 @@ export function ElementEditorPanel({
return ( return (
<div <div
ref={elementEditorRef} 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 }} style={{ left: position.x, top: position.y }}
> >
<ElementEditorHeader <ElementEditorHeader

View File

@ -574,9 +574,10 @@ export default function RuntimePresentation({
{/* Outer container: full viewport with black background for letterbox bars */} {/* Outer container: full viewport with black background for letterbox bars */}
<div className='relative w-screen h-screen overflow-hidden bg-black'> <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 <div
className='overflow-hidden' className='relative z-[46] overflow-hidden'
style={{ style={{
...cssVars, ...cssVars,
...letterboxStyles, ...letterboxStyles,
@ -630,19 +631,17 @@ export default function RuntimePresentation({
/> />
)} )}
{/* New page content wrapper - fades in for non-transition navigation. {/* Page background wrapper - z-5 keeps it BELOW carousel slide (z-10).
z-1 ensures it's above previous backgrounds (z-0) during fade. Fades in for non-transition navigation.
onAnimationEnd resets isFadingIn when CSS animation completes. */} onAnimationEnd resets isFadingIn when CSS animation completes. */}
<div <div
data-testid='page-content-wrapper' data-testid='page-background-wrapper'
className={`absolute inset-0 z-1 ${isFadingIn ? 'animate-crossfade-in' : ''}`} className={`absolute inset-0 z-5 ${isFadingIn ? 'animate-crossfade-in' : ''}`}
onAnimationEnd={onFadeInAnimationEnd} onAnimationEnd={onFadeInAnimationEnd}
> >
{/* Background image element - z-1 keeps it below backdrop blur (z-5). {/* Background image element */}
CSS backgroundImage provides instant display.
Use native img for blob URLs to prevent repeated fetch requests from Next.js Image. */}
{backgroundImageUrl && !backgroundVideoUrl && ( {backgroundImageUrl && !backgroundVideoUrl && (
<div className='absolute inset-0 z-1 pointer-events-none'> <div className='absolute inset-0 pointer-events-none'>
{backgroundImageUrl.startsWith('blob:') ? ( {backgroundImageUrl.startsWith('blob:') ? (
// eslint-disable-next-line @next/next/no-img-element // eslint-disable-next-line @next/next/no-img-element
<img <img
@ -682,12 +681,12 @@ export default function RuntimePresentation({
</div> </div>
)} )}
{/* Background video - z-1 keeps it below backdrop blur (z-5) */} {/* Background video */}
{backgroundVideoUrl && ( {backgroundVideoUrl && (
<video <video
ref={bgVideoRef} ref={bgVideoRef}
key={backgroundVideoUrl} 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} src={backgroundVideoUrl}
autoPlay={videoAutoplay} autoPlay={videoAutoplay}
loop={useNativeLoop} loop={useNativeLoop}
@ -695,23 +694,29 @@ export default function RuntimePresentation({
playsInline 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> </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 */} {/* Controls: Offline toggle and Fullscreen button */}
<div className='absolute top-4 right-4 z-50 flex items-center gap-2'> <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: // Full-width carousel - two layers:
// 1. Background image layer at z-10 (behind canvas elements) // 1. Background image layer at z-10 (behind canvas z-[46])
// 2. Navigation/caption layer at z-30 (above everything for clickability) // 2. Navigation/caption layer at z-[47] (above canvas z-[46], below UI controls z-50)
const fullWidthBackground = ( const fullWidthBackground = (
<div className='fixed inset-0 z-10 overflow-hidden bg-black pointer-events-none'> <div className='fixed inset-0 z-10 overflow-hidden bg-black pointer-events-none'>
{currentSlide?.imageUrl && ( {currentSlide?.imageUrl && (
@ -359,7 +359,7 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
const fullWidthControls = ( const fullWidthControls = (
<div <div
ref={overlayRef} ref={overlayRef}
className='fixed inset-0 z-30 pointer-events-none' className='fixed inset-0 z-[47] pointer-events-none'
onTouchStart={handleTouchStart} onTouchStart={handleTouchStart}
onTouchEnd={handleTouchEnd} onTouchEnd={handleTouchEnd}
> >
@ -400,7 +400,7 @@ const CarouselElement: React.FC<CarouselElementProps> = ({
); );
// Full-width mode: use portal to render outside transform hierarchy // 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) { if (isFullWidth) {
// SSR safety: only use portal when mounted in browser // SSR safety: only use portal when mounted in browser
if (!isMounted) { if (!isMounted) {

View File

@ -1426,7 +1426,7 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
</title> </title>
</Head> </Head>
<div className='relative w-screen h-screen bg-black overflow-hidden'> <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'> <p className='text-xs font-semibold text-gray-700'>
{projectName || 'Loading project...'} {projectName || 'Loading project...'}
</p> </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 <div
ref={canvasRef} ref={canvasRef}
tabIndex={-1} tabIndex={-1}
className={`z-20 overflow-clip ${hasFullWidthCarousel ? 'bg-transparent' : 'bg-black'}`} className={`relative z-[46] overflow-clip ${hasFullWidthCarousel ? 'bg-transparent' : 'bg-black'}`}
style={{ style={{
...canvasCssVars, ...canvasCssVars,
...letterboxStyles, ...letterboxStyles,
@ -1487,29 +1488,33 @@ const ConstructorPage = ({ mode = 'constructor' }: ConstructorPageProps) => {
}} }}
> >
<BackdropPortalProvider> <BackdropPortalProvider>
<CanvasBackground {/* Background wrapper - z-5 keeps it BELOW carousel slide (z-10) */}
backgroundImageUrl={backgroundImageSrc} <div className={`absolute inset-0 z-5 ${isFadingIn ? 'animate-crossfade-in' : ''}`}>
backgroundVideoUrl={backgroundVideoSrc} <CanvasBackground
backgroundAudioUrl={backgroundAudioSrc} backgroundImageUrl={backgroundImageSrc}
previousBgImageUrl={pageSwitch.previousBgImageUrl} backgroundVideoUrl={backgroundVideoSrc}
previousBgVideoUrl={pageSwitch.previousBgVideoUrl} backgroundAudioUrl={backgroundAudioSrc}
isSwitching={pageSwitch.isSwitching} previousBgImageUrl={pageSwitch.previousBgImageUrl}
isNewBgReady={pageSwitch.isNewBgReady} previousBgVideoUrl={pageSwitch.previousBgVideoUrl}
isFadingIn={isFadingIn} isSwitching={pageSwitch.isSwitching}
onBackgroundReady={() => { isNewBgReady={pageSwitch.isNewBgReady}
pageSwitch.markBackgroundReady(); isFadingIn={isFadingIn}
setIsBackgroundReady(true); onBackgroundReady={() => {
}} pageSwitch.markBackgroundReady();
videoAutoplay={backgroundVideoAutoplay} setIsBackgroundReady(true);
videoLoop={backgroundVideoLoop} }}
videoMuted={backgroundVideoMuted} videoAutoplay={backgroundVideoAutoplay}
videoStartTime={backgroundVideoStartTime} videoLoop={backgroundVideoLoop}
videoEndTime={backgroundVideoEndTime} 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 <div
className={`absolute inset-0 z-10 ${isFadingIn ? 'animate-crossfade-in' : ''}`} className={`absolute inset-0 z-[46] ${isFadingIn ? 'animate-crossfade-in' : ''}`}
> >
{isLoading ? ( {isLoading ? (
<div className='absolute inset-0 flex items-center justify-center'> <div className='absolute inset-0 flex items-center justify-center'>