plotlink-ows 1.0.5 → 1.0.10
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/app/routes/stories.ts +18 -1
- package/app/routes/terminal.ts +3 -1
- package/app/server.ts +45 -33
- package/app/web/components/StoriesPage.tsx +61 -1
- package/app/web/components/StoryBrowser.tsx +32 -4
- package/app/web/components/TerminalPanel.tsx +10 -9
- package/app/web/dist/assets/index-W1yUR9t5.css +32 -0
- package/app/web/dist/assets/{index-De8CpT47.js → index-arJRA1vb.js} +36 -36
- package/app/web/dist/index.html +2 -2
- package/app/web/styles.css +24 -12
- package/package.json +2 -1
- package/app/web/dist/assets/index-BuOxhUWG.css +0 -32
package/app/routes/stories.ts
CHANGED
|
@@ -27,6 +27,7 @@ interface FileStatus {
|
|
|
27
27
|
|
|
28
28
|
interface StoryInfo {
|
|
29
29
|
name: string;
|
|
30
|
+
title: string | null;
|
|
30
31
|
files: FileStatus[];
|
|
31
32
|
hasStructure: boolean;
|
|
32
33
|
hasGenesis: boolean;
|
|
@@ -66,7 +67,23 @@ function scanStory(storyDir: string, name: string): StoryInfo {
|
|
|
66
67
|
const plotCount = entries.filter((f) => f.match(/^plot-\d+\.md$/)).length;
|
|
67
68
|
const publishedCount = files.filter((f) => f.status === "published" || f.status === "published-not-indexed").length;
|
|
68
69
|
|
|
69
|
-
|
|
70
|
+
// Extract title from structure.md or genesis.md
|
|
71
|
+
let title: string | null = null;
|
|
72
|
+
try {
|
|
73
|
+
const structPath = path.join(storyDir, "structure.md");
|
|
74
|
+
const genesisPath = path.join(storyDir, "genesis.md");
|
|
75
|
+
if (fs.existsSync(structPath)) {
|
|
76
|
+
const content = fs.readFileSync(structPath, "utf-8");
|
|
77
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
78
|
+
if (match) title = match[1];
|
|
79
|
+
} else if (fs.existsSync(genesisPath)) {
|
|
80
|
+
const content = fs.readFileSync(genesisPath, "utf-8");
|
|
81
|
+
const match = content.match(/^#\s+(.+)$/m);
|
|
82
|
+
if (match) title = match[1];
|
|
83
|
+
}
|
|
84
|
+
} catch { /* best effort */ }
|
|
85
|
+
|
|
86
|
+
return { name, title, files, hasStructure, hasGenesis, plotCount, publishedCount };
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
/** GET /api/stories — list all stories */
|
package/app/routes/terminal.ts
CHANGED
|
@@ -43,7 +43,9 @@ function saveSessionMap(map: Record<string, string>) {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
function spawnPty(storyName: string, opts?: { sessionId?: string; resume?: boolean }) {
|
|
46
|
-
|
|
46
|
+
// New story sessions spawn in the stories root so Claude can create any folder
|
|
47
|
+
const isNewStory = storyName.startsWith("_new_");
|
|
48
|
+
const storyDir = isNewStory ? STORIES_DIR : path.join(STORIES_DIR, storyName);
|
|
47
49
|
if (!fs.existsSync(storyDir)) fs.mkdirSync(storyDir, { recursive: true });
|
|
48
50
|
const shell = process.env.SHELL || "/bin/zsh";
|
|
49
51
|
|
package/app/server.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import dotenv from "dotenv";
|
|
2
|
+
import os from "os";
|
|
2
3
|
import path from "path";
|
|
3
4
|
import { fileURLToPath } from "url";
|
|
4
5
|
import { ENV_FILE, DATA_DIR, STORIES_DIR, DATABASE_URL } from "./lib/paths";
|
|
@@ -60,45 +61,56 @@ if (fs.existsSync(distPath)) {
|
|
|
60
61
|
});
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
/**
|
|
64
|
-
function
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (!fs.existsSync(dest)) {
|
|
76
|
-
try {
|
|
77
|
-
fs.renameSync(src, dest);
|
|
78
|
-
} catch {
|
|
79
|
-
// EXDEV: cross-device move — fall back to copy
|
|
80
|
-
fs.cpSync(src, dest, { recursive: true });
|
|
81
|
-
}
|
|
82
|
-
console.log(` Migrated story "${entry.name}" → ${STORIES_DIR}`);
|
|
83
|
-
}
|
|
64
|
+
/** Copy story directories from a source dir into STORIES_DIR, skipping duplicates */
|
|
65
|
+
function migrateStoriesFrom(srcDir: string, label: string) {
|
|
66
|
+
if (!fs.existsSync(srcDir) || srcDir === STORIES_DIR) return;
|
|
67
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true })
|
|
68
|
+
.filter((d) => d.isDirectory() && !d.name.startsWith(".") && d.name !== "_example");
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
const dest = path.join(STORIES_DIR, entry.name);
|
|
71
|
+
if (fs.existsSync(dest)) continue;
|
|
72
|
+
try {
|
|
73
|
+
fs.renameSync(path.join(srcDir, entry.name), dest);
|
|
74
|
+
} catch {
|
|
75
|
+
fs.cpSync(path.join(srcDir, entry.name), dest, { recursive: true });
|
|
84
76
|
}
|
|
77
|
+
console.log(` Migrated story "${entry.name}" from ${label}`);
|
|
85
78
|
}
|
|
79
|
+
}
|
|
86
80
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
console.log(` Migrated database → ${DATA_DIR}`);
|
|
81
|
+
/** Copy a single file if source exists and destination doesn't */
|
|
82
|
+
function migrateFileFrom(src: string, dest: string, label: string) {
|
|
83
|
+
if (fs.existsSync(src) && !fs.existsSync(dest)) {
|
|
84
|
+
fs.copyFileSync(src, dest);
|
|
85
|
+
console.log(` Migrated ${label} → ${path.dirname(dest)}`);
|
|
93
86
|
}
|
|
87
|
+
}
|
|
94
88
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
89
|
+
/** Migrate stories/data from old locations to ~/.plotlink-ows/ */
|
|
90
|
+
function migrateOldData() {
|
|
91
|
+
// 1. Scan all previous npx cache directories
|
|
92
|
+
const npxBase = path.join(os.homedir(), ".npm", "_npx");
|
|
93
|
+
if (fs.existsSync(npxBase)) {
|
|
94
|
+
try {
|
|
95
|
+
// Only migrate stories from npx caches — db/sessions are singletons and
|
|
96
|
+
// picking from a random cache entry could restore stale state
|
|
97
|
+
for (const hash of fs.readdirSync(npxBase)) {
|
|
98
|
+
const pkgRoot = path.join(npxBase, hash, "node_modules", "plotlink-ows");
|
|
99
|
+
migrateStoriesFrom(path.join(pkgRoot, "stories"), `npx cache (${hash.slice(0, 8)})`);
|
|
100
|
+
}
|
|
101
|
+
} catch { /* npx cache scan best-effort */ }
|
|
101
102
|
}
|
|
103
|
+
|
|
104
|
+
// 2. Current package-relative path (dev → npx transition)
|
|
105
|
+
const oldStoriesDir = path.join(__dirname, "..", "stories");
|
|
106
|
+
const oldDataDir = path.join(__dirname, "..", "data");
|
|
107
|
+
migrateStoriesFrom(oldStoriesDir, "package directory");
|
|
108
|
+
migrateFileFrom(path.join(oldDataDir, "local.db"), path.join(DATA_DIR, "local.db"), "database");
|
|
109
|
+
migrateFileFrom(
|
|
110
|
+
path.join(oldDataDir, "terminal-sessions.json"),
|
|
111
|
+
path.join(DATA_DIR, "terminal-sessions.json"),
|
|
112
|
+
"terminal sessions",
|
|
113
|
+
);
|
|
102
114
|
}
|
|
103
115
|
|
|
104
116
|
async function start() {
|
|
@@ -39,6 +39,8 @@ export function StoriesPage({ token, authFetch }: StoriesPageProps) {
|
|
|
39
39
|
const [publishingFile, setPublishingFile] = useState<string | null>(null);
|
|
40
40
|
const [publishProgress, setPublishProgress] = useState<string>("");
|
|
41
41
|
const [ratio, setRatio] = useState(loadRatio);
|
|
42
|
+
const [untitledSessions, setUntitledSessions] = useState<string[]>([]);
|
|
43
|
+
const knownStoriesRef = useRef<Set<string>>(new Set());
|
|
42
44
|
const containerRef = useRef<HTMLDivElement>(null);
|
|
43
45
|
const dragging = useRef(false);
|
|
44
46
|
|
|
@@ -60,6 +62,56 @@ export function StoriesPage({ token, authFetch }: StoriesPageProps) {
|
|
|
60
62
|
return () => window.removeEventListener("resize", onResize);
|
|
61
63
|
}, []);
|
|
62
64
|
|
|
65
|
+
const handleNewStory = useCallback(() => {
|
|
66
|
+
const id = `_new_${Date.now()}`;
|
|
67
|
+
setUntitledSessions((prev) => [...prev, id]);
|
|
68
|
+
setSelectedStory(id);
|
|
69
|
+
setSelectedFile(null);
|
|
70
|
+
}, []);
|
|
71
|
+
|
|
72
|
+
// Poll for new stories and auto-transition untitled sessions
|
|
73
|
+
useEffect(() => {
|
|
74
|
+
if (untitledSessions.length === 0) return;
|
|
75
|
+
const interval = setInterval(async () => {
|
|
76
|
+
try {
|
|
77
|
+
const res = await authFetch("/api/stories");
|
|
78
|
+
if (!res.ok) return;
|
|
79
|
+
const data = await res.json();
|
|
80
|
+
const currentNames = new Set<string>(
|
|
81
|
+
(data.stories as { name: string }[])
|
|
82
|
+
.filter((s) => s.name !== "_example")
|
|
83
|
+
.map((s) => s.name)
|
|
84
|
+
);
|
|
85
|
+
// Detect newly appeared stories
|
|
86
|
+
for (const name of currentNames) {
|
|
87
|
+
if (!knownStoriesRef.current.has(name) && untitledSessions.length > 0) {
|
|
88
|
+
// New story appeared — transition the oldest untitled session
|
|
89
|
+
setUntitledSessions((prev) => prev.slice(1));
|
|
90
|
+
setSelectedStory(name);
|
|
91
|
+
setSelectedFile(null);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
knownStoriesRef.current = currentNames;
|
|
95
|
+
} catch { /* ignore */ }
|
|
96
|
+
}, 3000);
|
|
97
|
+
return () => clearInterval(interval);
|
|
98
|
+
}, [authFetch, untitledSessions]);
|
|
99
|
+
|
|
100
|
+
// Initialize known stories on mount
|
|
101
|
+
useEffect(() => {
|
|
102
|
+
authFetch("/api/stories").then((res) => {
|
|
103
|
+
if (res.ok) return res.json();
|
|
104
|
+
}).then((data) => {
|
|
105
|
+
if (data?.stories) {
|
|
106
|
+
knownStoriesRef.current = new Set(
|
|
107
|
+
(data.stories as { name: string }[])
|
|
108
|
+
.filter((s) => s.name !== "_example")
|
|
109
|
+
.map((s) => s.name)
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}).catch(() => {});
|
|
113
|
+
}, [authFetch]);
|
|
114
|
+
|
|
63
115
|
const handleSelectFile = useCallback((storyName: string, fileName: string) => {
|
|
64
116
|
setSelectedStory(storyName);
|
|
65
117
|
setSelectedFile(fileName);
|
|
@@ -220,6 +272,12 @@ export function StoriesPage({ token, authFetch }: StoriesPageProps) {
|
|
|
220
272
|
}
|
|
221
273
|
}, [authFetch]);
|
|
222
274
|
|
|
275
|
+
const handleDestroySession = useCallback((name: string) => {
|
|
276
|
+
if (name.startsWith("_new_")) {
|
|
277
|
+
setUntitledSessions((prev) => prev.filter((id) => id !== name));
|
|
278
|
+
}
|
|
279
|
+
}, []);
|
|
280
|
+
|
|
223
281
|
return (
|
|
224
282
|
<div ref={containerRef} className="h-[calc(100vh-3.5rem)] flex">
|
|
225
283
|
{/* Story Browser Sidebar */}
|
|
@@ -229,12 +287,14 @@ export function StoriesPage({ token, authFetch }: StoriesPageProps) {
|
|
|
229
287
|
selectedStory={selectedStory}
|
|
230
288
|
selectedFile={selectedFile}
|
|
231
289
|
onSelectFile={handleSelectFile}
|
|
290
|
+
onNewStory={handleNewStory}
|
|
291
|
+
untitledSessions={untitledSessions}
|
|
232
292
|
/>
|
|
233
293
|
</div>
|
|
234
294
|
|
|
235
295
|
{/* Terminal — sized by ratio of available space */}
|
|
236
296
|
<div className="min-w-0 border-r border-border" style={{ flex: `${ratio} 0 0` }}>
|
|
237
|
-
<TerminalPanel token={token} storyName={selectedStory} authFetch={authFetch} onSelectStory={handleSelectStory} />
|
|
297
|
+
<TerminalPanel token={token} storyName={selectedStory} authFetch={authFetch} onSelectStory={handleSelectStory} onDestroySession={handleDestroySession} />
|
|
238
298
|
</div>
|
|
239
299
|
|
|
240
300
|
{/* Drag Handle */}
|
|
@@ -9,6 +9,7 @@ interface FileStatus {
|
|
|
9
9
|
|
|
10
10
|
interface StoryInfo {
|
|
11
11
|
name: string;
|
|
12
|
+
title: string | null;
|
|
12
13
|
files: FileStatus[];
|
|
13
14
|
hasStructure: boolean;
|
|
14
15
|
hasGenesis: boolean;
|
|
@@ -21,6 +22,8 @@ interface StoryBrowserProps {
|
|
|
21
22
|
selectedStory: string | null;
|
|
22
23
|
selectedFile: string | null;
|
|
23
24
|
onSelectFile: (storyName: string, fileName: string) => void;
|
|
25
|
+
onNewStory?: () => void;
|
|
26
|
+
untitledSessions?: string[];
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
const STATUS_ICON: Record<string, string> = {
|
|
@@ -37,7 +40,7 @@ const STATUS_COLOR: Record<string, string> = {
|
|
|
37
40
|
"draft": "text-muted",
|
|
38
41
|
};
|
|
39
42
|
|
|
40
|
-
export function StoryBrowser({ authFetch, selectedStory, selectedFile, onSelectFile }: StoryBrowserProps) {
|
|
43
|
+
export function StoryBrowser({ authFetch, selectedStory, selectedFile, onSelectFile, onNewStory, untitledSessions = [] }: StoryBrowserProps) {
|
|
41
44
|
const [stories, setStories] = useState<StoryInfo[]>([]);
|
|
42
45
|
const [expanded, setExpanded] = useState<Set<string>>(new Set());
|
|
43
46
|
|
|
@@ -115,10 +118,24 @@ export function StoryBrowser({ authFetch, selectedStory, selectedFile, onSelectF
|
|
|
115
118
|
<span className="text-xs text-muted">{stories.length}</span>
|
|
116
119
|
</div>
|
|
117
120
|
<div className="flex-1 min-h-0 overflow-y-auto">
|
|
118
|
-
{
|
|
121
|
+
{/* Untitled new story sessions */}
|
|
122
|
+
{untitledSessions.map((id) => (
|
|
123
|
+
<div key={id}>
|
|
124
|
+
<button
|
|
125
|
+
onClick={() => onSelectFile(id, "")}
|
|
126
|
+
className={`w-full px-3 py-2 text-left flex items-center gap-2 hover:bg-surface text-sm ${
|
|
127
|
+
selectedStory === id ? "bg-surface" : ""
|
|
128
|
+
}`}
|
|
129
|
+
>
|
|
130
|
+
<span className="w-1.5 h-1.5 rounded-full bg-green-600 flex-shrink-0" />
|
|
131
|
+
<span className="font-medium italic text-muted">Untitled</span>
|
|
132
|
+
</button>
|
|
133
|
+
</div>
|
|
134
|
+
))}
|
|
135
|
+
{stories.length === 0 && untitledSessions.length === 0 ? (
|
|
119
136
|
<div className="p-3 text-sm text-muted">
|
|
120
137
|
<p>No stories yet.</p>
|
|
121
|
-
<p className="mt-1 text-xs">
|
|
138
|
+
<p className="mt-1 text-xs">Click "+ New Story" below to start writing.</p>
|
|
122
139
|
</div>
|
|
123
140
|
) : (
|
|
124
141
|
stories.filter((s) => s.name !== "_example").map((story) => (
|
|
@@ -128,7 +145,7 @@ export function StoryBrowser({ authFetch, selectedStory, selectedFile, onSelectF
|
|
|
128
145
|
className="w-full px-3 py-2 text-left flex items-center gap-2 hover:bg-surface text-sm"
|
|
129
146
|
>
|
|
130
147
|
<span className="text-xs text-muted">{expanded.has(story.name) ? "\u25BC" : "\u25B6"}</span>
|
|
131
|
-
<span className="font-medium truncate">{story.name}</span>
|
|
148
|
+
<span className="font-medium truncate" title={story.name}>{story.title || story.name}</span>
|
|
132
149
|
<span className="ml-auto text-xs text-muted">
|
|
133
150
|
{story.publishedCount}/{story.files.length}
|
|
134
151
|
</span>
|
|
@@ -156,6 +173,17 @@ export function StoryBrowser({ authFetch, selectedStory, selectedFile, onSelectF
|
|
|
156
173
|
))
|
|
157
174
|
)}
|
|
158
175
|
</div>
|
|
176
|
+
{onNewStory && (
|
|
177
|
+
<div className="px-3 py-2 border-t border-border">
|
|
178
|
+
<button
|
|
179
|
+
onClick={onNewStory}
|
|
180
|
+
className="w-full px-3 py-1.5 text-sm text-accent hover:bg-surface rounded flex items-center gap-1.5"
|
|
181
|
+
>
|
|
182
|
+
<span>+</span>
|
|
183
|
+
<span>New Story</span>
|
|
184
|
+
</button>
|
|
185
|
+
</div>
|
|
186
|
+
)}
|
|
159
187
|
</div>
|
|
160
188
|
);
|
|
161
189
|
}
|
|
@@ -9,6 +9,7 @@ interface TerminalPanelProps {
|
|
|
9
9
|
storyName: string | null;
|
|
10
10
|
authFetch: (url: string, opts?: RequestInit) => Promise<Response>;
|
|
11
11
|
onSelectStory?: (storyName: string) => void;
|
|
12
|
+
onDestroySession?: (storyName: string) => void;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
15
|
interface TerminalSession {
|
|
@@ -36,7 +37,7 @@ const THEME = {
|
|
|
36
37
|
blue: "#4A6FA5",
|
|
37
38
|
magenta: "#7B4B8A",
|
|
38
39
|
cyan: "#3D7A7A",
|
|
39
|
-
white: "#
|
|
40
|
+
white: "#E6DDD0", // subtle cream tint for input line backgrounds
|
|
40
41
|
brightBlack: "#8B7355",
|
|
41
42
|
brightRed: "#B85C5C", // muted red — readable as text, soft as diff bg
|
|
42
43
|
brightGreen: "#5A8A5A", // muted green — readable as text, soft as diff bg
|
|
@@ -92,7 +93,7 @@ async function loadScrollback(storyName: string): Promise<string | null> {
|
|
|
92
93
|
// Sessions live outside React state to avoid ref-in-effect lint issues
|
|
93
94
|
const sessions = new Map<string, TerminalSession>();
|
|
94
95
|
|
|
95
|
-
export function TerminalPanel({ token, storyName, authFetch, onSelectStory }: TerminalPanelProps) {
|
|
96
|
+
export function TerminalPanel({ token, storyName, authFetch, onSelectStory, onDestroySession }: TerminalPanelProps) {
|
|
96
97
|
const wrapperRef = useRef<HTMLDivElement>(null);
|
|
97
98
|
const authFetchRef = useRef(authFetch);
|
|
98
99
|
const [sessionList, setSessionList] = useState<string[]>([]);
|
|
@@ -184,6 +185,8 @@ export function TerminalPanel({ token, storyName, authFetch, onSelectStory }: Te
|
|
|
184
185
|
container.style.width = "100%";
|
|
185
186
|
container.style.height = "100%";
|
|
186
187
|
container.style.display = "none";
|
|
188
|
+
container.style.paddingLeft = "10px";
|
|
189
|
+
container.style.boxSizing = "border-box";
|
|
187
190
|
wrapperRef.current.appendChild(container);
|
|
188
191
|
|
|
189
192
|
const term = new Terminal({
|
|
@@ -207,11 +210,6 @@ export function TerminalPanel({ token, storyName, authFetch, onSelectStory }: Te
|
|
|
207
210
|
term.loadAddon(serialize);
|
|
208
211
|
term.open(container);
|
|
209
212
|
|
|
210
|
-
// Apply padding to term.element so FitAddon measures correctly
|
|
211
|
-
if (term.element) {
|
|
212
|
-
term.element.style.paddingLeft = "10px";
|
|
213
|
-
}
|
|
214
|
-
|
|
215
213
|
const session: TerminalSession = { term, fit, serialize, ws: null, container, observer: null as unknown as ResizeObserver, connected: false };
|
|
216
214
|
|
|
217
215
|
const observer = new ResizeObserver(() => {
|
|
@@ -286,7 +284,8 @@ export function TerminalPanel({ token, storyName, authFetch, onSelectStory }: Te
|
|
|
286
284
|
setDisconnected((prev) => { const next = new Set(prev); next.delete(name); return next; });
|
|
287
285
|
|
|
288
286
|
authFetch(`/api/terminal/${encodeURIComponent(name)}`, { method: "DELETE" }).catch(() => {});
|
|
289
|
-
|
|
287
|
+
onDestroySession?.(name);
|
|
288
|
+
}, [authFetch, onDestroySession]);
|
|
290
289
|
|
|
291
290
|
// Auto-spawn + show/hide when story changes
|
|
292
291
|
useEffect(() => {
|
|
@@ -368,7 +367,9 @@ export function TerminalPanel({ token, storyName, authFetch, onSelectStory }: Te
|
|
|
368
367
|
<span className={`w-1.5 h-1.5 rounded-full ${
|
|
369
368
|
disconnected.has(name) ? "bg-amber-500" : name === storyName ? "bg-green-600" : "bg-muted/50"
|
|
370
369
|
}`} />
|
|
371
|
-
<span className=
|
|
370
|
+
<span className={`truncate max-w-[120px] ${name.startsWith("_new_") ? "italic" : ""}`}>
|
|
371
|
+
{name.startsWith("_new_") ? "Untitled" : name}
|
|
372
|
+
</span>
|
|
372
373
|
<button
|
|
373
374
|
onClick={(e) => {
|
|
374
375
|
e.stopPropagation();
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) 2014 The xterm.js authors. All rights reserved.
|
|
3
|
+
* Copyright (c) 2012-2013, Christopher Jeffrey (MIT License)
|
|
4
|
+
* https://github.com/chjj/term.js
|
|
5
|
+
* @license MIT
|
|
6
|
+
*
|
|
7
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
8
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
9
|
+
* in the Software without restriction, including without limitation the rights
|
|
10
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
11
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
12
|
+
* furnished to do so, subject to the following conditions:
|
|
13
|
+
*
|
|
14
|
+
* The above copyright notice and this permission notice shall be included in
|
|
15
|
+
* all copies or substantial portions of the Software.
|
|
16
|
+
*
|
|
17
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
18
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
19
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
20
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
21
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
22
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
23
|
+
* THE SOFTWARE.
|
|
24
|
+
*
|
|
25
|
+
* Originally forked from (with the author's permission):
|
|
26
|
+
* Fabrice Bellard's javascript vt100 for jslinux:
|
|
27
|
+
* http://bellard.org/jslinux/
|
|
28
|
+
* Copyright (c) 2011 Fabrice Bellard
|
|
29
|
+
* The original design remains. The terminal itself
|
|
30
|
+
* has been extended to include xterm CSI codes, among
|
|
31
|
+
* other features.
|
|
32
|
+
*/.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:none}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{background-color:#000;overflow-y:scroll;cursor:default;position:absolute;right:0;left:0;top:0;bottom:0}.xterm .xterm-screen{position:relative}.xterm .xterm-screen canvas{position:absolute;left:0;top:0}.xterm-char-measure-element{display:inline-block;visibility:hidden;position:absolute;top:0;left:-9999em;line-height:normal}.xterm.enable-mouse-events{cursor:default}.xterm.xterm-cursor-pointer,.xterm .xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) *::selection{color:transparent}.xterm .xterm-accessibility-tree{font-family:monospace;-webkit-user-select:text;user-select:text;white-space:pre}.xterm .xterm-accessibility-tree>div{transform-origin:left;width:fit-content}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{opacity:1!important}.xterm-underline-1{text-decoration:underline}.xterm-underline-2{text-decoration:double underline}.xterm-underline-3{text-decoration:wavy underline}.xterm-underline-4{text-decoration:dotted underline}.xterm-underline-5{text-decoration:dashed underline}.xterm-overline{text-decoration:overline}.xterm-overline.xterm-underline-1{text-decoration:overline underline}.xterm-overline.xterm-underline-2{text-decoration:overline double underline}.xterm-overline.xterm-underline-3{text-decoration:overline wavy underline}.xterm-overline.xterm-underline-4{text-decoration:overline dotted underline}.xterm-overline.xterm-underline-5{text-decoration:overline dashed underline}.xterm-strikethrough{text-decoration:line-through}.xterm-screen .xterm-decoration-container .xterm-decoration{z-index:6;position:absolute}.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer{z-index:7}.xterm-decoration-overview-ruler{z-index:8;position:absolute;top:0;right:0;pointer-events:none}.xterm-decoration-top{z-index:2;position:relative}.xterm .xterm-scrollable-element>.scrollbar{cursor:default}.xterm .xterm-scrollable-element>.scrollbar>.scra{cursor:pointer;font-size:11px!important}.xterm .xterm-scrollable-element>.visible{opacity:1;background:#0000;transition:opacity .1s linear;z-index:11}.xterm .xterm-scrollable-element>.invisible{opacity:0;pointer-events:none}.xterm .xterm-scrollable-element>.invisible.fade{transition:opacity .8s linear}.xterm .xterm-scrollable-element>.shadow{position:absolute;display:none}.xterm .xterm-scrollable-element>.shadow.top{display:block;top:0;left:3px;height:3px;width:100%;box-shadow:var(--vscode-scrollbar-shadow, #000) 0 6px 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.left{display:block;top:3px;left:0;height:100%;width:3px;box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}.xterm .xterm-scrollable-element>.shadow.top-left-corner{display:block;top:0;left:0;height:3px;width:3px}.xterm .xterm-scrollable-element>.shadow.top.left{box-shadow:var(--vscode-scrollbar-shadow, #000) 6px 0 6px -6px inset}/*! tailwindcss v4.2.2 | MIT License | https://tailwindcss.com */@layer properties{@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))){*,:before,:after,::backdrop{--tw-rotate-x:initial;--tw-rotate-y:initial;--tw-rotate-z:initial;--tw-skew-x:initial;--tw-skew-y:initial;--tw-space-y-reverse:0;--tw-border-style:solid;--tw-leading:initial;--tw-font-weight:initial;--tw-tracking:initial;--tw-shadow:0 0 #0000;--tw-shadow-color:initial;--tw-shadow-alpha:100%;--tw-inset-shadow:0 0 #0000;--tw-inset-shadow-color:initial;--tw-inset-shadow-alpha:100%;--tw-ring-color:initial;--tw-ring-shadow:0 0 #0000;--tw-inset-ring-color:initial;--tw-inset-ring-shadow:0 0 #0000;--tw-ring-inset:initial;--tw-ring-offset-width:0px;--tw-ring-offset-color:#fff;--tw-ring-offset-shadow:0 0 #0000;--tw-outline-style:solid;--tw-blur:initial;--tw-brightness:initial;--tw-contrast:initial;--tw-grayscale:initial;--tw-hue-rotate:initial;--tw-invert:initial;--tw-opacity:initial;--tw-saturate:initial;--tw-sepia:initial;--tw-drop-shadow:initial;--tw-drop-shadow-color:initial;--tw-drop-shadow-alpha:100%;--tw-drop-shadow-size:initial}}}@layer theme{:root,:host{--font-serif:ui-serif, Georgia, Cambria, "Times New Roman", Times, serif;--font-mono:ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;--color-red-700:oklch(50.5% .213 27.518);--color-amber-500:oklch(76.9% .188 70.08);--color-amber-600:oklch(66.6% .179 58.318);--color-amber-700:oklch(55.5% .163 48.998);--color-green-600:oklch(62.7% .194 149.214);--color-green-700:oklch(52.7% .154 150.069);--color-white:#fff;--spacing:.25rem;--container-sm:24rem;--container-lg:32rem;--container-2xl:42rem;--text-xs:.75rem;--text-xs--line-height:calc(1 / .75);--text-sm:.875rem;--text-sm--line-height:calc(1.25 / .875);--text-lg:1.125rem;--text-lg--line-height:calc(1.75 / 1.125);--text-2xl:1.5rem;--text-2xl--line-height:calc(2 / 1.5);--font-weight-medium:500;--font-weight-bold:700;--tracking-tight:-.025em;--tracking-wider:.05em;--leading-relaxed:1.625;--default-transition-duration:.15s;--default-transition-timing-function:cubic-bezier(.4, 0, .2, 1);--default-mono-font-family:var(--font-mono)}}@layer base{*,:after,:before,::backdrop{box-sizing:border-box;border:0 solid;margin:0;padding:0}::file-selector-button{box-sizing:border-box;border:0 solid;margin:0;padding:0}html,:host{-webkit-text-size-adjust:100%;-moz-tab-size:4;tab-size:4;font-feature-settings:var(--default-font-feature-settings,normal);font-variation-settings:var(--default-font-variation-settings,normal);-webkit-tap-highlight-color:transparent;font-family:Inter,system-ui,-apple-system,sans-serif;line-height:1.5}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;-webkit-text-decoration:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:var(--default-mono-font-family,ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace);font-feature-settings:var(--default-mono-font-feature-settings,normal);font-variation-settings:var(--default-mono-font-variation-settings,normal);font-size:1em}small{font-size:80%}sub,sup{vertical-align:baseline;font-size:75%;line-height:0;position:relative}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}:-moz-focusring{outline:auto}progress{vertical-align:baseline}summary{display:list-item}ol,ul,menu{list-style:none}img,svg,video,canvas,audio,iframe,embed,object{vertical-align:middle;display:block}img,video{max-width:100%;height:auto}button,input,select,optgroup,textarea{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}::file-selector-button{font:inherit;font-feature-settings:inherit;font-variation-settings:inherit;letter-spacing:inherit;color:inherit;opacity:1;background-color:#0000;border-radius:0}:where(select:is([multiple],[size])) optgroup{font-weight:bolder}:where(select:is([multiple],[size])) optgroup option{padding-inline-start:20px}::file-selector-button{margin-inline-end:4px}::placeholder{opacity:1}@supports (not ((-webkit-appearance:-apple-pay-button))) or (contain-intrinsic-size:1px){::placeholder{color:currentColor}@supports (color:color-mix(in lab,red,red)){::placeholder{color:color-mix(in oklab,currentcolor 50%,transparent)}}}textarea{resize:vertical}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-date-and-time-value{min-height:1lh;text-align:inherit}::-webkit-datetime-edit{display:inline-flex}::-webkit-datetime-edit-fields-wrapper{padding:0}::-webkit-datetime-edit{padding-block:0}::-webkit-datetime-edit-year-field{padding-block:0}::-webkit-datetime-edit-month-field{padding-block:0}::-webkit-datetime-edit-day-field{padding-block:0}::-webkit-datetime-edit-hour-field{padding-block:0}::-webkit-datetime-edit-minute-field{padding-block:0}::-webkit-datetime-edit-second-field{padding-block:0}::-webkit-datetime-edit-millisecond-field{padding-block:0}::-webkit-datetime-edit-meridiem-field{padding-block:0}::-webkit-calendar-picker-indicator{line-height:1}:-moz-ui-invalid{box-shadow:none}button,input:where([type=button],[type=reset],[type=submit]){-webkit-appearance:button;-moz-appearance:button;appearance:button}::file-selector-button{-webkit-appearance:button;-moz-appearance:button;appearance:button}::-webkit-inner-spin-button{height:auto}::-webkit-outer-spin-button{height:auto}[hidden]:where(:not([hidden=until-found])){display:none!important}}@layer components;@layer utilities{.invisible{visibility:hidden}.visible{visibility:visible}.sr-only{clip-path:inset(50%);white-space:nowrap;border-width:0;width:1px;height:1px;margin:-1px;padding:0;position:absolute;overflow:hidden}.absolute{position:absolute}.relative{position:relative}.static{position:static}.inset-0{inset:calc(var(--spacing) * 0)}.start{inset-inline-start:var(--spacing)}.start\!{inset-inline-start:var(--spacing)!important}.end{inset-inline-end:var(--spacing)}.top-1{top:calc(var(--spacing) * 1)}.right-1{right:calc(var(--spacing) * 1)}.container{width:100%}@media(min-width:40rem){.container{max-width:40rem}}@media(min-width:48rem){.container{max-width:48rem}}@media(min-width:64rem){.container{max-width:64rem}}@media(min-width:80rem){.container{max-width:80rem}}@media(min-width:96rem){.container{max-width:96rem}}.mx-auto{margin-inline:auto}.prose{color:var(--tw-prose-body);max-width:65ch}.prose :where(p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where([class~=lead]):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-lead);margin-top:1.2em;margin-bottom:1.2em;font-size:1.25em;line-height:1.6}.prose :where(a):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-links);font-weight:500;text-decoration:underline}.prose :where(strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-bold);font-weight:600}.prose :where(a strong):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(blockquote strong):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(thead th strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:decimal}.prose :where(ol[type=A]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=A s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-alpha}.prose :where(ol[type=a s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-alpha}.prose :where(ol[type=I]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type=I s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:upper-roman}.prose :where(ol[type=i s]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:lower-roman}.prose :where(ol[type="1"]):not(:where([class~=not-prose],[class~=not-prose] *)){list-style-type:decimal}.prose :where(ul):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em;padding-inline-start:1.625em;list-style-type:disc}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-counters);font-weight:400}.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *))::marker{color:var(--tw-prose-bullets)}.prose :where(dt):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.25em;font-weight:600}.prose :where(hr):not(:where([class~=not-prose],[class~=not-prose] *)){border-color:var(--tw-prose-hr);border-top-width:1px;margin-top:3em;margin-bottom:3em}.prose :where(blockquote):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-quotes);border-inline-start-width:.25rem;border-inline-start-color:var(--tw-prose-quote-borders);quotes:"“""”""‘""’";margin-top:1.6em;margin-bottom:1.6em;padding-inline-start:1em;font-style:italic;font-weight:500}.prose :where(blockquote p:first-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):before{content:open-quote}.prose :where(blockquote p:last-of-type):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:close-quote}.prose :where(h1):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:0;margin-bottom:.888889em;font-size:2.25em;font-weight:800;line-height:1.11111}.prose :where(h1 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:900}.prose :where(h2):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:2em;margin-bottom:1em;font-size:1.5em;font-weight:700;line-height:1.33333}.prose :where(h2 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:800}.prose :where(h3):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.6em;margin-bottom:.6em;font-size:1.25em;font-weight:600;line-height:1.6}.prose :where(h3 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(h4):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);margin-top:1.5em;margin-bottom:.5em;font-weight:600;line-height:1.5}.prose :where(h4 strong):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-weight:700}.prose :where(img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(picture):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em;display:block}.prose :where(video):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(kbd):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-kbd);box-shadow:0 0 0 1px var(--tw-prose-kbd-shadows),0 3px 0 var(--tw-prose-kbd-shadows);padding-top:.1875em;padding-inline-end:.375em;padding-bottom:.1875em;border-radius:.3125rem;padding-inline-start:.375em;font-family:inherit;font-size:.875em;font-weight:500}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-code);font-size:.875em;font-weight:600}.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.prose :where(code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:"`"}.prose :where(a code):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h1 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(h2 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.875em}.prose :where(h3 code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit;font-size:.9em}.prose :where(h4 code):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(blockquote code):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(thead th code):not(:where([class~=not-prose],[class~=not-prose] *)){color:inherit}.prose :where(pre):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-pre-code);background-color:var(--tw-prose-pre-bg);padding-top:.857143em;padding-inline-end:1.14286em;padding-bottom:.857143em;border-radius:.375rem;margin-top:1.71429em;margin-bottom:1.71429em;padding-inline-start:1.14286em;font-size:.875em;font-weight:400;line-height:1.71429;overflow-x:auto}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)){font-weight:inherit;color:inherit;font-size:inherit;font-family:inherit;line-height:inherit;background-color:#0000;border-width:0;border-radius:0;padding:0}.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):before,.prose :where(pre code):not(:where([class~=not-prose],[class~=not-prose] *)):after{content:none}.prose :where(table):not(:where([class~=not-prose],[class~=not-prose] *)){table-layout:auto;width:100%;margin-top:2em;margin-bottom:2em;font-size:.875em;line-height:1.71429}.prose :where(thead):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-th-borders)}.prose :where(thead th):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-headings);vertical-align:bottom;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em;font-weight:600}.prose :where(tbody tr):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:1px;border-bottom-color:var(--tw-prose-td-borders)}.prose :where(tbody tr:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){border-bottom-width:0}.prose :where(tbody td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:baseline}.prose :where(tfoot):not(:where([class~=not-prose],[class~=not-prose] *)){border-top-width:1px;border-top-color:var(--tw-prose-th-borders)}.prose :where(tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){vertical-align:top}.prose :where(th,td):not(:where([class~=not-prose],[class~=not-prose] *)){text-align:start}.prose :where(figure>*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(figcaption):not(:where([class~=not-prose],[class~=not-prose] *)){color:var(--tw-prose-captions);margin-top:.857143em;font-size:.875em;line-height:1.42857}.prose{--tw-prose-body:oklch(37.3% .034 259.733);--tw-prose-headings:oklch(21% .034 264.665);--tw-prose-lead:oklch(44.6% .03 256.802);--tw-prose-links:oklch(21% .034 264.665);--tw-prose-bold:oklch(21% .034 264.665);--tw-prose-counters:oklch(55.1% .027 264.364);--tw-prose-bullets:oklch(87.2% .01 258.338);--tw-prose-hr:oklch(92.8% .006 264.531);--tw-prose-quotes:oklch(21% .034 264.665);--tw-prose-quote-borders:oklch(92.8% .006 264.531);--tw-prose-captions:oklch(55.1% .027 264.364);--tw-prose-kbd:oklch(21% .034 264.665);--tw-prose-kbd-shadows:oklab(21% -.00316127 -.0338527/.1);--tw-prose-code:oklch(21% .034 264.665);--tw-prose-pre-code:oklch(92.8% .006 264.531);--tw-prose-pre-bg:oklch(27.8% .033 256.848);--tw-prose-th-borders:oklch(87.2% .01 258.338);--tw-prose-td-borders:oklch(92.8% .006 264.531);--tw-prose-invert-body:oklch(87.2% .01 258.338);--tw-prose-invert-headings:#fff;--tw-prose-invert-lead:oklch(70.7% .022 261.325);--tw-prose-invert-links:#fff;--tw-prose-invert-bold:#fff;--tw-prose-invert-counters:oklch(70.7% .022 261.325);--tw-prose-invert-bullets:oklch(44.6% .03 256.802);--tw-prose-invert-hr:oklch(37.3% .034 259.733);--tw-prose-invert-quotes:oklch(96.7% .003 264.542);--tw-prose-invert-quote-borders:oklch(37.3% .034 259.733);--tw-prose-invert-captions:oklch(70.7% .022 261.325);--tw-prose-invert-kbd:#fff;--tw-prose-invert-kbd-shadows:#ffffff1a;--tw-prose-invert-code:#fff;--tw-prose-invert-pre-code:oklch(87.2% .01 258.338);--tw-prose-invert-pre-bg:#00000080;--tw-prose-invert-th-borders:oklch(44.6% .03 256.802);--tw-prose-invert-td-borders:oklch(37.3% .034 259.733);font-size:1rem;line-height:1.75}.prose :where(picture>img):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0;margin-bottom:0}.prose :where(li):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;margin-bottom:.5em}.prose :where(ol>li):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(ul>li):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:.375em}.prose :where(.prose>ul>li p):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(.prose>ul>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ul>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(.prose>ol>li>p:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em}.prose :where(.prose>ol>li>p:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:1.25em}.prose :where(ul ul,ul ol,ol ul,ol ol):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.75em;margin-bottom:.75em}.prose :where(dl):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:1.25em;margin-bottom:1.25em}.prose :where(dd):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:.5em;padding-inline-start:1.625em}.prose :where(hr+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h2+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h3+*):not(:where([class~=not-prose],[class~=not-prose] *)),.prose :where(h4+*):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(thead th:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(thead th:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(tbody td,tfoot td):not(:where([class~=not-prose],[class~=not-prose] *)){padding-top:.571429em;padding-inline-end:.571429em;padding-bottom:.571429em;padding-inline-start:.571429em}.prose :where(tbody td:first-child,tfoot td:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-start:0}.prose :where(tbody td:last-child,tfoot td:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){padding-inline-end:0}.prose :where(figure):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:2em;margin-bottom:2em}.prose :where(.prose>:first-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-top:0}.prose :where(.prose>:last-child):not(:where([class~=not-prose],[class~=not-prose] *)){margin-bottom:0}.mt-0\.5{margin-top:calc(var(--spacing) * .5)}.mt-1{margin-top:calc(var(--spacing) * 1)}.mt-2{margin-top:calc(var(--spacing) * 2)}.mt-3{margin-top:calc(var(--spacing) * 3)}.mt-4{margin-top:calc(var(--spacing) * 4)}.mb-1{margin-bottom:calc(var(--spacing) * 1)}.mb-1\.5{margin-bottom:calc(var(--spacing) * 1.5)}.mb-2{margin-bottom:calc(var(--spacing) * 2)}.mb-3{margin-bottom:calc(var(--spacing) * 3)}.mb-4{margin-bottom:calc(var(--spacing) * 4)}.mb-6{margin-bottom:calc(var(--spacing) * 6)}.ml-0\.5{margin-left:calc(var(--spacing) * .5)}.ml-1{margin-left:calc(var(--spacing) * 1)}.ml-auto{margin-left:auto}.block{display:block}.flex{display:flex}.grid{display:grid}.hidden{display:none}.table{display:table}.h-0\.5{height:calc(var(--spacing) * .5)}.h-1\.5{height:calc(var(--spacing) * 1.5)}.h-14{height:calc(var(--spacing) * 14)}.h-\[calc\(100vh-3\.5rem\)\]{height:calc(100vh - 3.5rem)}.h-full{height:100%}.h-screen{height:100vh}.min-h-0{min-height:calc(var(--spacing) * 0)}.w-0\.5{width:calc(var(--spacing) * .5)}.w-1\.5{width:calc(var(--spacing) * 1.5)}.w-56{width:calc(var(--spacing) * 56)}.w-full{width:100%}.max-w-2xl{max-width:var(--container-2xl)}.max-w-\[120px\]{max-width:120px}.max-w-lg{max-width:var(--container-lg)}.max-w-none{max-width:none}.max-w-sm{max-width:var(--container-sm)}.min-w-0{min-width:calc(var(--spacing) * 0)}.flex-1{flex:1}.flex-shrink-0{flex-shrink:0}.transform{transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)}.cursor-pointer{cursor:pointer}.resize{resize:both}.resize-none{resize:none}.list-inside{list-style-position:inside}.list-decimal{list-style-type:decimal}.grid-cols-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-cols-4{grid-template-columns:repeat(4,minmax(0,1fr))}.flex-col{flex-direction:column}.items-center{align-items:center}.items-start{align-items:flex-start}.justify-between{justify-content:space-between}.justify-center{justify-content:center}.gap-1{gap:calc(var(--spacing) * 1)}.gap-1\.5{gap:calc(var(--spacing) * 1.5)}.gap-2{gap:calc(var(--spacing) * 2)}.gap-3{gap:calc(var(--spacing) * 3)}.gap-4{gap:calc(var(--spacing) * 4)}:where(.space-y-1>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-1\.5>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 1.5) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 1.5) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-2>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 2) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 2) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-3>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 3) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 3) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-4>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 4) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 4) * calc(1 - var(--tw-space-y-reverse)))}:where(.space-y-6>:not(:last-child)){--tw-space-y-reverse:0;margin-block-start:calc(calc(var(--spacing) * 6) * var(--tw-space-y-reverse));margin-block-end:calc(calc(var(--spacing) * 6) * calc(1 - var(--tw-space-y-reverse)))}.truncate{text-overflow:ellipsis;white-space:nowrap;overflow:hidden}.overflow-x-auto{overflow-x:auto}.overflow-y-auto{overflow-y:auto}.rounded{border-radius:.25rem}.rounded-full{border-radius:3.40282e38px}.border{border-style:var(--tw-border-style);border-width:1px}.border-t{border-top-style:var(--tw-border-style);border-top-width:1px}.border-r{border-right-style:var(--tw-border-style);border-right-width:1px}.border-b{border-bottom-style:var(--tw-border-style);border-bottom-width:1px}.border-b-2{border-bottom-style:var(--tw-border-style);border-bottom-width:2px}.border-accent{border-color:#8b4513}.border-accent-dim\/30{border-color:#6b34104d}.border-accent\/30{border-color:#8b45134d}.border-amber-600\/30{border-color:#dd74004d}@supports (color:color-mix(in lab,red,red)){.border-amber-600\/30{border-color:color-mix(in oklab,var(--color-amber-600) 30%,transparent)}}.border-border{border-color:#d4c5b0}.border-green-700\/30{border-color:#0081384d}@supports (color:color-mix(in lab,red,red)){.border-green-700\/30{border-color:color-mix(in oklab,var(--color-green-700) 30%,transparent)}}.border-red-700\/30{border-color:#bf000f4d}@supports (color:color-mix(in lab,red,red)){.border-red-700\/30{border-color:color-mix(in oklab,var(--color-red-700) 30%,transparent)}}.border-transparent{border-color:#0000}.bg-accent{background-color:#8b4513}.bg-accent\/10{background-color:#8b45131a}.bg-amber-500{background-color:var(--color-amber-500)}.bg-background{background-color:#e8dfd0}.bg-green-600{background-color:var(--color-green-600)}.bg-muted\/50{background-color:#8b735580}.bg-surface{background-color:#f0ebe1}.p-1\.5{padding:calc(var(--spacing) * 1.5)}.p-2{padding:calc(var(--spacing) * 2)}.p-3{padding:calc(var(--spacing) * 3)}.p-4{padding:calc(var(--spacing) * 4)}.p-6{padding:calc(var(--spacing) * 6)}.p-8{padding:calc(var(--spacing) * 8)}.px-1\.5{padding-inline:calc(var(--spacing) * 1.5)}.px-2{padding-inline:calc(var(--spacing) * 2)}.px-3{padding-inline:calc(var(--spacing) * 3)}.px-4{padding-inline:calc(var(--spacing) * 4)}.px-6{padding-inline:calc(var(--spacing) * 6)}.py-0\.5{padding-block:calc(var(--spacing) * .5)}.py-1{padding-block:calc(var(--spacing) * 1)}.py-1\.5{padding-block:calc(var(--spacing) * 1.5)}.py-2{padding-block:calc(var(--spacing) * 2)}.py-2\.5{padding-block:calc(var(--spacing) * 2.5)}.py-3{padding-block:calc(var(--spacing) * 3)}.py-4{padding-block:calc(var(--spacing) * 4)}.pt-1\.5{padding-top:calc(var(--spacing) * 1.5)}.pt-3{padding-top:calc(var(--spacing) * 3)}.pt-4{padding-top:calc(var(--spacing) * 4)}.pr-16{padding-right:calc(var(--spacing) * 16)}.pl-3{padding-left:calc(var(--spacing) * 3)}.pl-4{padding-left:calc(var(--spacing) * 4)}.text-center{text-align:center}.text-left{text-align:left}.font-mono{font-family:var(--font-mono)}.font-serif{font-family:var(--font-serif)}.text-2xl{font-size:var(--text-2xl);line-height:var(--tw-leading,var(--text-2xl--line-height))}.text-lg{font-size:var(--text-lg);line-height:var(--tw-leading,var(--text-lg--line-height))}.text-sm{font-size:var(--text-sm);line-height:var(--tw-leading,var(--text-sm--line-height))}.text-xs{font-size:var(--text-xs);line-height:var(--tw-leading,var(--text-xs--line-height))}.text-\[9px\]{font-size:9px}.text-\[10px\]{font-size:10px}.leading-none{--tw-leading:1;line-height:1}.leading-relaxed{--tw-leading:var(--leading-relaxed);line-height:var(--leading-relaxed)}.font-bold{--tw-font-weight:var(--font-weight-bold);font-weight:var(--font-weight-bold)}.font-medium{--tw-font-weight:var(--font-weight-medium);font-weight:var(--font-weight-medium)}.tracking-tight{--tw-tracking:var(--tracking-tight);letter-spacing:var(--tracking-tight)}.tracking-wider{--tw-tracking:var(--tracking-wider);letter-spacing:var(--tracking-wider)}.break-all{word-break:break-all}.text-accent{color:#8b4513}.text-accent-dim{color:#6b3410}.text-amber-600{color:var(--color-amber-600)}.text-amber-700{color:var(--color-amber-700)}.text-error{color:#c33}.text-foreground{color:#2c1810}.text-green-700{color:var(--color-green-700)}.text-muted{color:#8b7355}.text-red-700{color:var(--color-red-700)}.text-white{color:var(--color-white)}.uppercase{text-transform:uppercase}.italic{font-style:italic}.overline{text-decoration-line:overline}.underline{text-decoration-line:underline}.shadow{--tw-shadow:0 1px 3px 0 var(--tw-shadow-color,#0000001a), 0 1px 2px -1px var(--tw-shadow-color,#0000001a);box-shadow:var(--tw-inset-shadow),var(--tw-inset-ring-shadow),var(--tw-ring-offset-shadow),var(--tw-ring-shadow),var(--tw-shadow)}.outline{outline-style:var(--tw-outline-style);outline-width:1px}.blur{--tw-blur:blur(8px);filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.filter{filter:var(--tw-blur,) var(--tw-brightness,) var(--tw-contrast,) var(--tw-grayscale,) var(--tw-hue-rotate,) var(--tw-invert,) var(--tw-saturate,) var(--tw-sepia,) var(--tw-drop-shadow,)}.transition{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to,opacity,box-shadow,transform,translate,scale,rotate,filter,-webkit-backdrop-filter,backdrop-filter,display,content-visibility,overlay,pointer-events;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.transition-colors{transition-property:color,background-color,border-color,outline-color,text-decoration-color,fill,stroke,--tw-gradient-from,--tw-gradient-via,--tw-gradient-to;transition-timing-function:var(--tw-ease,var(--default-transition-timing-function));transition-duration:var(--tw-duration,var(--default-transition-duration))}.outline-none{--tw-outline-style:none;outline-style:none}.placeholder\:text-muted\/50::placeholder{color:#8b735580}@media(hover:hover){.hover\:border-accent:hover{border-color:#8b4513}.hover\:border-error:hover{border-color:#c33}.hover\:bg-accent-dim:hover{background-color:#6b3410}.hover\:bg-accent\/10:hover{background-color:#8b45131a}.hover\:bg-border\/50:hover{background-color:#d4c5b080}.hover\:bg-surface:hover{background-color:#f0ebe1}.hover\:text-accent:hover{color:#8b4513}.hover\:text-error:hover{color:#c33}.hover\:text-foreground:hover{color:#2c1810}.hover\:opacity-80:hover{opacity:.8}}.focus\:border-accent:focus{border-color:#8b4513}.focus\:outline-none:focus{--tw-outline-style:none;outline-style:none}.disabled\:cursor-not-allowed:disabled{cursor:not-allowed}.disabled\:opacity-40:disabled{opacity:.4}.disabled\:opacity-50:disabled{opacity:.5}}:root{--bg:#e8dfd0;--bg-surface:#f0ebe1;--bg-shelf:#ddd3c2;--text:#2c1810;--text-muted:#8b7355;--accent:#8b4513;--accent-dim:#6b3410;--border:#d4c5b0;--error:#c33;--paper-bg:#f5f0e8}body{background:var(--bg);color:var(--text);-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,system-ui,-apple-system,sans-serif}::selection{background:var(--accent);color:#fff}h1,h2,h3,h4{font-family:Lora,Georgia,Times New Roman,serif}.prose{--tw-prose-body:var(--text);--tw-prose-headings:var(--text);--tw-prose-links:var(--accent);--tw-prose-bold:var(--text);--tw-prose-quotes:var(--text-muted);--tw-prose-quote-borders:var(--border);--tw-prose-code:var(--text);--tw-prose-hr:var(--border)}.prose,.prose p,.prose li,.prose blockquote{font-family:Lora,Georgia,Times New Roman,serif}code,pre{font-family:Geist Mono,ui-monospace,monospace}.xterm .xterm-dim{opacity:1!important;color:#8b7355!important}.xterm,.xterm-viewport{border:none!important;outline:none!important}@property --tw-rotate-x{syntax:"*";inherits:false}@property --tw-rotate-y{syntax:"*";inherits:false}@property --tw-rotate-z{syntax:"*";inherits:false}@property --tw-skew-x{syntax:"*";inherits:false}@property --tw-skew-y{syntax:"*";inherits:false}@property --tw-space-y-reverse{syntax:"*";inherits:false;initial-value:0}@property --tw-border-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-leading{syntax:"*";inherits:false}@property --tw-font-weight{syntax:"*";inherits:false}@property --tw-tracking{syntax:"*";inherits:false}@property --tw-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-shadow-color{syntax:"*";inherits:false}@property --tw-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-inset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-shadow-color{syntax:"*";inherits:false}@property --tw-inset-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-ring-color{syntax:"*";inherits:false}@property --tw-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-inset-ring-color{syntax:"*";inherits:false}@property --tw-inset-ring-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-ring-inset{syntax:"*";inherits:false}@property --tw-ring-offset-width{syntax:"<length>";inherits:false;initial-value:0}@property --tw-ring-offset-color{syntax:"*";inherits:false;initial-value:#fff}@property --tw-ring-offset-shadow{syntax:"*";inherits:false;initial-value:0 0 #0000}@property --tw-outline-style{syntax:"*";inherits:false;initial-value:solid}@property --tw-blur{syntax:"*";inherits:false}@property --tw-brightness{syntax:"*";inherits:false}@property --tw-contrast{syntax:"*";inherits:false}@property --tw-grayscale{syntax:"*";inherits:false}@property --tw-hue-rotate{syntax:"*";inherits:false}@property --tw-invert{syntax:"*";inherits:false}@property --tw-opacity{syntax:"*";inherits:false}@property --tw-saturate{syntax:"*";inherits:false}@property --tw-sepia{syntax:"*";inherits:false}@property --tw-drop-shadow{syntax:"*";inherits:false}@property --tw-drop-shadow-color{syntax:"*";inherits:false}@property --tw-drop-shadow-alpha{syntax:"<percentage>";inherits:false;initial-value:100%}@property --tw-drop-shadow-size{syntax:"*";inherits:false}
|