@jxrstudios/jxr 1.0.10 → 1.1.11
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/bin/jxr.js +6 -0
- package/dist/index.js +57 -2
- package/dist/jxr-server-manager.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/jxr-server-manager.ts +65 -2
- package/zzz_react_template/App.tsx +43 -156
- package/zzz_react_template/components/ErrorBoundary.tsx +62 -0
- package/zzz_react_template/components/ManusDialog.tsx +85 -0
- package/zzz_react_template/components/Map.tsx +155 -0
- package/zzz_react_template/components/jxr/CodeEditor.tsx +313 -0
- package/zzz_react_template/components/jxr/FileExplorer.tsx +230 -0
- package/zzz_react_template/components/jxr/IDEShell.tsx +159 -0
- package/zzz_react_template/components/jxr/LandingPage.tsx +414 -0
- package/zzz_react_template/components/jxr/LivePreview.tsx +169 -0
- package/zzz_react_template/components/jxr/PerformanceDashboard.tsx +379 -0
- package/zzz_react_template/components/jxr/TopBar.tsx +149 -0
- package/zzz_react_template/components/ui/accordion.tsx +64 -0
- package/zzz_react_template/components/ui/alert-dialog.tsx +155 -0
- package/zzz_react_template/components/ui/alert.tsx +66 -0
- package/zzz_react_template/components/ui/aspect-ratio.tsx +9 -0
- package/zzz_react_template/components/ui/avatar.tsx +51 -0
- package/zzz_react_template/components/ui/badge.tsx +46 -0
- package/zzz_react_template/components/ui/breadcrumb.tsx +109 -0
- package/zzz_react_template/components/ui/button-group.tsx +83 -0
- package/zzz_react_template/components/ui/button.tsx +60 -0
- package/zzz_react_template/components/ui/calendar.tsx +211 -0
- package/zzz_react_template/components/ui/card.tsx +92 -0
- package/zzz_react_template/components/ui/carousel.tsx +239 -0
- package/zzz_react_template/components/ui/chart.tsx +355 -0
- package/zzz_react_template/components/ui/checkbox.tsx +30 -0
- package/zzz_react_template/components/ui/collapsible.tsx +31 -0
- package/zzz_react_template/components/ui/command.tsx +184 -0
- package/zzz_react_template/components/ui/context-menu.tsx +250 -0
- package/zzz_react_template/components/ui/dialog.tsx +209 -0
- package/zzz_react_template/components/ui/drawer.tsx +133 -0
- package/zzz_react_template/components/ui/dropdown-menu.tsx +255 -0
- package/zzz_react_template/components/ui/empty.tsx +104 -0
- package/zzz_react_template/components/ui/field.tsx +242 -0
- package/zzz_react_template/components/ui/form.tsx +168 -0
- package/zzz_react_template/components/ui/hover-card.tsx +42 -0
- package/zzz_react_template/components/ui/input-group.tsx +168 -0
- package/zzz_react_template/components/ui/input-otp.tsx +75 -0
- package/zzz_react_template/components/ui/input.tsx +70 -0
- package/zzz_react_template/components/ui/item.tsx +193 -0
- package/zzz_react_template/components/ui/kbd.tsx +28 -0
- package/zzz_react_template/components/ui/label.tsx +22 -0
- package/zzz_react_template/components/ui/menubar.tsx +274 -0
- package/zzz_react_template/components/ui/navigation-menu.tsx +168 -0
- package/zzz_react_template/components/ui/pagination.tsx +127 -0
- package/zzz_react_template/components/ui/popover.tsx +46 -0
- package/zzz_react_template/components/ui/progress.tsx +29 -0
- package/zzz_react_template/components/ui/radio-group.tsx +43 -0
- package/zzz_react_template/components/ui/resizable.tsx +54 -0
- package/zzz_react_template/components/ui/scroll-area.tsx +56 -0
- package/zzz_react_template/components/ui/select.tsx +185 -0
- package/zzz_react_template/components/ui/separator.tsx +26 -0
- package/zzz_react_template/components/ui/sheet.tsx +139 -0
- package/zzz_react_template/components/ui/sidebar.tsx +734 -0
- package/zzz_react_template/components/ui/skeleton.tsx +13 -0
- package/zzz_react_template/components/ui/slider.tsx +61 -0
- package/zzz_react_template/components/ui/sonner.tsx +23 -0
- package/zzz_react_template/components/ui/spinner.tsx +16 -0
- package/zzz_react_template/components/ui/switch.tsx +29 -0
- package/zzz_react_template/components/ui/table.tsx +114 -0
- package/zzz_react_template/components/ui/tabs.tsx +64 -0
- package/zzz_react_template/components/ui/textarea.tsx +67 -0
- package/zzz_react_template/components/ui/toggle-group.tsx +73 -0
- package/zzz_react_template/components/ui/toggle.tsx +45 -0
- package/zzz_react_template/components/ui/tooltip.tsx +59 -0
- package/zzz_react_template/const.ts +17 -0
- package/zzz_react_template/contexts/JXRContext.tsx +264 -0
- package/zzz_react_template/contexts/ThemeContext.tsx +64 -0
- package/zzz_react_template/hooks/useComposition.ts +81 -0
- package/zzz_react_template/hooks/useMobile.tsx +21 -0
- package/zzz_react_template/hooks/usePersistFn.ts +20 -0
- package/zzz_react_template/index.css +518 -11
- package/zzz_react_template/lib/jxr-runtime/index.ts +201 -0
- package/zzz_react_template/lib/jxr-runtime/module-resolver.ts +520 -0
- package/zzz_react_template/lib/jxr-runtime/moq-transport.ts +267 -0
- package/zzz_react_template/lib/jxr-runtime/web-crypto.ts +279 -0
- package/zzz_react_template/lib/jxr-runtime/worker-pool.ts +321 -0
- package/zzz_react_template/lib/utils.ts +6 -0
- package/zzz_react_template/main.tsx +4 -9
- package/zzz_react_template/pages/Docs.tsx +955 -0
- package/zzz_react_template/pages/Home.tsx +1080 -0
- package/zzz_react_template/pages/NotFound.tsx +105 -0
- package/zzz_react_template/tsconfig.json +24 -0
|
@@ -0,0 +1,955 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JXR.js — Documentation Page
|
|
3
|
+
* LavaFlow OS Design System
|
|
4
|
+
* Powered by JXR Studios × DamascusAI
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { useState, useMemo } from 'react';
|
|
8
|
+
import { Link } from 'wouter';
|
|
9
|
+
import {
|
|
10
|
+
Zap, Terminal, Package, GitBranch, Shield, Cpu,
|
|
11
|
+
Activity, ChevronRight, Copy, Check,
|
|
12
|
+
BookOpen, Code2, Layers, Globe, ArrowLeft,
|
|
13
|
+
} from 'lucide-react';
|
|
14
|
+
|
|
15
|
+
// ─── Copy button ──────────────────────────────────────────────────────────────
|
|
16
|
+
|
|
17
|
+
function CopyBtn({ text }: { text: string }) {
|
|
18
|
+
const [copied, setCopied] = useState(false);
|
|
19
|
+
const copy = async () => {
|
|
20
|
+
await navigator.clipboard.writeText(text);
|
|
21
|
+
setCopied(true);
|
|
22
|
+
setTimeout(() => setCopied(false), 2000);
|
|
23
|
+
};
|
|
24
|
+
return (
|
|
25
|
+
<button
|
|
26
|
+
onClick={copy}
|
|
27
|
+
className="absolute top-3 right-3 p-1.5 rounded transition-all"
|
|
28
|
+
style={{ color: '#4b5563', background: 'rgba(255,255,255,0.04)', border: '1px solid rgba(255,255,255,0.06)' }}
|
|
29
|
+
title="Copy"
|
|
30
|
+
>
|
|
31
|
+
{copied ? <Check size={13} style={{ color: '#22c55e' }} /> : <Copy size={13} />}
|
|
32
|
+
</button>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// ─── Syntax highlighter ──────────────────────────────────────────────────────
|
|
37
|
+
|
|
38
|
+
type Token = { text: string; color: string };
|
|
39
|
+
|
|
40
|
+
function tokenizeBash(code: string): Token[][] {
|
|
41
|
+
return code.split('\n').map((line) => {
|
|
42
|
+
const tokens: Token[] = [];
|
|
43
|
+
// Comment lines
|
|
44
|
+
if (/^\s*#/.test(line)) {
|
|
45
|
+
tokens.push({ text: line, color: '#6b7280' });
|
|
46
|
+
return tokens;
|
|
47
|
+
}
|
|
48
|
+
// Prompt line: $ command [args]
|
|
49
|
+
const promptMatch = line.match(/^(\$\s*)(.+)$/);
|
|
50
|
+
if (promptMatch) {
|
|
51
|
+
tokens.push({ text: promptMatch[1], color: '#4b5563' });
|
|
52
|
+
const rest = promptMatch[2];
|
|
53
|
+
// Split into cmd + args
|
|
54
|
+
const parts = rest.split(/(?<=^\S+)\s+/);
|
|
55
|
+
const cmd = parts[0];
|
|
56
|
+
const args = parts.slice(1).join(' ');
|
|
57
|
+
// Highlight flags (--foo), strings, and scoped packages
|
|
58
|
+
const cmdTokens: Token[] = [];
|
|
59
|
+
cmdTokens.push({ text: cmd, color: '#f97316' });
|
|
60
|
+
if (args) {
|
|
61
|
+
const argParts = args.split(/((?:@[\w-]+\/[\w-]+)|(?:--[\w-]+)|(?:'[^']*')|(?:"[^"]*"))/g);
|
|
62
|
+
argParts.forEach((p) => {
|
|
63
|
+
if (!p) return;
|
|
64
|
+
if (p.startsWith('--')) cmdTokens.push({ text: ' ' + p, color: '#22c55e' });
|
|
65
|
+
else if (p.startsWith('@') || p.startsWith('\'') || p.startsWith('"')) cmdTokens.push({ text: ' ' + p, color: '#f97316' });
|
|
66
|
+
else cmdTokens.push({ text: ' ' + p, color: '#d1d5db' });
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
tokens.push(...cmdTokens);
|
|
70
|
+
return tokens;
|
|
71
|
+
}
|
|
72
|
+
// Output / plain lines
|
|
73
|
+
if (/^→|^✓|^✗/.test(line.trim())) {
|
|
74
|
+
tokens.push({ text: line, color: '#22c55e' });
|
|
75
|
+
} else if (/^#/.test(line.trim())) {
|
|
76
|
+
tokens.push({ text: line, color: '#6b7280' });
|
|
77
|
+
} else {
|
|
78
|
+
// Flags inline
|
|
79
|
+
const flagParts = line.split(/(--[\w-]+(?:\s+<[^>]+>)?)/g);
|
|
80
|
+
flagParts.forEach((p) => {
|
|
81
|
+
if (!p) return;
|
|
82
|
+
if (p.startsWith('--')) tokens.push({ text: p, color: '#22c55e' });
|
|
83
|
+
else tokens.push({ text: p, color: '#9ca3af' });
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
return tokens;
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function tokenizeTS(code: string): Token[][] {
|
|
91
|
+
const KEYWORDS = /\b(import|export|from|default|const|let|var|function|return|type|interface|extends|implements|class|new|if|else|for|while|async|await|true|false|null|undefined)\b/g;
|
|
92
|
+
const STRINGS = /('[^']*'|"[^"]*"|`[^`]*`)/g;
|
|
93
|
+
const COMMENTS = /(\/\/[^\n]*)/g;
|
|
94
|
+
const NUMBERS = /\b(\d+(\.\d+)?)\b/g;
|
|
95
|
+
const TYPES = /\b([A-Z][A-Za-z0-9_]*)\b/g;
|
|
96
|
+
return code.split('\n').map((line) => {
|
|
97
|
+
// Simple single-pass tokenizer
|
|
98
|
+
const tokens: Token[] = [];
|
|
99
|
+
let remaining = line;
|
|
100
|
+
// Comment
|
|
101
|
+
const commentIdx = remaining.indexOf('//');
|
|
102
|
+
let commentSuffix = '';
|
|
103
|
+
if (commentIdx !== -1) {
|
|
104
|
+
commentSuffix = remaining.slice(commentIdx);
|
|
105
|
+
remaining = remaining.slice(0, commentIdx);
|
|
106
|
+
}
|
|
107
|
+
// Tokenize remaining by strings first
|
|
108
|
+
const parts = remaining.split(/((?:'[^']*')|(?:"[^"]*")|(?:`[^`]*`))/g);
|
|
109
|
+
parts.forEach((p) => {
|
|
110
|
+
if (!p) return;
|
|
111
|
+
if ((p.startsWith("'") || p.startsWith('"') || p.startsWith('`')) && p.length > 1) {
|
|
112
|
+
tokens.push({ text: p, color: '#22c55e' });
|
|
113
|
+
} else {
|
|
114
|
+
// Keywords, types, numbers in non-string segments
|
|
115
|
+
const sub = p.split(/(\b(?:import|export|from|default|const|let|var|function|return|type|interface|extends|implements|class|new|if|else|for|while|async|await|true|false|null|undefined)\b)/g);
|
|
116
|
+
sub.forEach((s) => {
|
|
117
|
+
if (!s) return;
|
|
118
|
+
if (/^(import|export|from|default|const|let|var|function|return|type|interface|extends|implements|class|new|if|else|for|while|async|await|true|false|null|undefined)$/.test(s)) {
|
|
119
|
+
tokens.push({ text: s, color: '#ea580c' });
|
|
120
|
+
} else if (/^[A-Z][A-Za-z0-9_]*$/.test(s.trim())) {
|
|
121
|
+
tokens.push({ text: s, color: '#f97316' });
|
|
122
|
+
} else if (/^\d+(\.\d+)?$/.test(s.trim())) {
|
|
123
|
+
tokens.push({ text: s, color: '#22c55e' });
|
|
124
|
+
} else {
|
|
125
|
+
tokens.push({ text: s, color: '#d1d5db' });
|
|
126
|
+
}
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
if (commentSuffix) tokens.push({ text: commentSuffix, color: '#6b7280' });
|
|
131
|
+
return tokens;
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function tokenizeJSON(code: string): Token[][] {
|
|
136
|
+
return code.split('\n').map((line) => {
|
|
137
|
+
const tokens: Token[] = [];
|
|
138
|
+
const parts = line.split(/("[^"]*")/g);
|
|
139
|
+
parts.forEach((p, i) => {
|
|
140
|
+
if (!p) return;
|
|
141
|
+
if (p.startsWith('"')) {
|
|
142
|
+
// Key vs value: keys are followed by ':'
|
|
143
|
+
const after = parts[i + 1] || '';
|
|
144
|
+
if (after.trimStart().startsWith(':')) {
|
|
145
|
+
tokens.push({ text: p, color: '#9ca3af' });
|
|
146
|
+
} else {
|
|
147
|
+
tokens.push({ text: p, color: '#22c55e' });
|
|
148
|
+
}
|
|
149
|
+
} else {
|
|
150
|
+
// Numbers, booleans, punctuation
|
|
151
|
+
const sub = p.split(/(\b(?:true|false|null)\b|\b\d+\b)/g);
|
|
152
|
+
sub.forEach((s) => {
|
|
153
|
+
if (!s) return;
|
|
154
|
+
if (/^(true|false|null)$/.test(s)) tokens.push({ text: s, color: '#f97316' });
|
|
155
|
+
else if (/^\d+$/.test(s)) tokens.push({ text: s, color: '#22c55e' });
|
|
156
|
+
else tokens.push({ text: s, color: '#4b5563' });
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return tokens;
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function tokenizeTree(code: string): Token[][] {
|
|
165
|
+
return code.split('\n').map((line) => {
|
|
166
|
+
const tokens: Token[] = [];
|
|
167
|
+
const commentMatch = line.match(/^(.+?)(#.+)$/);
|
|
168
|
+
if (commentMatch) {
|
|
169
|
+
tokens.push({ text: commentMatch[1], color: '#d1d5db' });
|
|
170
|
+
tokens.push({ text: commentMatch[2], color: '#6b7280' });
|
|
171
|
+
} else if (/\.ts$|\.tsx$|\.js$|\.jsx$|\.json$|\.css$|\.html$/.test(line)) {
|
|
172
|
+
tokens.push({ text: line, color: '#f97316' });
|
|
173
|
+
} else if (/\/\s*$/.test(line.trim()) || /^[\w-]+\/$/.test(line.trim().replace(/[├└─│ ]/g, ''))) {
|
|
174
|
+
tokens.push({ text: line, color: '#22c55e' });
|
|
175
|
+
} else {
|
|
176
|
+
tokens.push({ text: line, color: '#d1d5db' });
|
|
177
|
+
}
|
|
178
|
+
return tokens;
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function SyntaxLine({ tokens }: { tokens: Token[] }) {
|
|
183
|
+
return (
|
|
184
|
+
<div style={{ minHeight: '1.4em' }}>
|
|
185
|
+
{tokens.map((t, i) => (
|
|
186
|
+
<span key={i} style={{ color: t.color }}>{t.text}</span>
|
|
187
|
+
))}
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
function CodeBlock({ children, lang = '' }: { children: string; lang?: string }) {
|
|
193
|
+
const lines = useMemo(() => {
|
|
194
|
+
if (lang === 'bash') return tokenizeBash(children);
|
|
195
|
+
if (lang === 'typescript' || lang === 'ts') return tokenizeTS(children);
|
|
196
|
+
if (lang === 'json') return tokenizeJSON(children);
|
|
197
|
+
if (lang === 'tree') return tokenizeTree(children);
|
|
198
|
+
// Plain fallback
|
|
199
|
+
return children.split('\n').map((l) => [{ text: l, color: '#d1d5db' }] as Token[]);
|
|
200
|
+
}, [children, lang]);
|
|
201
|
+
|
|
202
|
+
const langLabels: Record<string, string> = {
|
|
203
|
+
bash: 'bash', typescript: 'typescript', ts: 'typescript',
|
|
204
|
+
json: 'json', tree: 'tree', '': 'plain',
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
return (
|
|
208
|
+
<div className="terminal-chrome relative my-5">
|
|
209
|
+
<div className="terminal-header">
|
|
210
|
+
<div className="terminal-dot" style={{ background: '#ff5f57' }} />
|
|
211
|
+
<div className="terminal-dot" style={{ background: '#febc2e' }} />
|
|
212
|
+
<div className="terminal-dot" style={{ background: '#28c840' }} />
|
|
213
|
+
{lang && (
|
|
214
|
+
<span className="terminal-label" style={{ marginLeft: 'auto', color: '#4b5563', fontSize: '0.68rem', fontFamily: 'JetBrains Mono', letterSpacing: '0.08em' }}>
|
|
215
|
+
{langLabels[lang] ?? lang}
|
|
216
|
+
</span>
|
|
217
|
+
)}
|
|
218
|
+
</div>
|
|
219
|
+
<div className="terminal-body relative">
|
|
220
|
+
<CopyBtn text={children} />
|
|
221
|
+
<pre style={{ margin: 0, fontSize: '0.8rem', lineHeight: 1.85, whiteSpace: 'pre-wrap', wordBreak: 'break-word', fontFamily: 'JetBrains Mono' }}>
|
|
222
|
+
{lines.map((lineTokens: Token[], i: number) => (
|
|
223
|
+
<SyntaxLine key={i} tokens={lineTokens} />
|
|
224
|
+
))}
|
|
225
|
+
</pre>
|
|
226
|
+
</div>
|
|
227
|
+
</div>
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ─── Sidebar nav ─────────────────────────────────────────────────────────────
|
|
232
|
+
|
|
233
|
+
const NAV_SECTIONS = [
|
|
234
|
+
{
|
|
235
|
+
title: 'Getting Started',
|
|
236
|
+
items: [
|
|
237
|
+
{ id: 'introduction', label: 'Introduction', icon: BookOpen },
|
|
238
|
+
{ id: 'installation', label: 'Installation', icon: Package },
|
|
239
|
+
{ id: 'quick-start', label: 'Quick Start', icon: Zap },
|
|
240
|
+
],
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
title: 'CLI',
|
|
244
|
+
items: [
|
|
245
|
+
{ id: 'cli', label: 'CLI Reference', icon: Terminal },
|
|
246
|
+
{ id: 'init', label: 'jxr init', icon: Layers },
|
|
247
|
+
{ id: 'dev', label: 'jxr dev', icon: Activity },
|
|
248
|
+
{ id: 'build', label: 'jxr build', icon: Code2 },
|
|
249
|
+
{ id: 'migrate', label: 'jxr migrate', icon: GitBranch },
|
|
250
|
+
{ id: 'deploy', label: 'jxr deploy', icon: Globe },
|
|
251
|
+
],
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
title: 'Configuration',
|
|
255
|
+
items: [
|
|
256
|
+
{ id: 'config', label: 'jxr.config.ts', icon: Code2 },
|
|
257
|
+
{ id: 'workers', label: 'Worker Pool', icon: Cpu },
|
|
258
|
+
{ id: 'moq', label: 'MoQ Transport', icon: Activity },
|
|
259
|
+
{ id: 'crypto', label: 'Web Crypto', icon: Shield },
|
|
260
|
+
],
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
title: 'MCP Server',
|
|
264
|
+
items: [
|
|
265
|
+
{ id: 'mcp', label: 'MCP Overview', icon: Package },
|
|
266
|
+
{ id: 'mcp-tools', label: 'Tool Reference', icon: Code2 },
|
|
267
|
+
],
|
|
268
|
+
},
|
|
269
|
+
];
|
|
270
|
+
|
|
271
|
+
// ─── Doc content ──────────────────────────────────────────────────────────────
|
|
272
|
+
|
|
273
|
+
const DOC_CONTENT: Record<string, React.ReactNode> = {
|
|
274
|
+
introduction: (
|
|
275
|
+
<div>
|
|
276
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>
|
|
277
|
+
Introduction to <span style={{ color: '#f97316' }}>JXR.js</span>
|
|
278
|
+
</h1>
|
|
279
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1.5rem', fontSize: '1.05rem' }}>
|
|
280
|
+
JXR.js is a next-generation build framework and edge runtime for React Native and React projects.
|
|
281
|
+
It is designed to be used in any IDE — VSCode, Warp, Cursor, or any terminal — and provides
|
|
282
|
+
a Model Context Protocol (MCP) server so AI agents can manage your entire project lifecycle.
|
|
283
|
+
</p>
|
|
284
|
+
<div className="grid md:grid-cols-3 gap-3 my-6">
|
|
285
|
+
{[
|
|
286
|
+
{ icon: Cpu, color: '#ea580c', title: 'Worker Pools', desc: 'Pre-warmed worker_threads for parallel builds and transforms' },
|
|
287
|
+
{ icon: Activity, color: '#22c55e', title: 'MoQ Transport', desc: 'Media over QUIC for sub-RTT module streaming and HMR' },
|
|
288
|
+
{ icon: Shield, color: '#ea580c', title: 'Web Crypto', desc: 'AES-GCM-256 module caching and ECDSA manifest signing' },
|
|
289
|
+
].map((item) => {
|
|
290
|
+
const Icon = item.icon;
|
|
291
|
+
return (
|
|
292
|
+
<div key={item.title} className="jxr-card">
|
|
293
|
+
<div className="w-8 h-8 rounded-lg flex items-center justify-center mb-3" style={{ background: `${item.color}18`, border: `1px solid ${item.color}30` }}>
|
|
294
|
+
<Icon size={15} style={{ color: item.color }} />
|
|
295
|
+
</div>
|
|
296
|
+
<h3 style={{ fontFamily: 'Inter', fontWeight: 700, fontSize: '0.9rem', color: '#ffffff', marginBottom: '0.35rem' }}>{item.title}</h3>
|
|
297
|
+
<p style={{ fontSize: '0.78rem', color: '#6b7280', lineHeight: 1.55 }}>{item.desc}</p>
|
|
298
|
+
</div>
|
|
299
|
+
);
|
|
300
|
+
})}
|
|
301
|
+
</div>
|
|
302
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', letterSpacing: '-0.01em', marginTop: '2rem', marginBottom: '0.75rem' }}>Why JXR?</h2>
|
|
303
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
304
|
+
Existing frameworks like Next.js, Vite, and Bun are excellent tools, but they were not designed
|
|
305
|
+
with edge-first, AI-native, or MoQ-based workflows in mind. JXR fills this gap by providing
|
|
306
|
+
a framework that is simultaneously a CLI tool, a build engine, and an MCP server.
|
|
307
|
+
</p>
|
|
308
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7 }}>
|
|
309
|
+
JXR is powered by <strong style={{ color: '#f97316' }}>JXR Studios</strong> and{' '}
|
|
310
|
+
<strong style={{ color: '#22c55e' }}>DamascusAI</strong>.
|
|
311
|
+
It is the future of edge cloud OS deployments for developers who want to take their game to the next level.
|
|
312
|
+
</p>
|
|
313
|
+
</div>
|
|
314
|
+
),
|
|
315
|
+
|
|
316
|
+
installation: (
|
|
317
|
+
<div>
|
|
318
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>Installation</h1>
|
|
319
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
320
|
+
Install the JXR CLI globally using your preferred package manager.
|
|
321
|
+
</p>
|
|
322
|
+
<CodeBlock lang="bash">{`# npm
|
|
323
|
+
npm install -g @jxrstudios/jxr
|
|
324
|
+
|
|
325
|
+
# pnpm
|
|
326
|
+
pnpm add -g @jxrstudios/jxr
|
|
327
|
+
|
|
328
|
+
# yarn
|
|
329
|
+
yarn global add @jxrstudios/jxr
|
|
330
|
+
|
|
331
|
+
# bun
|
|
332
|
+
bun add -g @jxrstudios/jxr`}</CodeBlock>
|
|
333
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '0.75rem' }}>Verify the installation:</p>
|
|
334
|
+
<CodeBlock lang="bash">{`jxr --version
|
|
335
|
+
# JXR.js v1.0.0-edge
|
|
336
|
+
|
|
337
|
+
jxr info
|
|
338
|
+
# Prints full environment and project info`}</CodeBlock>
|
|
339
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Requirements</h2>
|
|
340
|
+
<div className="jxr-card">
|
|
341
|
+
<div className="grid grid-cols-2 gap-3">
|
|
342
|
+
{[
|
|
343
|
+
['Node.js', '≥ 20.0.0'],
|
|
344
|
+
['npm / pnpm / yarn / bun', 'Any version'],
|
|
345
|
+
['TypeScript', '≥ 5.0 (optional)'],
|
|
346
|
+
['Platform', 'macOS, Linux, Windows'],
|
|
347
|
+
].map(([k, v]) => (
|
|
348
|
+
<div key={k} className="flex justify-between">
|
|
349
|
+
<span style={{ fontSize: '0.82rem', color: '#6b7280' }}>{k}</span>
|
|
350
|
+
<span style={{ fontFamily: 'JetBrains Mono', fontSize: '0.8rem', color: '#f97316' }}>{v}</span>
|
|
351
|
+
</div>
|
|
352
|
+
))}
|
|
353
|
+
</div>
|
|
354
|
+
</div>
|
|
355
|
+
</div>
|
|
356
|
+
),
|
|
357
|
+
|
|
358
|
+
'quick-start': (
|
|
359
|
+
<div>
|
|
360
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>Quick Start</h1>
|
|
361
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>Get a JXR project running in under 60 seconds.</p>
|
|
362
|
+
<CodeBlock lang="bash">{`# 1. Create a new project
|
|
363
|
+
jxr init my-app
|
|
364
|
+
|
|
365
|
+
# 2. Enter the project directory
|
|
366
|
+
cd my-app
|
|
367
|
+
|
|
368
|
+
# 3. Start the dev server
|
|
369
|
+
jxr dev
|
|
370
|
+
# → Local: http://localhost:3000`}</CodeBlock>
|
|
371
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '0.75rem' }}>
|
|
372
|
+
The interactive <code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>jxr init</code> prompt
|
|
373
|
+
will ask for your project name, template, and package manager. You can also pass flags directly:
|
|
374
|
+
</p>
|
|
375
|
+
<CodeBlock lang="bash">{`jxr init my-app --template react-native --package-manager pnpm
|
|
376
|
+
jxr init my-worker --template cloudflare
|
|
377
|
+
jxr init my-expo-app --template expo`}</CodeBlock>
|
|
378
|
+
</div>
|
|
379
|
+
),
|
|
380
|
+
|
|
381
|
+
cli: (
|
|
382
|
+
<div>
|
|
383
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>CLI Reference</h1>
|
|
384
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1.25rem' }}>
|
|
385
|
+
The <code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>jxr</code> CLI is the primary interface for JXR.js.
|
|
386
|
+
It works in any terminal — VSCode integrated terminal, Warp, Cursor, iTerm, Windows Terminal, or any shell.
|
|
387
|
+
</p>
|
|
388
|
+
{[
|
|
389
|
+
{ cmd: 'jxr init [name]', desc: 'Scaffold a new JXR project', flags: ['--template react-web|react-native|expo|cloudflare', '--platform web|native|expo|cloudflare-worker|deno|node', '--package-manager npm|yarn|pnpm|bun', '--no-install', '--no-git'] },
|
|
390
|
+
{ cmd: 'jxr dev', desc: 'Start the development server', flags: ['--port <port>', '--host <host>', '--open', '--https', '--no-hmr', '--config <path>'] },
|
|
391
|
+
{ cmd: 'jxr build', desc: 'Production build', flags: ['--platform <platform>', '--out-dir <dir>', '--no-minify', '--no-sourcemap', '--analyze', '--config <path>'] },
|
|
392
|
+
{ cmd: 'jxr migrate', desc: 'Migrate from another framework', flags: ['--from nextjs|vite|bun|cra|expo|remix|nuxt|auto', '--dry-run', '--no-backup', '--config <path>'] },
|
|
393
|
+
{ cmd: 'jxr deploy', desc: 'Deploy to an edge platform', flags: ['--target cloudflare|deno|node|vercel', '--env production|preview|staging', '--no-build', '--config <path>'] },
|
|
394
|
+
{ cmd: 'jxr add <plugin>', desc: 'Add a JXR plugin', flags: ['--dev'] },
|
|
395
|
+
{ cmd: 'jxr info', desc: 'Print environment and project info', flags: [] },
|
|
396
|
+
].map((item) => (
|
|
397
|
+
<div key={item.cmd} className="jxr-card mb-3">
|
|
398
|
+
<div style={{ fontFamily: 'JetBrains Mono', fontSize: '0.88rem', color: '#f97316', fontWeight: 700, marginBottom: '0.35rem' }}>{item.cmd}</div>
|
|
399
|
+
<div style={{ fontSize: '0.82rem', color: '#6b7280', marginBottom: item.flags.length ? '0.75rem' : 0 }}>{item.desc}</div>
|
|
400
|
+
{item.flags.length > 0 && (
|
|
401
|
+
<div className="space-y-1">
|
|
402
|
+
{item.flags.map((f) => (
|
|
403
|
+
<div key={f} style={{ fontFamily: 'JetBrains Mono', fontSize: '0.72rem', color: '#374151' }}> {f}</div>
|
|
404
|
+
))}
|
|
405
|
+
</div>
|
|
406
|
+
)}
|
|
407
|
+
</div>
|
|
408
|
+
))}
|
|
409
|
+
</div>
|
|
410
|
+
),
|
|
411
|
+
|
|
412
|
+
config: (
|
|
413
|
+
<div>
|
|
414
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>jxr.config.ts</h1>
|
|
415
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
416
|
+
The JXR configuration file is a TypeScript file at the root of your project.
|
|
417
|
+
It is fully typed via <code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>defineConfig</code> from <code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>@jxrstudios/core</code>.
|
|
418
|
+
</p>
|
|
419
|
+
<CodeBlock lang="typescript">{`import { defineConfig } from '@jxrstudios/core';
|
|
420
|
+
|
|
421
|
+
export default defineConfig({
|
|
422
|
+
name: 'my-app',
|
|
423
|
+
platform: 'web', // 'web' | 'native' | 'expo' | 'cloudflare-worker' | 'deno' | 'node'
|
|
424
|
+
|
|
425
|
+
workers: {
|
|
426
|
+
size: 4, // Number of worker threads
|
|
427
|
+
enablePriority: true, // Enable priority queue
|
|
428
|
+
maxQueueSize: 1000, // Max pending tasks
|
|
429
|
+
},
|
|
430
|
+
|
|
431
|
+
moq: {
|
|
432
|
+
enabled: true,
|
|
433
|
+
relayUrl: 'wss://relay.jxr.dev', // Optional: MoQ relay server
|
|
434
|
+
},
|
|
435
|
+
|
|
436
|
+
crypto: {
|
|
437
|
+
enabled: true,
|
|
438
|
+
algorithm: 'AES-GCM', // 'AES-GCM' | 'AES-CBC'
|
|
439
|
+
signing: true, // ECDSA P-256 manifest signing
|
|
440
|
+
keyDerivation: 'HKDF',
|
|
441
|
+
},
|
|
442
|
+
|
|
443
|
+
build: {
|
|
444
|
+
entry: 'src/main.tsx',
|
|
445
|
+
outDir: 'dist',
|
|
446
|
+
minify: true,
|
|
447
|
+
sourcemap: true,
|
|
448
|
+
splitting: 'auto', // 'auto' | 'manual' | false
|
|
449
|
+
target: ['es2022'],
|
|
450
|
+
external: [],
|
|
451
|
+
},
|
|
452
|
+
|
|
453
|
+
devServer: {
|
|
454
|
+
port: 3000,
|
|
455
|
+
host: 'localhost',
|
|
456
|
+
hmr: true,
|
|
457
|
+
open: false,
|
|
458
|
+
https: false,
|
|
459
|
+
},
|
|
460
|
+
|
|
461
|
+
plugins: [], // JXR plugins
|
|
462
|
+
});`}</CodeBlock>
|
|
463
|
+
</div>
|
|
464
|
+
),
|
|
465
|
+
|
|
466
|
+
mcp: (
|
|
467
|
+
<div>
|
|
468
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>MCP Server</h1>
|
|
469
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1.25rem' }}>
|
|
470
|
+
The JXR MCP server (<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>@jxrstudios/mcp</code>) implements the
|
|
471
|
+
Model Context Protocol, allowing AI agents like Claude, Cursor, and GitHub Copilot to manage
|
|
472
|
+
JXR projects autonomously — initializing, building, migrating, and deploying without leaving the chat.
|
|
473
|
+
</p>
|
|
474
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Setup</h2>
|
|
475
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '0.75rem' }}>Add to your MCP client configuration:</p>
|
|
476
|
+
<CodeBlock lang="json">{`// Claude Desktop: ~/Library/Application Support/Claude/claude_desktop_config.json
|
|
477
|
+
// Cursor: .cursor/mcp.json
|
|
478
|
+
// Any MCP client: see client docs
|
|
479
|
+
|
|
480
|
+
{
|
|
481
|
+
"mcpServers": {
|
|
482
|
+
"jxrstudios": {
|
|
483
|
+
"command": "npx",
|
|
484
|
+
"args": ["-y", "@jxrstudios/mcp"]
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
}`}</CodeBlock>
|
|
488
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Example AI Prompts</h2>
|
|
489
|
+
<div className="space-y-2">
|
|
490
|
+
{[
|
|
491
|
+
'Create a new JXR React Native project called "my-app" and start the dev server',
|
|
492
|
+
'Migrate this Next.js project to JXR, then build it for Cloudflare Workers',
|
|
493
|
+
'Add the Tailwind plugin to this JXR project and update the config',
|
|
494
|
+
'Deploy this JXR project to Cloudflare Workers in production mode',
|
|
495
|
+
].map((prompt) => (
|
|
496
|
+
<div key={prompt} className="jxr-card flex items-start gap-3 py-3">
|
|
497
|
+
<ChevronRight size={14} style={{ color: '#ea580c', marginTop: '2px', flexShrink: 0 }} />
|
|
498
|
+
<p style={{ fontSize: '0.85rem', color: '#9ca3af', fontStyle: 'italic' }}>"{prompt}"</p>
|
|
499
|
+
</div>
|
|
500
|
+
))}
|
|
501
|
+
</div>
|
|
502
|
+
</div>
|
|
503
|
+
),
|
|
504
|
+
|
|
505
|
+
migrate: (
|
|
506
|
+
<div>
|
|
507
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>Migration Guide</h1>
|
|
508
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
509
|
+
JXR can migrate projects from Next.js, Vite, Bun, Create React App, Expo, Remix, and Nuxt.
|
|
510
|
+
The migration engine performs AST-level transforms and creates a full backup before touching any files.
|
|
511
|
+
</p>
|
|
512
|
+
<CodeBlock lang="bash">{`# Auto-detect and migrate
|
|
513
|
+
jxr migrate
|
|
514
|
+
|
|
515
|
+
# Specify source framework
|
|
516
|
+
jxr migrate --from nextjs
|
|
517
|
+
jxr migrate --from vite
|
|
518
|
+
jxr migrate --from bun
|
|
519
|
+
jxr migrate --from cra
|
|
520
|
+
jxr migrate --from expo
|
|
521
|
+
|
|
522
|
+
# Preview changes without writing
|
|
523
|
+
jxr migrate --dry-run
|
|
524
|
+
|
|
525
|
+
# Skip backup (not recommended)
|
|
526
|
+
jxr migrate --no-backup`}</CodeBlock>
|
|
527
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>What gets migrated</h2>
|
|
528
|
+
<div className="grid md:grid-cols-2 gap-3">
|
|
529
|
+
{[
|
|
530
|
+
{ from: 'next.config.js', to: 'jxr.config.ts' },
|
|
531
|
+
{ from: 'vite.config.ts', to: 'jxr.config.ts' },
|
|
532
|
+
{ from: 'next/image', to: '@jxrstudios/runtime/image' },
|
|
533
|
+
{ from: 'next/link', to: '@jxrstudios/runtime/link' },
|
|
534
|
+
{ from: 'next/router', to: '@jxrstudios/runtime/router' },
|
|
535
|
+
{ from: 'react-scripts', to: 'jxr (CLI)' },
|
|
536
|
+
{ from: 'REACT_APP_*', to: 'VITE_* (import.meta.env)' },
|
|
537
|
+
{ from: 'pages/api/*', to: 'JXR edge handlers' },
|
|
538
|
+
].map((item) => (
|
|
539
|
+
<div key={item.from} className="flex items-center gap-3 p-2.5 rounded" style={{ background: '#111111', border: '1px solid rgba(255,255,255,0.05)' }}>
|
|
540
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.75rem', color: '#4b5563', textDecoration: 'line-through' }}>{item.from}</code>
|
|
541
|
+
<ChevronRight size={11} style={{ color: '#ea580c', flexShrink: 0 }} />
|
|
542
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.75rem', color: '#f97316' }}>{item.to}</code>
|
|
543
|
+
</div>
|
|
544
|
+
))}
|
|
545
|
+
</div>
|
|
546
|
+
</div>
|
|
547
|
+
),
|
|
548
|
+
|
|
549
|
+
init: (
|
|
550
|
+
<div>
|
|
551
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>jxr init</h1>
|
|
552
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
553
|
+
Scaffold a new JXR project interactively or via flags. Supports React (web), React Native, Expo, and Cloudflare Worker templates.
|
|
554
|
+
</p>
|
|
555
|
+
<CodeBlock lang="bash">{`# Interactive scaffold
|
|
556
|
+
jxr init my-app
|
|
557
|
+
|
|
558
|
+
# With flags
|
|
559
|
+
jxr init my-app --template react-native --package-manager pnpm
|
|
560
|
+
jxr init my-worker --template cloudflare
|
|
561
|
+
jxr init my-expo-app --template expo --no-git`}</CodeBlock>
|
|
562
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Flags</h2>
|
|
563
|
+
<div className="space-y-2">
|
|
564
|
+
{[
|
|
565
|
+
['--template', 'react-web | react-native | expo | cloudflare', 'Project template'],
|
|
566
|
+
['--platform', 'web | native | expo | cloudflare-worker | deno | node', 'Target platform'],
|
|
567
|
+
['--package-manager', 'npm | yarn | pnpm | bun', 'Package manager to use'],
|
|
568
|
+
['--no-install', '', 'Skip dependency installation'],
|
|
569
|
+
['--no-git', '', 'Skip git repository initialization'],
|
|
570
|
+
].map(([flag, values, desc]) => (
|
|
571
|
+
<div key={flag} className="jxr-card py-3">
|
|
572
|
+
<div className="flex items-start gap-3">
|
|
573
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.8rem', color: '#f97316', flexShrink: 0 }}>{flag}</code>
|
|
574
|
+
{values && <code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.75rem', color: '#4b5563' }}>{values}</code>}
|
|
575
|
+
</div>
|
|
576
|
+
<p style={{ fontSize: '0.8rem', color: '#6b7280', marginTop: '0.25rem' }}>{desc}</p>
|
|
577
|
+
</div>
|
|
578
|
+
))}
|
|
579
|
+
</div>
|
|
580
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Generated structure</h2>
|
|
581
|
+
<CodeBlock lang="tree">{`my-app/
|
|
582
|
+
├── jxr.config.ts # JXR configuration
|
|
583
|
+
├── src/
|
|
584
|
+
│ ├── main.tsx # Entry point
|
|
585
|
+
│ ├── App.tsx # Root component
|
|
586
|
+
│ └── components/
|
|
587
|
+
├── public/
|
|
588
|
+
├── package.json
|
|
589
|
+
└── tsconfig.json`}</CodeBlock>
|
|
590
|
+
</div>
|
|
591
|
+
),
|
|
592
|
+
|
|
593
|
+
dev: (
|
|
594
|
+
<div>
|
|
595
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>jxr dev</h1>
|
|
596
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
597
|
+
Start the JXR development server with Hot Module Replacement (HMR), Worker pool pre-warming, and MoQ transport initialization.
|
|
598
|
+
No build step is required — modules are served directly from the virtual file system.
|
|
599
|
+
</p>
|
|
600
|
+
<CodeBlock lang="bash">{`# Start on default port 3000
|
|
601
|
+
jxr dev
|
|
602
|
+
|
|
603
|
+
# Custom port and host
|
|
604
|
+
jxr dev --port 8080 --host 0.0.0.0
|
|
605
|
+
|
|
606
|
+
# Open browser automatically
|
|
607
|
+
jxr dev --open
|
|
608
|
+
|
|
609
|
+
# HTTPS with self-signed cert
|
|
610
|
+
jxr dev --https`}</CodeBlock>
|
|
611
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>What happens on start</h2>
|
|
612
|
+
<div className="space-y-2">
|
|
613
|
+
{[
|
|
614
|
+
{ step: '1', label: 'Worker pool pre-warm', desc: 'Spawns worker_threads up to hardware concurrency. Subsequent builds use the warm pool with zero startup cost.' },
|
|
615
|
+
{ step: '2', label: 'MoQ transport init', desc: 'Initializes the Media over QUIC transport layer. If a relay URL is configured, connects and subscribes to the project track.' },
|
|
616
|
+
{ step: '3', label: 'Web Crypto engine', desc: 'Derives the session key via HKDF and initializes the AES-GCM module cache. All served modules are integrity-checked.' },
|
|
617
|
+
{ step: '4', label: 'File watcher', desc: 'Watches src/ for changes. On change, the module is transformed in a Worker and pushed to connected clients via HMR.' },
|
|
618
|
+
].map((item) => (
|
|
619
|
+
<div key={item.step} className="jxr-card flex gap-4">
|
|
620
|
+
<div className="w-7 h-7 rounded-full flex items-center justify-center flex-shrink-0" style={{ background: 'rgba(234,88,12,0.12)', border: '1px solid rgba(234,88,12,0.25)' }}>
|
|
621
|
+
<span style={{ fontFamily: 'JetBrains Mono', fontSize: '0.7rem', color: '#f97316', fontWeight: 700 }}>{item.step}</span>
|
|
622
|
+
</div>
|
|
623
|
+
<div>
|
|
624
|
+
<div style={{ fontFamily: 'Inter', fontWeight: 700, fontSize: '0.88rem', color: '#ffffff', marginBottom: '0.25rem' }}>{item.label}</div>
|
|
625
|
+
<p style={{ fontSize: '0.8rem', color: '#6b7280', lineHeight: 1.55 }}>{item.desc}</p>
|
|
626
|
+
</div>
|
|
627
|
+
</div>
|
|
628
|
+
))}
|
|
629
|
+
</div>
|
|
630
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Flags</h2>
|
|
631
|
+
<CodeBlock lang="bash">{`--port <port> # Dev server port (default: 3000)
|
|
632
|
+
--host <host> # Dev server host (default: localhost)
|
|
633
|
+
--open # Open browser on start
|
|
634
|
+
--https # Enable HTTPS
|
|
635
|
+
--no-hmr # Disable Hot Module Replacement
|
|
636
|
+
--config <path> # Path to jxr.config.ts`}</CodeBlock>
|
|
637
|
+
</div>
|
|
638
|
+
),
|
|
639
|
+
|
|
640
|
+
build: (
|
|
641
|
+
<div>
|
|
642
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>jxr build</h1>
|
|
643
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
644
|
+
Produce a production-optimized build. The JXR build engine uses esbuild under the hood with Worker-parallel transforms,
|
|
645
|
+
automatic code splitting, and a cryptographically signed build manifest.
|
|
646
|
+
</p>
|
|
647
|
+
<CodeBlock lang="bash">{`# Standard production build
|
|
648
|
+
jxr build
|
|
649
|
+
|
|
650
|
+
# Target a specific platform
|
|
651
|
+
jxr build --platform cloudflare-worker
|
|
652
|
+
jxr build --platform deno
|
|
653
|
+
jxr build --platform node
|
|
654
|
+
|
|
655
|
+
# Analyze bundle
|
|
656
|
+
jxr build --analyze
|
|
657
|
+
|
|
658
|
+
# Skip minification (for debugging)
|
|
659
|
+
jxr build --no-minify`}</CodeBlock>
|
|
660
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Build outputs</h2>
|
|
661
|
+
<CodeBlock lang="tree">{`dist/
|
|
662
|
+
├── assets/
|
|
663
|
+
│ ├── index-[hash].js # Main bundle
|
|
664
|
+
│ ├── vendor-[hash].js # Vendor chunk
|
|
665
|
+
│ └── *.css
|
|
666
|
+
├── index.html
|
|
667
|
+
└── jxr-manifest.json # Crypto-signed build manifest`}</CodeBlock>
|
|
668
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Build manifest</h2>
|
|
669
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '0.75rem' }}>
|
|
670
|
+
Every JXR build produces a <code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>jxr-manifest.json</code> file
|
|
671
|
+
signed with ECDSA P-256. The runtime verifies this signature before serving any module.
|
|
672
|
+
</p>
|
|
673
|
+
<CodeBlock lang="json">{`{
|
|
674
|
+
"version": "1.0.0",
|
|
675
|
+
"platform": "web",
|
|
676
|
+
"buildTime": "2026-03-13T00:00:00Z",
|
|
677
|
+
"entries": {
|
|
678
|
+
"main": "assets/index-abc123.js",
|
|
679
|
+
"vendor": "assets/vendor-def456.js"
|
|
680
|
+
},
|
|
681
|
+
"signature": "MEQCIBx...",
|
|
682
|
+
"algorithm": "ECDSA-P256"
|
|
683
|
+
}`}</CodeBlock>
|
|
684
|
+
</div>
|
|
685
|
+
),
|
|
686
|
+
|
|
687
|
+
deploy: (
|
|
688
|
+
<div>
|
|
689
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>jxr deploy</h1>
|
|
690
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
691
|
+
Deploy your JXR project to Cloudflare Workers, Deno Deploy, or a Node.js server. JXR automatically
|
|
692
|
+
selects the correct adapter and builds for the target platform.
|
|
693
|
+
</p>
|
|
694
|
+
<CodeBlock lang="bash">{`# Deploy to Cloudflare Workers
|
|
695
|
+
jxr deploy --target cloudflare
|
|
696
|
+
|
|
697
|
+
# Deploy to Deno Deploy
|
|
698
|
+
jxr deploy --target deno
|
|
699
|
+
|
|
700
|
+
# Deploy to Node.js (Docker / VPS)
|
|
701
|
+
jxr deploy --target node
|
|
702
|
+
|
|
703
|
+
# Deploy to preview environment
|
|
704
|
+
jxr deploy --target cloudflare --env preview
|
|
705
|
+
|
|
706
|
+
# Skip build step (use existing dist/)
|
|
707
|
+
jxr deploy --target cloudflare --no-build`}</CodeBlock>
|
|
708
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Platform requirements</h2>
|
|
709
|
+
<div className="space-y-2">
|
|
710
|
+
{[
|
|
711
|
+
{ target: 'cloudflare', req: 'wrangler CLI installed and authenticated (npx wrangler login)', color: '#f97316' },
|
|
712
|
+
{ target: 'deno', req: 'Deno CLI installed and DENO_DEPLOY_TOKEN set in environment', color: '#22c55e' },
|
|
713
|
+
{ target: 'node', req: 'Node.js ≥ 20 on target server, SSH access or CI pipeline configured', color: '#9ca3af' },
|
|
714
|
+
].map((item) => (
|
|
715
|
+
<div key={item.target} className="jxr-card flex items-start gap-3">
|
|
716
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.8rem', color: item.color, flexShrink: 0, minWidth: '90px' }}>{item.target}</code>
|
|
717
|
+
<p style={{ fontSize: '0.8rem', color: '#6b7280', lineHeight: 1.55 }}>{item.req}</p>
|
|
718
|
+
</div>
|
|
719
|
+
))}
|
|
720
|
+
</div>
|
|
721
|
+
</div>
|
|
722
|
+
),
|
|
723
|
+
|
|
724
|
+
workers: (
|
|
725
|
+
<div>
|
|
726
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>Worker Pool</h1>
|
|
727
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
728
|
+
The JXR Worker Pool engine manages a fleet of Node.js <code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>worker_threads</code> that
|
|
729
|
+
are pre-warmed at startup. All CPU-intensive operations — JSX transforms, crypto ops, module resolution — run off the main thread.
|
|
730
|
+
</p>
|
|
731
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Configuration</h2>
|
|
732
|
+
<CodeBlock lang="typescript">{`// jxr.config.ts
|
|
733
|
+
export default defineConfig({
|
|
734
|
+
workers: {
|
|
735
|
+
size: 8, // Worker thread count (default: os.cpus().length)
|
|
736
|
+
enablePriority: true, // Priority queue for task scheduling
|
|
737
|
+
maxQueueSize: 1000, // Max pending tasks before backpressure
|
|
738
|
+
taskTimeout: 30000, // Task timeout in ms (default: 30s)
|
|
739
|
+
},
|
|
740
|
+
});`}</CodeBlock>
|
|
741
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>How it works</h2>
|
|
742
|
+
<div className="space-y-2">
|
|
743
|
+
{[
|
|
744
|
+
{ title: 'Pre-warming', desc: 'Workers are spawned at dev server start or build init. The first task has zero cold-start cost because the pool is already live.' },
|
|
745
|
+
{ title: 'Priority queue', desc: 'Tasks are enqueued with a priority level (high / normal / low). HMR updates are always high priority; background cache writes are low.' },
|
|
746
|
+
{ title: 'Backpressure', desc: 'When the queue exceeds maxQueueSize, new tasks are rejected with a JXRWorkerPoolError. The caller should retry with exponential backoff.' },
|
|
747
|
+
{ title: 'Auto-scaling', desc: 'The pool monitors queue depth and worker utilization. Under sustained load it will spawn additional workers up to 2× the configured size.' },
|
|
748
|
+
].map((item) => (
|
|
749
|
+
<div key={item.title} className="jxr-card">
|
|
750
|
+
<div style={{ fontFamily: 'Inter', fontWeight: 700, fontSize: '0.88rem', color: '#ea580c', marginBottom: '0.3rem' }}>{item.title}</div>
|
|
751
|
+
<p style={{ fontSize: '0.8rem', color: '#6b7280', lineHeight: 1.55 }}>{item.desc}</p>
|
|
752
|
+
</div>
|
|
753
|
+
))}
|
|
754
|
+
</div>
|
|
755
|
+
</div>
|
|
756
|
+
),
|
|
757
|
+
|
|
758
|
+
moq: (
|
|
759
|
+
<div>
|
|
760
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>MoQ Transport</h1>
|
|
761
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
762
|
+
JXR implements Media over QUIC (MoQ) transport semantics for sub-RTT module streaming and HMR delivery.
|
|
763
|
+
The track/object/subscription model means module updates are pushed to the browser before the file is even saved to disk.
|
|
764
|
+
</p>
|
|
765
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Configuration</h2>
|
|
766
|
+
<CodeBlock lang="typescript">{`// jxr.config.ts
|
|
767
|
+
export default defineConfig({
|
|
768
|
+
moq: {
|
|
769
|
+
enabled: true,
|
|
770
|
+
relayUrl: 'wss://relay.jxr.dev', // Optional MoQ relay
|
|
771
|
+
trackPriority: 'high', // 'high' | 'normal' | 'low'
|
|
772
|
+
maxSubscriptions: 100,
|
|
773
|
+
reconnectDelay: 1000, // ms before reconnect attempt
|
|
774
|
+
},
|
|
775
|
+
});`}</CodeBlock>
|
|
776
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Core concepts</h2>
|
|
777
|
+
<div className="space-y-2">
|
|
778
|
+
{[
|
|
779
|
+
{ term: 'Track', desc: 'A named stream of related objects. JXR creates one track per project (e.g. jxr/my-app/modules). Subscribers receive all objects on the track.' },
|
|
780
|
+
{ term: 'Object', desc: 'A single versioned payload on a track — typically a transformed module. Objects are immutable and content-addressed by their crypto hash.' },
|
|
781
|
+
{ term: 'Subscription', desc: 'A client subscribes to a track and receives new objects in real time. The browser HMR client subscribes to the project track on connect.' },
|
|
782
|
+
{ term: 'Relay', desc: 'An optional server that fans out track objects to multiple subscribers. Without a relay, JXR falls back to WebSocket-based HMR.' },
|
|
783
|
+
].map((item) => (
|
|
784
|
+
<div key={item.term} className="jxr-card">
|
|
785
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.82rem', color: '#22c55e', fontWeight: 700 }}>{item.term}</code>
|
|
786
|
+
<p style={{ fontSize: '0.8rem', color: '#6b7280', lineHeight: 1.55, marginTop: '0.3rem' }}>{item.desc}</p>
|
|
787
|
+
</div>
|
|
788
|
+
))}
|
|
789
|
+
</div>
|
|
790
|
+
</div>
|
|
791
|
+
),
|
|
792
|
+
|
|
793
|
+
crypto: (
|
|
794
|
+
<div>
|
|
795
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>Web Crypto</h1>
|
|
796
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1rem' }}>
|
|
797
|
+
The JXR crypto engine is built entirely on the W3C Web Crypto API (<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85em', color: '#f97316', background: 'rgba(234,88,12,0.1)', padding: '1px 5px', borderRadius: '4px' }}>SubtleCrypto</code>).
|
|
798
|
+
It has zero native dependencies and runs identically in Node.js, Deno, Cloudflare Workers, and the browser.
|
|
799
|
+
</p>
|
|
800
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Configuration</h2>
|
|
801
|
+
<CodeBlock lang="typescript">{`// jxr.config.ts
|
|
802
|
+
export default defineConfig({
|
|
803
|
+
crypto: {
|
|
804
|
+
enabled: true,
|
|
805
|
+
algorithm: 'AES-GCM', // 'AES-GCM' | 'AES-CBC'
|
|
806
|
+
keySize: 256, // Key size in bits
|
|
807
|
+
signing: true, // ECDSA P-256 manifest signing
|
|
808
|
+
keyDerivation: 'HKDF', // Key derivation function
|
|
809
|
+
},
|
|
810
|
+
});`}</CodeBlock>
|
|
811
|
+
<h2 style={{ fontFamily: 'Inter', fontWeight: 800, fontSize: '1.4rem', color: '#ffffff', marginTop: '2rem', marginBottom: '0.75rem' }}>Primitives</h2>
|
|
812
|
+
<div className="space-y-2">
|
|
813
|
+
{[
|
|
814
|
+
{ name: 'AES-GCM-256', use: 'Module cache encryption', detail: 'Each cached module is encrypted with a unique IV. The key is derived from the project secret via HKDF.' },
|
|
815
|
+
{ name: 'ECDSA P-256', use: 'Build manifest signing', detail: 'The build manifest is signed with an ECDSA P-256 private key. The runtime verifies the signature before serving any module.' },
|
|
816
|
+
{ name: 'HKDF', use: 'Key derivation', detail: 'Session and cache keys are derived from a master secret using HKDF with SHA-256. Each project gets a unique derived key.' },
|
|
817
|
+
{ name: 'SHA-256', use: 'Content addressing', detail: 'Every module object is content-addressed by its SHA-256 hash. This enables deterministic caching and integrity verification.' },
|
|
818
|
+
].map((item) => (
|
|
819
|
+
<div key={item.name} className="jxr-card">
|
|
820
|
+
<div className="flex items-center justify-between mb-1">
|
|
821
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.82rem', color: '#f97316', fontWeight: 700 }}>{item.name}</code>
|
|
822
|
+
<span style={{ fontSize: '0.72rem', color: '#22c55e', fontFamily: 'JetBrains Mono' }}>{item.use}</span>
|
|
823
|
+
</div>
|
|
824
|
+
<p style={{ fontSize: '0.8rem', color: '#6b7280', lineHeight: 1.55 }}>{item.detail}</p>
|
|
825
|
+
</div>
|
|
826
|
+
))}
|
|
827
|
+
</div>
|
|
828
|
+
</div>
|
|
829
|
+
),
|
|
830
|
+
|
|
831
|
+
'mcp-tools': (
|
|
832
|
+
<div>
|
|
833
|
+
<h1 style={{ fontFamily: 'Inter', fontWeight: 900, fontSize: '2.2rem', color: '#ffffff', letterSpacing: '-0.02em', marginBottom: '1rem', lineHeight: 1.1 }}>MCP Tool Reference</h1>
|
|
834
|
+
<p style={{ color: '#9ca3af', lineHeight: 1.7, marginBottom: '1.25rem' }}>
|
|
835
|
+
The JXR MCP server exposes 14 tools over the Model Context Protocol. Each tool accepts a JSON input object
|
|
836
|
+
and returns a structured result. Tools can be called by any MCP-compatible AI agent.
|
|
837
|
+
</p>
|
|
838
|
+
<div className="space-y-3">
|
|
839
|
+
{[
|
|
840
|
+
{ name: 'jxr_init', input: '{ name, template?, platform?, packageManager? }', output: 'Project scaffold path and generated files list', desc: 'Scaffold a new JXR project in the specified directory.' },
|
|
841
|
+
{ name: 'jxr_dev', input: '{ projectPath, port?, open? }', output: 'Dev server URL and PID', desc: 'Start the JXR dev server with HMR and MoQ transport.' },
|
|
842
|
+
{ name: 'jxr_build', input: '{ projectPath, platform?, analyze? }', output: 'Build stats, output files, and manifest path', desc: 'Run a production build and return the signed manifest.' },
|
|
843
|
+
{ name: 'jxr_migrate', input: '{ projectPath, from?, dryRun? }', output: 'Migration report with changed files and backup path', desc: 'Migrate a project from another framework to JXR.' },
|
|
844
|
+
{ name: 'jxr_deploy', input: '{ projectPath, target, env? }', output: 'Deployment URL and platform response', desc: 'Deploy to Cloudflare Workers, Deno Deploy, or Node.js.' },
|
|
845
|
+
{ name: 'jxr_detect_framework', input: '{ projectPath }', output: 'Detected framework name and confidence score', desc: 'Auto-detect the source framework of an existing project.' },
|
|
846
|
+
{ name: 'jxr_read_config', input: '{ projectPath }', output: 'Parsed jxr.config.ts as a JSON object', desc: 'Read and parse the JXR configuration file.' },
|
|
847
|
+
{ name: 'jxr_write_config', input: '{ projectPath, config }', output: 'Updated config file path', desc: 'Write or update the jxr.config.ts file.' },
|
|
848
|
+
{ name: 'jxr_list_files', input: '{ projectPath, pattern? }', output: 'File tree as a nested JSON object', desc: 'List all files in the project directory.' },
|
|
849
|
+
{ name: 'jxr_read_file', input: '{ projectPath, filePath }', output: 'File contents as a string', desc: 'Read the contents of any file in the project.' },
|
|
850
|
+
{ name: 'jxr_write_file', input: '{ projectPath, filePath, content }', output: 'Written file path', desc: 'Write or create a file in the project.' },
|
|
851
|
+
{ name: 'jxr_add_plugin', input: '{ projectPath, plugin }', output: 'Updated package.json and config', desc: 'Add a JXR plugin and update the config.' },
|
|
852
|
+
{ name: 'jxr_run_command', input: '{ projectPath, command }', output: 'stdout, stderr, and exit code', desc: 'Run an arbitrary shell command inside the project directory.' },
|
|
853
|
+
{ name: 'jxr_info', input: '{ projectPath? }', output: 'Full environment and project metadata', desc: 'Get JXR version, Node version, platform, and project info.' },
|
|
854
|
+
].map((tool) => (
|
|
855
|
+
<div key={tool.name} className="jxr-card">
|
|
856
|
+
<div className="flex items-center gap-2 mb-2">
|
|
857
|
+
<Code2 size={13} style={{ color: '#22c55e', flexShrink: 0 }} />
|
|
858
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.85rem', color: '#22c55e', fontWeight: 700 }}>{tool.name}</code>
|
|
859
|
+
</div>
|
|
860
|
+
<p style={{ fontSize: '0.82rem', color: '#9ca3af', marginBottom: '0.75rem', lineHeight: 1.55 }}>{tool.desc}</p>
|
|
861
|
+
<div className="grid grid-cols-1 gap-1.5">
|
|
862
|
+
<div className="flex items-start gap-2">
|
|
863
|
+
<span style={{ fontFamily: 'JetBrains Mono', fontSize: '0.68rem', color: '#4b5563', flexShrink: 0, marginTop: '1px', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Input</span>
|
|
864
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.72rem', color: '#6b7280' }}>{tool.input}</code>
|
|
865
|
+
</div>
|
|
866
|
+
<div className="flex items-start gap-2">
|
|
867
|
+
<span style={{ fontFamily: 'JetBrains Mono', fontSize: '0.68rem', color: '#4b5563', flexShrink: 0, marginTop: '1px', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Output</span>
|
|
868
|
+
<code style={{ fontFamily: 'JetBrains Mono', fontSize: '0.72rem', color: '#f97316' }}>{tool.output}</code>
|
|
869
|
+
</div>
|
|
870
|
+
</div>
|
|
871
|
+
</div>
|
|
872
|
+
))}
|
|
873
|
+
</div>
|
|
874
|
+
</div>
|
|
875
|
+
),
|
|
876
|
+
};
|
|
877
|
+
|
|
878
|
+
// ─── Page ─────────────────────────────────────────────────────────────────────
|
|
879
|
+
|
|
880
|
+
export default function DocsPage() {
|
|
881
|
+
const [activeSection, setActiveSection] = useState('introduction');
|
|
882
|
+
const content = DOC_CONTENT[activeSection] ?? DOC_CONTENT['introduction'];
|
|
883
|
+
|
|
884
|
+
return (
|
|
885
|
+
<div style={{ background: '#0a0a0a', minHeight: '100vh', display: 'flex', flexDirection: 'column' }}>
|
|
886
|
+
{/* Top bar */}
|
|
887
|
+
<header style={{ position: 'sticky', top: 0, zIndex: 50, background: 'rgba(10,10,10,0.95)', backdropFilter: 'blur(12px)', borderBottom: '1px solid rgba(255,255,255,0.06)' }}>
|
|
888
|
+
<div className="container flex items-center justify-between h-14">
|
|
889
|
+
<div className="flex items-center gap-4">
|
|
890
|
+
<Link href="/" className="flex items-center gap-1.5 no-underline" style={{ color: '#6b7280' }}>
|
|
891
|
+
<ArrowLeft size={14} />
|
|
892
|
+
<span style={{ fontSize: '0.85rem' }}>Back</span>
|
|
893
|
+
</Link>
|
|
894
|
+
<div style={{ width: '1px', height: '16px', background: 'rgba(255,255,255,0.08)' }} />
|
|
895
|
+
<div className="flex items-center gap-2">
|
|
896
|
+
<div className="w-6 h-6 rounded flex items-center justify-center" style={{ background: '#ea580c' }}>
|
|
897
|
+
<Zap size={11} color="#ffffff" strokeWidth={2.5} />
|
|
898
|
+
</div>
|
|
899
|
+
<span style={{ fontFamily: 'Inter', fontWeight: 700, color: '#ffffff', fontSize: '0.9rem' }}>JXR.js</span>
|
|
900
|
+
<span style={{ color: '#374151', fontSize: '0.85rem' }}>/</span>
|
|
901
|
+
<span style={{ color: '#6b7280', fontSize: '0.85rem' }}>docs</span>
|
|
902
|
+
</div>
|
|
903
|
+
</div>
|
|
904
|
+
<div className="badge-lava hidden sm:flex items-center gap-1.5">
|
|
905
|
+
<span className="live-dot" style={{ width: '6px', height: '6px' }} />
|
|
906
|
+
v1.0.0-edge
|
|
907
|
+
</div>
|
|
908
|
+
</div>
|
|
909
|
+
</header>
|
|
910
|
+
|
|
911
|
+
<div className="flex flex-1">
|
|
912
|
+
{/* Sidebar */}
|
|
913
|
+
<aside className="hidden md:block flex-shrink-0" style={{ width: '220px', borderRight: '1px solid rgba(255,255,255,0.06)', padding: '1.5rem 0', position: 'sticky', top: '56px', height: 'calc(100vh - 56px)', overflowY: 'auto' }}>
|
|
914
|
+
{NAV_SECTIONS.map((group) => (
|
|
915
|
+
<div key={group.title} className="mb-5">
|
|
916
|
+
<div style={{ padding: '0 1.25rem', marginBottom: '0.4rem', fontSize: '0.62rem', fontWeight: 700, letterSpacing: '0.15em', textTransform: 'uppercase', color: '#374151', fontFamily: 'JetBrains Mono' }}>
|
|
917
|
+
{group.title}
|
|
918
|
+
</div>
|
|
919
|
+
{group.items.map((item) => {
|
|
920
|
+
const Icon = item.icon;
|
|
921
|
+
return (
|
|
922
|
+
<button
|
|
923
|
+
key={item.id}
|
|
924
|
+
onClick={() => setActiveSection(item.id)}
|
|
925
|
+
className="w-full text-left flex items-center gap-2 px-4 py-2 transition-all"
|
|
926
|
+
style={{
|
|
927
|
+
background: activeSection === item.id ? 'rgba(234,88,12,0.08)' : 'transparent',
|
|
928
|
+
color: activeSection === item.id ? '#f97316' : '#6b7280',
|
|
929
|
+
borderLeft: activeSection === item.id ? '2px solid #ea580c' : '2px solid transparent',
|
|
930
|
+
fontFamily: 'Inter',
|
|
931
|
+
fontSize: '0.84rem',
|
|
932
|
+
}}
|
|
933
|
+
>
|
|
934
|
+
<Icon size={13} style={{ flexShrink: 0 }} />
|
|
935
|
+
{item.label}
|
|
936
|
+
</button>
|
|
937
|
+
);
|
|
938
|
+
})}
|
|
939
|
+
</div>
|
|
940
|
+
))}
|
|
941
|
+
</aside>
|
|
942
|
+
|
|
943
|
+
{/* Content */}
|
|
944
|
+
<main className="flex-1 min-w-0 p-8 md:p-12" style={{ maxWidth: '760px' }}>
|
|
945
|
+
{content}
|
|
946
|
+
<div className="mt-12 pt-8" style={{ borderTop: '1px solid rgba(255,255,255,0.06)' }}>
|
|
947
|
+
<a href="https://github.com/jxrstudios/jxr" target="_blank" rel="noopener noreferrer" className="btn-lava no-underline inline-flex">
|
|
948
|
+
View source on GitHub <ChevronRight size={14} />
|
|
949
|
+
</a>
|
|
950
|
+
</div>
|
|
951
|
+
</main>
|
|
952
|
+
</div>
|
|
953
|
+
</div>
|
|
954
|
+
);
|
|
955
|
+
}
|