create-cajoa 0.3.1 → 0.4.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/package.json +1 -1
- package/template/index.html +17 -1
- package/template/public/favicon.svg +10 -0
- package/template/src/App.tsx +135 -3
- package/template/src/components/CopyCommand.tsx +24 -0
- package/template/src/components/Counter.tsx +36 -5
- package/template/src/components/Logo.tsx +20 -0
- package/template/src/components/ThemeToggle.tsx +83 -0
- package/template/src/main.tsx +1 -0
- package/template/src/style.css +483 -0
package/package.json
CHANGED
package/template/index.html
CHANGED
|
@@ -1,9 +1,25 @@
|
|
|
1
1
|
<!doctype html>
|
|
2
|
-
<html lang="en">
|
|
2
|
+
<html lang="en" data-theme="dark">
|
|
3
3
|
<head>
|
|
4
4
|
<meta charset="UTF-8" />
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<meta name="description" content="A Cajoa app" />
|
|
7
|
+
<meta name="theme-color" content="#0a0b10" />
|
|
8
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
6
9
|
<title>My Cajoa App</title>
|
|
10
|
+
<script>
|
|
11
|
+
// Apply the saved (or system) theme before the first paint, to avoid a flash
|
|
12
|
+
try {
|
|
13
|
+
var saved = localStorage.getItem("theme");
|
|
14
|
+
var theme =
|
|
15
|
+
saved === "light" || saved === "dark"
|
|
16
|
+
? saved
|
|
17
|
+
: matchMedia("(prefers-color-scheme: light)").matches
|
|
18
|
+
? "light"
|
|
19
|
+
: "dark";
|
|
20
|
+
document.documentElement.dataset.theme = theme;
|
|
21
|
+
} catch (e) {}
|
|
22
|
+
</script>
|
|
7
23
|
</head>
|
|
8
24
|
<body>
|
|
9
25
|
<div id="app"></div>
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
|
2
|
+
<defs>
|
|
3
|
+
<linearGradient id="g" x1="0" y1="0" x2="1" y2="1">
|
|
4
|
+
<stop offset="0" stop-color="#ffd84d" />
|
|
5
|
+
<stop offset="0.55" stop-color="#ff6ac1" />
|
|
6
|
+
<stop offset="1" stop-color="#7c5cff" />
|
|
7
|
+
</linearGradient>
|
|
8
|
+
</defs>
|
|
9
|
+
<path d="M38 4 14 35h15l-4 25 25-34H35z" fill="url(#g)" />
|
|
10
|
+
</svg>
|
package/template/src/App.tsx
CHANGED
|
@@ -1,10 +1,142 @@
|
|
|
1
|
+
import { For } from "cajoa";
|
|
1
2
|
import { Counter } from "./components/Counter";
|
|
3
|
+
import { CopyCommand } from "./components/CopyCommand";
|
|
4
|
+
import { Logo } from "./components/Logo";
|
|
5
|
+
import { ThemeToggle } from "./components/ThemeToggle";
|
|
2
6
|
|
|
7
|
+
interface Feature {
|
|
8
|
+
title: string;
|
|
9
|
+
text: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const features: Feature[] = [
|
|
13
|
+
{
|
|
14
|
+
title: "Signals, not re-renders",
|
|
15
|
+
text: "Components run once. Only the exact text or attribute that depends on a signal updates.",
|
|
16
|
+
},
|
|
17
|
+
{
|
|
18
|
+
title: "Plain JSX",
|
|
19
|
+
text: "Write calls like count() * 2 right in your markup. The Vite plugin makes them reactive.",
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
title: "Tiny and readable",
|
|
23
|
+
text: "No virtual DOM and no hooks rules. The whole core is small enough to read in one sitting.",
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
title: "Renderer-agnostic",
|
|
27
|
+
text: "The core knows nothing about the DOM, so other platforms can plug in their own renderer.",
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
|
|
31
|
+
// This page is just a starting point. Replace it with your own app.
|
|
3
32
|
export function App() {
|
|
4
33
|
return (
|
|
5
|
-
<div>
|
|
6
|
-
<
|
|
7
|
-
|
|
34
|
+
<div class="page">
|
|
35
|
+
<header class="container nav">
|
|
36
|
+
<a class="brand" href="/" aria-label="Cajoa home">
|
|
37
|
+
<Logo />
|
|
38
|
+
<span>Cajoa</span>
|
|
39
|
+
</a>
|
|
40
|
+
<nav class="nav-links" aria-label="Main">
|
|
41
|
+
<a
|
|
42
|
+
href="https://github.com/CajoaTS/Cajoa"
|
|
43
|
+
target="_blank"
|
|
44
|
+
rel="noreferrer"
|
|
45
|
+
>
|
|
46
|
+
GitHub
|
|
47
|
+
</a>
|
|
48
|
+
<a
|
|
49
|
+
href="https://www.npmjs.com/package/cajoa"
|
|
50
|
+
target="_blank"
|
|
51
|
+
rel="noreferrer"
|
|
52
|
+
>
|
|
53
|
+
npm
|
|
54
|
+
</a>
|
|
55
|
+
<ThemeToggle />
|
|
56
|
+
</nav>
|
|
57
|
+
</header>
|
|
58
|
+
|
|
59
|
+
<main class="container">
|
|
60
|
+
<section class="hero">
|
|
61
|
+
<span class="badge">Signals · JSX · Vite</span>
|
|
62
|
+
<h1>
|
|
63
|
+
Build interfaces with <span class="gradient-text">signals</span>,
|
|
64
|
+
not re-renders.
|
|
65
|
+
</h1>
|
|
66
|
+
<p class="lead">
|
|
67
|
+
Cajoa is a small, beginner-friendly framework. Components run once,
|
|
68
|
+
and only what changed updates.
|
|
69
|
+
</p>
|
|
70
|
+
<div class="actions">
|
|
71
|
+
<a class="btn btn-primary" href="#start">
|
|
72
|
+
Get started
|
|
73
|
+
</a>
|
|
74
|
+
<a
|
|
75
|
+
class="btn btn-ghost"
|
|
76
|
+
href="https://github.com/CajoaTS/Cajoa"
|
|
77
|
+
target="_blank"
|
|
78
|
+
rel="noreferrer"
|
|
79
|
+
>
|
|
80
|
+
View on GitHub
|
|
81
|
+
</a>
|
|
82
|
+
</div>
|
|
83
|
+
</section>
|
|
84
|
+
|
|
85
|
+
<section id="demo" aria-label="Live demo">
|
|
86
|
+
<Counter />
|
|
87
|
+
</section>
|
|
88
|
+
|
|
89
|
+
<section class="features" aria-label="Features">
|
|
90
|
+
<For each={features}>
|
|
91
|
+
{(feature: Feature, i: number) => {
|
|
92
|
+
const num = String(i + 1).padStart(2, "0");
|
|
93
|
+
return (
|
|
94
|
+
<article class="feature">
|
|
95
|
+
<span class="feature-num">{num}</span>
|
|
96
|
+
<h3>{feature.title}</h3>
|
|
97
|
+
<p>{feature.text}</p>
|
|
98
|
+
</article>
|
|
99
|
+
);
|
|
100
|
+
}}
|
|
101
|
+
</For>
|
|
102
|
+
</section>
|
|
103
|
+
|
|
104
|
+
<section id="start" class="start">
|
|
105
|
+
<h2>Get started</h2>
|
|
106
|
+
<ol class="steps">
|
|
107
|
+
<li>
|
|
108
|
+
<p>
|
|
109
|
+
<strong>Make it yours.</strong> Edit <code>src/App.tsx</code>{" "}
|
|
110
|
+
and save. Changes appear instantly.
|
|
111
|
+
</p>
|
|
112
|
+
</li>
|
|
113
|
+
<li>
|
|
114
|
+
<p>
|
|
115
|
+
<strong>Play with signals.</strong> Open{" "}
|
|
116
|
+
<code>src/components/Counter.tsx</code> and change what it does.
|
|
117
|
+
</p>
|
|
118
|
+
</li>
|
|
119
|
+
<li>
|
|
120
|
+
<p>
|
|
121
|
+
<strong>Ship it.</strong> Build your app for production:
|
|
122
|
+
</p>
|
|
123
|
+
<CopyCommand command="npm run build" />
|
|
124
|
+
</li>
|
|
125
|
+
</ol>
|
|
126
|
+
</section>
|
|
127
|
+
</main>
|
|
128
|
+
|
|
129
|
+
<footer class="container footer">
|
|
130
|
+
Built with{" "}
|
|
131
|
+
<a
|
|
132
|
+
href="https://github.com/CajoaTS/Cajoa"
|
|
133
|
+
target="_blank"
|
|
134
|
+
rel="noreferrer"
|
|
135
|
+
>
|
|
136
|
+
Cajoa
|
|
137
|
+
</a>
|
|
138
|
+
. Replace this page with your own.
|
|
139
|
+
</footer>
|
|
8
140
|
</div>
|
|
9
141
|
);
|
|
10
142
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { createSignal } from "cajoa";
|
|
2
|
+
|
|
3
|
+
export function CopyCommand(props: { command: string }) {
|
|
4
|
+
const [copied, setCopied] = createSignal(false);
|
|
5
|
+
|
|
6
|
+
const copy = async () => {
|
|
7
|
+
try {
|
|
8
|
+
await navigator.clipboard.writeText(props.command);
|
|
9
|
+
setCopied(true);
|
|
10
|
+
setTimeout(() => setCopied(false), 1600);
|
|
11
|
+
} catch {
|
|
12
|
+
// clipboard is not available
|
|
13
|
+
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div class="code">
|
|
18
|
+
<code>$ {props.command}</code>
|
|
19
|
+
<button class="copy" onClick={copy} aria-live="polite">
|
|
20
|
+
{copied() ? "Copied!" : "Copy"}
|
|
21
|
+
</button>
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
@@ -4,11 +4,42 @@ export function Counter() {
|
|
|
4
4
|
const [count, setCount] = createSignal(0);
|
|
5
5
|
|
|
6
6
|
return (
|
|
7
|
-
<div>
|
|
8
|
-
<
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
<div class="demo-card">
|
|
8
|
+
<div class="demo-head">
|
|
9
|
+
<span class="demo-label">Live demo</span>
|
|
10
|
+
<span class="demo-hint">Every number below is a signal.</span>
|
|
11
|
+
</div>
|
|
12
|
+
|
|
13
|
+
<div class="demo-value" aria-live="polite">
|
|
14
|
+
{count()}
|
|
15
|
+
</div>
|
|
16
|
+
|
|
17
|
+
<div class="meter" aria-hidden="true">
|
|
18
|
+
<div
|
|
19
|
+
class="meter-fill"
|
|
20
|
+
style={{ width: `${Math.min(count(), 10) * 10}%` }}
|
|
21
|
+
/>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<p class="demo-meta">
|
|
25
|
+
double: <strong>{count() * 2}</strong> ·{" "}
|
|
26
|
+
{count() % 2 === 0 ? "even" : "odd"}
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<div class="demo-actions">
|
|
30
|
+
<button
|
|
31
|
+
class="btn btn-ghost"
|
|
32
|
+
onClick={() => setCount((c) => Math.max(0, c - 1))}
|
|
33
|
+
>
|
|
34
|
+
−
|
|
35
|
+
</button>
|
|
36
|
+
<button class="btn btn-primary" onClick={() => setCount((c) => c + 1)}>
|
|
37
|
+
+1
|
|
38
|
+
</button>
|
|
39
|
+
<button class="btn btn-ghost" onClick={() => setCount(0)}>
|
|
40
|
+
Reset
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
12
43
|
</div>
|
|
13
44
|
);
|
|
14
45
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export function Logo() {
|
|
2
|
+
return (
|
|
3
|
+
<svg
|
|
4
|
+
class="logo"
|
|
5
|
+
viewBox="0 0 64 64"
|
|
6
|
+
width="32"
|
|
7
|
+
height="32"
|
|
8
|
+
aria-hidden="true"
|
|
9
|
+
>
|
|
10
|
+
<defs>
|
|
11
|
+
<linearGradient id="cajoa-bolt" x1="0" y1="0" x2="1" y2="1">
|
|
12
|
+
<stop offset="0" stop-color="#ffd84d" />
|
|
13
|
+
<stop offset="0.55" stop-color="#ff6ac1" />
|
|
14
|
+
<stop offset="1" stop-color="#7c5cff" />
|
|
15
|
+
</linearGradient>
|
|
16
|
+
</defs>
|
|
17
|
+
<path d="M38 4 14 35h15l-4 25 25-34H35z" fill="url(#cajoa-bolt)" />
|
|
18
|
+
</svg>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { createEffect, createSignal, Show } from "cajoa";
|
|
2
|
+
|
|
3
|
+
type Theme = "dark" | "light";
|
|
4
|
+
|
|
5
|
+
function initialTheme(): Theme {
|
|
6
|
+
try {
|
|
7
|
+
const saved = localStorage.getItem("theme");
|
|
8
|
+
if (saved === "dark" || saved === "light") return saved;
|
|
9
|
+
} catch {
|
|
10
|
+
// storage is not available
|
|
11
|
+
}
|
|
12
|
+
return window.matchMedia("(prefers-color-scheme: light)").matches
|
|
13
|
+
? "light"
|
|
14
|
+
: "dark";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function SunIcon() {
|
|
18
|
+
return (
|
|
19
|
+
<svg
|
|
20
|
+
viewBox="0 0 24 24"
|
|
21
|
+
width="18"
|
|
22
|
+
height="18"
|
|
23
|
+
fill="none"
|
|
24
|
+
stroke="currentColor"
|
|
25
|
+
stroke-width="2"
|
|
26
|
+
stroke-linecap="round"
|
|
27
|
+
stroke-linejoin="round"
|
|
28
|
+
aria-hidden="true"
|
|
29
|
+
>
|
|
30
|
+
<circle cx="12" cy="12" r="4" />
|
|
31
|
+
<path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4" />
|
|
32
|
+
</svg>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function MoonIcon() {
|
|
37
|
+
return (
|
|
38
|
+
<svg
|
|
39
|
+
viewBox="0 0 24 24"
|
|
40
|
+
width="18"
|
|
41
|
+
height="18"
|
|
42
|
+
fill="none"
|
|
43
|
+
stroke="currentColor"
|
|
44
|
+
stroke-width="2"
|
|
45
|
+
stroke-linecap="round"
|
|
46
|
+
stroke-linejoin="round"
|
|
47
|
+
aria-hidden="true"
|
|
48
|
+
>
|
|
49
|
+
<path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z" />
|
|
50
|
+
</svg>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function ThemeToggle() {
|
|
55
|
+
const [theme, setTheme] = createSignal<Theme>(initialTheme());
|
|
56
|
+
|
|
57
|
+
createEffect(() => {
|
|
58
|
+
document.documentElement.dataset.theme = theme();
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const toggle = () => {
|
|
62
|
+
const next: Theme = theme() === "dark" ? "light" : "dark";
|
|
63
|
+
setTheme(next);
|
|
64
|
+
try {
|
|
65
|
+
localStorage.setItem("theme", next);
|
|
66
|
+
} catch {
|
|
67
|
+
// storage is not available
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
return (
|
|
72
|
+
<button
|
|
73
|
+
class="icon-btn"
|
|
74
|
+
onClick={toggle}
|
|
75
|
+
aria-label="Toggle color theme"
|
|
76
|
+
title="Toggle color theme"
|
|
77
|
+
>
|
|
78
|
+
<Show when={theme() === "dark"} fallback={<MoonIcon />}>
|
|
79
|
+
<SunIcon />
|
|
80
|
+
</Show>
|
|
81
|
+
</button>
|
|
82
|
+
);
|
|
83
|
+
}
|
package/template/src/main.tsx
CHANGED
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
:root {
|
|
2
|
+
--bg: #0a0b10;
|
|
3
|
+
--surface: rgba(255, 255, 255, 0.045);
|
|
4
|
+
--border: rgba(255, 255, 255, 0.1);
|
|
5
|
+
--text: #eef0f6;
|
|
6
|
+
--muted: #9aa2b8;
|
|
7
|
+
--a1: #ffd84d;
|
|
8
|
+
--a2: #ff6ac1;
|
|
9
|
+
--a3: #7c5cff;
|
|
10
|
+
--gradient: linear-gradient(115deg, var(--a1), var(--a2) 55%, var(--a3));
|
|
11
|
+
--radius: 20px;
|
|
12
|
+
--glow: 0 24px 70px -28px rgba(124, 92, 255, 0.6);
|
|
13
|
+
color-scheme: dark;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
:root[data-theme="light"] {
|
|
17
|
+
--bg: #fbfaf6;
|
|
18
|
+
--surface: rgba(18, 20, 28, 0.04);
|
|
19
|
+
--border: rgba(18, 20, 28, 0.1);
|
|
20
|
+
--text: #12141c;
|
|
21
|
+
--muted: #5b6377;
|
|
22
|
+
--a1: #e59a00;
|
|
23
|
+
--glow: 0 24px 60px -30px rgba(124, 92, 255, 0.35);
|
|
24
|
+
color-scheme: light;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
* {
|
|
28
|
+
box-sizing: border-box;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
html {
|
|
32
|
+
scroll-behavior: smooth;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
body {
|
|
36
|
+
margin: 0;
|
|
37
|
+
min-height: 100vh;
|
|
38
|
+
font-family:
|
|
39
|
+
ui-sans-serif,
|
|
40
|
+
system-ui,
|
|
41
|
+
-apple-system,
|
|
42
|
+
"Segoe UI",
|
|
43
|
+
Roboto,
|
|
44
|
+
"Helvetica Neue",
|
|
45
|
+
sans-serif;
|
|
46
|
+
background: var(--bg);
|
|
47
|
+
color: var(--text);
|
|
48
|
+
line-height: 1.6;
|
|
49
|
+
-webkit-font-smoothing: antialiased;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/* Soft glows behind the page */
|
|
53
|
+
body::before {
|
|
54
|
+
content: "";
|
|
55
|
+
position: fixed;
|
|
56
|
+
inset: 0;
|
|
57
|
+
z-index: -1;
|
|
58
|
+
pointer-events: none;
|
|
59
|
+
background:
|
|
60
|
+
radial-gradient(
|
|
61
|
+
60rem 30rem at 15% -10%,
|
|
62
|
+
rgba(124, 92, 255, 0.22),
|
|
63
|
+
transparent 60%
|
|
64
|
+
),
|
|
65
|
+
radial-gradient(
|
|
66
|
+
50rem 28rem at 90% 0%,
|
|
67
|
+
rgba(255, 106, 193, 0.16),
|
|
68
|
+
transparent 60%
|
|
69
|
+
),
|
|
70
|
+
radial-gradient(
|
|
71
|
+
40rem 24rem at 50% 110%,
|
|
72
|
+
rgba(255, 216, 77, 0.1),
|
|
73
|
+
transparent 60%
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
a {
|
|
78
|
+
color: inherit;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
code,
|
|
82
|
+
.code {
|
|
83
|
+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
p code {
|
|
87
|
+
padding: 0.1rem 0.4rem;
|
|
88
|
+
border: 1px solid var(--border);
|
|
89
|
+
border-radius: 6px;
|
|
90
|
+
background: var(--surface);
|
|
91
|
+
font-size: 0.9em;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
:focus-visible {
|
|
95
|
+
outline: 2px solid var(--a3);
|
|
96
|
+
outline-offset: 3px;
|
|
97
|
+
border-radius: 8px;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.container {
|
|
101
|
+
width: min(1080px, calc(100% - 2.5rem));
|
|
102
|
+
margin-inline: auto;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/* Navigation */
|
|
106
|
+
.nav {
|
|
107
|
+
display: flex;
|
|
108
|
+
align-items: center;
|
|
109
|
+
justify-content: space-between;
|
|
110
|
+
padding: 1.25rem 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.brand {
|
|
114
|
+
display: inline-flex;
|
|
115
|
+
align-items: center;
|
|
116
|
+
gap: 0.6rem;
|
|
117
|
+
font-size: 1.15rem;
|
|
118
|
+
font-weight: 700;
|
|
119
|
+
letter-spacing: -0.02em;
|
|
120
|
+
text-decoration: none;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
.logo {
|
|
124
|
+
filter: drop-shadow(0 6px 14px rgba(124, 92, 255, 0.5));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
.nav-links {
|
|
128
|
+
display: flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 1.25rem;
|
|
131
|
+
font-size: 0.95rem;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
.nav-links a {
|
|
135
|
+
color: var(--muted);
|
|
136
|
+
text-decoration: none;
|
|
137
|
+
transition: color 0.2s;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.nav-links a:hover {
|
|
141
|
+
color: var(--text);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.icon-btn {
|
|
145
|
+
display: grid;
|
|
146
|
+
place-items: center;
|
|
147
|
+
width: 38px;
|
|
148
|
+
height: 38px;
|
|
149
|
+
border: 1px solid var(--border);
|
|
150
|
+
border-radius: 12px;
|
|
151
|
+
background: var(--surface);
|
|
152
|
+
color: var(--text);
|
|
153
|
+
cursor: pointer;
|
|
154
|
+
transition:
|
|
155
|
+
transform 0.2s,
|
|
156
|
+
border-color 0.2s;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
.icon-btn:hover {
|
|
160
|
+
transform: translateY(-1px);
|
|
161
|
+
border-color: var(--a3);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/* Hero */
|
|
165
|
+
.hero {
|
|
166
|
+
padding: clamp(3rem, 9vw, 6.5rem) 0 2.5rem;
|
|
167
|
+
text-align: center;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.badge {
|
|
171
|
+
display: inline-block;
|
|
172
|
+
padding: 0.35rem 0.9rem;
|
|
173
|
+
border: 1px solid var(--border);
|
|
174
|
+
border-radius: 999px;
|
|
175
|
+
background: var(--surface);
|
|
176
|
+
color: var(--muted);
|
|
177
|
+
font-size: 0.85rem;
|
|
178
|
+
letter-spacing: 0.02em;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
.hero h1 {
|
|
182
|
+
max-width: 18ch;
|
|
183
|
+
margin: 1.4rem auto 0;
|
|
184
|
+
font-size: clamp(2.6rem, 7vw, 4.8rem);
|
|
185
|
+
line-height: 1.05;
|
|
186
|
+
letter-spacing: -0.04em;
|
|
187
|
+
text-wrap: balance;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.gradient-text {
|
|
191
|
+
background: var(--gradient);
|
|
192
|
+
-webkit-background-clip: text;
|
|
193
|
+
background-clip: text;
|
|
194
|
+
color: transparent;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
.lead {
|
|
198
|
+
max-width: 52ch;
|
|
199
|
+
margin: 1.4rem auto 0;
|
|
200
|
+
color: var(--muted);
|
|
201
|
+
font-size: clamp(1.05rem, 2vw, 1.25rem);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.actions {
|
|
205
|
+
display: flex;
|
|
206
|
+
flex-wrap: wrap;
|
|
207
|
+
justify-content: center;
|
|
208
|
+
gap: 0.8rem;
|
|
209
|
+
margin-top: 2rem;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/* Buttons */
|
|
213
|
+
.btn {
|
|
214
|
+
display: inline-flex;
|
|
215
|
+
align-items: center;
|
|
216
|
+
justify-content: center;
|
|
217
|
+
padding: 0.75rem 1.4rem;
|
|
218
|
+
border: 1px solid var(--border);
|
|
219
|
+
border-radius: 14px;
|
|
220
|
+
font: inherit;
|
|
221
|
+
font-weight: 600;
|
|
222
|
+
text-decoration: none;
|
|
223
|
+
cursor: pointer;
|
|
224
|
+
transition:
|
|
225
|
+
transform 0.2s,
|
|
226
|
+
box-shadow 0.2s,
|
|
227
|
+
border-color 0.2s;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
.btn:hover {
|
|
231
|
+
transform: translateY(-2px);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
.btn:active {
|
|
235
|
+
transform: translateY(0);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
.btn-primary {
|
|
239
|
+
border-color: transparent;
|
|
240
|
+
background: var(--gradient);
|
|
241
|
+
color: #14110a;
|
|
242
|
+
box-shadow: var(--glow);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
.btn-ghost {
|
|
246
|
+
background: var(--surface);
|
|
247
|
+
color: var(--text);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
.btn-ghost:hover {
|
|
251
|
+
border-color: var(--a3);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/* Live demo */
|
|
255
|
+
.demo-card {
|
|
256
|
+
max-width: 440px;
|
|
257
|
+
margin: 1rem auto 0;
|
|
258
|
+
padding: 1.6rem;
|
|
259
|
+
border: 1px solid var(--border);
|
|
260
|
+
border-radius: var(--radius);
|
|
261
|
+
background: var(--surface);
|
|
262
|
+
backdrop-filter: blur(14px);
|
|
263
|
+
box-shadow: var(--glow);
|
|
264
|
+
text-align: center;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
.demo-head {
|
|
268
|
+
display: flex;
|
|
269
|
+
flex-direction: column;
|
|
270
|
+
gap: 0.15rem;
|
|
271
|
+
margin-bottom: 0.6rem;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
.demo-label {
|
|
275
|
+
color: var(--a2);
|
|
276
|
+
font-size: 0.75rem;
|
|
277
|
+
font-weight: 700;
|
|
278
|
+
letter-spacing: 0.08em;
|
|
279
|
+
text-transform: uppercase;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
.demo-hint {
|
|
283
|
+
color: var(--muted);
|
|
284
|
+
font-size: 0.9rem;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
.demo-value {
|
|
288
|
+
background: var(--gradient);
|
|
289
|
+
-webkit-background-clip: text;
|
|
290
|
+
background-clip: text;
|
|
291
|
+
color: transparent;
|
|
292
|
+
font-size: 4.5rem;
|
|
293
|
+
font-weight: 800;
|
|
294
|
+
font-variant-numeric: tabular-nums;
|
|
295
|
+
letter-spacing: -0.04em;
|
|
296
|
+
line-height: 1.1;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
.meter {
|
|
300
|
+
height: 8px;
|
|
301
|
+
margin: 0.6rem 0 1rem;
|
|
302
|
+
overflow: hidden;
|
|
303
|
+
border-radius: 999px;
|
|
304
|
+
background: var(--border);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
.meter-fill {
|
|
308
|
+
height: 100%;
|
|
309
|
+
border-radius: inherit;
|
|
310
|
+
background: var(--gradient);
|
|
311
|
+
transition: width 0.35s cubic-bezier(0.22, 1, 0.36, 1);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
.demo-meta {
|
|
315
|
+
margin: 0 0 1.2rem;
|
|
316
|
+
color: var(--muted);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
.demo-meta strong {
|
|
320
|
+
color: var(--text);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
.demo-actions {
|
|
324
|
+
display: flex;
|
|
325
|
+
justify-content: center;
|
|
326
|
+
gap: 0.6rem;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/* Features */
|
|
330
|
+
.features {
|
|
331
|
+
display: grid;
|
|
332
|
+
grid-template-columns: repeat(auto-fit, minmax(230px, 1fr));
|
|
333
|
+
gap: 1rem;
|
|
334
|
+
padding: 4rem 0 1rem;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
.feature {
|
|
338
|
+
padding: 1.4rem;
|
|
339
|
+
border: 1px solid var(--border);
|
|
340
|
+
border-radius: var(--radius);
|
|
341
|
+
background: var(--surface);
|
|
342
|
+
transition:
|
|
343
|
+
transform 0.25s,
|
|
344
|
+
border-color 0.25s;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.feature:hover {
|
|
348
|
+
transform: translateY(-4px);
|
|
349
|
+
border-color: var(--a3);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.feature-num {
|
|
353
|
+
background: var(--gradient);
|
|
354
|
+
-webkit-background-clip: text;
|
|
355
|
+
background-clip: text;
|
|
356
|
+
color: transparent;
|
|
357
|
+
font-size: 0.8rem;
|
|
358
|
+
font-weight: 700;
|
|
359
|
+
letter-spacing: 0.1em;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
.feature h3 {
|
|
363
|
+
margin: 0.5rem 0 0.4rem;
|
|
364
|
+
font-size: 1.1rem;
|
|
365
|
+
letter-spacing: -0.01em;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
.feature p {
|
|
369
|
+
margin: 0;
|
|
370
|
+
color: var(--muted);
|
|
371
|
+
font-size: 0.95rem;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/* Get started */
|
|
375
|
+
.start {
|
|
376
|
+
padding: 4rem 0 3rem;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
.start h2 {
|
|
380
|
+
margin: 0;
|
|
381
|
+
font-size: clamp(1.7rem, 4vw, 2.4rem);
|
|
382
|
+
letter-spacing: -0.03em;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.steps {
|
|
386
|
+
display: grid;
|
|
387
|
+
gap: 1rem;
|
|
388
|
+
margin: 1.6rem 0 0;
|
|
389
|
+
padding: 0;
|
|
390
|
+
list-style: none;
|
|
391
|
+
counter-reset: step;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
.steps li {
|
|
395
|
+
position: relative;
|
|
396
|
+
padding: 1.1rem 1.2rem 1.1rem 4rem;
|
|
397
|
+
border: 1px solid var(--border);
|
|
398
|
+
border-radius: var(--radius);
|
|
399
|
+
background: var(--surface);
|
|
400
|
+
counter-increment: step;
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
.steps li::before {
|
|
404
|
+
content: counter(step);
|
|
405
|
+
position: absolute;
|
|
406
|
+
top: 1.05rem;
|
|
407
|
+
left: 1.2rem;
|
|
408
|
+
display: grid;
|
|
409
|
+
place-items: center;
|
|
410
|
+
width: 2rem;
|
|
411
|
+
height: 2rem;
|
|
412
|
+
border-radius: 10px;
|
|
413
|
+
background: var(--gradient);
|
|
414
|
+
color: #14110a;
|
|
415
|
+
font-weight: 800;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
.steps p {
|
|
419
|
+
margin: 0;
|
|
420
|
+
color: var(--muted);
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
.steps strong {
|
|
424
|
+
color: var(--text);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
.code {
|
|
428
|
+
display: flex;
|
|
429
|
+
align-items: center;
|
|
430
|
+
justify-content: space-between;
|
|
431
|
+
gap: 1rem;
|
|
432
|
+
margin-top: 0.7rem;
|
|
433
|
+
padding: 0.7rem 0.8rem 0.7rem 1rem;
|
|
434
|
+
overflow-x: auto;
|
|
435
|
+
border: 1px solid var(--border);
|
|
436
|
+
border-radius: 12px;
|
|
437
|
+
background: rgba(0, 0, 0, 0.25);
|
|
438
|
+
font-size: 0.9rem;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
:root[data-theme="light"] .code {
|
|
442
|
+
background: rgba(18, 20, 28, 0.05);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
.copy {
|
|
446
|
+
flex: none;
|
|
447
|
+
padding: 0.35rem 0.8rem;
|
|
448
|
+
border: 1px solid var(--border);
|
|
449
|
+
border-radius: 9px;
|
|
450
|
+
background: var(--surface);
|
|
451
|
+
color: var(--text);
|
|
452
|
+
font: inherit;
|
|
453
|
+
font-size: 0.8rem;
|
|
454
|
+
cursor: pointer;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
.copy:hover {
|
|
458
|
+
border-color: var(--a3);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/* Footer */
|
|
462
|
+
.footer {
|
|
463
|
+
padding: 2rem 0 3rem;
|
|
464
|
+
border-top: 1px solid var(--border);
|
|
465
|
+
color: var(--muted);
|
|
466
|
+
font-size: 0.9rem;
|
|
467
|
+
text-align: center;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
.footer a {
|
|
471
|
+
color: var(--text);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
@media (prefers-reduced-motion: reduce) {
|
|
475
|
+
html {
|
|
476
|
+
scroll-behavior: auto;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
* {
|
|
480
|
+
transition: none !important;
|
|
481
|
+
animation: none !important;
|
|
482
|
+
}
|
|
483
|
+
}
|