diff --git a/frontend/apps/artcraft-webapp/src/pages/pagescene/pagescene.tsx b/frontend/apps/artcraft-webapp/src/pages/pagescene/pagescene.tsx index 6ffa5a5296..711646dcdc 100644 --- a/frontend/apps/artcraft-webapp/src/pages/pagescene/pagescene.tsx +++ b/frontend/apps/artcraft-webapp/src/pages/pagescene/pagescene.tsx @@ -5,32 +5,23 @@ import { useSession } from "../../lib/session"; import { useSidebar } from "../../components/ui/sidebar"; import { useWebAppPageSceneAdapter } from "./web-adapter"; -// Stage3D was designed for the Tauri host: its outermost div is -// `w-screen` × `calc(100vh - 68px)` and SceneContainer subtracts a -// hardcoded 56px from the adapter-supplied viewport height to leave -// room for the host's TopBar. The webapp lives inside a sidebar + -// topbar shell that already takes its slice out of the viewport, so -// this wrapper translates the lib's assumptions into webapp space: +// Stage3D fills its parent box; this wrapper is that box. It clamps +// the editor to the SidebarInset area and feeds the lib its rect: // -// - `overflow-hidden h-full w-full` clamps the lib's viewport-sized -// chrome to the area SidebarInset gives us, so the inset never -// grows a scrollbar around the editor. -// - `getViewportSize` reads the wrapper's current rect on every call -// and adds the lib's hardcoded 56px topbar compensation, so the -// SceneContainer's `height - 56` math lands on the wrapper rect. +// - `overflow-hidden h-full w-full` sizes Stage3D to the inset and +// prevents the inset from growing a scrollbar around the editor. +// - `getViewportSize` reports the wrapper's current rect. Consumers +// in the lib (getScale, absolute-positioned chrome) read it via +// `useViewportSize`. // - The lib's `useViewportSize` only re-reads on `window` resize, so // a ResizeObserver here dispatches a synthetic resize whenever our -// rect changes (sidebar collapse, peer mounts, browser resize) and -// once on mount (the lib's `useState` initializer ran before any -// refs were attached and saw the window-size fallback). +// rect changes (sidebar collapse, browser resize). // - `transform: translateZ(0)` makes this div a containing block // for the lib's `position: fixed` overlays so they scope here. // // On entry the sidebar auto-collapses on desktop to give the lib's // 100vw-leaning layout the room it expects. -const LIB_TOP_BAR_COMPENSATION_PX = 56; - export default function PageScene() { const { sceneToken } = useParams<{ sceneToken?: string }>(); const { user } = useSession(); @@ -78,7 +69,7 @@ export default function PageScene() { } return { width: node.clientWidth, - height: node.clientHeight + LIB_TOP_BAR_COMPENSATION_PX, + height: node.clientHeight, }; }, []); diff --git a/frontend/apps/artcraft-webapp/src/styles.css b/frontend/apps/artcraft-webapp/src/styles.css index 5fdc065999..de1b8a41a0 100644 --- a/frontend/apps/artcraft-webapp/src/styles.css +++ b/frontend/apps/artcraft-webapp/src/styles.css @@ -176,9 +176,11 @@ } } -.glass { - @apply relative border border-white/10 shadow-lg backdrop-blur-xl transition-colors duration-300; - @apply bg-ui-controls/85 !important; +@layer components { + .glass { + @apply relative border border-white/10 shadow-lg backdrop-blur-xl transition-colors duration-300; + @apply bg-ui-controls/85 !important; + } } .liquid-glass { diff --git a/frontend/apps/artcraft/app/src/pages/PageScene/PageScene.tsx b/frontend/apps/artcraft/app/src/pages/PageScene/PageScene.tsx index f4b22f3979..6d69e79263 100644 --- a/frontend/apps/artcraft/app/src/pages/PageScene/PageScene.tsx +++ b/frontend/apps/artcraft/app/src/pages/PageScene/PageScene.tsx @@ -60,12 +60,17 @@ export const PageScene = ({ sceneToken }: { sceneToken?: string }) => { } }); + // Sized container for Stage3D. The lib fills its parent; the + // Tauri host's TopBar lives outside this box, so we reserve space + // here. 56px matches the `.topbar-spacer` height in base.css. return ( - +
+ +
); }; diff --git a/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx b/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx index 63fabc53ff..567f2d6a20 100644 --- a/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx +++ b/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx @@ -108,7 +108,6 @@ export const Stage3DBody = () => { // window.innerWidth/innerHeight when the host adapter doesn't // supply getViewportSize, so the layout always has sane values. const viewport = useViewportSize(); - const height = viewport.height - 56; const getScale = () => { const h = viewport.height - 56; @@ -320,17 +319,13 @@ export const Stage3DBody = () => { }, [editor]); return ( -
+
-
+
diff --git a/frontend/libs/components/pagescene/src/lib/comps/SceneContainer/SceneContainer.tsx b/frontend/libs/components/pagescene/src/lib/comps/SceneContainer/SceneContainer.tsx index 53ed071ff4..7ed2f7c3b8 100644 --- a/frontend/libs/components/pagescene/src/lib/comps/SceneContainer/SceneContainer.tsx +++ b/frontend/libs/components/pagescene/src/lib/comps/SceneContainer/SceneContainer.tsx @@ -1,43 +1,45 @@ -import { useCallback } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { usePageSceneStore } from "../../PageSceneStore"; -import { useViewportSize } from "../../hooks/useViewportSize"; import { Letterbox } from "./Letterbox"; -// 56px is the artcraft host's TopBar; subtracted from the viewport -// height so the scene container fills the area below the bar. Hosts -// without a TopBar can pass a getViewportSize that already accounts -// for their chrome. -const TOP_BAR_PX = 56; - export const SceneContainer = ({ children }: { children: React.ReactNode }) => { const editorLetterBox = usePageSceneStore((s) => s.editorLetterBox); - const { width, height } = useViewportSize(); - const containerWidth = width; - const containerHeight = height - TOP_BAR_PX; + const [size, setSize] = useState({ width: 0, height: 0 }); + const nodeRef = useRef(null); // Sets the DOM node both on mount (truthy) and unmount (null) so the // engine lifecycle effect can react to the canvas unmounting when the // tab switches away from 3D. The previous `if (node)` filter swallowed // the unmount case and leaked the Editor instance. const callbackRef = useCallback((node: HTMLDivElement | null) => { + nodeRef.current = node; usePageSceneStore.getState().setSceneContainerEl(node); + if (node) { + setSize({ width: node.clientWidth, height: node.clientHeight }); + } + }, []); + + useEffect(() => { + const node = nodeRef.current; + if (!node) return undefined; + const observer = new ResizeObserver(() => { + setSize({ width: node.clientWidth, height: node.clientHeight }); + }); + observer.observe(node); + return () => observer.disconnect(); }, []); return (
{children}
);