refactor: simplify presentation audience surface
This commit is contained in:
@@ -1,15 +0,0 @@
|
|||||||
type ChatDockProps = {
|
|
||||||
readonly openChat: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const ChatDock = ({ openChat }: ChatDockProps) => (
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="chat-dock"
|
|
||||||
aria-label="open agent chat"
|
|
||||||
onClick={openChat}
|
|
||||||
>
|
|
||||||
<span className="chat-dock__icon">💬</span>
|
|
||||||
<span className="chat-dock__label">Chat</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
import { discussionBranches, findScene, type MainSceneId } from "./storyboard.js";
|
|
||||||
|
|
||||||
type DiscussionIndexProps = {
|
|
||||||
readonly onSelect: (branchId: string) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
type BranchGroup = {
|
|
||||||
readonly parentSceneId: MainSceneId;
|
|
||||||
readonly sceneTitle: string;
|
|
||||||
readonly branches: typeof discussionBranches[number][];
|
|
||||||
};
|
|
||||||
|
|
||||||
const groupByParentScene = (): readonly BranchGroup[] => {
|
|
||||||
const groups = new Map<string, BranchGroup>();
|
|
||||||
for (const branch of discussionBranches) {
|
|
||||||
let group = groups.get(branch.parentSceneId);
|
|
||||||
if (!group) {
|
|
||||||
const scene = findScene(branch.parentSceneId);
|
|
||||||
group = {
|
|
||||||
parentSceneId: branch.parentSceneId,
|
|
||||||
sceneTitle: scene?.title ?? branch.parentSceneId,
|
|
||||||
branches: [],
|
|
||||||
};
|
|
||||||
groups.set(branch.parentSceneId, group);
|
|
||||||
}
|
|
||||||
group.branches.push(branch);
|
|
||||||
}
|
|
||||||
return Array.from(groups.values());
|
|
||||||
};
|
|
||||||
|
|
||||||
export const DiscussionIndex = ({ onSelect }: DiscussionIndexProps) => {
|
|
||||||
const groups = groupByParentScene();
|
|
||||||
return (
|
|
||||||
<div className="discussion-index" role="dialog" aria-label="discussion topics">
|
|
||||||
<h2>Discussion Topics</h2>
|
|
||||||
{groups.map((group) => (
|
|
||||||
<section key={group.parentSceneId}>
|
|
||||||
<h3>{group.sceneTitle}</h3>
|
|
||||||
<ul>
|
|
||||||
{group.branches.map((branch) => (
|
|
||||||
<li key={branch.id}>
|
|
||||||
<button type="button" onClick={() => onSelect(branch.id)}>
|
|
||||||
{branch.title}
|
|
||||||
</button>
|
|
||||||
</li>
|
|
||||||
))}
|
|
||||||
</ul>
|
|
||||||
</section>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -112,7 +112,7 @@ export const OperatorChat = ({ state, messages, onApprove, onDeny }: OperatorCha
|
|||||||
const visibleMessages = messages && messages.length > 0 ? messages : fallbackMessages(state);
|
const visibleMessages = messages && messages.length > 0 ? messages : fallbackMessages(state);
|
||||||
const composition = compositionForState(state);
|
const composition = compositionForState(state);
|
||||||
return (
|
return (
|
||||||
<aside className="operator-chat" data-mode={composition.chatMode} data-chat-theme={composition.chatTheme} aria-label="scripted operator chat">
|
<aside className="operator-chat" data-mode={composition.chatMode} aria-label="scripted operator chat">
|
||||||
{visibleMessages.map((message) => (
|
{visibleMessages.map((message) => (
|
||||||
<div key={message.id} className={`chat-message chat-message--${message.role === "user" ? "operator" : "system"}`}>
|
<div key={message.id} className={`chat-message chat-message--${message.role === "user" ? "operator" : "system"}`}>
|
||||||
<strong>{message.role === "user" ? "Operator" : "lda.chat"}</strong>
|
<strong>{message.role === "user" ? "Operator" : "lda.chat"}</strong>
|
||||||
|
|||||||
@@ -23,12 +23,12 @@ describe("PresentationRoute", () => {
|
|||||||
expect(await screen.findByText(/The interface delegates durable work to lda\.chat/i)).toBeInTheDocument();
|
expect(await screen.findByText(/The interface delegates durable work to lda\.chat/i)).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders replay-first chat, beat rail, and stage caption", () => {
|
it("renders audience progress chrome without rail or mode label", () => {
|
||||||
render(<PresentationRoute />);
|
render(<PresentationRoute />);
|
||||||
|
|
||||||
expect(screen.getByText("Prepare the thesis readiness report.")).toBeInTheDocument();
|
expect(screen.getByText("Prepare the thesis readiness report.")).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText(/presentation scene rail/i)).toBeInTheDocument();
|
expect(screen.queryByLabelText(/presentation scene rail/i)).not.toBeInTheDocument();
|
||||||
expect(screen.getByText("Replay · running")).toBeInTheDocument();
|
expect(screen.getByLabelText("scene position")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows node spotlight when a graph node is selected", async () => {
|
it("shows node spotlight when a graph node is selected", async () => {
|
||||||
@@ -63,16 +63,13 @@ describe("PresentationRoute", () => {
|
|||||||
expect(await screen.findByRole("dialog", { name: /issue review/i })).toBeInTheDocument();
|
expect(await screen.findByRole("dialog", { name: /issue review/i })).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
it("opens a positioning branch and returns to the exact originating beat", async () => {
|
it("opens a positioning branch via hash and returns to the parent scene first beat", async () => {
|
||||||
window.location.hash = "#scene/positioning/lda-position";
|
window.location.hash = "#discuss/hosted-automation";
|
||||||
render(<PresentationRoute />);
|
render(<PresentationRoute />);
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: /discussion topics/i }));
|
expect(await screen.findByRole("button", { name: /return to positioning/i })).toBeInTheDocument();
|
||||||
await userEvent.click(screen.getByRole("button", { name: /hosted automation/i }));
|
|
||||||
expect(window.location.hash).toBe("#discuss/hosted-automation");
|
|
||||||
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: /return to positioning/i }));
|
await userEvent.click(screen.getByRole("button", { name: /return to positioning/i }));
|
||||||
expect(window.location.hash).toBe("#scene/positioning/lda-position");
|
expect(window.location.hash).toBe("#scene/positioning/landscape");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("uses the parent scene as return location for a directly linked branch", async () => {
|
it("uses the parent scene as return location for a directly linked branch", async () => {
|
||||||
@@ -82,11 +79,11 @@ describe("PresentationRoute", () => {
|
|||||||
expect(window.location.hash).toBe("#scene/positioning/landscape");
|
expect(window.location.hash).toBe("#scene/positioning/landscape");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders stable chat, primary, evidence, and navigation regions", () => {
|
it("renders stable chat, primary, evidence, and progress regions", () => {
|
||||||
render(<PresentationRoute />);
|
render(<PresentationRoute />);
|
||||||
expect(screen.getByLabelText(/agent chat region/i)).toBeInTheDocument();
|
expect(screen.getByLabelText(/agent chat region/i)).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText(/primary presentation region/i)).toBeInTheDocument();
|
expect(screen.getByLabelText(/primary presentation region/i)).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText(/evidence region/i)).toBeInTheDocument();
|
expect(screen.getByLabelText(/evidence region/i)).toBeInTheDocument();
|
||||||
expect(screen.getByLabelText(/presentation scene rail/i)).toBeInTheDocument();
|
expect(screen.getByLabelText("scene position")).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -6,12 +6,9 @@ import { loadCanonicalDemoRecording } from "../demo/timeline/replay.js";
|
|||||||
import { useDemoTimeline } from "../demo/useDemoTimeline.js";
|
import { useDemoTimeline } from "../demo/useDemoTimeline.js";
|
||||||
import { PresentationCanvas } from "./PresentationCanvas.js";
|
import { PresentationCanvas } from "./PresentationCanvas.js";
|
||||||
import { PresentationStage } from "./PresentationStage.js";
|
import { PresentationStage } from "./PresentationStage.js";
|
||||||
import { PresenterControls } from "./PresenterControls.js";
|
|
||||||
import { ChatDock } from "./ChatDock.js";
|
|
||||||
import {
|
import {
|
||||||
initialPresentationState,
|
initialPresentationState,
|
||||||
presentationReducer,
|
presentationReducer,
|
||||||
compositionForState,
|
|
||||||
} from "./presentation-state.js";
|
} from "./presentation-state.js";
|
||||||
import { hashForLocation } from "./storyboard-navigation.js";
|
import { hashForLocation } from "./storyboard-navigation.js";
|
||||||
import { findScene } from "./storyboard.js";
|
import { findScene } from "./storyboard.js";
|
||||||
@@ -93,9 +90,6 @@ export const PresentationRoute = () => {
|
|||||||
dispatch({ type: "previous" });
|
dispatch({ type: "previous" });
|
||||||
} else if (event.key === "Escape") {
|
} else if (event.key === "Escape") {
|
||||||
dispatch({ type: "close_overlay" });
|
dispatch({ type: "close_overlay" });
|
||||||
} else if (event.key === "p" || event.key === "P") {
|
|
||||||
if (!isBodyEvent) return;
|
|
||||||
dispatch({ type: "toggle_controls" });
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
window.addEventListener("keydown", onKeyDown);
|
window.addEventListener("keydown", onKeyDown);
|
||||||
@@ -164,12 +158,6 @@ export const PresentationRoute = () => {
|
|||||||
setEvidence(replayEvidence);
|
setEvidence(replayEvidence);
|
||||||
}, [demo, replayEvidence]);
|
}, [demo, replayEvidence]);
|
||||||
|
|
||||||
const handleResetOverrides = useCallback(() => {
|
|
||||||
dispatch({ type: "set_stage_theme", theme: null });
|
|
||||||
dispatch({ type: "set_chat_theme", theme: null });
|
|
||||||
dispatch({ type: "set_chat_mode", mode: null });
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleResetScene = useCallback(() => {
|
const handleResetScene = useCallback(() => {
|
||||||
if (state.location.kind !== "main") return;
|
if (state.location.kind !== "main") return;
|
||||||
const scene = findScene(state.location.sceneId);
|
const scene = findScene(state.location.sceneId);
|
||||||
@@ -181,9 +169,6 @@ export const PresentationRoute = () => {
|
|||||||
dispatch({ type: "toggle_motion" });
|
dispatch({ type: "toggle_motion" });
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const composition = compositionForState(state);
|
|
||||||
const showChatDock = composition.chatMode === "dock" && state.location.kind !== "discussion";
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<main className="presentation-route" aria-label="lda.chat presentation" data-motion={state.motionDisabled ? "disabled" : "enabled"}>
|
<main className="presentation-route" aria-label="lda.chat presentation" data-motion={state.motionDisabled ? "disabled" : "enabled"}>
|
||||||
<PresentationCanvas>
|
<PresentationCanvas>
|
||||||
@@ -200,25 +185,8 @@ export const PresentationRoute = () => {
|
|||||||
closeOverlay={() => dispatch({ type: "close_overlay" })}
|
closeOverlay={() => dispatch({ type: "close_overlay" })}
|
||||||
openDiscussion={handleOpenDiscussion}
|
openDiscussion={handleOpenDiscussion}
|
||||||
closeDiscussion={handleCloseDiscussion}
|
closeDiscussion={handleCloseDiscussion}
|
||||||
toggleDiscussionIndex={() => dispatch({ type: "toggle_discussion_index" })}
|
|
||||||
/>
|
/>
|
||||||
</PresentationCanvas>
|
</PresentationCanvas>
|
||||||
{showChatDock && <ChatDock openChat={() => dispatch({ type: "set_chat_mode", mode: "rail" })} />}
|
|
||||||
{state.controlsOpen && (
|
|
||||||
<PresenterControls
|
|
||||||
state={state}
|
|
||||||
next={() => dispatch({ type: "next" })}
|
|
||||||
previous={() => dispatch({ type: "previous" })}
|
|
||||||
jump={handleJump}
|
|
||||||
setStageTheme={(theme) => dispatch({ type: "set_stage_theme", theme })}
|
|
||||||
setChatTheme={(theme) => dispatch({ type: "set_chat_theme", theme })}
|
|
||||||
setChatMode={(mode) => dispatch({ type: "set_chat_mode", mode })}
|
|
||||||
forceReplay={handleForceReplay}
|
|
||||||
resetOverrides={handleResetOverrides}
|
|
||||||
resetScene={handleResetScene}
|
|
||||||
toggleMotion={handleToggleMotion}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => agent.startPreparedReplay()}
|
onClick={() => agent.startPreparedReplay()}
|
||||||
|
|||||||
@@ -2,15 +2,14 @@ import { domAnimation, LayoutGroup, LazyMotion } from "motion/react";
|
|||||||
import type { EvidenceRecord } from "../app/state.js";
|
import type { EvidenceRecord } from "../app/state.js";
|
||||||
import type { AgentMessage } from "../demo/agent/events.js";
|
import type { AgentMessage } from "../demo/agent/events.js";
|
||||||
import { SceneBody } from "./SceneBody.js";
|
import { SceneBody } from "./SceneBody.js";
|
||||||
import { SceneRail } from "./SceneRail.js";
|
|
||||||
import { DiscussionIndex } from "./DiscussionIndex.js";
|
|
||||||
import { DiscussionPanel } from "./DiscussionPanel.js";
|
import { DiscussionPanel } from "./DiscussionPanel.js";
|
||||||
import { EvidenceDrawer } from "./EvidenceDrawer.js";
|
import { EvidenceDrawer } from "./EvidenceDrawer.js";
|
||||||
import { OperatorChat } from "./OperatorChat.js";
|
import { OperatorChat } from "./OperatorChat.js";
|
||||||
|
import { SceneProgress } from "./SceneProgress.js";
|
||||||
import type { PresentationState } from "./presentation-state.js";
|
import type { PresentationState } from "./presentation-state.js";
|
||||||
import { compositionForState } from "./presentation-state.js";
|
import { compositionForState } from "./presentation-state.js";
|
||||||
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
import type { DemoTimelineController } from "../demo/useDemoTimeline.js";
|
||||||
import { findScene, type PresentationLocation } from "./storyboard.js";
|
import { findScene, type MainLocation, type PresentationLocation } from "./storyboard.js";
|
||||||
|
|
||||||
type PresentationStageProps = {
|
type PresentationStageProps = {
|
||||||
readonly state: PresentationState;
|
readonly state: PresentationState;
|
||||||
@@ -25,7 +24,6 @@ type PresentationStageProps = {
|
|||||||
readonly closeOverlay: () => void;
|
readonly closeOverlay: () => void;
|
||||||
readonly openDiscussion: (branchId: string) => void;
|
readonly openDiscussion: (branchId: string) => void;
|
||||||
readonly closeDiscussion: () => void;
|
readonly closeDiscussion: () => void;
|
||||||
readonly toggleDiscussionIndex: () => void;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PresentationStage = ({
|
export const PresentationStage = ({
|
||||||
@@ -41,7 +39,6 @@ export const PresentationStage = ({
|
|||||||
closeOverlay,
|
closeOverlay,
|
||||||
openDiscussion,
|
openDiscussion,
|
||||||
closeDiscussion,
|
closeDiscussion,
|
||||||
toggleDiscussionIndex,
|
|
||||||
}: PresentationStageProps) => {
|
}: PresentationStageProps) => {
|
||||||
const composition = compositionForState(state);
|
const composition = compositionForState(state);
|
||||||
|
|
||||||
@@ -55,8 +52,6 @@ export const PresentationStage = ({
|
|||||||
<LayoutGroup id="presentation-stage">
|
<LayoutGroup id="presentation-stage">
|
||||||
<div
|
<div
|
||||||
className="presentation-stage"
|
className="presentation-stage"
|
||||||
data-stage-theme={composition.stageTheme}
|
|
||||||
data-chat-theme={composition.chatTheme}
|
|
||||||
data-chat-mode={composition.chatMode}
|
data-chat-mode={composition.chatMode}
|
||||||
data-evidence-mode={composition.evidenceMode}
|
data-evidence-mode={composition.evidenceMode}
|
||||||
data-scene-view={activeSceneView}
|
data-scene-view={activeSceneView}
|
||||||
@@ -68,45 +63,27 @@ export const PresentationStage = ({
|
|||||||
{state.location.kind === "discussion" ? (
|
{state.location.kind === "discussion" ? (
|
||||||
<DiscussionPanel branchId={state.location.branchId} onClose={closeDiscussion} />
|
<DiscussionPanel branchId={state.location.branchId} onClose={closeDiscussion} />
|
||||||
) : (
|
) : (
|
||||||
<>
|
<SceneBody
|
||||||
<SceneBody
|
location={state.location}
|
||||||
location={state.location}
|
demo={demo}
|
||||||
demo={demo}
|
selectedNodeId={state.selectedNodeId}
|
||||||
selectedNodeId={state.selectedNodeId}
|
selectNode={selectNode}
|
||||||
selectNode={selectNode}
|
openEvidence={openEvidence}
|
||||||
openEvidence={openEvidence}
|
onFocusPathChange={(path) => {
|
||||||
onFocusPathChange={(path) => {
|
if (state.location.kind === "main") {
|
||||||
if (state.location.kind === "main") {
|
jump({ ...state.location, focusPath: path });
|
||||||
jump({ ...state.location, focusPath: path });
|
}
|
||||||
}
|
|
||||||
}}
|
|
||||||
motionDisabled={false}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
className="presentation-stage__discussion-toggle"
|
|
||||||
onClick={toggleDiscussionIndex}
|
|
||||||
>
|
|
||||||
Discussion topics
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
{state.discussionIndexOpen && (
|
|
||||||
<DiscussionIndex
|
|
||||||
onSelect={(branchId) => {
|
|
||||||
toggleDiscussionIndex();
|
|
||||||
openDiscussion(branchId);
|
|
||||||
}}
|
}}
|
||||||
|
motionDisabled={false}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<p className="presentation-stage__mode">
|
|
||||||
{demo.state.mode === "replay" ? "Replay" : "Live"} · {demo.state.phase}
|
|
||||||
</p>
|
|
||||||
</section>
|
</section>
|
||||||
<aside className="presentation-stage__evidence" aria-label="evidence region">
|
<aside className="presentation-stage__evidence" aria-label="evidence region">
|
||||||
<EvidenceDrawer records={evidence} mode={composition.evidenceMode} close={closeOverlay} />
|
<EvidenceDrawer records={evidence} mode={composition.evidenceMode} close={closeOverlay} />
|
||||||
</aside>
|
</aside>
|
||||||
<SceneRail location={state.location} jump={jump} />
|
{state.location.kind === "main" && (
|
||||||
|
<SceneProgress location={state.location} />
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</LayoutGroup>
|
</LayoutGroup>
|
||||||
</LazyMotion>
|
</LazyMotion>
|
||||||
|
|||||||
@@ -1,48 +0,0 @@
|
|||||||
import { cleanup, render, screen } from "@testing-library/react";
|
|
||||||
import userEvent from "@testing-library/user-event";
|
|
||||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
||||||
import { PresenterControls } from "./PresenterControls.js";
|
|
||||||
import { ChatDock } from "./ChatDock.js";
|
|
||||||
import { initialPresentationState } from "./presentation-state.js";
|
|
||||||
|
|
||||||
afterEach(() => cleanup());
|
|
||||||
|
|
||||||
describe("PresenterControls", () => {
|
|
||||||
const props = {
|
|
||||||
state: initialPresentationState,
|
|
||||||
next: vi.fn(),
|
|
||||||
previous: vi.fn(),
|
|
||||||
jump: vi.fn(),
|
|
||||||
setStageTheme: vi.fn(),
|
|
||||||
setChatTheme: vi.fn(),
|
|
||||||
setChatMode: vi.fn(),
|
|
||||||
forceReplay: vi.fn(),
|
|
||||||
resetOverrides: vi.fn(),
|
|
||||||
resetScene: vi.fn(),
|
|
||||||
toggleMotion: vi.fn(),
|
|
||||||
};
|
|
||||||
|
|
||||||
it("changes stage and chat themes independently", async () => {
|
|
||||||
render(<PresenterControls {...props} />);
|
|
||||||
await userEvent.selectOptions(screen.getByLabelText(/stage theme/i), "night");
|
|
||||||
await userEvent.selectOptions(screen.getByLabelText(/chat theme/i), "light");
|
|
||||||
expect(props.setStageTheme).toHaveBeenCalledWith("night");
|
|
||||||
expect(props.setChatTheme).toHaveBeenCalledWith("light");
|
|
||||||
});
|
|
||||||
|
|
||||||
it("forces replay without changing the current presentation location", async () => {
|
|
||||||
render(<PresenterControls {...props} />);
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: /force replay fallback/i }));
|
|
||||||
expect(props.forceReplay).toHaveBeenCalledTimes(1);
|
|
||||||
expect(props.jump).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("ChatDock", () => {
|
|
||||||
it("opens docked chat by click and keyboard", async () => {
|
|
||||||
const openChat = vi.fn();
|
|
||||||
render(<ChatDock openChat={openChat} />);
|
|
||||||
await userEvent.click(screen.getByRole("button", { name: /open agent chat/i }));
|
|
||||||
expect(openChat).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
@@ -1,134 +0,0 @@
|
|||||||
import { useEffect, useRef, useState } from "react";
|
|
||||||
import type { PresentationState } from "./presentation-state.js";
|
|
||||||
import { compositionForState } from "./presentation-state.js";
|
|
||||||
import type { ChatMode, ChatTheme, MainLocation, PresentationLocation, StageTheme } from "./storyboard.js";
|
|
||||||
import { findScene, mainScenes } from "./storyboard.js";
|
|
||||||
|
|
||||||
type PresenterControlsProps = {
|
|
||||||
readonly state: PresentationState;
|
|
||||||
readonly next: () => void;
|
|
||||||
readonly previous: () => void;
|
|
||||||
readonly jump: (location: PresentationLocation) => void;
|
|
||||||
readonly setStageTheme: (theme: StageTheme | null) => void;
|
|
||||||
readonly setChatTheme: (theme: ChatTheme | null) => void;
|
|
||||||
readonly setChatMode: (mode: ChatMode | null) => void;
|
|
||||||
readonly forceReplay: () => void;
|
|
||||||
readonly resetOverrides: () => void;
|
|
||||||
readonly resetScene: () => void;
|
|
||||||
readonly toggleMotion: () => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const PresenterControls = ({
|
|
||||||
state,
|
|
||||||
next,
|
|
||||||
previous,
|
|
||||||
jump,
|
|
||||||
setStageTheme,
|
|
||||||
setChatTheme,
|
|
||||||
setChatMode,
|
|
||||||
forceReplay,
|
|
||||||
resetOverrides,
|
|
||||||
resetScene,
|
|
||||||
toggleMotion,
|
|
||||||
}: PresenterControlsProps) => {
|
|
||||||
const composition = compositionForState(state);
|
|
||||||
const isMain = state.location.kind === "main";
|
|
||||||
const currentScene = isMain ? findScene(state.location.sceneId) : null;
|
|
||||||
const currentBeat = currentScene?.beats.find((b) => b.id === (state.location as MainLocation).beatId);
|
|
||||||
const beatIndex = currentScene && isMain
|
|
||||||
? currentScene.beats.findIndex((b) => b.id === (state.location as MainLocation).beatId)
|
|
||||||
: -1;
|
|
||||||
const totalBeats = currentScene?.beats.length ?? 0;
|
|
||||||
const sceneNumber = currentScene?.number ?? 0;
|
|
||||||
const totalScenes = mainScenes.length;
|
|
||||||
|
|
||||||
const [elapsed, setElapsed] = useState(0);
|
|
||||||
useEffect(() => {
|
|
||||||
const interval = setInterval(() => {
|
|
||||||
setElapsed(Math.floor((Date.now() - state.startedAt) / 1000));
|
|
||||||
}, 1000);
|
|
||||||
return () => clearInterval(interval);
|
|
||||||
}, [state.startedAt]);
|
|
||||||
|
|
||||||
const formatTime = (seconds: number) => {
|
|
||||||
const m = Math.floor(seconds / 60);
|
|
||||||
const s = seconds % 60;
|
|
||||||
return `${m}:${s.toString().padStart(2, "0")}`;
|
|
||||||
};
|
|
||||||
|
|
||||||
const plannedMinutes = 15;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="presenter-controls" role="dialog" aria-label="presenter controls">
|
|
||||||
<div className="presenter-controls__nav">
|
|
||||||
<button type="button" onClick={previous}>Previous</button>
|
|
||||||
<button type="button" onClick={next}>Next</button>
|
|
||||||
</div>
|
|
||||||
<div className="presenter-controls__info">
|
|
||||||
<span>Scene {sceneNumber}/{totalScenes}: {currentScene?.title ?? "Discussion"}</span>
|
|
||||||
{currentBeat && <span> · {beatIndex + 1}/{totalBeats} {currentBeat.title}</span>}
|
|
||||||
</div>
|
|
||||||
<div className="presenter-controls__timer">
|
|
||||||
<span>Elapsed: {formatTime(elapsed)}</span>
|
|
||||||
<span> / {plannedMinutes}:00 planned</span>
|
|
||||||
</div>
|
|
||||||
<div className="presenter-controls__notes">
|
|
||||||
<label>
|
|
||||||
Speaker notes
|
|
||||||
<textarea
|
|
||||||
key={currentBeat?.id ?? "none"}
|
|
||||||
defaultValue={currentBeat?.caption ?? ""}
|
|
||||||
rows={3}
|
|
||||||
placeholder="Add notes for this beat..."
|
|
||||||
/>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="presenter-controls__over">
|
|
||||||
<label>
|
|
||||||
Stage theme
|
|
||||||
<select
|
|
||||||
value={state.stageThemeOverride ?? "scene default"}
|
|
||||||
onChange={(e) => setStageTheme(e.target.value === "scene default" ? null : e.target.value as StageTheme)}
|
|
||||||
>
|
|
||||||
<option value="scene default">scene default</option>
|
|
||||||
<option value="paper">paper</option>
|
|
||||||
<option value="night">night</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Chat theme
|
|
||||||
<select
|
|
||||||
value={state.chatThemeOverride ?? "scene default"}
|
|
||||||
onChange={(e) => setChatTheme(e.target.value === "scene default" ? null : e.target.value as ChatTheme)}
|
|
||||||
>
|
|
||||||
<option value="scene default">scene default</option>
|
|
||||||
<option value="light">light</option>
|
|
||||||
<option value="dark">dark</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
<label>
|
|
||||||
Chat mode
|
|
||||||
<select
|
|
||||||
value={state.chatModeOverride ?? "scene default"}
|
|
||||||
onChange={(e) => setChatMode(e.target.value === "scene default" ? null : e.target.value as ChatMode)}
|
|
||||||
>
|
|
||||||
<option value="scene default">scene default</option>
|
|
||||||
<option value="hidden">hidden</option>
|
|
||||||
<option value="full">full</option>
|
|
||||||
<option value="rail">rail</option>
|
|
||||||
<option value="dock">dock</option>
|
|
||||||
</select>
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
<div className="presenter-controls__actions">
|
|
||||||
<span>{state.playbackMode === "replay" ? "Replay" : "Live"}</span>
|
|
||||||
<button type="button" onClick={forceReplay}>Force replay fallback</button>
|
|
||||||
<button type="button" onClick={resetScene}>Reset scene</button>
|
|
||||||
<button type="button" onClick={toggleMotion}>
|
|
||||||
Motion: {state.motionDisabled ? "off" : "on"}
|
|
||||||
</button>
|
|
||||||
<button type="button" onClick={resetOverrides}>Reset overrides</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
import { cleanup, render, screen } from "@testing-library/react";
|
||||||
|
import { afterEach, describe, expect, it } from "vitest";
|
||||||
|
import { SceneProgress } from "./SceneProgress.js";
|
||||||
|
import type { MainLocation } from "./storyboard.js";
|
||||||
|
|
||||||
|
afterEach(() => cleanup());
|
||||||
|
|
||||||
|
describe("SceneProgress", () => {
|
||||||
|
it("shows scene and beat position for architecture/runtime", () => {
|
||||||
|
const location: MainLocation = {
|
||||||
|
kind: "main",
|
||||||
|
sceneId: "architecture",
|
||||||
|
beatId: "runtime",
|
||||||
|
focusPath: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<SceneProgress location={location} />);
|
||||||
|
|
||||||
|
expect(screen.getByText("6 / 12")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("3 / 4")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows scene position for workflow-demo/graph", () => {
|
||||||
|
const location: MainLocation = {
|
||||||
|
kind: "main",
|
||||||
|
sceneId: "workflow-demo",
|
||||||
|
beatId: "graph",
|
||||||
|
focusPath: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<SceneProgress location={location} />);
|
||||||
|
|
||||||
|
expect(screen.getByText("9 / 12")).toBeInTheDocument();
|
||||||
|
expect(screen.getByText("2 / 3")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has an accessible label", () => {
|
||||||
|
const location: MainLocation = {
|
||||||
|
kind: "main",
|
||||||
|
sceneId: "architecture",
|
||||||
|
beatId: "runtime",
|
||||||
|
focusPath: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
render(<SceneProgress location={location} />);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText("scene position")).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
import { findScene, mainScenes, type MainLocation } from "./storyboard.js";
|
||||||
|
|
||||||
|
type SceneProgressProps = {
|
||||||
|
readonly location: MainLocation;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const SceneProgress = ({ location }: SceneProgressProps) => {
|
||||||
|
const scene = findScene(location.sceneId);
|
||||||
|
const sceneIndex = mainScenes.findIndex((s) => s.id === location.sceneId);
|
||||||
|
const totalScenes = mainScenes.length;
|
||||||
|
const beatIndex = scene?.beats.findIndex((b) => b.id === location.beatId) ?? -1;
|
||||||
|
const totalBeats = scene?.beats.length ?? 0;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<p className="scene-progress" aria-label="scene position">
|
||||||
|
{sceneIndex >= 0 && (
|
||||||
|
<span className="scene-progress__scene">
|
||||||
|
{sceneIndex + 1} / {totalScenes}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
{totalBeats > 1 && (
|
||||||
|
<span className="scene-progress__beat">
|
||||||
|
{beatIndex >= 0 ? beatIndex + 1 : 1} / {totalBeats}
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</p>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
import { mainScenes, type MainLocation, type PresentationLocation } from "./storyboard.js";
|
|
||||||
|
|
||||||
type SceneRailProps = {
|
|
||||||
readonly location: PresentationLocation;
|
|
||||||
readonly jump: (location: MainLocation) => void;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const SceneRail = ({ location, jump }: SceneRailProps) => {
|
|
||||||
const activeSceneId = location.kind === "main" ? location.sceneId : "positioning";
|
|
||||||
return (
|
|
||||||
<nav className="scene-rail" aria-label="presentation scene rail">
|
|
||||||
{mainScenes.map((scene) => {
|
|
||||||
const isActive = scene.id === activeSceneId;
|
|
||||||
const activeBeatId =
|
|
||||||
isActive && location.kind === "main" ? location.beatId : scene.beats[0]!.id;
|
|
||||||
return (
|
|
||||||
<button
|
|
||||||
key={scene.id}
|
|
||||||
type="button"
|
|
||||||
data-active={isActive}
|
|
||||||
aria-current={isActive ? "step" : undefined}
|
|
||||||
onClick={() => jump({ kind: "main", sceneId: scene.id as MainLocation["sceneId"], beatId: scene.beats[0]!.id, focusPath: scene.beats[0]!.figure?.focusPath ?? [] })}
|
|
||||||
className="scene-rail__scene"
|
|
||||||
>
|
|
||||||
<span className="scene-rail__number">{scene.number}</span>
|
|
||||||
<small className="scene-rail__title">{scene.title}</small>
|
|
||||||
<span className="scene-rail__progress">
|
|
||||||
{scene.beats.map((beat) => (
|
|
||||||
<span
|
|
||||||
key={beat.id}
|
|
||||||
className={`scene-rail__beat ${beat.id === activeBeatId ? "scene-rail__beat--active" : ""}`}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</span>
|
|
||||||
</button>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -111,14 +111,12 @@ describe("presentationReducer", () => {
|
|||||||
expect(nexted.location).toEqual(opened.location);
|
expect(nexted.location).toEqual(opened.location);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("derives act and chat composition from the current beat", () => {
|
it("derives chatMode from the current beat", () => {
|
||||||
const state = presentationReducer(initialPresentationState, {
|
const state = presentationReducer(initialPresentationState, {
|
||||||
type: "jump",
|
type: "jump",
|
||||||
location: { kind: "main", sceneId: "workflow-demo", beatId: "graph", focusPath: [] },
|
location: { kind: "main", sceneId: "workflow-demo", beatId: "graph", focusPath: [] },
|
||||||
});
|
});
|
||||||
expect(compositionForState(state)).toMatchObject({
|
expect(compositionForState(state)).toMatchObject({
|
||||||
stageTheme: "night",
|
|
||||||
chatTheme: "light",
|
|
||||||
chatMode: "rail",
|
chatMode: "rail",
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,12 +3,10 @@ import {
|
|||||||
findDiscussionBranch,
|
findDiscussionBranch,
|
||||||
findScene,
|
findScene,
|
||||||
type ChatMode,
|
type ChatMode,
|
||||||
type ChatTheme,
|
|
||||||
type DiscussionBranchId,
|
type DiscussionBranchId,
|
||||||
type EvidenceMode,
|
type EvidenceMode,
|
||||||
type MainLocation,
|
type MainLocation,
|
||||||
type PresentationLocation,
|
type PresentationLocation,
|
||||||
type StageTheme,
|
|
||||||
} from "./storyboard.js";
|
} from "./storyboard.js";
|
||||||
import {
|
import {
|
||||||
hashForLocation,
|
hashForLocation,
|
||||||
@@ -23,11 +21,6 @@ export type PresentationState = {
|
|||||||
readonly selectedNodeId: string | null;
|
readonly selectedNodeId: string | null;
|
||||||
readonly evidenceModeOverride: EvidenceMode | null;
|
readonly evidenceModeOverride: EvidenceMode | null;
|
||||||
readonly playbackMode: "replay" | "live";
|
readonly playbackMode: "replay" | "live";
|
||||||
readonly stageThemeOverride: StageTheme | null;
|
|
||||||
readonly chatThemeOverride: ChatTheme | null;
|
|
||||||
readonly chatModeOverride: ChatMode | null;
|
|
||||||
readonly controlsOpen: boolean;
|
|
||||||
readonly discussionIndexOpen: boolean;
|
|
||||||
readonly motionDisabled: boolean;
|
readonly motionDisabled: boolean;
|
||||||
readonly startedAt: number;
|
readonly startedAt: number;
|
||||||
};
|
};
|
||||||
@@ -44,12 +37,7 @@ export type PresentationAction =
|
|||||||
| { readonly type: "set_evidence_mode"; readonly mode: EvidenceMode }
|
| { readonly type: "set_evidence_mode"; readonly mode: EvidenceMode }
|
||||||
| { readonly type: "close_overlay" }
|
| { readonly type: "close_overlay" }
|
||||||
| { readonly type: "set_playback_mode"; readonly mode: PresentationState["playbackMode"] }
|
| { readonly type: "set_playback_mode"; readonly mode: PresentationState["playbackMode"] }
|
||||||
| { readonly type: "set_stage_theme"; readonly theme: StageTheme | null }
|
|
||||||
| { readonly type: "set_chat_theme"; readonly theme: ChatTheme | null }
|
|
||||||
| { readonly type: "set_chat_mode"; readonly mode: ChatMode | null }
|
|
||||||
| { readonly type: "set_focus_path"; readonly path: readonly string[] }
|
| { readonly type: "set_focus_path"; readonly path: readonly string[] }
|
||||||
| { readonly type: "toggle_controls" }
|
|
||||||
| { readonly type: "toggle_discussion_index" }
|
|
||||||
| { readonly type: "toggle_motion" };
|
| { readonly type: "toggle_motion" };
|
||||||
|
|
||||||
export const initialPresentationState: PresentationState = {
|
export const initialPresentationState: PresentationState = {
|
||||||
@@ -58,11 +46,6 @@ export const initialPresentationState: PresentationState = {
|
|||||||
selectedNodeId: null,
|
selectedNodeId: null,
|
||||||
evidenceModeOverride: null,
|
evidenceModeOverride: null,
|
||||||
playbackMode: "replay",
|
playbackMode: "replay",
|
||||||
stageThemeOverride: null,
|
|
||||||
chatThemeOverride: null,
|
|
||||||
chatModeOverride: null,
|
|
||||||
controlsOpen: false,
|
|
||||||
discussionIndexOpen: false,
|
|
||||||
motionDisabled: false,
|
motionDisabled: false,
|
||||||
startedAt: Date.now(),
|
startedAt: Date.now(),
|
||||||
};
|
};
|
||||||
@@ -70,31 +53,20 @@ export const initialPresentationState: PresentationState = {
|
|||||||
const compositionForLocation = (
|
const compositionForLocation = (
|
||||||
location: PresentationLocation,
|
location: PresentationLocation,
|
||||||
evidenceOverride: EvidenceMode | null,
|
evidenceOverride: EvidenceMode | null,
|
||||||
stageThemeOverride: StageTheme | null,
|
|
||||||
chatThemeOverride: ChatTheme | null,
|
|
||||||
chatModeOverride: ChatMode | null,
|
|
||||||
): {
|
): {
|
||||||
readonly stageTheme: StageTheme;
|
|
||||||
readonly chatTheme: ChatTheme;
|
|
||||||
readonly chatMode: ChatMode;
|
readonly chatMode: ChatMode;
|
||||||
readonly evidenceMode: EvidenceMode;
|
readonly evidenceMode: EvidenceMode;
|
||||||
} => {
|
} => {
|
||||||
if (location.kind === "discussion") {
|
if (location.kind === "discussion") {
|
||||||
const branch = findDiscussionBranch(location.branchId);
|
|
||||||
const parentScene = branch ? findScene(branch.parentSceneId) : findScene("positioning");
|
|
||||||
return {
|
return {
|
||||||
stageTheme: stageThemeOverride ?? parentScene?.stageTheme ?? "paper",
|
chatMode: "hidden",
|
||||||
chatTheme: chatThemeOverride ?? "dark",
|
|
||||||
chatMode: chatModeOverride ?? "hidden",
|
|
||||||
evidenceMode: evidenceOverride ?? "hidden",
|
evidenceMode: evidenceOverride ?? "hidden",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
const scene = findScene(location.sceneId);
|
const scene = findScene(location.sceneId);
|
||||||
const beat = scene?.beats.find((b) => b.id === location.beatId);
|
const beat = scene?.beats.find((b) => b.id === location.beatId);
|
||||||
return {
|
return {
|
||||||
stageTheme: stageThemeOverride ?? scene?.stageTheme ?? "paper",
|
chatMode: beat?.chatMode ?? "hidden",
|
||||||
chatTheme: chatThemeOverride ?? beat?.chatTheme ?? "dark",
|
|
||||||
chatMode: chatModeOverride ?? beat?.chatMode ?? "hidden",
|
|
||||||
evidenceMode: evidenceOverride ?? beat?.evidenceMode ?? "hidden",
|
evidenceMode: evidenceOverride ?? beat?.evidenceMode ?? "hidden",
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -103,9 +75,6 @@ export const compositionForState = (state: PresentationState) =>
|
|||||||
compositionForLocation(
|
compositionForLocation(
|
||||||
state.location,
|
state.location,
|
||||||
state.evidenceModeOverride,
|
state.evidenceModeOverride,
|
||||||
state.stageThemeOverride,
|
|
||||||
state.chatThemeOverride,
|
|
||||||
state.chatModeOverride,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
const isValidMainLocation = (location: PresentationLocation): location is MainLocation =>
|
const isValidMainLocation = (location: PresentationLocation): location is MainLocation =>
|
||||||
@@ -190,8 +159,6 @@ export const presentationReducer = (
|
|||||||
return beat?.evidenceMode === "open" || beat?.evidenceMode === "peek";
|
return beat?.evidenceMode === "open" || beat?.evidenceMode === "peek";
|
||||||
})();
|
})();
|
||||||
if (isEvidenceVisible) return { ...state, evidenceModeOverride: "hidden" };
|
if (isEvidenceVisible) return { ...state, evidenceModeOverride: "hidden" };
|
||||||
if (state.discussionIndexOpen) return { ...state, discussionIndexOpen: false };
|
|
||||||
if (state.controlsOpen) return { ...state, controlsOpen: false };
|
|
||||||
if (state.location.kind === "discussion") {
|
if (state.location.kind === "discussion") {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@@ -203,22 +170,12 @@ export const presentationReducer = (
|
|||||||
}
|
}
|
||||||
case "set_playback_mode":
|
case "set_playback_mode":
|
||||||
return { ...state, playbackMode: action.mode };
|
return { ...state, playbackMode: action.mode };
|
||||||
case "set_stage_theme":
|
|
||||||
return { ...state, stageThemeOverride: action.theme };
|
|
||||||
case "set_chat_theme":
|
|
||||||
return { ...state, chatThemeOverride: action.theme };
|
|
||||||
case "set_chat_mode":
|
|
||||||
return { ...state, chatModeOverride: action.mode };
|
|
||||||
case "set_focus_path":
|
case "set_focus_path":
|
||||||
if (state.location.kind !== "main") return state;
|
if (state.location.kind !== "main") return state;
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
location: { ...state.location, focusPath: action.path },
|
location: { ...state.location, focusPath: action.path },
|
||||||
};
|
};
|
||||||
case "toggle_controls":
|
|
||||||
return { ...state, controlsOpen: !state.controlsOpen };
|
|
||||||
case "toggle_discussion_index":
|
|
||||||
return { ...state, discussionIndexOpen: !state.discussionIndexOpen };
|
|
||||||
case "toggle_motion":
|
case "toggle_motion":
|
||||||
return { ...state, motionDisabled: !state.motionDisabled };
|
return { ...state, motionDisabled: !state.motionDisabled };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,13 +18,6 @@
|
|||||||
--failure: oklch(0.65 0.2 25);
|
--failure: oklch(0.65 0.2 25);
|
||||||
--chat-width: 18rem;
|
--chat-width: 18rem;
|
||||||
--evidence-width: 0rem;
|
--evidence-width: 0rem;
|
||||||
/* Paper theme tokens */
|
|
||||||
--paper-bg: oklch(0.97 0.005 80);
|
|
||||||
--paper-text: oklch(0.15 0.01 80);
|
|
||||||
--paper-muted: oklch(0.5 0.03 80);
|
|
||||||
--paper-accent: oklch(0.45 0.14 80);
|
|
||||||
--paper-border: oklch(0.75 0.04 80);
|
|
||||||
--paper-surface: oklch(0.92 0.01 80);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-stage {
|
.presentation-stage {
|
||||||
@@ -32,193 +25,14 @@
|
|||||||
min-height: 0;
|
min-height: 0;
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template:
|
grid-template:
|
||||||
"chat primary evidence" minmax(0, 1fr)
|
"chat primary evidence" minmax(0, 1fr) /
|
||||||
"rail rail rail" auto /
|
|
||||||
var(--chat-width) minmax(0, 1fr) var(--evidence-width);
|
var(--chat-width) minmax(0, 1fr) var(--evidence-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Stage themes */
|
|
||||||
.presentation-stage[data-stage-theme="paper"] {
|
|
||||||
background: var(--paper-bg);
|
|
||||||
color: var(--paper-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="night"] {
|
|
||||||
background: var(--stage-canvas);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Paper theme child-element overrides */
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .stage-caption__eyebrow {
|
|
||||||
color: var(--paper-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .stage-caption h1 {
|
|
||||||
color: var(--paper-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__evidence {
|
|
||||||
color: var(--paper-muted);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-rail__scene {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
background: oklch(0.92 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-rail__scene[data-active="true"] {
|
|
||||||
background: oklch(0.35 0.12 80);
|
|
||||||
color: oklch(0.97 0.005 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-rail__beat {
|
|
||||||
background: oklch(0.7 0.04 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-rail__beat--active {
|
|
||||||
background: oklch(0.3 0.08 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .presentation-stage__mode {
|
|
||||||
color: oklch(0.5 0.03 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .presentation-stage__discussion-toggle {
|
|
||||||
border-color: oklch(0.7 0.05 80);
|
|
||||||
background: oklch(0.9 0.01 80);
|
|
||||||
color: oklch(0.2 0.02 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Paper theme scene-body element overrides */
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__positioning-card {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
background: oklch(0.92 0.01 80);
|
|
||||||
color: oklch(0.15 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__positioning-card span {
|
|
||||||
color: oklch(0.4 0.03 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__positioning-card--active {
|
|
||||||
border-color: oklch(0.45 0.14 80);
|
|
||||||
background: oklch(0.85 0.04 80);
|
|
||||||
color: oklch(0.12 0.02 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__boundary-side {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
background: oklch(0.92 0.01 80);
|
|
||||||
color: oklch(0.15 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__boundary-side--dim {
|
|
||||||
opacity: 0.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__boundary-divider {
|
|
||||||
background: oklch(0.5 0.08 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-stage,
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-step {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
background: oklch(0.92 0.01 80);
|
|
||||||
color: oklch(0.15 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-number,
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-number {
|
|
||||||
background: oklch(0.45 0.14 80);
|
|
||||||
color: oklch(0.97 0.005 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-arrow,
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-arrow {
|
|
||||||
color: oklch(0.5 0.06 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__lifecycle-stage--active,
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__authoring-step--active {
|
|
||||||
border-color: oklch(0.45 0.14 80);
|
|
||||||
background: oklch(0.85 0.04 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-layer {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
background: oklch(0.92 0.01 80);
|
|
||||||
color: oklch(0.15 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-layer--node {
|
|
||||||
border-color: oklch(0.45 0.14 80);
|
|
||||||
background: oklch(0.85 0.04 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-layer--active {
|
|
||||||
border-color: oklch(0.45 0.14 80);
|
|
||||||
background: oklch(0.85 0.04 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__architecture-arrow {
|
|
||||||
color: oklch(0.5 0.06 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
background: oklch(0.92 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat strong {
|
|
||||||
color: oklch(0.45 0.14 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat span {
|
|
||||||
color: oklch(0.4 0.03 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-stage-theme="paper"] .scene-body__evaluation-stat--active {
|
|
||||||
border-color: oklch(0.45 0.14 80);
|
|
||||||
background: oklch(0.85 0.04 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chat themes */
|
|
||||||
.presentation-stage[data-chat-theme="light"] .presentation-stage__chat,
|
|
||||||
.operator-chat[data-chat-theme="light"] {
|
|
||||||
background: oklch(0.96 0.005 80);
|
|
||||||
color: oklch(0.18 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="dark"] .presentation-stage__chat,
|
|
||||||
.operator-chat[data-chat-theme="dark"] {
|
|
||||||
background: oklch(0.14 0.025 250);
|
|
||||||
color: oklch(0.92 0.01 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Light chat message overrides */
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-message,
|
|
||||||
.operator-chat[data-chat-theme="light"] .chat-message {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
background: oklch(0.92 0.01 80);
|
|
||||||
color: oklch(0.18 0.01 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Light chat tool-part overrides */
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-tool-part,
|
|
||||||
.operator-chat[data-chat-theme="light"] .chat-tool-part {
|
|
||||||
border-color: oklch(0.75 0.04 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-tool-part span,
|
|
||||||
.operator-chat[data-chat-theme="light"] .chat-tool-part span {
|
|
||||||
color: oklch(0.45 0.03 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Chat modes */
|
/* Chat modes */
|
||||||
.presentation-stage[data-chat-mode="hidden"] {
|
.presentation-stage[data-chat-mode="hidden"] {
|
||||||
grid-template:
|
grid-template:
|
||||||
"primary evidence" minmax(0, 1fr)
|
"primary evidence" minmax(0, 1fr) /
|
||||||
"rail rail" auto /
|
|
||||||
minmax(0, 1fr) var(--evidence-width);
|
minmax(0, 1fr) var(--evidence-width);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -230,17 +44,6 @@
|
|||||||
--chat-width: 16rem;
|
--chat-width: 16rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-stage[data-chat-mode="dock"] {
|
|
||||||
grid-template:
|
|
||||||
"primary evidence" minmax(0, 1fr)
|
|
||||||
"rail rail" auto /
|
|
||||||
minmax(0, 1fr) var(--evidence-width);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-mode="dock"] .presentation-stage__chat {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Evidence modes */
|
/* Evidence modes */
|
||||||
.presentation-stage[data-evidence-mode="peek"] {
|
.presentation-stage[data-evidence-mode="peek"] {
|
||||||
--evidence-width: 20rem;
|
--evidence-width: 20rem;
|
||||||
@@ -313,59 +116,6 @@
|
|||||||
font-size: 0.85rem;
|
font-size: 0.85rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scene-rail {
|
|
||||||
grid-area: rail;
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25rem;
|
|
||||||
padding: 0.35rem 0.75rem;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__scene {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.15rem;
|
|
||||||
min-width: 4.5rem;
|
|
||||||
text-align: center;
|
|
||||||
border: 1px solid oklch(0.36 0.04 250);
|
|
||||||
background: oklch(0.16 0.025 250);
|
|
||||||
color: inherit;
|
|
||||||
padding: 0.35rem 0.5rem;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
font-size: 0.7rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__scene[data-active="true"] {
|
|
||||||
background: oklch(0.7 0.16 195);
|
|
||||||
color: oklch(0.12 0.025 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__number {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__title {
|
|
||||||
font-size: 0.6rem;
|
|
||||||
opacity: 0.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__progress {
|
|
||||||
display: flex;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__beat {
|
|
||||||
width: 4px;
|
|
||||||
height: 4px;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: oklch(0.5 0.04 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__beat--active {
|
|
||||||
background: oklch(0.9 0.06 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.operation-block {
|
.operation-block {
|
||||||
border: 1px solid var(--stage-line);
|
border: 1px solid var(--stage-line);
|
||||||
background: var(--stage-surface);
|
background: var(--stage-surface);
|
||||||
@@ -601,25 +351,23 @@
|
|||||||
font-size: 0.8rem;
|
font-size: 0.8rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-stage__mode {
|
.scene-progress {
|
||||||
flex-shrink: 0;
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
font-size: 0.75rem;
|
font-size: 0.75rem;
|
||||||
color: oklch(0.65 0.03 250);
|
color: oklch(0.65 0.03 250);
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
padding: 0 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presentation-stage__discussion-toggle {
|
.scene-progress__scene {
|
||||||
flex-shrink: 0;
|
font-weight: 600;
|
||||||
align-self: flex-start;
|
}
|
||||||
font-size: 0.8rem;
|
|
||||||
padding: 0.35rem 0.65rem;
|
.scene-progress__beat {
|
||||||
border: 1px solid oklch(0.4 0.05 250);
|
opacity: 0.8;
|
||||||
background: oklch(0.2 0.03 250);
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion-index,
|
|
||||||
.discussion-panel {
|
.discussion-panel {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
border: 1px solid oklch(0.36 0.04 250);
|
border: 1px solid oklch(0.36 0.04 250);
|
||||||
@@ -627,36 +375,11 @@
|
|||||||
border-radius: 0.65rem;
|
border-radius: 0.65rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion-index h2,
|
|
||||||
.discussion-panel h2 {
|
.discussion-panel h2 {
|
||||||
font-size: 1.1rem;
|
font-size: 1.1rem;
|
||||||
margin: 0 0 0.5rem;
|
margin: 0 0 0.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
.discussion-index h3 {
|
|
||||||
font-size: 0.9rem;
|
|
||||||
margin: 0.5rem 0 0.25rem;
|
|
||||||
color: oklch(0.78 0.035 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.discussion-index ul {
|
|
||||||
list-style: none;
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discussion-index button {
|
|
||||||
padding: 0.3rem 0.6rem;
|
|
||||||
border: 1px solid oklch(0.4 0.05 250);
|
|
||||||
background: oklch(0.2 0.03 250);
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.4rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.discussion-panel__badge {
|
.discussion-panel__badge {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
padding: 0.15rem 0.4rem;
|
padding: 0.15rem 0.4rem;
|
||||||
@@ -694,118 +417,6 @@
|
|||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
}
|
}
|
||||||
|
|
||||||
.presenter-controls {
|
|
||||||
position: fixed;
|
|
||||||
top: 0.75rem;
|
|
||||||
left: 0.75rem;
|
|
||||||
width: min(20rem, calc(100vw - 1.5rem));
|
|
||||||
padding: 0.75rem;
|
|
||||||
border: 1px solid oklch(0.4 0.05 250);
|
|
||||||
background: oklch(0.17 0.025 250);
|
|
||||||
z-index: 40;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.5rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__nav {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__nav button {
|
|
||||||
flex: 1;
|
|
||||||
padding: 0.3rem 0.5rem;
|
|
||||||
border: 1px solid oklch(0.4 0.05 250);
|
|
||||||
background: oklch(0.2 0.03 250);
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__info {
|
|
||||||
color: oklch(0.78 0.035 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__timer {
|
|
||||||
display: flex;
|
|
||||||
gap: 0.25rem;
|
|
||||||
font-family: var(--font-mono, monospace);
|
|
||||||
font-size: 0.8rem;
|
|
||||||
color: oklch(0.78 0.035 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__notes label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.15rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__notes textarea {
|
|
||||||
width: 100%;
|
|
||||||
padding: 0.35rem;
|
|
||||||
border: 1px solid oklch(0.36 0.04 250);
|
|
||||||
background: oklch(0.18 0.025 250);
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
font-family: inherit;
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__over label {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 0.15rem;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__over select {
|
|
||||||
padding: 0.25rem;
|
|
||||||
border: 1px solid oklch(0.36 0.04 250);
|
|
||||||
background: oklch(0.18 0.025 250);
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 0.35rem;
|
|
||||||
margin-top: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presenter-controls__actions button {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
border: 1px solid oklch(0.4 0.05 250);
|
|
||||||
background: oklch(0.2 0.03 250);
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.3rem;
|
|
||||||
font-size: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-dock {
|
|
||||||
position: fixed;
|
|
||||||
bottom: 2.5rem;
|
|
||||||
left: 0.75rem;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 0.35rem;
|
|
||||||
padding: 0.45rem 0.75rem;
|
|
||||||
border: 1px solid oklch(0.4 0.05 250);
|
|
||||||
background: oklch(0.2 0.03 250);
|
|
||||||
color: inherit;
|
|
||||||
border-radius: 0.5rem;
|
|
||||||
z-index: 25;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.chat-dock:hover,
|
|
||||||
.chat-dock:focus-visible {
|
|
||||||
background: oklch(0.25 0.04 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-route__agent-button {
|
.presentation-route__agent-button {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
bottom: 0.75rem;
|
bottom: 0.75rem;
|
||||||
@@ -1083,15 +694,6 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-height: 760px) {
|
@media (max-height: 760px) {
|
||||||
.scene-rail {
|
|
||||||
padding: 0.25rem 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.scene-rail__scene {
|
|
||||||
min-width: 3.5rem;
|
|
||||||
padding: 0.25rem 0.35rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.stage-caption h1 {
|
.stage-caption h1 {
|
||||||
font-size: clamp(1.4rem, 3vw, 2.2rem);
|
font-size: clamp(1.4rem, 3vw, 2.2rem);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -574,131 +574,6 @@
|
|||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Chat remains an app surface while the workflow stage stays cinematic. */
|
|
||||||
.presentation-stage[data-chat-theme="light"] .presentation-stage__chat {
|
|
||||||
border-right: 1px solid oklch(0.82 0.015 80);
|
|
||||||
background: oklch(0.975 0.004 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .operator-chat {
|
|
||||||
gap: 1.05rem;
|
|
||||||
padding: 1rem 0.85rem;
|
|
||||||
background: transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-message {
|
|
||||||
border: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0;
|
|
||||||
background: transparent;
|
|
||||||
box-shadow: none;
|
|
||||||
color: oklch(0.22 0.015 80);
|
|
||||||
font-size: 0.78rem;
|
|
||||||
line-height: 1.45;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-message > strong {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 0.35rem;
|
|
||||||
color: oklch(0.42 0.025 80);
|
|
||||||
font: 650 0.6rem/1 var(--font-mono, monospace);
|
|
||||||
letter-spacing: 0.09em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-message--operator {
|
|
||||||
margin-left: 1rem;
|
|
||||||
border-radius: 0.85rem;
|
|
||||||
padding: 0.65rem 0.75rem;
|
|
||||||
background: oklch(0.925 0.012 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-message p {
|
|
||||||
margin: 0.2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-tool-part {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: minmax(0, 1fr) auto;
|
|
||||||
gap: 0.3rem;
|
|
||||||
margin-top: 0.5rem;
|
|
||||||
border: 1px solid oklch(0.82 0.015 80);
|
|
||||||
border-radius: 0.55rem;
|
|
||||||
padding: 0.55rem 0.65rem;
|
|
||||||
background: oklch(0.95 0.006 80);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-tool-part span {
|
|
||||||
grid-column: 1 / -1;
|
|
||||||
color: oklch(0.48 0.04 195);
|
|
||||||
font: 650 0.56rem/1 var(--font-mono, monospace);
|
|
||||||
letter-spacing: 0.09em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-tool-part code {
|
|
||||||
overflow: hidden;
|
|
||||||
color: oklch(0.26 0.025 250);
|
|
||||||
font-size: 0.68rem;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-stage[data-chat-theme="light"] .chat-tool-part--handoff {
|
|
||||||
border-color: oklch(0.56 0.13 195 / 0.55);
|
|
||||||
background: oklch(0.94 0.025 195);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-route .presentation-stage__discussion-toggle {
|
|
||||||
position: absolute;
|
|
||||||
right: 1.25rem;
|
|
||||||
bottom: 0.7rem;
|
|
||||||
z-index: 8;
|
|
||||||
border: 0;
|
|
||||||
padding: 0.25rem 0;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font: 600 0.62rem/1 var(--font-mono, monospace);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-route .presentation-stage__mode {
|
|
||||||
position: absolute;
|
|
||||||
bottom: 0.7rem;
|
|
||||||
left: 1.25rem;
|
|
||||||
z-index: 8;
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-muted);
|
|
||||||
font: 600 0.6rem/1 var(--font-mono, monospace);
|
|
||||||
letter-spacing: 0.06em;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-route .scene-rail {
|
|
||||||
gap: 0.25rem;
|
|
||||||
min-height: 2.7rem;
|
|
||||||
border-top: 1px solid oklch(0.36 0.04 250 / 0.7);
|
|
||||||
padding: 0.35rem 0.45rem;
|
|
||||||
background: oklch(0.095 0.02 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-route .scene-rail__scene {
|
|
||||||
min-width: 2.5rem;
|
|
||||||
min-height: 1.9rem;
|
|
||||||
border: 0;
|
|
||||||
border-radius: 0.35rem;
|
|
||||||
padding: 0.25rem 0.35rem;
|
|
||||||
background: transparent;
|
|
||||||
color: oklch(0.62 0.025 250);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-route .scene-rail__scene[data-active="true"] {
|
|
||||||
background: oklch(0.72 0.17 195 / 0.13);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.presentation-route .scene-rail__title {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 1050px) {
|
@media (max-width: 1050px) {
|
||||||
.demo-workflow-stage__graph:has(.interrupt-contract-preview) {
|
.demo-workflow-stage__graph:has(.interrupt-contract-preview) {
|
||||||
grid-template-columns: minmax(0, 1fr) 15rem;
|
grid-template-columns: minmax(0, 1fr) 15rem;
|
||||||
|
|||||||
Reference in New Issue
Block a user