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.
@@ -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
- return { name, files, hasStructure, hasGenesis, plotCount, publishedCount };
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 */
@@ -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
- const storyDir = path.join(STORIES_DIR, storyName);
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
- /** Migrate stories/data from old package-relative paths to ~/.plotlink-ows/ */
64
- function migrateOldData() {
65
- const oldStoriesDir = path.join(__dirname, "..", "stories");
66
- const oldDataDir = path.join(__dirname, "..", "data");
67
-
68
- // Migrate stories
69
- if (fs.existsSync(oldStoriesDir) && oldStoriesDir !== STORIES_DIR) {
70
- const oldEntries = fs.readdirSync(oldStoriesDir, { withFileTypes: true })
71
- .filter((d) => d.isDirectory() && !d.name.startsWith(".") && d.name !== "_example");
72
- for (const entry of oldEntries) {
73
- const src = path.join(oldStoriesDir, entry.name);
74
- const dest = path.join(STORIES_DIR, entry.name);
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
- // Migrate database
88
- const oldDb = path.join(oldDataDir, "local.db");
89
- const newDb = path.join(DATA_DIR, "local.db");
90
- if (fs.existsSync(oldDb) && !fs.existsSync(newDb)) {
91
- fs.copyFileSync(oldDb, newDb);
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
- // Migrate terminal sessions
96
- const oldSessions = path.join(oldDataDir, "terminal-sessions.json");
97
- const newSessions = path.join(DATA_DIR, "terminal-sessions.json");
98
- if (fs.existsSync(oldSessions) && !fs.existsSync(newSessions)) {
99
- fs.copyFileSync(oldSessions, newSessions);
100
- console.log(` Migrated terminal sessions → ${DATA_DIR}`);
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
- {stories.length === 0 ? (
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">Use the terminal to start writing with Claude.</p>
138
+ <p className="mt-1 text-xs">Click &quot;+ New Story&quot; 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: "#3A2A1E",
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
- }, [authFetch]);
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="truncate max-w-[120px]">{name}</span>
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}