@cryptiklemur/lattice 0.0.0
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/.editorconfig +12 -0
- package/.github/workflows/release.yml +44 -0
- package/.impeccable.md +66 -0
- package/.releaserc.json +32 -0
- package/.serena/project.yml +138 -0
- package/CLAUDE.md +35 -0
- package/CONTRIBUTING.md +93 -0
- package/LICENSE +21 -0
- package/README.md +83 -0
- package/bun.lock +1459 -0
- package/bunfig.toml +2 -0
- package/client/index.html +32 -0
- package/client/package.json +37 -0
- package/client/public/icons/icon-192.svg +11 -0
- package/client/public/icons/icon-512.svg +11 -0
- package/client/public/manifest.json +24 -0
- package/client/public/sw.js +61 -0
- package/client/src/App.tsx +28 -0
- package/client/src/components/auth/PassphrasePrompt.tsx +70 -0
- package/client/src/components/chat/ChatInput.tsx +241 -0
- package/client/src/components/chat/ChatView.tsx +727 -0
- package/client/src/components/chat/Message.tsx +362 -0
- package/client/src/components/chat/ModelSelector.tsx +87 -0
- package/client/src/components/chat/PermissionModeSelector.tsx +41 -0
- package/client/src/components/chat/StatusBar.tsx +50 -0
- package/client/src/components/chat/ToolGroup.tsx +129 -0
- package/client/src/components/chat/ToolResultRenderer.tsx +343 -0
- package/client/src/components/chat/toolSummary.ts +41 -0
- package/client/src/components/dashboard/DashboardView.tsx +219 -0
- package/client/src/components/dashboard/ProjectDashboardView.tsx +168 -0
- package/client/src/components/mesh/NodeBadge.tsx +24 -0
- package/client/src/components/mesh/PairingDialog.tsx +281 -0
- package/client/src/components/panels/FileBrowser.tsx +241 -0
- package/client/src/components/panels/StickyNotes.tsx +187 -0
- package/client/src/components/panels/Terminal.tsx +128 -0
- package/client/src/components/project-settings/ProjectClaude.tsx +304 -0
- package/client/src/components/project-settings/ProjectEnvironment.tsx +235 -0
- package/client/src/components/project-settings/ProjectGeneral.tsx +76 -0
- package/client/src/components/project-settings/ProjectMcp.tsx +232 -0
- package/client/src/components/project-settings/ProjectPermissions.tsx +209 -0
- package/client/src/components/project-settings/ProjectRules.tsx +277 -0
- package/client/src/components/project-settings/ProjectSettingsView.tsx +99 -0
- package/client/src/components/project-settings/ProjectSkills.tsx +91 -0
- package/client/src/components/settings/Appearance.tsx +151 -0
- package/client/src/components/settings/ClaudeSettings.tsx +151 -0
- package/client/src/components/settings/Environment.tsx +185 -0
- package/client/src/components/settings/GlobalMcp.tsx +207 -0
- package/client/src/components/settings/GlobalSkills.tsx +125 -0
- package/client/src/components/settings/MeshStatus.tsx +145 -0
- package/client/src/components/settings/SettingsView.tsx +57 -0
- package/client/src/components/settings/SkillMarketplace.tsx +175 -0
- package/client/src/components/settings/mcp-shared.tsx +194 -0
- package/client/src/components/settings/skill-shared.tsx +177 -0
- package/client/src/components/setup/SetupWizard.tsx +750 -0
- package/client/src/components/sidebar/NodeSettingsModal.tsx +180 -0
- package/client/src/components/sidebar/ProjectDropdown.tsx +43 -0
- package/client/src/components/sidebar/ProjectRail.tsx +291 -0
- package/client/src/components/sidebar/SearchFilter.tsx +52 -0
- package/client/src/components/sidebar/SessionList.tsx +384 -0
- package/client/src/components/sidebar/SettingsSidebar.tsx +128 -0
- package/client/src/components/sidebar/Sidebar.tsx +209 -0
- package/client/src/components/sidebar/UserIsland.tsx +59 -0
- package/client/src/components/sidebar/UserMenu.tsx +101 -0
- package/client/src/components/ui/CommandPalette.tsx +321 -0
- package/client/src/components/ui/ErrorBoundary.tsx +56 -0
- package/client/src/components/ui/IconPicker.tsx +209 -0
- package/client/src/components/ui/LatticeLogomark.tsx +19 -0
- package/client/src/components/ui/PopupMenu.tsx +98 -0
- package/client/src/components/ui/SaveFooter.tsx +38 -0
- package/client/src/components/ui/Toast.tsx +112 -0
- package/client/src/hooks/useMesh.ts +89 -0
- package/client/src/hooks/useProjectSettings.ts +56 -0
- package/client/src/hooks/useProjects.ts +66 -0
- package/client/src/hooks/useSaveState.ts +59 -0
- package/client/src/hooks/useSession.ts +317 -0
- package/client/src/hooks/useSidebar.ts +74 -0
- package/client/src/hooks/useSkills.ts +30 -0
- package/client/src/hooks/useTheme.ts +114 -0
- package/client/src/hooks/useWebSocket.ts +26 -0
- package/client/src/main.tsx +10 -0
- package/client/src/providers/WebSocketProvider.tsx +146 -0
- package/client/src/router.tsx +391 -0
- package/client/src/stores/mesh.ts +78 -0
- package/client/src/stores/session.ts +322 -0
- package/client/src/stores/sidebar.ts +336 -0
- package/client/src/stores/theme.ts +44 -0
- package/client/src/styles/global.css +167 -0
- package/client/src/styles/theme-vars.css +18 -0
- package/client/src/themes/index.ts +79 -0
- package/client/src/utils/findDuplicateKeys.ts +12 -0
- package/client/tsconfig.json +14 -0
- package/client/vite.config.ts +20 -0
- package/package.json +46 -0
- package/server/package.json +22 -0
- package/server/src/auth/passphrase.ts +48 -0
- package/server/src/config.ts +55 -0
- package/server/src/daemon.ts +338 -0
- package/server/src/features/ralph-loop.ts +173 -0
- package/server/src/features/scheduler.ts +281 -0
- package/server/src/features/sticky-notes.ts +102 -0
- package/server/src/handlers/chat.ts +194 -0
- package/server/src/handlers/fs.ts +84 -0
- package/server/src/handlers/loop.ts +37 -0
- package/server/src/handlers/mesh.ts +125 -0
- package/server/src/handlers/notes.ts +45 -0
- package/server/src/handlers/project-settings.ts +174 -0
- package/server/src/handlers/scheduler.ts +47 -0
- package/server/src/handlers/session.ts +159 -0
- package/server/src/handlers/settings.ts +109 -0
- package/server/src/handlers/skills.ts +380 -0
- package/server/src/handlers/terminal.ts +70 -0
- package/server/src/identity.ts +26 -0
- package/server/src/index.ts +190 -0
- package/server/src/mesh/connector.ts +209 -0
- package/server/src/mesh/discovery.ts +123 -0
- package/server/src/mesh/pairing.ts +94 -0
- package/server/src/mesh/peers.ts +52 -0
- package/server/src/mesh/proxy.ts +103 -0
- package/server/src/mesh/session-sync.ts +107 -0
- package/server/src/project/context-breakdown.ts +289 -0
- package/server/src/project/file-browser.ts +106 -0
- package/server/src/project/project-files.ts +267 -0
- package/server/src/project/registry.ts +57 -0
- package/server/src/project/sdk-bridge.ts +566 -0
- package/server/src/project/session.ts +432 -0
- package/server/src/project/terminal.ts +69 -0
- package/server/src/tls.ts +51 -0
- package/server/src/ws/broadcast.ts +31 -0
- package/server/src/ws/router.ts +104 -0
- package/server/src/ws/server.ts +2 -0
- package/server/tsconfig.json +16 -0
- package/shared/package.json +11 -0
- package/shared/src/constants.ts +7 -0
- package/shared/src/index.ts +4 -0
- package/shared/src/messages.ts +638 -0
- package/shared/src/models.ts +136 -0
- package/shared/src/project-settings.ts +45 -0
- package/shared/tsconfig.json +11 -0
- package/themes/amoled.json +20 -0
- package/themes/ayu-light.json +9 -0
- package/themes/catppuccin-latte.json +9 -0
- package/themes/catppuccin-mocha.json +9 -0
- package/themes/clay-light.json +10 -0
- package/themes/clay.json +10 -0
- package/themes/dracula.json +9 -0
- package/themes/everforest-light.json +9 -0
- package/themes/everforest.json +9 -0
- package/themes/github-light.json +9 -0
- package/themes/gruvbox-dark.json +9 -0
- package/themes/gruvbox-light.json +9 -0
- package/themes/monokai.json +9 -0
- package/themes/nord-light.json +9 -0
- package/themes/nord.json +9 -0
- package/themes/one-dark.json +9 -0
- package/themes/one-light.json +9 -0
- package/themes/rose-pine-dawn.json +9 -0
- package/themes/rose-pine.json +9 -0
- package/themes/solarized-dark.json +9 -0
- package/themes/solarized-light.json +9 -0
- package/themes/tokyo-night-light.json +9 -0
- package/themes/tokyo-night.json +9 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
import { useState, useEffect } from "react";
|
|
2
|
+
import { ArrowRight, ChevronRight, ChevronLeft, Server, Palette, Lock, Folder, Info, Moon, Sun, Check, CheckCircle } from "lucide-react";
|
|
3
|
+
import { useTheme } from "../../hooks/useTheme";
|
|
4
|
+
import { useWebSocket } from "../../hooks/useWebSocket";
|
|
5
|
+
import { themes } from "../../themes/index";
|
|
6
|
+
import type { ThemeEntry } from "../../themes/index";
|
|
7
|
+
import { LatticeLogomark } from "../ui/LatticeLogomark";
|
|
8
|
+
|
|
9
|
+
var POPULAR_DARK_THEMES = ["dracula", "catppuccin-mocha", "tokyo-night", "one-dark", "amoled"];
|
|
10
|
+
var POPULAR_LIGHT_THEMES = ["ayu-light", "catppuccin-latte", "github-light", "one-light", "rose-pine-dawn"];
|
|
11
|
+
|
|
12
|
+
var TOTAL_STEPS = 6;
|
|
13
|
+
|
|
14
|
+
interface SetupWizardProps {
|
|
15
|
+
onComplete: () => void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function SetupWizard(props: SetupWizardProps) {
|
|
19
|
+
var [step, setStep] = useState(1);
|
|
20
|
+
var [prevStep, setPrevStep] = useState(1);
|
|
21
|
+
var [animating, setAnimating] = useState(false);
|
|
22
|
+
var [nodeName, setNodeName] = useState("");
|
|
23
|
+
var [passphrase, setPassphrase] = useState("");
|
|
24
|
+
var [passphraseConfirm, setPassphraseConfirm] = useState("");
|
|
25
|
+
var [passphraseError, setPassphraseError] = useState("");
|
|
26
|
+
var [projectPath, setProjectPath] = useState("");
|
|
27
|
+
var [projectTitle, setProjectTitle] = useState("");
|
|
28
|
+
var [configured, setConfigured] = useState<string[]>([]);
|
|
29
|
+
|
|
30
|
+
var theme = useTheme();
|
|
31
|
+
var ws = useWebSocket();
|
|
32
|
+
|
|
33
|
+
function navigateTo(next: number) {
|
|
34
|
+
if (animating) return;
|
|
35
|
+
setPrevStep(step);
|
|
36
|
+
setAnimating(true);
|
|
37
|
+
setTimeout(function () {
|
|
38
|
+
setStep(next);
|
|
39
|
+
setAnimating(false);
|
|
40
|
+
}, 180);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function goNext() {
|
|
44
|
+
navigateTo(Math.min(step + 1, TOTAL_STEPS));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function goBack() {
|
|
48
|
+
navigateTo(Math.max(step - 1, 1));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function skipToNext() {
|
|
52
|
+
goNext();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function handleNameNext() {
|
|
56
|
+
var name = nodeName.trim();
|
|
57
|
+
if (name.length > 0) {
|
|
58
|
+
ws.send({ type: "settings:update", settings: { name } });
|
|
59
|
+
setConfigured(function (c) { return [...c.filter(function (x) { return x !== "name"; }), "name: " + name]; });
|
|
60
|
+
}
|
|
61
|
+
goNext();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function handleAppearanceNext() {
|
|
65
|
+
setConfigured(function (c) {
|
|
66
|
+
var label = "theme: " + theme.currentThemeId + " (" + theme.mode + ")";
|
|
67
|
+
return [...c.filter(function (x) { return !x.startsWith("theme:"); }), label];
|
|
68
|
+
});
|
|
69
|
+
goNext();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleSecurityNext() {
|
|
73
|
+
if (passphrase.length === 0) {
|
|
74
|
+
goNext();
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (passphrase !== passphraseConfirm) {
|
|
78
|
+
setPassphraseError("Passphrases do not match");
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
ws.send({ type: "settings:update", settings: { passphraseHash: passphrase } });
|
|
82
|
+
setConfigured(function (c) { return [...c.filter(function (x) { return x !== "security"; }), "security: passphrase set"]; });
|
|
83
|
+
goNext();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function handleProjectNext() {
|
|
87
|
+
var path = projectPath.trim();
|
|
88
|
+
if (path.length > 0) {
|
|
89
|
+
var derivedName = path.replace(/\/+$/, "").split("/").pop() || path;
|
|
90
|
+
var title = projectTitle.trim() || derivedName;
|
|
91
|
+
ws.send({ type: "settings:update", settings: { projects: [{ path: path, slug: "", title: title, env: {} }] } });
|
|
92
|
+
setConfigured(function (c) { return [...c.filter(function (x) { return !x.startsWith("project:"); }), "project: " + title]; });
|
|
93
|
+
}
|
|
94
|
+
goNext();
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function handleDone() {
|
|
98
|
+
localStorage.setItem("lattice-setup-complete", "1");
|
|
99
|
+
props.onComplete();
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
var darkQuickPicks = themes.filter(function (e: ThemeEntry) { return POPULAR_DARK_THEMES.includes(e.id); });
|
|
103
|
+
var lightQuickPicks = themes.filter(function (e: ThemeEntry) { return POPULAR_LIGHT_THEMES.includes(e.id); });
|
|
104
|
+
var isForward = step > prevStep;
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div className="fixed inset-0 z-[9999] flex items-center justify-center bg-base-100 transition-colors duration-300">
|
|
108
|
+
<style>{wizardCSS}</style>
|
|
109
|
+
|
|
110
|
+
{step === 1 ? (
|
|
111
|
+
<div className="relative w-full max-w-[520px] px-6 py-12 flex flex-col items-center text-center">
|
|
112
|
+
<div
|
|
113
|
+
className="fixed inset-0 pointer-events-none z-0"
|
|
114
|
+
aria-hidden="true"
|
|
115
|
+
style={{
|
|
116
|
+
backgroundImage: "linear-gradient(oklch(from var(--color-primary) l c h / 0.07) 1px, transparent 1px), linear-gradient(90deg, oklch(from var(--color-primary) l c h / 0.07) 1px, transparent 1px)",
|
|
117
|
+
backgroundSize: "40px 40px",
|
|
118
|
+
animation: "wizard-grid-shift 8s linear infinite",
|
|
119
|
+
}}
|
|
120
|
+
/>
|
|
121
|
+
<div className="relative z-[1] flex flex-col items-center gap-0">
|
|
122
|
+
<div className="wizard-fade-in text-primary" style={{ animationDelay: "0ms" }}>
|
|
123
|
+
<LatticeLogomark size={64} />
|
|
124
|
+
</div>
|
|
125
|
+
<h1
|
|
126
|
+
className="wizard-fade-in font-mono font-bold text-base-content leading-none mt-5 mb-0"
|
|
127
|
+
style={{ fontSize: "clamp(48px, 10vw, 72px)", letterSpacing: "-0.04em", animationDelay: "80ms" }}
|
|
128
|
+
>
|
|
129
|
+
Lattice
|
|
130
|
+
</h1>
|
|
131
|
+
<p className="wizard-fade-in text-[17px] text-base-content/60 mt-3 mb-8 tracking-[0.01em]" style={{ animationDelay: "160ms" }}>
|
|
132
|
+
One dashboard. Every machine.
|
|
133
|
+
</p>
|
|
134
|
+
<div className="wizard-fade-in" style={{ animationDelay: "240ms" }}>
|
|
135
|
+
<TerminalPreview />
|
|
136
|
+
</div>
|
|
137
|
+
<div className="wizard-fade-in" style={{ animationDelay: "320ms" }}>
|
|
138
|
+
<button
|
|
139
|
+
onClick={goNext}
|
|
140
|
+
className="wizard-btn-primary btn btn-primary inline-flex items-center gap-2 mt-7 h-[52px] px-8 text-[15px] font-semibold cursor-pointer"
|
|
141
|
+
>
|
|
142
|
+
Get Started
|
|
143
|
+
<ArrowRight size={16} />
|
|
144
|
+
</button>
|
|
145
|
+
</div>
|
|
146
|
+
<p className="wizard-fade-in text-[12px] text-base-content/30 mt-4" style={{ animationDelay: "380ms" }}>
|
|
147
|
+
Takes about 2 minutes
|
|
148
|
+
</p>
|
|
149
|
+
</div>
|
|
150
|
+
</div>
|
|
151
|
+
) : (
|
|
152
|
+
<div className="w-[480px] max-w-[calc(100vw-24px)] bg-base-200 border border-base-300 rounded-xl flex flex-col overflow-hidden shadow-2xl">
|
|
153
|
+
<div className="flex items-center justify-between px-6 py-4 border-b border-base-300 bg-base-100">
|
|
154
|
+
<div className="flex items-center gap-1.5">
|
|
155
|
+
{Array.from({ length: TOTAL_STEPS - 1 }, function (_, i) {
|
|
156
|
+
var dotStep = i + 2;
|
|
157
|
+
var isComplete = step > dotStep;
|
|
158
|
+
var isActive = step === dotStep;
|
|
159
|
+
return (
|
|
160
|
+
<div
|
|
161
|
+
key={i}
|
|
162
|
+
className="h-1.5 rounded-full transition-all duration-[250ms]"
|
|
163
|
+
style={{
|
|
164
|
+
background: (isComplete || isActive) ? "var(--color-primary)" : "var(--color-base-300)",
|
|
165
|
+
opacity: isActive ? 1 : isComplete ? 0.7 : 0.35,
|
|
166
|
+
width: isActive ? "24px" : "8px",
|
|
167
|
+
}}
|
|
168
|
+
/>
|
|
169
|
+
);
|
|
170
|
+
})}
|
|
171
|
+
</div>
|
|
172
|
+
<span className="text-[11px] text-base-content/40 font-mono tracking-[0.06em]">
|
|
173
|
+
{step - 1} / {TOTAL_STEPS - 1}
|
|
174
|
+
</span>
|
|
175
|
+
</div>
|
|
176
|
+
|
|
177
|
+
<div
|
|
178
|
+
className={"px-7 pt-7 pb-2 flex-1 min-h-[320px] " + (animating ? (isForward ? "wizard-slide-out-left" : "wizard-slide-out-right") : (isForward ? "wizard-slide-in-right" : "wizard-slide-in-left"))}
|
|
179
|
+
key={step}
|
|
180
|
+
>
|
|
181
|
+
{step === 2 && (
|
|
182
|
+
<NameStep value={nodeName} onChange={setNodeName} />
|
|
183
|
+
)}
|
|
184
|
+
{step === 3 && (
|
|
185
|
+
<AppearanceStep
|
|
186
|
+
theme={theme}
|
|
187
|
+
darkQuickPicks={darkQuickPicks}
|
|
188
|
+
lightQuickPicks={lightQuickPicks}
|
|
189
|
+
/>
|
|
190
|
+
)}
|
|
191
|
+
{step === 4 && (
|
|
192
|
+
<SecurityStep
|
|
193
|
+
passphrase={passphrase}
|
|
194
|
+
passphraseConfirm={passphraseConfirm}
|
|
195
|
+
error={passphraseError}
|
|
196
|
+
onPassphraseChange={function (v: string) {
|
|
197
|
+
setPassphrase(v);
|
|
198
|
+
setPassphraseError("");
|
|
199
|
+
}}
|
|
200
|
+
onConfirmChange={function (v: string) {
|
|
201
|
+
setPassphraseConfirm(v);
|
|
202
|
+
setPassphraseError("");
|
|
203
|
+
}}
|
|
204
|
+
/>
|
|
205
|
+
)}
|
|
206
|
+
{step === 5 && (
|
|
207
|
+
<ProjectStep
|
|
208
|
+
path={projectPath}
|
|
209
|
+
title={projectTitle}
|
|
210
|
+
onPathChange={setProjectPath}
|
|
211
|
+
onTitleChange={setProjectTitle}
|
|
212
|
+
/>
|
|
213
|
+
)}
|
|
214
|
+
{step === 6 && <DoneStep configured={configured} />}
|
|
215
|
+
</div>
|
|
216
|
+
|
|
217
|
+
<div className="flex items-center gap-2 px-6 py-4 border-t border-base-300 bg-base-100">
|
|
218
|
+
{step >= 2 && step < TOTAL_STEPS && (
|
|
219
|
+
<button onClick={goBack} className="wizard-btn-back btn btn-ghost btn-sm gap-1 text-base-content/40">
|
|
220
|
+
<ChevronLeft size={14} />
|
|
221
|
+
Back
|
|
222
|
+
</button>
|
|
223
|
+
)}
|
|
224
|
+
<div className="flex items-center gap-2 ml-auto">
|
|
225
|
+
{step === 2 && (
|
|
226
|
+
<button onClick={skipToNext} className="wizard-btn-skip btn btn-ghost btn-sm text-base-content/40">Skip</button>
|
|
227
|
+
)}
|
|
228
|
+
{step === 3 && (
|
|
229
|
+
<button onClick={handleAppearanceNext} className="wizard-btn-primary btn btn-primary btn-sm gap-1">
|
|
230
|
+
Continue
|
|
231
|
+
<ChevronRight size={14} />
|
|
232
|
+
</button>
|
|
233
|
+
)}
|
|
234
|
+
{step === 4 && (
|
|
235
|
+
<>
|
|
236
|
+
<button onClick={skipToNext} className="wizard-btn-skip btn btn-ghost btn-sm text-base-content/40">Skip</button>
|
|
237
|
+
<button onClick={handleSecurityNext} className="wizard-btn-primary btn btn-primary btn-sm gap-1">
|
|
238
|
+
Continue
|
|
239
|
+
<ChevronRight size={14} />
|
|
240
|
+
</button>
|
|
241
|
+
</>
|
|
242
|
+
)}
|
|
243
|
+
{step === 5 && (
|
|
244
|
+
<>
|
|
245
|
+
<button onClick={skipToNext} className="wizard-btn-skip btn btn-ghost btn-sm text-base-content/40">Skip</button>
|
|
246
|
+
<button onClick={handleProjectNext} className="wizard-btn-primary btn btn-primary btn-sm gap-1">
|
|
247
|
+
Add & Continue
|
|
248
|
+
<ChevronRight size={14} />
|
|
249
|
+
</button>
|
|
250
|
+
</>
|
|
251
|
+
)}
|
|
252
|
+
{step === 6 && (
|
|
253
|
+
<button onClick={handleDone} className="wizard-btn-done btn btn-success btn-sm gap-2 font-bold">
|
|
254
|
+
Open Dashboard
|
|
255
|
+
<ArrowRight size={16} />
|
|
256
|
+
</button>
|
|
257
|
+
)}
|
|
258
|
+
{step === 2 && (
|
|
259
|
+
<button onClick={handleNameNext} className="wizard-btn-primary btn btn-primary btn-sm gap-1">
|
|
260
|
+
Continue
|
|
261
|
+
<ChevronRight size={14} />
|
|
262
|
+
</button>
|
|
263
|
+
)}
|
|
264
|
+
</div>
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
)}
|
|
268
|
+
</div>
|
|
269
|
+
);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function TerminalPreview() {
|
|
273
|
+
var [visible, setVisible] = useState(0);
|
|
274
|
+
var lines = [
|
|
275
|
+
{ prefix: "$ ", text: "lattice", color: "var(--color-base-content)" },
|
|
276
|
+
{ prefix: "", text: " [lattice] Daemon started (PID 4821)", color: "oklch(from var(--color-base-content) l c h / 0.5)" },
|
|
277
|
+
{ prefix: "", text: " [lattice] Listening on https://0.0.0.0:7654", color: "var(--color-success)" },
|
|
278
|
+
{ prefix: "", text: " [discovery] Found 2 nodes on mesh", color: "var(--color-primary)" },
|
|
279
|
+
];
|
|
280
|
+
|
|
281
|
+
useEffect(function () {
|
|
282
|
+
var timers: ReturnType<typeof setTimeout>[] = [];
|
|
283
|
+
lines.forEach(function (_, i) {
|
|
284
|
+
timers.push(setTimeout(function () { setVisible(i + 1); }, 600 + i * 600));
|
|
285
|
+
});
|
|
286
|
+
return function () { timers.forEach(clearTimeout); };
|
|
287
|
+
}, []);
|
|
288
|
+
|
|
289
|
+
return (
|
|
290
|
+
<div className="w-[380px] max-w-[calc(100vw-48px)] bg-base-200 border border-base-300 rounded-xl overflow-hidden shadow-2xl">
|
|
291
|
+
<div className="flex items-center gap-1.5 px-3.5 py-2.5 bg-base-300 border-b border-base-300">
|
|
292
|
+
<span className="w-2.5 h-2.5 rounded-full bg-error opacity-80 flex-shrink-0" />
|
|
293
|
+
<span className="w-2.5 h-2.5 rounded-full bg-warning opacity-80 flex-shrink-0" />
|
|
294
|
+
<span className="w-2.5 h-2.5 rounded-full bg-success opacity-80 flex-shrink-0" />
|
|
295
|
+
<span className="text-[11px] text-base-content/40 font-mono mx-auto tracking-[0.02em]">lattice — zsh</span>
|
|
296
|
+
</div>
|
|
297
|
+
<div className="px-4 py-3.5 flex flex-col gap-1 min-h-[96px]">
|
|
298
|
+
{lines.map(function (line, i) {
|
|
299
|
+
return (
|
|
300
|
+
<div
|
|
301
|
+
key={i}
|
|
302
|
+
className="text-[12px] font-mono leading-relaxed whitespace-pre transition-all duration-200"
|
|
303
|
+
style={{
|
|
304
|
+
opacity: i < visible ? 1 : 0,
|
|
305
|
+
transform: i < visible ? "translateY(0)" : "translateY(4px)",
|
|
306
|
+
color: line.color,
|
|
307
|
+
}}
|
|
308
|
+
>
|
|
309
|
+
{line.prefix && <span style={{ color: "var(--color-primary)" }}>{line.prefix}</span>}
|
|
310
|
+
<span>{line.text}</span>
|
|
311
|
+
</div>
|
|
312
|
+
);
|
|
313
|
+
})}
|
|
314
|
+
<div className="flex items-center gap-0.5 text-[12px] font-mono mt-0.5">
|
|
315
|
+
<span style={{ color: "var(--color-primary)" }}>$ </span>
|
|
316
|
+
<span
|
|
317
|
+
className="inline-block w-2 h-3.5 rounded-[1px] align-middle opacity-90"
|
|
318
|
+
style={{ background: "var(--color-primary)", animation: "wizard-cursor-blink 1s step-end infinite" }}
|
|
319
|
+
/>
|
|
320
|
+
</div>
|
|
321
|
+
</div>
|
|
322
|
+
</div>
|
|
323
|
+
);
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
interface NameStepProps {
|
|
327
|
+
value: string;
|
|
328
|
+
onChange: (v: string) => void;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function NameStep(props: NameStepProps) {
|
|
332
|
+
var displayName = props.value.trim() || "this-machine";
|
|
333
|
+
return (
|
|
334
|
+
<div className="flex flex-col">
|
|
335
|
+
<div className="flex items-center mb-3.5">
|
|
336
|
+
<Server size={22} className="text-primary" />
|
|
337
|
+
</div>
|
|
338
|
+
<h2 className="font-mono text-[22px] font-bold text-base-content tracking-tight mb-2 leading-tight">
|
|
339
|
+
Name this machine
|
|
340
|
+
</h2>
|
|
341
|
+
<p className="text-[13px] text-base-content/60 leading-relaxed mb-5">
|
|
342
|
+
Give this node a recognizable name. It appears in your mesh when you connect multiple computers.
|
|
343
|
+
</p>
|
|
344
|
+
<fieldset className="fieldset mb-3.5">
|
|
345
|
+
<legend className="fieldset-legend text-[11px] font-semibold text-base-content/40 uppercase tracking-[0.08em]">
|
|
346
|
+
Machine name
|
|
347
|
+
</legend>
|
|
348
|
+
<div className="flex items-center gap-2 bg-base-300 border border-base-content/20 rounded-md px-3 h-[44px] focus-within:border-primary transition-colors duration-[120ms]">
|
|
349
|
+
<span className="text-primary font-mono font-bold text-[16px]">></span>
|
|
350
|
+
<input
|
|
351
|
+
type="text"
|
|
352
|
+
value={props.value}
|
|
353
|
+
onChange={function (e) { props.onChange(e.target.value); }}
|
|
354
|
+
placeholder="my-laptop"
|
|
355
|
+
className="flex-1 bg-transparent text-base-content font-mono text-[14px] outline-none"
|
|
356
|
+
autoFocus
|
|
357
|
+
spellCheck={false}
|
|
358
|
+
/>
|
|
359
|
+
</div>
|
|
360
|
+
</fieldset>
|
|
361
|
+
<div className="flex items-center gap-2 px-3 py-2 bg-base-300 border border-base-content/10 rounded-md">
|
|
362
|
+
<span className="text-[10px] uppercase tracking-[0.08em] text-base-content/40 font-semibold">Preview</span>
|
|
363
|
+
<span className="font-mono text-[13px] font-semibold text-base-content">{displayName}</span>
|
|
364
|
+
<span className="text-base-content/30 text-[12px]">will appear on your mesh</span>
|
|
365
|
+
</div>
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
interface AppearanceStepProps {
|
|
371
|
+
theme: ReturnType<typeof useTheme>;
|
|
372
|
+
darkQuickPicks: ThemeEntry[];
|
|
373
|
+
lightQuickPicks: ThemeEntry[];
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function AppearanceStep(props: AppearanceStepProps) {
|
|
377
|
+
var { theme, darkQuickPicks, lightQuickPicks } = props;
|
|
378
|
+
var quickPicks = theme.mode === "dark" ? darkQuickPicks : lightQuickPicks;
|
|
379
|
+
|
|
380
|
+
return (
|
|
381
|
+
<div className="flex flex-col">
|
|
382
|
+
<div className="flex items-center mb-3.5">
|
|
383
|
+
<Palette size={22} className="text-primary" />
|
|
384
|
+
</div>
|
|
385
|
+
<h2 className="font-mono text-[22px] font-bold text-base-content tracking-tight mb-2 leading-tight">
|
|
386
|
+
Choose appearance
|
|
387
|
+
</h2>
|
|
388
|
+
<p className="text-[13px] text-base-content/60 leading-relaxed mb-4">
|
|
389
|
+
Pick a color theme. You can always change this in settings.
|
|
390
|
+
</p>
|
|
391
|
+
|
|
392
|
+
<div className="flex gap-1.5 mb-4 p-1 bg-base-300 rounded-lg w-fit">
|
|
393
|
+
<button
|
|
394
|
+
onClick={function () { if (theme.mode !== "dark") { theme.toggleMode(); } }}
|
|
395
|
+
className={
|
|
396
|
+
"flex items-center gap-1.5 px-3 py-1.5 rounded-md text-[13px] font-medium transition-all duration-[120ms] cursor-pointer " +
|
|
397
|
+
(theme.mode === "dark" ? "bg-primary text-primary-content" : "text-base-content/60 hover:text-base-content")
|
|
398
|
+
}
|
|
399
|
+
>
|
|
400
|
+
<Moon size={13} />
|
|
401
|
+
Dark
|
|
402
|
+
</button>
|
|
403
|
+
<button
|
|
404
|
+
onClick={function () { if (theme.mode !== "light") { theme.toggleMode(); } }}
|
|
405
|
+
className={
|
|
406
|
+
"flex items-center gap-1.5 px-3 py-1.5 rounded-md text-[13px] font-medium transition-all duration-[120ms] cursor-pointer " +
|
|
407
|
+
(theme.mode === "light" ? "bg-primary text-primary-content" : "text-base-content/60 hover:text-base-content")
|
|
408
|
+
}
|
|
409
|
+
>
|
|
410
|
+
<Sun size={13} />
|
|
411
|
+
Light
|
|
412
|
+
</button>
|
|
413
|
+
</div>
|
|
414
|
+
|
|
415
|
+
<div className="grid gap-2 mb-4" style={{ gridTemplateColumns: "repeat(auto-fill, minmax(80px, 1fr))" }}>
|
|
416
|
+
{quickPicks.map(function (entry: ThemeEntry) {
|
|
417
|
+
var isActive = entry.id === theme.currentThemeId;
|
|
418
|
+
var bg = "#" + entry.theme.base00;
|
|
419
|
+
var accent = "#" + entry.theme.base0D;
|
|
420
|
+
var text = "#" + entry.theme.base05;
|
|
421
|
+
var red = "#" + entry.theme.base08;
|
|
422
|
+
var green = "#" + entry.theme.base0B;
|
|
423
|
+
return (
|
|
424
|
+
<button
|
|
425
|
+
key={entry.id}
|
|
426
|
+
onClick={function () { theme.setTheme(entry.id); }}
|
|
427
|
+
title={entry.theme.name}
|
|
428
|
+
className={
|
|
429
|
+
"relative flex flex-col gap-1.5 p-0 rounded-md overflow-hidden cursor-pointer border-2 transition-all duration-[120ms] " +
|
|
430
|
+
(isActive ? "border-primary" : "border-transparent hover:border-base-content/20")
|
|
431
|
+
}
|
|
432
|
+
>
|
|
433
|
+
<div className="w-full h-[48px] flex flex-col" style={{ background: bg }}>
|
|
434
|
+
<div className="flex items-center gap-1 px-1.5 py-1" style={{ background: "#" + entry.theme.base01 }}>
|
|
435
|
+
<span className="w-1.5 h-1.5 rounded-full" style={{ background: red }} />
|
|
436
|
+
<span className="w-6 h-1 rounded" style={{ background: accent, opacity: 0.7 }} />
|
|
437
|
+
</div>
|
|
438
|
+
<div className="flex flex-col gap-[3px] px-1.5 py-1">
|
|
439
|
+
<div className="h-[3px] rounded w-[80%]" style={{ background: accent, opacity: 0.8 }} />
|
|
440
|
+
<div className="h-[3px] rounded w-[60%]" style={{ background: text, opacity: 0.4 }} />
|
|
441
|
+
<div className="h-[3px] rounded w-[70%]" style={{ background: green, opacity: 0.6 }} />
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
<span className="text-[10px] text-base-content/60 truncate px-1.5 pb-1">{entry.theme.name}</span>
|
|
445
|
+
{isActive && (
|
|
446
|
+
<div className="absolute top-1 right-1 w-3.5 h-3.5 rounded-full bg-primary flex items-center justify-center">
|
|
447
|
+
<Check size={8} className="text-primary-content" />
|
|
448
|
+
</div>
|
|
449
|
+
)}
|
|
450
|
+
</button>
|
|
451
|
+
);
|
|
452
|
+
})}
|
|
453
|
+
</div>
|
|
454
|
+
|
|
455
|
+
<div className="rounded-lg border border-base-300 overflow-hidden transition-all duration-300">
|
|
456
|
+
<div className="text-[9px] font-mono tracking-[0.05em] uppercase text-base-content/40 px-2.5 py-1.5 bg-base-300 border-b border-base-300 transition-all duration-300">
|
|
457
|
+
Preview
|
|
458
|
+
</div>
|
|
459
|
+
<div className="flex h-[100px] bg-base-100 transition-all duration-300">
|
|
460
|
+
<div className="w-[72px] flex-shrink-0 border-r border-base-300 p-2 flex flex-col gap-1">
|
|
461
|
+
<div className="text-[8px] font-mono text-base-content/30 uppercase tracking-[0.05em] mb-0.5">Projects</div>
|
|
462
|
+
<div className="h-1.5 w-[90%] rounded bg-primary opacity-80" />
|
|
463
|
+
<div className="h-1.5 w-[70%] rounded bg-base-300" />
|
|
464
|
+
<div className="h-1.5 w-[80%] rounded bg-base-300" />
|
|
465
|
+
</div>
|
|
466
|
+
<div className="flex-1 p-2.5 flex flex-col gap-1.5">
|
|
467
|
+
<div className="text-[9px] font-mono font-semibold text-base-content">New Session</div>
|
|
468
|
+
<div className="flex-1 flex flex-col gap-[3px] justify-center">
|
|
469
|
+
<div className="flex gap-1 items-start">
|
|
470
|
+
<div className="w-3 h-3 rounded-full bg-primary flex-shrink-0 opacity-60" />
|
|
471
|
+
<div className="flex flex-col gap-0.5">
|
|
472
|
+
<div className="h-1 w-[120px] rounded bg-base-content/20" />
|
|
473
|
+
<div className="h-1 w-[80px] rounded bg-base-content/15" />
|
|
474
|
+
</div>
|
|
475
|
+
</div>
|
|
476
|
+
</div>
|
|
477
|
+
<div className="h-[18px] rounded bg-base-200 border border-base-300" />
|
|
478
|
+
</div>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
</div>
|
|
482
|
+
);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
interface SecurityStepProps {
|
|
486
|
+
passphrase: string;
|
|
487
|
+
passphraseConfirm: string;
|
|
488
|
+
error: string;
|
|
489
|
+
onPassphraseChange: (v: string) => void;
|
|
490
|
+
onConfirmChange: (v: string) => void;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
function SecurityStep(props: SecurityStepProps) {
|
|
494
|
+
var strength = getPassphraseStrength(props.passphrase);
|
|
495
|
+
|
|
496
|
+
return (
|
|
497
|
+
<div className="flex flex-col">
|
|
498
|
+
<div className="flex items-center mb-3.5">
|
|
499
|
+
<Lock size={22} className="text-primary" />
|
|
500
|
+
</div>
|
|
501
|
+
<h2 className="font-mono text-[22px] font-bold text-base-content tracking-tight mb-2 leading-tight">
|
|
502
|
+
Set a passphrase
|
|
503
|
+
</h2>
|
|
504
|
+
|
|
505
|
+
<div className="flex gap-2 p-3 bg-base-300 border border-base-300 rounded-md mb-4">
|
|
506
|
+
<Info size={16} className="text-base-content/40 flex-shrink-0 mt-[1px]" />
|
|
507
|
+
<p className="text-[12px] text-base-content/50 leading-relaxed">
|
|
508
|
+
Optional. Protects your dashboard on shared networks. Node-to-node connections use separate key-based auth.
|
|
509
|
+
</p>
|
|
510
|
+
</div>
|
|
511
|
+
|
|
512
|
+
<fieldset className="fieldset mb-3.5">
|
|
513
|
+
<legend className="fieldset-legend text-[11px] font-semibold text-base-content/40 uppercase tracking-[0.08em]">
|
|
514
|
+
Passphrase
|
|
515
|
+
</legend>
|
|
516
|
+
<input
|
|
517
|
+
type="password"
|
|
518
|
+
value={props.passphrase}
|
|
519
|
+
onChange={function (e) { props.onPassphraseChange(e.target.value); }}
|
|
520
|
+
placeholder="Leave blank to skip"
|
|
521
|
+
className="input input-bordered w-full bg-base-300 text-base-content text-[14px] focus:border-primary"
|
|
522
|
+
autoFocus
|
|
523
|
+
/>
|
|
524
|
+
{props.passphrase.length > 0 && (
|
|
525
|
+
<div className="flex items-center gap-2 mt-1.5">
|
|
526
|
+
<div className="flex-1 h-1 bg-base-300 rounded-full overflow-hidden">
|
|
527
|
+
<div
|
|
528
|
+
className="h-full rounded-full transition-all duration-200"
|
|
529
|
+
style={{ width: strength.pct + "%", background: strength.color }}
|
|
530
|
+
/>
|
|
531
|
+
</div>
|
|
532
|
+
<span className="text-[11px] font-semibold" style={{ color: strength.color }}>{strength.label}</span>
|
|
533
|
+
</div>
|
|
534
|
+
)}
|
|
535
|
+
</fieldset>
|
|
536
|
+
|
|
537
|
+
{props.passphrase.length > 0 && (
|
|
538
|
+
<fieldset className="fieldset mb-3.5">
|
|
539
|
+
<legend className="fieldset-legend text-[11px] font-semibold text-base-content/40 uppercase tracking-[0.08em]">
|
|
540
|
+
Confirm passphrase
|
|
541
|
+
</legend>
|
|
542
|
+
<input
|
|
543
|
+
type="password"
|
|
544
|
+
value={props.passphraseConfirm}
|
|
545
|
+
onChange={function (e) { props.onConfirmChange(e.target.value); }}
|
|
546
|
+
placeholder="Repeat passphrase"
|
|
547
|
+
className={
|
|
548
|
+
"input input-bordered w-full bg-base-300 text-base-content text-[14px] focus:border-primary " +
|
|
549
|
+
(props.error ? "border-error" : "")
|
|
550
|
+
}
|
|
551
|
+
/>
|
|
552
|
+
</fieldset>
|
|
553
|
+
)}
|
|
554
|
+
|
|
555
|
+
{props.error && (
|
|
556
|
+
<p className="text-[12px] text-error mt-1">{props.error}</p>
|
|
557
|
+
)}
|
|
558
|
+
</div>
|
|
559
|
+
);
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
function getPassphraseStrength(p: string) {
|
|
563
|
+
if (p.length === 0) return { pct: 0, label: "", color: "oklch(from var(--color-base-content) l c h / 0.5)" };
|
|
564
|
+
if (p.length < 8) return { pct: 25, label: "Weak", color: "var(--color-error)" };
|
|
565
|
+
if (p.length < 14) return { pct: 55, label: "Fair", color: "var(--color-warning)" };
|
|
566
|
+
if (p.length < 20) return { pct: 80, label: "Good", color: "var(--color-primary)" };
|
|
567
|
+
return { pct: 100, label: "Strong", color: "var(--color-success)" };
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
interface ProjectStepProps {
|
|
571
|
+
path: string;
|
|
572
|
+
title: string;
|
|
573
|
+
onPathChange: (v: string) => void;
|
|
574
|
+
onTitleChange: (v: string) => void;
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
function ProjectStep(props: ProjectStepProps) {
|
|
578
|
+
return (
|
|
579
|
+
<div className="flex flex-col">
|
|
580
|
+
<div className="flex items-center mb-3.5">
|
|
581
|
+
<Folder size={22} className="text-primary" />
|
|
582
|
+
</div>
|
|
583
|
+
<h2 className="font-mono text-[22px] font-bold text-base-content tracking-tight mb-2 leading-tight">
|
|
584
|
+
Add your first project
|
|
585
|
+
</h2>
|
|
586
|
+
<p className="text-[13px] text-base-content/60 leading-relaxed mb-5">
|
|
587
|
+
Point Lattice at a local directory. Claude runs inside that workspace. Add more projects from the sidebar anytime.
|
|
588
|
+
</p>
|
|
589
|
+
<fieldset className="fieldset mb-3.5">
|
|
590
|
+
<legend className="fieldset-legend text-[11px] font-semibold text-base-content/40 uppercase tracking-[0.08em]">
|
|
591
|
+
Project path
|
|
592
|
+
</legend>
|
|
593
|
+
<input
|
|
594
|
+
type="text"
|
|
595
|
+
value={props.path}
|
|
596
|
+
onChange={function (e) { props.onPathChange(e.target.value); }}
|
|
597
|
+
placeholder="/home/you/projects/my-app"
|
|
598
|
+
className="input input-bordered w-full bg-base-300 text-base-content font-mono text-[14px] focus:border-primary"
|
|
599
|
+
autoFocus
|
|
600
|
+
spellCheck={false}
|
|
601
|
+
/>
|
|
602
|
+
<p className="fieldset-label text-[11px] text-base-content/30 mt-1">
|
|
603
|
+
Absolute path to a local directory on this machine.
|
|
604
|
+
</p>
|
|
605
|
+
</fieldset>
|
|
606
|
+
<fieldset className="fieldset mb-3.5">
|
|
607
|
+
<legend className="fieldset-legend text-[11px] font-semibold text-base-content/40 uppercase tracking-[0.08em]">
|
|
608
|
+
Display name <span className="font-normal normal-case tracking-normal">(optional)</span>
|
|
609
|
+
</legend>
|
|
610
|
+
<input
|
|
611
|
+
type="text"
|
|
612
|
+
value={props.title}
|
|
613
|
+
onChange={function (e) { props.onTitleChange(e.target.value); }}
|
|
614
|
+
placeholder={props.path ? (props.path.replace(/\/+$/, "").split("/").pop() || "My App") : "My App"}
|
|
615
|
+
className="input input-bordered w-full bg-base-300 text-base-content text-[14px] focus:border-primary"
|
|
616
|
+
/>
|
|
617
|
+
</fieldset>
|
|
618
|
+
</div>
|
|
619
|
+
);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
interface DoneStepProps {
|
|
623
|
+
configured: string[];
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
function DoneStep(props: DoneStepProps) {
|
|
627
|
+
return (
|
|
628
|
+
<div className="flex flex-col items-center text-center">
|
|
629
|
+
<div className="wizard-check-pop mb-4">
|
|
630
|
+
<CheckCircle size={36} className="text-success" strokeWidth={1.5} aria-hidden="true" />
|
|
631
|
+
</div>
|
|
632
|
+
<h2 className="font-mono text-[26px] font-bold text-base-content tracking-tight mb-2">
|
|
633
|
+
You're all set
|
|
634
|
+
</h2>
|
|
635
|
+
<p className="text-[13px] text-base-content/60 leading-relaxed mb-5">
|
|
636
|
+
Lattice is configured and ready to go.
|
|
637
|
+
</p>
|
|
638
|
+
|
|
639
|
+
{props.configured.length > 0 ? (
|
|
640
|
+
<ul className="list-none p-0 m-0 flex flex-col gap-1.5 text-left w-full">
|
|
641
|
+
{props.configured.map(function (item: string, i: number) {
|
|
642
|
+
return (
|
|
643
|
+
<li key={i} className="wizard-fade-in flex items-center gap-2 bg-base-300 px-3 py-2 rounded-md" data-delay={i * 60}>
|
|
644
|
+
<span className="w-4 h-4 rounded-full bg-success/20 text-success flex items-center justify-center flex-shrink-0">
|
|
645
|
+
<Check size={10} />
|
|
646
|
+
</span>
|
|
647
|
+
<span className="font-mono text-[12px] text-base-content/60">{item}</span>
|
|
648
|
+
</li>
|
|
649
|
+
);
|
|
650
|
+
})}
|
|
651
|
+
</ul>
|
|
652
|
+
) : (
|
|
653
|
+
<p className="text-[11px] text-base-content/30">
|
|
654
|
+
Everything was skipped — configure it from settings anytime.
|
|
655
|
+
</p>
|
|
656
|
+
)}
|
|
657
|
+
</div>
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
var wizardCSS = `
|
|
662
|
+
@keyframes wizard-fade-in {
|
|
663
|
+
from { opacity: 0; transform: translateY(12px); }
|
|
664
|
+
to { opacity: 1; transform: translateY(0); }
|
|
665
|
+
}
|
|
666
|
+
@keyframes wizard-slide-in-right {
|
|
667
|
+
from { opacity: 0; transform: translateX(24px); }
|
|
668
|
+
to { opacity: 1; transform: translateX(0); }
|
|
669
|
+
}
|
|
670
|
+
@keyframes wizard-slide-in-left {
|
|
671
|
+
from { opacity: 0; transform: translateX(-24px); }
|
|
672
|
+
to { opacity: 1; transform: translateX(0); }
|
|
673
|
+
}
|
|
674
|
+
@keyframes wizard-slide-out-left {
|
|
675
|
+
from { opacity: 1; transform: translateX(0); }
|
|
676
|
+
to { opacity: 0; transform: translateX(-24px); }
|
|
677
|
+
}
|
|
678
|
+
@keyframes wizard-slide-out-right {
|
|
679
|
+
from { opacity: 1; transform: translateX(0); }
|
|
680
|
+
to { opacity: 0; transform: translateX(24px); }
|
|
681
|
+
}
|
|
682
|
+
@keyframes wizard-check-pop {
|
|
683
|
+
0% { transform: scale(0.5); opacity: 0; }
|
|
684
|
+
70% { transform: scale(1.15); }
|
|
685
|
+
100% { transform: scale(1); opacity: 1; }
|
|
686
|
+
}
|
|
687
|
+
@keyframes wizard-cursor-blink {
|
|
688
|
+
0%, 100% { opacity: 1; }
|
|
689
|
+
50% { opacity: 0; }
|
|
690
|
+
}
|
|
691
|
+
@keyframes wizard-grid-shift {
|
|
692
|
+
0% { background-position: 0 0; }
|
|
693
|
+
100% { background-position: 40px 40px; }
|
|
694
|
+
}
|
|
695
|
+
.wizard-fade-in {
|
|
696
|
+
animation: wizard-fade-in 400ms ease both;
|
|
697
|
+
}
|
|
698
|
+
.wizard-slide-in-right {
|
|
699
|
+
animation: wizard-slide-in-right 220ms ease both;
|
|
700
|
+
}
|
|
701
|
+
.wizard-slide-in-left {
|
|
702
|
+
animation: wizard-slide-in-left 220ms ease both;
|
|
703
|
+
}
|
|
704
|
+
.wizard-slide-out-left {
|
|
705
|
+
animation: wizard-slide-out-left 180ms ease both;
|
|
706
|
+
}
|
|
707
|
+
.wizard-slide-out-right {
|
|
708
|
+
animation: wizard-slide-out-right 180ms ease both;
|
|
709
|
+
}
|
|
710
|
+
.wizard-check-pop {
|
|
711
|
+
animation: wizard-check-pop 500ms cubic-bezier(0.175, 0.885, 0.32, 1.275) both;
|
|
712
|
+
}
|
|
713
|
+
.wizard-btn-primary {
|
|
714
|
+
transition: background 150ms ease, transform 100ms ease, box-shadow 150ms ease !important;
|
|
715
|
+
}
|
|
716
|
+
.wizard-btn-primary:hover {
|
|
717
|
+
transform: translateY(-1px);
|
|
718
|
+
box-shadow: 0 4px 16px oklch(from var(--color-primary) l c h / 0.35);
|
|
719
|
+
}
|
|
720
|
+
.wizard-btn-primary:active {
|
|
721
|
+
transform: translateY(0);
|
|
722
|
+
}
|
|
723
|
+
.wizard-btn-done {
|
|
724
|
+
transition: background 150ms ease, transform 100ms ease, box-shadow 150ms ease !important;
|
|
725
|
+
}
|
|
726
|
+
.wizard-btn-done:hover {
|
|
727
|
+
filter: brightness(1.08);
|
|
728
|
+
transform: translateY(-1px);
|
|
729
|
+
box-shadow: 0 4px 20px oklch(from var(--color-success) l c h / 0.4);
|
|
730
|
+
}
|
|
731
|
+
.wizard-btn-done:active {
|
|
732
|
+
transform: translateY(0);
|
|
733
|
+
}
|
|
734
|
+
@media (prefers-reduced-motion: reduce) {
|
|
735
|
+
.wizard-fade-in,
|
|
736
|
+
.wizard-slide-in-right,
|
|
737
|
+
.wizard-slide-in-left,
|
|
738
|
+
.wizard-slide-out-left,
|
|
739
|
+
.wizard-slide-out-right,
|
|
740
|
+
.wizard-check-pop {
|
|
741
|
+
animation: none !important;
|
|
742
|
+
opacity: 1 !important;
|
|
743
|
+
transform: none !important;
|
|
744
|
+
}
|
|
745
|
+
.wizard-btn-primary:hover,
|
|
746
|
+
.wizard-btn-done:hover {
|
|
747
|
+
transform: none !important;
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
`;
|