diff --git a/frontend/libs/components/pagescene/src/index.ts b/frontend/libs/components/pagescene/src/index.ts
index b2a556e1bd..ae56930e8b 100644
--- a/frontend/libs/components/pagescene/src/index.ts
+++ b/frontend/libs/components/pagescene/src/index.ts
@@ -187,6 +187,7 @@ export { getSceneGenerationMetaData } from "./lib/sceneMetadata";
// signalScene, addToast, loadingBarData/IsShowing signals, etc.)
// are either surfaced via adapter slots or replaced with
// lib-internal state.
+export { AnonHintChip } from "./lib/comps/AnonHintChip";
export { AssetMenu, AssetModal } from "./lib/comps/AssetMenu";
export { Controls3D } from "./lib/comps/Controls3D";
export { ControlsTopButtons } from "./lib/comps/ControlsTopButtons";
@@ -199,6 +200,8 @@ export { EditorCanvas, CameraViewCanvas } from "./lib/comps/EngineCanvases";
export { Outliner } from "./lib/comps/Outliner";
export { ControlPanelSceneObject } from "./lib/comps/ControlPanelSceneObject";
export { SceneContainer } from "./lib/comps/SceneContainer";
+export { PreviewBox } from "./lib/comps/PreviewBox";
+export type { PreviewBoxProps } from "./lib/comps/PreviewBox";
export { PreviewEngineCamera } from "./lib/comps/PreviewEngineCamera";
export { PreviewImages } from "./lib/comps/PreviewImages";
export { OnboardingHelper } from "./lib/comps/OnboardingHelper";
diff --git a/frontend/libs/components/pagescene/src/lib/PageSceneStore.ts b/frontend/libs/components/pagescene/src/lib/PageSceneStore.ts
index b67a8bb043..288b38b265 100644
--- a/frontend/libs/components/pagescene/src/lib/PageSceneStore.ts
+++ b/frontend/libs/components/pagescene/src/lib/PageSceneStore.ts
@@ -22,6 +22,10 @@ export interface SceneMeta {
ownerToken: string | undefined;
isModified: boolean | undefined;
isInitializing: boolean;
+ // URL to the author's generation result for this scene, shown in
+ // view-only mode's PreviewBox. Host populates this from the
+ // adapter.loadScene response. Optional; absent ⇒ no preview rendered.
+ previewImageUrl?: string;
}
export type SceneObjectKind = "object" | "character" | "shape";
diff --git a/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx b/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx
index c2f17a5a28..41aea44557 100644
--- a/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx
+++ b/frontend/libs/components/pagescene/src/lib/Stage3DBody.tsx
@@ -33,6 +33,7 @@ import type { PopoverItem } from "@storyteller/ui-popover";
import { v4 as uuidv4 } from "uuid";
import { EngineContext } from "./contexts/EngineContext/EngineContext";
+import { AnonHintChip } from "./comps/AnonHintChip";
import { ControlPanelSceneObject } from "./comps/ControlPanelSceneObject";
import { Controls3D } from "./comps/Controls3D";
import { ControlsTopButtons } from "./comps/ControlsTopButtons";
@@ -41,6 +42,7 @@ import { FocalLengthDisplay } from "./comps/FocalLengthDisplay/FocalLengthDispla
import { OnboardingHelper } from "./comps/OnboardingHelper";
import { Outliner } from "./comps/Outliner";
import { PoseModeSelector } from "./comps/PoseModeSelector";
+import { PreviewBox } from "./comps/PreviewBox";
import { PreviewEngineCamera } from "./comps/PreviewEngineCamera";
import { SceneContainer } from "./comps/SceneContainer";
import { addCharacter, addObject, setCameraAspect } from "./actions";
@@ -74,6 +76,7 @@ export const Stage3DBody = () => {
(s) => s.setIsPromptBoxFocused,
);
const gridVisible = usePageSceneStore((s) => s.gridVisible);
+ const previewImageUrl = usePageSceneStore((s) => s.sceneMeta.previewImageUrl);
const isViewOnly = useIsViewOnly();
const addCamera = usePageSceneStore((s) => s.addCamera);
const updateCamera = usePageSceneStore((s) => s.updateCamera);
@@ -344,6 +347,9 @@ export const Stage3DBody = () => {
)}
@@ -364,8 +370,7 @@ export const Stage3DBody = () => {
{!isViewOnly && }
- {/* Future: slot when isViewOnly — shows the
- author's generated still next to the camera. */}
+ {isViewOnly && }
{!isViewOnly && {
+ const editor = useContext(EngineContext);
+ const currentUserToken = usePageSceneStore((s) => s.currentUserToken);
+ const isViewOnly = useIsViewOnly();
+
+ if (currentUserToken || isViewOnly) return null;
+
+ const handleClick = () => {
+ editor?.adapter.promptSignup?.("hint");
+ };
+
+ return (
+
+ );
+};
diff --git a/frontend/libs/components/pagescene/src/lib/comps/AnonHintChip/index.ts b/frontend/libs/components/pagescene/src/lib/comps/AnonHintChip/index.ts
new file mode 100644
index 0000000000..46540b15d2
--- /dev/null
+++ b/frontend/libs/components/pagescene/src/lib/comps/AnonHintChip/index.ts
@@ -0,0 +1 @@
+export { AnonHintChip } from "./AnonHintChip";
diff --git a/frontend/libs/components/pagescene/src/lib/comps/Controls3D/Controls3D.tsx b/frontend/libs/components/pagescene/src/lib/comps/Controls3D/Controls3D.tsx
index 602a6cc2f0..8bf2c472ed 100644
--- a/frontend/libs/components/pagescene/src/lib/comps/Controls3D/Controls3D.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/Controls3D/Controls3D.tsx
@@ -14,7 +14,6 @@ import {
import { ButtonIconSelect } from "@storyteller/ui-button-icon-select";
import { Button } from "@storyteller/ui-button";
import { Tooltip } from "@storyteller/ui-tooltip";
-import { SettingsModal } from "@storyteller/ui-settings-modal";
import { PopoverMenu } from "@storyteller/ui-popover";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import {
@@ -52,7 +51,6 @@ export const Controls3D = () => {
})),
);
const [showEmptySceneTooltip, setShowEmptySceneTooltip] = useState(false);
- const [isSettingsModalOpen, setIsSettingsModalOpen] = useState(false);
const [upload3DIsShowing, setUpload3DIsShowing] = useState(false);
const [isAddAssetPopoverOpen, setIsAddAssetPopoverOpen] = useState(false);
const [uploadImageIsShowing, setUploadImageIsShowing] = useState(false);
@@ -306,13 +304,6 @@ export const Controls3D = () => {
- setIsSettingsModalOpen(false)}
- globalAccountLogoutCallback={() => editor?.adapter.performLogout()}
- initialSection="accounts"
- />
-
{editor &&
editor.adapter.renderAssetUploader({
isOpen: upload3DIsShowing,
diff --git a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx
index e06a9e041b..f422d6d2c3 100644
--- a/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx
+++ b/frontend/libs/components/pagescene/src/lib/comps/ControlsTopButtons/ControlsTopButtons.tsx
@@ -169,20 +169,26 @@ export const ControlsTopButtons = () => {
{
label: "Load my scene",
description: "Ctrl+O",
- dialogProps: {
- title: "Load a Saved Scene",
- content: (
-
- ),
- confirmButtonProps: {
- label: "Load",
- disabled: sceneTokenSelected === "",
- onClick: handleButtonLoadScene,
- },
- closeButtonProps: { label: "Cancel" },
- showClose: true,
- className: "max-w-5xl",
- },
+ ...(currentUserToken
+ ? {
+ dialogProps: {
+ title: "Load a Saved Scene",
+ content: (
+
+ ),
+ confirmButtonProps: {
+ label: "Load",
+ disabled: sceneTokenSelected === "",
+ onClick: handleButtonLoadScene,
+ },
+ closeButtonProps: { label: "Cancel" },
+ showClose: true,
+ className: "max-w-5xl",
+ },
+ }
+ : {
+ onClick: () => editor?.adapter.promptSignup?.("load"),
+ }),
},
{
disabled: !canSave,
diff --git a/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx b/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx
new file mode 100644
index 0000000000..26f5ba683a
--- /dev/null
+++ b/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/PreviewBox.tsx
@@ -0,0 +1,42 @@
+import { useState } from "react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { faXmark } from "@fortawesome/pro-solid-svg-icons";
+
+// Small fixed-position card shown next to the camera in view-only mode.
+// Renders the author's generation result so a visitor can see what the
+// scene was made to produce. Today this is scaffolding only — the URL
+// data path (likely a new field returned by adapter.loadScene's response
+// payload, propagated into sceneMeta.previewImageUrl) is wired up in a
+// follow-up PR. Renders null when no URL is supplied, so it's safe to
+// mount unconditionally inside the isViewOnly branch.
+export interface PreviewBoxProps {
+ imageUrl?: string;
+}
+
+export const PreviewBox = ({ imageUrl }: PreviewBoxProps) => {
+ const [dismissed, setDismissed] = useState(false);
+
+ if (!imageUrl || dismissed) return null;
+
+ return (
+
+
+

+
+ Author's generation
+
+
+ );
+};
diff --git a/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/index.ts b/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/index.ts
new file mode 100644
index 0000000000..6780dfa3a5
--- /dev/null
+++ b/frontend/libs/components/pagescene/src/lib/comps/PreviewBox/index.ts
@@ -0,0 +1,2 @@
+export { PreviewBox } from "./PreviewBox";
+export type { PreviewBoxProps } from "./PreviewBox";