agent-state-machine 2.0.14 → 2.1.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/bin/cli.js +1 -1
- package/lib/index.js +33 -0
- package/lib/remote/client.js +7 -2
- package/lib/runtime/agent.js +102 -67
- package/lib/runtime/index.js +13 -0
- package/lib/runtime/interaction.js +304 -0
- package/lib/runtime/prompt.js +39 -12
- package/lib/runtime/runtime.js +11 -10
- package/package.json +2 -1
- package/templates/project-builder/README.md +119 -0
- package/templates/project-builder/agents/assumptions-clarifier.md +65 -0
- package/templates/project-builder/agents/code-reviewer.md +81 -0
- package/templates/project-builder/agents/code-writer.md +74 -0
- package/templates/project-builder/agents/requirements-clarifier.md +55 -0
- package/templates/project-builder/agents/response-interpreter.md +25 -0
- package/templates/project-builder/agents/roadmap-generator.md +73 -0
- package/templates/project-builder/agents/sanity-checker.md +45 -0
- package/templates/project-builder/agents/sanity-runner.js +161 -0
- package/templates/project-builder/agents/scope-clarifier.md +44 -0
- package/templates/project-builder/agents/security-clarifier.md +71 -0
- package/templates/project-builder/agents/security-reviewer.md +71 -0
- package/templates/project-builder/agents/task-planner.md +62 -0
- package/templates/project-builder/agents/test-planner.md +76 -0
- package/templates/project-builder/config.js +13 -0
- package/templates/project-builder/scripts/interaction-helpers.js +33 -0
- package/templates/project-builder/scripts/mac-notification.js +24 -0
- package/templates/project-builder/scripts/text-human.js +92 -0
- package/templates/project-builder/scripts/workflow-helpers.js +122 -0
- package/templates/project-builder/state/current.json +9 -0
- package/templates/project-builder/state/history.jsonl +0 -0
- package/templates/project-builder/steering/config.json +5 -0
- package/templates/project-builder/steering/global.md +19 -0
- package/templates/project-builder/workflow.js +554 -0
- package/templates/starter/README.md +118 -0
- package/templates/starter/agents/example.js +36 -0
- package/templates/starter/agents/yoda-greeter.md +12 -0
- package/templates/starter/agents/yoda-name-collector.md +12 -0
- package/templates/starter/config.js +12 -0
- package/templates/starter/interactions/.gitkeep +0 -0
- package/templates/starter/scripts/mac-notification.js +24 -0
- package/templates/starter/state/current.json +9 -0
- package/templates/starter/state/history.jsonl +0 -0
- package/templates/starter/steering/config.json +5 -0
- package/templates/starter/steering/global.md +19 -0
- package/templates/starter/workflow.js +52 -0
- package/vercel-server/api/session/[token].js +3 -3
- package/vercel-server/api/submit/[token].js +5 -3
- package/vercel-server/local-server.js +33 -6
- package/vercel-server/public/remote/index.html +17 -0
- package/vercel-server/ui/index.html +9 -1012
- package/vercel-server/ui/package-lock.json +2650 -0
- package/vercel-server/ui/package.json +25 -0
- package/vercel-server/ui/postcss.config.js +6 -0
- package/vercel-server/ui/src/App.jsx +236 -0
- package/vercel-server/ui/src/components/ChoiceInteraction.jsx +127 -0
- package/vercel-server/ui/src/components/ConfirmInteraction.jsx +51 -0
- package/vercel-server/ui/src/components/ContentCard.jsx +161 -0
- package/vercel-server/ui/src/components/CopyButton.jsx +27 -0
- package/vercel-server/ui/src/components/EventsLog.jsx +82 -0
- package/vercel-server/ui/src/components/Footer.jsx +66 -0
- package/vercel-server/ui/src/components/Header.jsx +38 -0
- package/vercel-server/ui/src/components/InteractionForm.jsx +42 -0
- package/vercel-server/ui/src/components/TextInteraction.jsx +72 -0
- package/vercel-server/ui/src/index.css +145 -0
- package/vercel-server/ui/src/main.jsx +8 -0
- package/vercel-server/ui/tailwind.config.js +19 -0
- package/vercel-server/ui/vite.config.js +11 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { Terminal, Send, Play, AlertCircle, CheckCircle2 } from "lucide-react";
|
|
2
|
+
|
|
3
|
+
const getEventIcon = (event) => {
|
|
4
|
+
if (!event) return <Terminal className="w-3.5 h-3.5 opacity-40" />;
|
|
5
|
+
const e = event.toUpperCase();
|
|
6
|
+
if (e.includes("STARTED") || e.includes("START")) return <Play className="w-3.5 h-3.5 text-blue-500" />;
|
|
7
|
+
if (e.includes("SUBMITTED") || e.includes("ANSWERED") || e.includes("SUCCESS")) return <CheckCircle2 className="w-3.5 h-3.5 text-green-500" />;
|
|
8
|
+
if (e.includes("REQUESTED") || e.includes("REQUEST")) return <Send className="w-3.5 h-3.5 text-accent" />;
|
|
9
|
+
if (e.includes("ERROR") || e.includes("FAILED")) return <AlertCircle className="w-3.5 h-3.5 text-red-500" />;
|
|
10
|
+
return <Terminal className="w-3.5 h-3.5 opacity-40" />;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const getEventSummary = (item) => {
|
|
14
|
+
const mainText = item.question || item.answer || item.prompt || item.error || item.status || item.message;
|
|
15
|
+
const subText = item.slug || (item.event && item.event.startsWith("WORKFLOW_") ? item.event.replace("WORKFLOW_", "") : null);
|
|
16
|
+
|
|
17
|
+
if (!mainText && !subText) return item.event || "Unknown Event";
|
|
18
|
+
|
|
19
|
+
return (
|
|
20
|
+
<div className="flex flex-col gap-0.5">
|
|
21
|
+
{subText && <span className="text-accent font-medium text-[10px] tracking-wider uppercase opacity-80">{subText}</span>}
|
|
22
|
+
{mainText && (
|
|
23
|
+
<span className="opacity-80 truncate max-w-md">
|
|
24
|
+
{typeof mainText === "string" ? mainText : JSON.stringify(mainText)}
|
|
25
|
+
</span>
|
|
26
|
+
)}
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export default function EventsLog({ history, onJump }) {
|
|
32
|
+
return (
|
|
33
|
+
<div className="w-full h-full flex flex-col overflow-hidden bg-bg">
|
|
34
|
+
<div className="flex-1 overflow-y-auto custom-scroll px-6 py-24">
|
|
35
|
+
<div className="max-w-4xl mx-auto space-y-1">
|
|
36
|
+
{history.length === 0 ? (
|
|
37
|
+
<div className="h-[40vh] flex flex-col items-center justify-center opacity-20 space-y-4">
|
|
38
|
+
<Terminal className="w-12 h-12" />
|
|
39
|
+
<div className="text-xs font-bold tracking-[0.4em] uppercase">No events yet</div>
|
|
40
|
+
</div>
|
|
41
|
+
) : (
|
|
42
|
+
history.map((item, idx) => {
|
|
43
|
+
const time = new Date(item.timestamp).toLocaleTimeString([], {
|
|
44
|
+
hour: "2-digit",
|
|
45
|
+
minute: "2-digit",
|
|
46
|
+
second: "2-digit",
|
|
47
|
+
hour12: false
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
return (
|
|
51
|
+
<button
|
|
52
|
+
key={idx}
|
|
53
|
+
onClick={() => onJump(idx)}
|
|
54
|
+
className="w-full text-left group flex items-start gap-6 p-4 rounded-2xl hover:bg-black/[0.03] dark:hover:bg-white/[0.03] transition-all border border-transparent hover:border-border"
|
|
55
|
+
>
|
|
56
|
+
<div className="flex-shrink-0 w-20 pt-1">
|
|
57
|
+
<span className="text-[10px] font-mono font-medium opacity-20 group-hover:opacity-40 transition-opacity">
|
|
58
|
+
{time}
|
|
59
|
+
</span>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div className="flex-shrink-0 pt-0.5">
|
|
63
|
+
{getEventIcon(item.event)}
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div className="flex-1 min-w-0 flex flex-col gap-1">
|
|
67
|
+
<div className="text-[10px] font-bold tracking-[0.2em] uppercase opacity-30 group-hover:opacity-50 transition-opacity">
|
|
68
|
+
{item.event?.replace(/_/g, " ") || "EVENT"}
|
|
69
|
+
</div>
|
|
70
|
+
<div className="text-sm leading-relaxed truncate group-hover:text-accent transition-colors">
|
|
71
|
+
{getEventSummary(item)}
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</button>
|
|
75
|
+
);
|
|
76
|
+
})
|
|
77
|
+
)}
|
|
78
|
+
</div>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useEffect, useState } from "react";
|
|
2
|
+
import { ChevronLeft, ChevronRight } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export default function Footer({ page, total, onNext, onPrev, onJump, hasNew, onJumpToLatest, className = "" }) {
|
|
5
|
+
const [inputValue, setInputValue] = useState(page + 1);
|
|
6
|
+
useEffect(() => setInputValue(page + 1), [page]);
|
|
7
|
+
|
|
8
|
+
const handleInputChange = (event) => {
|
|
9
|
+
const { value } = event.target;
|
|
10
|
+
setInputValue(value);
|
|
11
|
+
const num = parseInt(value, 10);
|
|
12
|
+
if (!Number.isNaN(num) && num >= 1 && num <= total) {
|
|
13
|
+
onJump(num - 1);
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<footer className={`nav-footer transition-opacity duration-300 ${className}`}>
|
|
19
|
+
<div className="footer-control">
|
|
20
|
+
<button
|
|
21
|
+
onClick={onPrev}
|
|
22
|
+
disabled={page === 0}
|
|
23
|
+
className="tooltip p-1 hover:text-accent disabled:opacity-0 transition-all pointer-events-auto"
|
|
24
|
+
data-tooltip="Previous"
|
|
25
|
+
aria-label="Previous event"
|
|
26
|
+
>
|
|
27
|
+
<ChevronLeft className="w-5 h-5" />
|
|
28
|
+
</button>
|
|
29
|
+
|
|
30
|
+
<div className="flex items-center gap-2 text-xs font-mono font-bold tracking-widest opacity-60">
|
|
31
|
+
<input
|
|
32
|
+
type="number"
|
|
33
|
+
value={inputValue}
|
|
34
|
+
onChange={handleInputChange}
|
|
35
|
+
className="jumper-input"
|
|
36
|
+
min="1"
|
|
37
|
+
max={total}
|
|
38
|
+
/>
|
|
39
|
+
<span className="opacity-20">/</span>
|
|
40
|
+
<span>{total}</span>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<button
|
|
44
|
+
onClick={onNext}
|
|
45
|
+
disabled={page === total - 1}
|
|
46
|
+
className="tooltip p-1 hover:text-accent disabled:opacity-0 transition-all pointer-events-auto"
|
|
47
|
+
data-tooltip="Next"
|
|
48
|
+
aria-label="Next event"
|
|
49
|
+
>
|
|
50
|
+
<ChevronRight className="w-5 h-5" />
|
|
51
|
+
</button>
|
|
52
|
+
|
|
53
|
+
{hasNew ? (
|
|
54
|
+
<button
|
|
55
|
+
onClick={onJumpToLatest}
|
|
56
|
+
className="tooltip px-3 py-1 rounded-full text-[10px] font-bold tracking-[0.2em] uppercase bg-accent text-white shadow-[0_10px_30px_rgba(0,122,255,0.25)] hover:scale-[1.02] transition-transform"
|
|
57
|
+
data-tooltip="Latest"
|
|
58
|
+
aria-label="Jump to latest event"
|
|
59
|
+
>
|
|
60
|
+
New
|
|
61
|
+
</button>
|
|
62
|
+
) : null}
|
|
63
|
+
</div>
|
|
64
|
+
</footer>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { Moon, Sun, LayoutList, Presentation } from "lucide-react";
|
|
2
|
+
import CopyButton from "./CopyButton.jsx";
|
|
3
|
+
|
|
4
|
+
export default function Header({ workflowName, status, theme, toggleTheme, viewMode, setViewMode, history }) {
|
|
5
|
+
return (
|
|
6
|
+
<header className="fixed top-0 inset-x-0 h-20 px-12 flex items-center justify-between z-50 bg-bg/80 backdrop-blur-3xl">
|
|
7
|
+
<div className="flex items-center gap-4">
|
|
8
|
+
<div className={`w-2 h-2 rounded-full ${status === "connected" ? "bg-green-500 shadow-[0_0_15px_rgba(34,197,94,0.6)]" : "bg-red-500"}`}></div>
|
|
9
|
+
<span className="font-bold text-[10px] tracking-[0.4em] uppercase opacity-30 truncate max-w-[300px]">{workflowName || "Workflow"}</span>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<div className="flex items-center gap-2">
|
|
13
|
+
<CopyButton text={history || []} label="Copy full history" disabled={!history || history.length === 0} />
|
|
14
|
+
<button
|
|
15
|
+
onClick={() => setViewMode(viewMode === "present" ? "log" : "present")}
|
|
16
|
+
className="tooltip w-10 h-10 flex items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
|
17
|
+
data-tooltip={viewMode === "present" ? "Log view" : "Present view"}
|
|
18
|
+
aria-label={viewMode === "present" ? "Switch to Log view" : "Switch to Presentation view"}
|
|
19
|
+
>
|
|
20
|
+
{viewMode === "present" ? (
|
|
21
|
+
<LayoutList className="w-5 h-5 opacity-40 hover:opacity-100" />
|
|
22
|
+
) : (
|
|
23
|
+
<Presentation className="w-5 h-5 opacity-40 hover:opacity-100" />
|
|
24
|
+
)}
|
|
25
|
+
</button>
|
|
26
|
+
|
|
27
|
+
<button
|
|
28
|
+
onClick={toggleTheme}
|
|
29
|
+
className="tooltip w-10 h-10 flex items-center justify-center rounded-full hover:bg-black/5 dark:hover:bg-white/10 transition-colors"
|
|
30
|
+
data-tooltip={theme === "dark" ? "Light theme" : "Dark theme"}
|
|
31
|
+
aria-label={theme === "dark" ? "Switch to Light theme" : "Switch to Dark theme"}
|
|
32
|
+
>
|
|
33
|
+
{theme === "dark" ? <Sun className="w-5 h-5 opacity-40 hover:opacity-100" /> : <Moon className="w-5 h-5 opacity-40 hover:opacity-100" />}
|
|
34
|
+
</button>
|
|
35
|
+
</div>
|
|
36
|
+
</header>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import ChoiceInteraction from "./ChoiceInteraction.jsx";
|
|
2
|
+
import ConfirmInteraction from "./ConfirmInteraction.jsx";
|
|
3
|
+
import TextInteraction from "./TextInteraction.jsx";
|
|
4
|
+
|
|
5
|
+
export default function InteractionForm({ interaction, onSubmit, disabled }) {
|
|
6
|
+
const type = interaction?.type || "text";
|
|
7
|
+
|
|
8
|
+
const handleResponse = (response) => {
|
|
9
|
+
onSubmit(interaction.slug, interaction.targetKey, response);
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
switch (type) {
|
|
13
|
+
case "choice":
|
|
14
|
+
return (
|
|
15
|
+
<ChoiceInteraction
|
|
16
|
+
key={interaction.slug}
|
|
17
|
+
interaction={interaction}
|
|
18
|
+
onSubmit={handleResponse}
|
|
19
|
+
disabled={disabled}
|
|
20
|
+
/>
|
|
21
|
+
);
|
|
22
|
+
case "confirm":
|
|
23
|
+
return (
|
|
24
|
+
<ConfirmInteraction
|
|
25
|
+
key={interaction.slug}
|
|
26
|
+
interaction={interaction}
|
|
27
|
+
onSubmit={handleResponse}
|
|
28
|
+
disabled={disabled}
|
|
29
|
+
/>
|
|
30
|
+
);
|
|
31
|
+
case "text":
|
|
32
|
+
default:
|
|
33
|
+
return (
|
|
34
|
+
<TextInteraction
|
|
35
|
+
key={interaction.slug}
|
|
36
|
+
interaction={interaction}
|
|
37
|
+
onSubmit={handleResponse}
|
|
38
|
+
disabled={disabled}
|
|
39
|
+
/>
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useMemo, useState } from "react";
|
|
2
|
+
import { Bot } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export default function TextInteraction({ interaction, onSubmit, disabled }) {
|
|
5
|
+
const { prompt, question, placeholder, validation } = interaction;
|
|
6
|
+
const [text, setText] = useState("");
|
|
7
|
+
const [error, setError] = useState(null);
|
|
8
|
+
|
|
9
|
+
const rules = useMemo(() => validation || {}, [validation]);
|
|
10
|
+
|
|
11
|
+
const validate = (value) => {
|
|
12
|
+
if (rules.minLength && value.length < rules.minLength) {
|
|
13
|
+
return `Minimum ${rules.minLength} characters required`;
|
|
14
|
+
}
|
|
15
|
+
if (rules.maxLength && value.length > rules.maxLength) {
|
|
16
|
+
return `Maximum ${rules.maxLength} characters allowed`;
|
|
17
|
+
}
|
|
18
|
+
if (rules.pattern && !new RegExp(rules.pattern).test(value)) {
|
|
19
|
+
return "Invalid format";
|
|
20
|
+
}
|
|
21
|
+
return null;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const handleSubmit = () => {
|
|
25
|
+
const trimmed = text.trim();
|
|
26
|
+
const err = validate(trimmed);
|
|
27
|
+
if (err) {
|
|
28
|
+
setError(err);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
onSubmit({ text: trimmed, raw: trimmed });
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return (
|
|
35
|
+
<div className="w-full h-full flex flex-col items-stretch overflow-hidden">
|
|
36
|
+
<div className="flex-1 overflow-y-auto custom-scroll px-6 py-12 space-y-8 flex flex-col items-center">
|
|
37
|
+
<div className="space-y-4 shrink-0">
|
|
38
|
+
<div className="w-16 h-16 rounded-3xl bg-accent text-white flex items-center justify-center mx-auto shadow-2xl shadow-accent/40">
|
|
39
|
+
<Bot className="w-8 h-8" />
|
|
40
|
+
</div>
|
|
41
|
+
<h3 className="text-4xl font-extrabold tracking-tight text-fg pt-4 text-center">Action required.</h3>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
<div className="w-full max-w-2xl space-y-4">
|
|
45
|
+
<div className="text-xl font-medium text-fg/70 text-center whitespace-pre-wrap">
|
|
46
|
+
{prompt || question || "Provide your response."}
|
|
47
|
+
</div>
|
|
48
|
+
<textarea
|
|
49
|
+
value={text}
|
|
50
|
+
onChange={(event) => { setText(event.target.value); setError(null); }}
|
|
51
|
+
disabled={disabled}
|
|
52
|
+
placeholder={placeholder || "Your response..."}
|
|
53
|
+
className={`w-full h-64 p-8 rounded-[32px] bg-black/[0.03] dark:bg-white/[0.03] border-2 ${
|
|
54
|
+
error ? "border-red-500" : "border-transparent"
|
|
55
|
+
} focus:ring-4 focus:ring-accent/10 focus:outline-none text-2xl font-medium transition-all text-center placeholder:opacity-20`}
|
|
56
|
+
/>
|
|
57
|
+
{error && <div className="text-red-500 text-center text-sm">{error}</div>}
|
|
58
|
+
</div>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div className="p-4 flex justify-center bg-gradient-to-t from-bg via-bg to-transparent shrink-0 border-t border-white/5">
|
|
62
|
+
<button
|
|
63
|
+
onClick={handleSubmit}
|
|
64
|
+
disabled={disabled || !text.trim()}
|
|
65
|
+
className="px-12 py-6 bg-fg text-bg rounded-full font-bold text-xl hover:scale-105 active:scale-95 transition-all disabled:opacity-30 shadow-2xl"
|
|
66
|
+
>
|
|
67
|
+
Submit Response
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
@tailwind base;
|
|
2
|
+
@tailwind components;
|
|
3
|
+
@tailwind utilities;
|
|
4
|
+
|
|
5
|
+
:root {
|
|
6
|
+
--bg: #ffffff;
|
|
7
|
+
--fg: #1d1d1f;
|
|
8
|
+
--subtle: #86868b;
|
|
9
|
+
--border: rgba(0, 0, 0, 0.06);
|
|
10
|
+
--accent: #007aff;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.dark {
|
|
14
|
+
--bg: #000000;
|
|
15
|
+
--fg: #f5f5f7;
|
|
16
|
+
--subtle: #86868b;
|
|
17
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
18
|
+
--accent: #0a84ff;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
body {
|
|
22
|
+
background-color: var(--bg);
|
|
23
|
+
color: var(--fg);
|
|
24
|
+
font-family: "Inter", sans-serif;
|
|
25
|
+
transition: background-color 0.4s ease, color 0.4s ease;
|
|
26
|
+
overflow: hidden;
|
|
27
|
+
margin: 0;
|
|
28
|
+
height: 100vh;
|
|
29
|
+
height: 100dvh;
|
|
30
|
+
width: 100vw;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.custom-scroll::-webkit-scrollbar {
|
|
34
|
+
width: 4px;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.custom-scroll::-webkit-scrollbar-track {
|
|
38
|
+
background: transparent;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
.custom-scroll::-webkit-scrollbar-thumb {
|
|
42
|
+
background: var(--subtle);
|
|
43
|
+
border-radius: 4px;
|
|
44
|
+
opacity: 0.2;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
.markdown-body pre {
|
|
48
|
+
background: rgba(127, 127, 127, 0.05);
|
|
49
|
+
padding: 1.5rem;
|
|
50
|
+
border-radius: 12px;
|
|
51
|
+
overflow-x: auto;
|
|
52
|
+
font-size: 0.85rem;
|
|
53
|
+
line-height: 1.6;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
.content-width {
|
|
57
|
+
width: 100%;
|
|
58
|
+
max-width: 900px;
|
|
59
|
+
margin: 0 auto;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.main-container {
|
|
63
|
+
height: 100vh;
|
|
64
|
+
width: 100vw;
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
.main-stage {
|
|
70
|
+
flex: 1;
|
|
71
|
+
position: relative;
|
|
72
|
+
z-index: 10;
|
|
73
|
+
padding-top: calc(80px + env(safe-area-inset-top));
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.jumper-input {
|
|
77
|
+
background: transparent;
|
|
78
|
+
border: none;
|
|
79
|
+
color: inherit;
|
|
80
|
+
font-family: inherit;
|
|
81
|
+
font-weight: 700;
|
|
82
|
+
text-align: center;
|
|
83
|
+
width: 2.2rem;
|
|
84
|
+
outline: none;
|
|
85
|
+
padding: 0;
|
|
86
|
+
margin: 0;
|
|
87
|
+
font-size: inherit;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.jumper-input::-webkit-inner-spin-button,
|
|
91
|
+
.jumper-input::-webkit-outer-spin-button {
|
|
92
|
+
-webkit-appearance: none;
|
|
93
|
+
margin: 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.nav-footer {
|
|
97
|
+
height: 80px;
|
|
98
|
+
display: flex;
|
|
99
|
+
align-items: center;
|
|
100
|
+
justify-content: center;
|
|
101
|
+
z-index: 50;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.footer-control {
|
|
105
|
+
display: flex;
|
|
106
|
+
align-items: center;
|
|
107
|
+
gap: 1.5rem;
|
|
108
|
+
padding: 0.5rem 1.2rem;
|
|
109
|
+
background: rgba(127, 127, 127, 0.05);
|
|
110
|
+
backdrop-filter: blur(20px);
|
|
111
|
+
border-radius: 99px;
|
|
112
|
+
border: 1px solid var(--border);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
.tooltip {
|
|
116
|
+
position: relative;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.tooltip::after {
|
|
120
|
+
content: attr(data-tooltip);
|
|
121
|
+
position: absolute;
|
|
122
|
+
bottom: -2.2rem;
|
|
123
|
+
left: 50%;
|
|
124
|
+
transform: translateX(-50%) translateY(4px);
|
|
125
|
+
background: var(--fg);
|
|
126
|
+
color: var(--bg);
|
|
127
|
+
padding: 0.35rem 0.55rem;
|
|
128
|
+
border-radius: 999px;
|
|
129
|
+
font-size: 10px;
|
|
130
|
+
font-weight: 700;
|
|
131
|
+
letter-spacing: 0.18em;
|
|
132
|
+
text-transform: uppercase;
|
|
133
|
+
white-space: nowrap;
|
|
134
|
+
opacity: 0;
|
|
135
|
+
pointer-events: none;
|
|
136
|
+
transition: opacity 0.12s ease, transform 0.12s ease;
|
|
137
|
+
box-shadow: 0 12px 30px rgba(0, 0, 0, 0.15);
|
|
138
|
+
z-index: 40;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
.tooltip:hover::after,
|
|
142
|
+
.tooltip:focus-visible::after {
|
|
143
|
+
opacity: 1;
|
|
144
|
+
transform: translateX(-50%) translateY(0);
|
|
145
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { createRoot } from "react-dom/client";
|
|
2
|
+
import App from "./App.jsx";
|
|
3
|
+
import "./index.css";
|
|
4
|
+
import "@fontsource-variable/inter/index.css";
|
|
5
|
+
import "@fontsource-variable/jetbrains-mono/index.css";
|
|
6
|
+
|
|
7
|
+
const root = createRoot(document.getElementById("root"));
|
|
8
|
+
root.render(<App />);
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
darkMode: "class",
|
|
3
|
+
content: ["./index.html", "./src/**/*.{js,jsx,ts,tsx}"],
|
|
4
|
+
theme: {
|
|
5
|
+
extend: {
|
|
6
|
+
fontFamily: {
|
|
7
|
+
sans: ["Inter", "system-ui", "-apple-system", "BlinkMacSystemFont", "Segoe UI", "Roboto", "sans-serif"],
|
|
8
|
+
mono: ["JetBrains Mono", "ui-monospace", "SFMono-Regular", "Menlo", "Monaco", "Consolas", "monospace"]
|
|
9
|
+
},
|
|
10
|
+
colors: {
|
|
11
|
+
bg: "var(--bg)",
|
|
12
|
+
fg: "var(--fg)",
|
|
13
|
+
subtle: "var(--subtle)",
|
|
14
|
+
border: "var(--border)",
|
|
15
|
+
accent: "var(--accent)"
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|