pmx-canvas 0.1.21 → 0.1.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +135 -0
- package/Readme.md +4 -3
- package/dist/canvas/global.css +23 -40
- package/dist/canvas/index.js +44 -44
- package/dist/types/server/canvas-db.d.ts +33 -0
- package/dist/types/server/canvas-serialization.d.ts +1 -0
- package/dist/types/server/canvas-state.d.ts +18 -14
- package/docs/screenshot.png +0 -0
- package/package.json +2 -2
- package/skills/pmx-canvas/SKILL.md +8 -4
- package/src/cli/agent.ts +25 -1
- package/src/cli/index.ts +3 -1
- package/src/client/canvas/ExpandedNodeOverlay.tsx +25 -15
- package/src/client/nodes/HtmlNode.tsx +1 -1
- package/src/client/theme/global.css +23 -40
- package/src/server/canvas-db.ts +710 -0
- package/src/server/canvas-operations.ts +9 -4
- package/src/server/canvas-schema.ts +4 -4
- package/src/server/canvas-serialization.ts +26 -0
- package/src/server/canvas-state.ts +277 -48
- package/src/server/canvas-validation.ts +6 -0
- package/src/server/html-primitives.ts +14 -4
- package/src/server/server.ts +14 -8
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SQLite persistence layer for canvas state.
|
|
3
|
+
*
|
|
4
|
+
* Uses Bun's built-in `bun:sqlite` for zero-dependency, synchronous,
|
|
5
|
+
* WAL-mode persistence. Replaces the previous JSON file-based approach.
|
|
6
|
+
*/
|
|
7
|
+
import { Database } from 'bun:sqlite';
|
|
8
|
+
import type { CanvasAnnotation, CanvasEdge, CanvasNodeState, CanvasSnapshot, CanvasSnapshotListOptions, ViewportState } from './canvas-state.js';
|
|
9
|
+
export interface PersistedCanvasState {
|
|
10
|
+
version: number;
|
|
11
|
+
viewport: ViewportState;
|
|
12
|
+
nodes: CanvasNodeState[];
|
|
13
|
+
edges: CanvasEdge[];
|
|
14
|
+
annotations?: CanvasAnnotation[];
|
|
15
|
+
contextPins: string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function openCanvasDb(dbPath: string): Database;
|
|
18
|
+
export declare function checkpointCanvasDb(db: Database): void;
|
|
19
|
+
export declare function finalizeCanvasDbForClose(db: Database): void;
|
|
20
|
+
export declare function saveStateToDB(db: Database, state: PersistedCanvasState): void;
|
|
21
|
+
/** Check if the DB has been populated with canvas state at least once. */
|
|
22
|
+
export declare function isDbPopulated(db: Database): boolean;
|
|
23
|
+
export declare function loadStateFromDB(db: Database): PersistedCanvasState | null;
|
|
24
|
+
export declare function saveSnapshotToDB(db: Database, snapshot: CanvasSnapshot, state: PersistedCanvasState): void;
|
|
25
|
+
export declare function loadSnapshotFromDB(db: Database, idOrName: string): {
|
|
26
|
+
snapshot: CanvasSnapshot;
|
|
27
|
+
state: PersistedCanvasState;
|
|
28
|
+
} | null;
|
|
29
|
+
export declare function listSnapshotsFromDB(db: Database, options?: CanvasSnapshotListOptions): CanvasSnapshot[];
|
|
30
|
+
export declare function deleteSnapshotFromDB(db: Database, id: string): boolean;
|
|
31
|
+
export declare function writeBlobToDB(db: Database, sha256: string, jsonValue: string): number;
|
|
32
|
+
export declare function readBlobFromDB(db: Database, sha256: string): string | null;
|
|
33
|
+
export declare function hasBlobInDB(db: Database, sha256: string): boolean;
|
|
@@ -35,6 +35,7 @@ export declare function getCanvasNodeTitle(node: CanvasNodeState): string | null
|
|
|
35
35
|
export declare function getCanvasNodeContent(node: CanvasNodeState): string | null;
|
|
36
36
|
export declare function serializeCanvasNode(node: CanvasNodeState): SerializedCanvasNode;
|
|
37
37
|
export declare function serializeCanvasNodeForAgent(node: CanvasNodeState): SerializedCanvasNode;
|
|
38
|
+
export declare function serializeCanvasNodeCompact(node: CanvasNodeState): SerializedCanvasNode;
|
|
38
39
|
export declare function serializeCanvasNodeWithBlobSummaries(node: CanvasNodeState): SerializedCanvasNode;
|
|
39
40
|
export declare function serializeCanvasLayout(layout: CanvasLayout): SerializedCanvasLayout;
|
|
40
41
|
export declare function serializeCanvasLayoutForAgent(layout: CanvasLayout): SerializedCanvasLayout;
|
|
@@ -5,9 +5,11 @@
|
|
|
5
5
|
* - Agent tools (Phase 3) can read/mutate canvas state
|
|
6
6
|
* - Client syncs bidirectionally (SSE for server→client, POST for client→server)
|
|
7
7
|
*
|
|
8
|
-
* Persistence: canvas state auto-saves to `.pmx-canvas/
|
|
9
|
-
* workspace root on every mutation (debounced). Auto-loads on `loadFromDisk()`.
|
|
8
|
+
* Persistence: canvas state auto-saves to `.pmx-canvas/canvas.db` (SQLite WAL mode)
|
|
9
|
+
* in the workspace root on every mutation (debounced). Auto-loads on `loadFromDisk()`.
|
|
10
|
+
* Legacy `.pmx-canvas/state.json` is auto-migrated on first boot.
|
|
10
11
|
*/
|
|
12
|
+
import { type PersistedCanvasState } from './canvas-db.js';
|
|
11
13
|
export declare const PMX_CANVAS_DIR = ".pmx-canvas";
|
|
12
14
|
export interface PersistedBlobRef {
|
|
13
15
|
__pmxCanvasBlob: 'v1';
|
|
@@ -17,14 +19,7 @@ export interface PersistedBlobRef {
|
|
|
17
19
|
bytes: number;
|
|
18
20
|
jsonBytes: number;
|
|
19
21
|
}
|
|
20
|
-
|
|
21
|
-
version: number;
|
|
22
|
-
viewport: ViewportState;
|
|
23
|
-
nodes: CanvasNodeState[];
|
|
24
|
-
edges: CanvasEdge[];
|
|
25
|
-
annotations?: CanvasAnnotation[];
|
|
26
|
-
contextPins: string[];
|
|
27
|
-
}
|
|
22
|
+
export type { PersistedCanvasState } from './canvas-db.js';
|
|
28
23
|
interface LoadFromDiskOptions {
|
|
29
24
|
clearExisting?: boolean;
|
|
30
25
|
}
|
|
@@ -166,6 +161,7 @@ declare class CanvasStateManager {
|
|
|
166
161
|
private recomputeParentGroupBounds;
|
|
167
162
|
private compactGroupChildren;
|
|
168
163
|
private _stateFilePath;
|
|
164
|
+
private _db;
|
|
169
165
|
private _saveTimer;
|
|
170
166
|
/** Set the workspace root to enable auto-persistence. */
|
|
171
167
|
setWorkspaceRoot(workspaceRoot: string): void;
|
|
@@ -185,15 +181,22 @@ declare class CanvasStateManager {
|
|
|
185
181
|
* No-op when the new layout already exists.
|
|
186
182
|
*/
|
|
187
183
|
private migrateLegacyLayout;
|
|
184
|
+
/**
|
|
185
|
+
* One-time migration: import state.json + snapshot JSON files + blob files
|
|
186
|
+
* into the SQLite database. Renames originals to `.bak`.
|
|
187
|
+
*/
|
|
188
|
+
private migrateJsonToSqlite;
|
|
188
189
|
getWorkspaceRoot(): string;
|
|
189
190
|
private emptyPersistedState;
|
|
190
|
-
/** Load canvas state from
|
|
191
|
+
/** Load canvas state from SQLite (or legacy JSON fallback). Call once on server startup. */
|
|
191
192
|
loadFromDisk(options?: LoadFromDiskOptions): boolean;
|
|
192
|
-
/** Debounced save — coalesces rapid mutations into a single
|
|
193
|
+
/** Debounced save — coalesces rapid mutations into a single write. */
|
|
193
194
|
private scheduleSave;
|
|
194
195
|
flushToDisk(): void;
|
|
195
|
-
/** Write current state to
|
|
196
|
+
/** Write current state to SQLite immediately. */
|
|
196
197
|
private saveToDisk;
|
|
198
|
+
/** Close the SQLite database cleanly. Call on server shutdown. */
|
|
199
|
+
close(): void;
|
|
197
200
|
private get snapshotsDir();
|
|
198
201
|
private applyPersistedState;
|
|
199
202
|
private readResolvedSnapshot;
|
|
@@ -217,6 +220,8 @@ declare class CanvasStateManager {
|
|
|
217
220
|
} | null;
|
|
218
221
|
/** Delete a snapshot. */
|
|
219
222
|
deleteSnapshot(id: string): boolean;
|
|
223
|
+
/** Remove all snapshots from the DB. Used by test teardown. */
|
|
224
|
+
clearAllSnapshots(): void;
|
|
220
225
|
get viewport(): ViewportState;
|
|
221
226
|
addNode(node: CanvasNodeState): void;
|
|
222
227
|
addJsonRenderNode(node: CanvasNodeState): void;
|
|
@@ -250,4 +255,3 @@ declare class CanvasStateManager {
|
|
|
250
255
|
clear(): void;
|
|
251
256
|
}
|
|
252
257
|
export declare const canvasState: CanvasStateManager;
|
|
253
|
-
export {};
|
package/docs/screenshot.png
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pmx-canvas",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.23",
|
|
4
4
|
"description": "Spatial canvas workbench for coding agents — infinite 2D canvas with agent-native CLI, MCP integration, nodes, edges, file watching, and snapshots",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/server/index.ts",
|
|
@@ -106,6 +106,6 @@
|
|
|
106
106
|
},
|
|
107
107
|
"homepage": "https://github.com/pskoett/pmx-canvas#readme",
|
|
108
108
|
"engines": {
|
|
109
|
-
"bun": ">=1.3.
|
|
109
|
+
"bun": ">=1.3.14"
|
|
110
110
|
}
|
|
111
111
|
}
|
|
@@ -960,12 +960,16 @@ When the human wants to explore a different approach without losing current work
|
|
|
960
960
|
|
|
961
961
|
## Persistence
|
|
962
962
|
|
|
963
|
-
Canvas state auto-saves to `.pmx-canvas/
|
|
964
|
-
loads automatically on server start. The
|
|
963
|
+
Canvas state auto-saves to `.pmx-canvas/canvas.db` on every mutation (debounced 500ms). State
|
|
964
|
+
loads automatically on server start. The SQLite DB is git-committable — spatial knowledge
|
|
965
965
|
persists across sessions.
|
|
966
966
|
|
|
967
|
-
Snapshots
|
|
968
|
-
|
|
967
|
+
Snapshots, context pins, and large node blobs are stored in the same DB. Web artifacts land in
|
|
968
|
+
`.pmx-canvas/artifacts/`. Legacy JSON state, snapshot, and blob files are auto-imported into
|
|
969
|
+
SQLite and renamed to `.bak` on first boot.
|
|
970
|
+
|
|
971
|
+
Stop the server or flush/close the SDK before committing `canvas.db`; shutdown checkpoints SQLite
|
|
972
|
+
WAL data into the DB file.
|
|
969
973
|
|
|
970
974
|
## Real-Time Collaboration
|
|
971
975
|
|
package/src/cli/agent.ts
CHANGED
|
@@ -164,7 +164,7 @@ function parseFlags(args: string[]): { positional: string[]; flags: Record<strin
|
|
|
164
164
|
// Boolean-only flags (never take a value argument)
|
|
165
165
|
const BOOL_FLAGS = new Set([
|
|
166
166
|
'help', 'h', 'ids', 'stdin', 'yes', 'list', 'clear', 'set', 'animated', 'dry-run', 'all',
|
|
167
|
-
'no-open-in-canvas', 'lock-arrange', 'unlock-arrange', 'json', 'compact',
|
|
167
|
+
'no-open-in-canvas', 'lock-arrange', 'unlock-arrange', 'json', 'compact',
|
|
168
168
|
'verbose', 'include-logs', 'no-pan', 'schema', 'example', 'examples', 'strict-size', 'scroll-overflow',
|
|
169
169
|
]);
|
|
170
170
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -1723,6 +1723,16 @@ cmd('external-app add', 'Create a hosted external app node', [
|
|
|
1723
1723
|
: result);
|
|
1724
1724
|
});
|
|
1725
1725
|
|
|
1726
|
+
cmd('diagram add', 'Create an Excalidraw diagram node', [
|
|
1727
|
+
'pmx-canvas diagram add --title "Architecture"',
|
|
1728
|
+
'pmx-canvas diagram add --title "Architecture" --elements \'[{"type":"rectangle","id":"r1","x":0,"y":0,"width":120,"height":80}]\'',
|
|
1729
|
+
], async (args) => {
|
|
1730
|
+
const { flags } = parseFlags(args);
|
|
1731
|
+
if (flags.help || flags.h) return showCommandHelp('diagram add');
|
|
1732
|
+
const externalAppAdd = COMMANDS['external-app add'];
|
|
1733
|
+
await externalAppAdd.run([...args, '--kind', 'excalidraw']);
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1726
1736
|
// ── pin ──────────────────────────────────────────────────────
|
|
1727
1737
|
cmd('pin', 'Manage context pins', [
|
|
1728
1738
|
'pmx-canvas pin node1 node2 node3',
|
|
@@ -2431,11 +2441,19 @@ function showCommandHelp(name: string): void {
|
|
|
2431
2441
|
if (name === 'node add') {
|
|
2432
2442
|
console.log('\nSchema help:');
|
|
2433
2443
|
console.log(' pmx-canvas node add --help --type webpage');
|
|
2444
|
+
console.log(' pmx-canvas node add --help --type html');
|
|
2434
2445
|
console.log(' pmx-canvas node add --help --type json-render --component Table');
|
|
2435
2446
|
console.log(' pmx-canvas node add --help --type graph');
|
|
2436
2447
|
console.log(' pmx-canvas html primitive schema --summary');
|
|
2437
2448
|
console.log(' pmx-canvas node add --help --type webpage --json');
|
|
2438
2449
|
console.log(' Use --strict-size to keep explicit width/height fixed and scroll overflowing content.');
|
|
2450
|
+
console.log('\nHTML sidecar flags:');
|
|
2451
|
+
console.log(' --summary <text> Explicit human/agent-readable summary');
|
|
2452
|
+
console.log(' --agent-summary <text> Semantic summary for search, pinned context, and spatial context');
|
|
2453
|
+
console.log(' --description <text> Optional longer semantic description');
|
|
2454
|
+
console.log(' --presentation true Mark raw HTML as an explicit presentation deck');
|
|
2455
|
+
console.log(' --slide-title <text> Add a presentation slide title sidecar');
|
|
2456
|
+
console.log(' --embedded-node-id <id> Link represented/embedded canvas node ID');
|
|
2439
2457
|
}
|
|
2440
2458
|
if (name === 'html primitive add' || name === 'html primitive schema') {
|
|
2441
2459
|
console.log('\nPrimitive flags:');
|
|
@@ -2534,6 +2552,10 @@ function showCommandHelp(name: string): void {
|
|
|
2534
2552
|
console.log(' --initial-file <path> Alias for --elements-file');
|
|
2535
2553
|
console.log(' --timeout-ms <number> Optional downstream MCP timeout for cold starts');
|
|
2536
2554
|
}
|
|
2555
|
+
if (name === 'diagram add') {
|
|
2556
|
+
console.log('\nAlias:');
|
|
2557
|
+
console.log(' Equivalent to: pmx-canvas external-app add --kind excalidraw ...');
|
|
2558
|
+
}
|
|
2537
2559
|
console.log('');
|
|
2538
2560
|
}
|
|
2539
2561
|
|
|
@@ -2561,6 +2583,7 @@ Node commands:
|
|
|
2561
2583
|
pmx-canvas graph add [options] Add a graph node
|
|
2562
2584
|
pmx-canvas html primitive add Add an HTML communication primitive
|
|
2563
2585
|
pmx-canvas html primitive schema List HTML primitive kinds and shapes
|
|
2586
|
+
pmx-canvas diagram add Add an Excalidraw diagram node
|
|
2564
2587
|
|
|
2565
2588
|
Edge commands:
|
|
2566
2589
|
pmx-canvas edge add [options] Add an edge between nodes
|
|
@@ -2636,6 +2659,7 @@ Examples:
|
|
|
2636
2659
|
pmx-canvas graph add --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
2637
2660
|
pmx-canvas html primitive add --kind choice-grid --data-file ./options.json --title "Options"
|
|
2638
2661
|
pmx-canvas html primitive schema --summary
|
|
2662
|
+
pmx-canvas diagram add --title "Architecture"
|
|
2639
2663
|
pmx-canvas node add --help --type webpage
|
|
2640
2664
|
pmx-canvas node schema --type json-render
|
|
2641
2665
|
pmx-canvas node schema --type json-render --component Table --summary
|
package/src/cli/index.ts
CHANGED
|
@@ -32,7 +32,7 @@ if (args.includes('--version') || args.includes('-v')) {
|
|
|
32
32
|
const AGENT_COMMANDS = new Set([
|
|
33
33
|
'node', 'edge', 'json-render', 'search', 'layout', 'status', 'arrange', 'focus',
|
|
34
34
|
'fit', 'screenshot', 'pin', 'undo', 'redo', 'history', 'snapshot', 'diff', 'group', 'webview', 'open',
|
|
35
|
-
'clear', 'code-graph', 'spatial', 'watch', 'web-artifact', 'external-app', 'graph', 'html', 'batch', 'validate', 'serve',
|
|
35
|
+
'clear', 'code-graph', 'spatial', 'watch', 'web-artifact', 'external-app', 'diagram', 'graph', 'html', 'batch', 'validate', 'serve',
|
|
36
36
|
]);
|
|
37
37
|
|
|
38
38
|
const firstArg = args[0] ?? '';
|
|
@@ -506,6 +506,7 @@ Agent CLI (works against running server):
|
|
|
506
506
|
watch [--json] [--events ...] Watch low-token semantic canvas changes
|
|
507
507
|
focus <node-id> Pan to node
|
|
508
508
|
external-app add Add hosted external apps like Excalidraw
|
|
509
|
+
diagram add Add an Excalidraw diagram node
|
|
509
510
|
pin <ids...> | --list | --clear Manage context pins
|
|
510
511
|
undo / redo / history Time travel
|
|
511
512
|
snapshot save|list|restore|diff|delete
|
|
@@ -550,6 +551,7 @@ Examples:
|
|
|
550
551
|
pmx-canvas node schema --type json-render Show running-server schema info
|
|
551
552
|
pmx-canvas web-artifact build --title "Dashboard" --app-file ./App.tsx
|
|
552
553
|
pmx-canvas external-app add --kind excalidraw --title "Diagram"
|
|
554
|
+
pmx-canvas diagram add --title "Diagram"
|
|
553
555
|
pmx-canvas validate spec --type graph --graph-type bar --data-file ./metrics.json --x-key label --y-key value
|
|
554
556
|
pmx-canvas open Open the workbench in a browser
|
|
555
557
|
pmx-canvas webview status Show WebView automation status
|
|
@@ -93,6 +93,10 @@ function isPresentationNavigationKey(key: string): boolean {
|
|
|
93
93
|
return key === 'ArrowRight' || key === 'PageDown' || key === ' ' || key === 'ArrowLeft' || key === 'PageUp' || key === 'Home' || key === 'End';
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
function isPresentationExitButtonTarget(target: EventTarget | null): boolean {
|
|
97
|
+
return target instanceof HTMLElement && Boolean(target.closest('.html-presentation-exit'));
|
|
98
|
+
}
|
|
99
|
+
|
|
96
100
|
export function ExpandedNodeOverlay() {
|
|
97
101
|
const nodeId = expandedNodeId.value;
|
|
98
102
|
const node = nodeId ? nodes.value.get(nodeId) : undefined;
|
|
@@ -100,6 +104,7 @@ export function ExpandedNodeOverlay() {
|
|
|
100
104
|
const [presenting, setPresenting] = useState(false);
|
|
101
105
|
const [presentationExitToken, setPresentationExitToken] = useState('');
|
|
102
106
|
const presentationOverlayRef = useRef<HTMLDivElement>(null);
|
|
107
|
+
const presentationExitButtonRef = useRef<HTMLButtonElement>(null);
|
|
103
108
|
|
|
104
109
|
const handleClose = useCallback(() => {
|
|
105
110
|
setPresenting(false);
|
|
@@ -131,6 +136,13 @@ export function ExpandedNodeOverlay() {
|
|
|
131
136
|
setPresenting(false);
|
|
132
137
|
return;
|
|
133
138
|
}
|
|
139
|
+
if (event.key === 'Tab' && !isPresentationExitButtonTarget(event.target)) {
|
|
140
|
+
event.preventDefault();
|
|
141
|
+
event.stopPropagation();
|
|
142
|
+
presentationExitButtonRef.current?.focus();
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
if ((event.key === ' ' || event.key === 'Enter') && isPresentationExitButtonTarget(event.target)) return;
|
|
134
146
|
if (!isPresentationNavigationKey(event.key)) return;
|
|
135
147
|
event.preventDefault();
|
|
136
148
|
event.stopPropagation();
|
|
@@ -165,7 +177,9 @@ export function ExpandedNodeOverlay() {
|
|
|
165
177
|
useLayoutEffect(() => {
|
|
166
178
|
if (!presenting) return;
|
|
167
179
|
const focusPresentationOverlay = () => {
|
|
168
|
-
presentationOverlayRef.current
|
|
180
|
+
const overlay = presentationOverlayRef.current;
|
|
181
|
+
if (!overlay || overlay.contains(document.activeElement)) return;
|
|
182
|
+
overlay.focus();
|
|
169
183
|
};
|
|
170
184
|
const focusTimers = [0, 50, 150].map((delay) => window.setTimeout(focusPresentationOverlay, delay));
|
|
171
185
|
const handleMessage = (event: MessageEvent) => {
|
|
@@ -354,20 +368,16 @@ export function ExpandedNodeOverlay() {
|
|
|
354
368
|
</div>
|
|
355
369
|
{canPresent && presenting && (
|
|
356
370
|
<div ref={presentationOverlayRef} class="html-presentation-overlay" role="dialog" aria-modal="true" aria-label={`Present ${title}`} tabIndex={-1} onKeyDownCapture={handlePresentationKeyDown}>
|
|
357
|
-
<
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
>
|
|
368
|
-
Exit presentation
|
|
369
|
-
</button>
|
|
370
|
-
</div>
|
|
371
|
+
<button
|
|
372
|
+
ref={presentationExitButtonRef}
|
|
373
|
+
type="button"
|
|
374
|
+
class="html-presentation-exit"
|
|
375
|
+
onClick={handleExitPresentation}
|
|
376
|
+
title="Exit presentation (Esc)"
|
|
377
|
+
aria-label="Exit presentation"
|
|
378
|
+
>
|
|
379
|
+
Exit presentation
|
|
380
|
+
</button>
|
|
371
381
|
<div class="html-presentation-stage">
|
|
372
382
|
<HtmlNode node={node} expanded presentation presentationExitToken={presentationExitToken} />
|
|
373
383
|
</div>
|
|
@@ -255,7 +255,7 @@ export function HtmlNode({
|
|
|
255
255
|
minHeight: presentation ? 0 : expanded ? '70vh' : '300px',
|
|
256
256
|
border: 'none',
|
|
257
257
|
background: 'var(--c-bg)',
|
|
258
|
-
borderRadius: presentation ?
|
|
258
|
+
borderRadius: presentation ? 0 : '6px',
|
|
259
259
|
display: 'block',
|
|
260
260
|
}}
|
|
261
261
|
/>
|
|
@@ -2472,75 +2472,58 @@ body,
|
|
|
2472
2472
|
inset: 0;
|
|
2473
2473
|
z-index: 10050;
|
|
2474
2474
|
display: flex;
|
|
2475
|
-
|
|
2476
|
-
gap: 14px;
|
|
2477
|
-
padding: clamp(12px, 2vw, 28px);
|
|
2475
|
+
padding: 0;
|
|
2478
2476
|
background:
|
|
2479
2477
|
radial-gradient(circle at top left, var(--c-accent-25), transparent 36rem),
|
|
2480
2478
|
rgba(3, 7, 18, 0.96);
|
|
2481
2479
|
color: var(--c-text);
|
|
2482
2480
|
}
|
|
2483
2481
|
|
|
2484
|
-
.html-presentation-toolbar {
|
|
2485
|
-
display: flex;
|
|
2486
|
-
align-items: center;
|
|
2487
|
-
justify-content: space-between;
|
|
2488
|
-
gap: 16px;
|
|
2489
|
-
flex-shrink: 0;
|
|
2490
|
-
padding: 10px 12px;
|
|
2491
|
-
border: 1px solid var(--c-line);
|
|
2492
|
-
border-radius: 16px;
|
|
2493
|
-
background: var(--c-panel-glass);
|
|
2494
|
-
box-shadow: 0 18px 50px var(--c-shadow-heavy);
|
|
2495
|
-
}
|
|
2496
|
-
|
|
2497
|
-
.html-presentation-kicker {
|
|
2498
|
-
color: var(--c-accent);
|
|
2499
|
-
font-size: 10px;
|
|
2500
|
-
font-weight: 800;
|
|
2501
|
-
letter-spacing: 0.14em;
|
|
2502
|
-
text-transform: uppercase;
|
|
2503
|
-
}
|
|
2504
|
-
|
|
2505
|
-
.html-presentation-title {
|
|
2506
|
-
max-width: min(72vw, 900px);
|
|
2507
|
-
overflow: hidden;
|
|
2508
|
-
color: var(--c-text);
|
|
2509
|
-
font-size: 14px;
|
|
2510
|
-
font-weight: 700;
|
|
2511
|
-
text-overflow: ellipsis;
|
|
2512
|
-
white-space: nowrap;
|
|
2513
|
-
}
|
|
2514
|
-
|
|
2515
2482
|
.html-presentation-exit {
|
|
2516
|
-
|
|
2517
|
-
|
|
2483
|
+
position: fixed;
|
|
2484
|
+
top: 12px;
|
|
2485
|
+
right: 12px;
|
|
2486
|
+
z-index: 1;
|
|
2487
|
+
padding: 10px 14px;
|
|
2518
2488
|
border: 1px solid var(--c-line);
|
|
2519
2489
|
border-radius: 999px;
|
|
2520
|
-
background: var(--c-panel-
|
|
2490
|
+
background: var(--c-panel-glass);
|
|
2491
|
+
box-shadow: 0 18px 50px var(--c-shadow-heavy);
|
|
2521
2492
|
color: var(--c-text-soft);
|
|
2522
2493
|
cursor: pointer;
|
|
2523
2494
|
font: 600 12px/1 var(--font);
|
|
2495
|
+
opacity: 0;
|
|
2496
|
+
pointer-events: none;
|
|
2497
|
+
transform: translateY(-6px);
|
|
2498
|
+
transition: opacity 0.15s ease, transform 0.15s ease, border-color 0.15s ease, color 0.15s ease;
|
|
2524
2499
|
}
|
|
2525
2500
|
|
|
2526
|
-
.html-presentation-exit:hover
|
|
2501
|
+
.html-presentation-exit:hover,
|
|
2502
|
+
.html-presentation-exit:focus-visible {
|
|
2527
2503
|
border-color: var(--c-accent);
|
|
2528
2504
|
color: var(--c-text);
|
|
2529
2505
|
}
|
|
2530
2506
|
|
|
2507
|
+
.html-presentation-exit:focus,
|
|
2508
|
+
.html-presentation-exit:focus-visible {
|
|
2509
|
+
opacity: 1;
|
|
2510
|
+
pointer-events: auto;
|
|
2511
|
+
transform: translateY(0);
|
|
2512
|
+
}
|
|
2513
|
+
|
|
2531
2514
|
.html-presentation-stage {
|
|
2532
2515
|
flex: 1;
|
|
2533
2516
|
min-height: 0;
|
|
2534
2517
|
display: flex;
|
|
2535
|
-
border-radius:
|
|
2518
|
+
border-radius: 0;
|
|
2536
2519
|
background: var(--c-bg);
|
|
2537
|
-
box-shadow: 0 24px 90px rgba(0, 0, 0, 0.55);
|
|
2538
2520
|
overflow: hidden;
|
|
2539
2521
|
}
|
|
2540
2522
|
|
|
2541
2523
|
.html-node-frame-presentation {
|
|
2542
2524
|
flex: 1;
|
|
2543
2525
|
min-height: 0;
|
|
2526
|
+
border-radius: 0 !important;
|
|
2544
2527
|
}
|
|
2545
2528
|
|
|
2546
2529
|
/* ── Context pin button on node title bar ────────────────────── */
|