@pyreon/create-zero 0.1.1
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/LICENSE +21 -0
- package/README.md +32 -0
- package/bin/create-zero.js +2 -0
- package/lib/index.js +28 -0
- package/lib/index.js.map +1 -0
- package/package.json +29 -0
- package/templates/default/env.d.ts +6 -0
- package/templates/default/index.html +17 -0
- package/templates/default/package.json +27 -0
- package/templates/default/public/favicon.svg +10 -0
- package/templates/default/src/entry-client.ts +5 -0
- package/templates/default/src/entry-server.ts +19 -0
- package/templates/default/src/global.css +629 -0
- package/templates/default/src/routes/(admin)/dashboard.tsx +133 -0
- package/templates/default/src/routes/_error.tsx +24 -0
- package/templates/default/src/routes/_layout.tsx +49 -0
- package/templates/default/src/routes/_loading.tsx +8 -0
- package/templates/default/src/routes/about.tsx +125 -0
- package/templates/default/src/routes/counter.tsx +94 -0
- package/templates/default/src/routes/index.tsx +225 -0
- package/templates/default/src/routes/posts/[id].tsx +97 -0
- package/templates/default/src/routes/posts/index.tsx +115 -0
- package/templates/default/tsconfig.json +16 -0
- package/templates/default/vite.config.ts +29 -0
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { useHead } from '@pyreon/head'
|
|
2
|
+
import type { LoaderContext } from '@pyreon/zero'
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
title: 'Dashboard — Pyreon Zero',
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Navigation guard — runs before the route renders.
|
|
10
|
+
* Demonstrates how to protect routes with authentication checks.
|
|
11
|
+
*
|
|
12
|
+
* In a real app, you'd check a session cookie, JWT token, or auth state.
|
|
13
|
+
* Returning a string redirects to that path.
|
|
14
|
+
*/
|
|
15
|
+
export function guard() {
|
|
16
|
+
// Simulate auth check — in a real app, check session/token
|
|
17
|
+
const isAuthenticated =
|
|
18
|
+
typeof window !== 'undefined' &&
|
|
19
|
+
localStorage.getItem('zero-demo-auth') === 'true'
|
|
20
|
+
|
|
21
|
+
if (!isAuthenticated) {
|
|
22
|
+
return '/about' // Redirect unauthenticated users
|
|
23
|
+
}
|
|
24
|
+
return true // Allow navigation
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Per-route middleware — runs on the server before the loader.
|
|
29
|
+
* Great for logging, auth checks, rate limiting per-route.
|
|
30
|
+
*/
|
|
31
|
+
export const middleware = async (
|
|
32
|
+
request: Request,
|
|
33
|
+
next: (req: Request) => Promise<Response>,
|
|
34
|
+
) => {
|
|
35
|
+
const start = Date.now()
|
|
36
|
+
const response = await next(request)
|
|
37
|
+
const duration = Date.now() - start
|
|
38
|
+
|
|
39
|
+
// Add server timing header
|
|
40
|
+
const headers = new Headers(response.headers)
|
|
41
|
+
headers.set('Server-Timing', `route;dur=${duration}`)
|
|
42
|
+
|
|
43
|
+
return new Response(response.body, {
|
|
44
|
+
status: response.status,
|
|
45
|
+
statusText: response.statusText,
|
|
46
|
+
headers,
|
|
47
|
+
})
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function loader(_ctx: LoaderContext) {
|
|
51
|
+
return {
|
|
52
|
+
user: 'Demo User',
|
|
53
|
+
stats: {
|
|
54
|
+
views: 12_847,
|
|
55
|
+
routes: 6,
|
|
56
|
+
buildTime: '1.2s',
|
|
57
|
+
},
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export default function Dashboard() {
|
|
62
|
+
useHead({ title: meta.title })
|
|
63
|
+
|
|
64
|
+
return (
|
|
65
|
+
<>
|
|
66
|
+
<div class="page-header">
|
|
67
|
+
<span class="badge">Protected Route</span>
|
|
68
|
+
<h1 style="margin-top: var(--space-md);">Dashboard</h1>
|
|
69
|
+
<p>
|
|
70
|
+
This route is protected by a <code>guard</code> function and uses
|
|
71
|
+
per-route <code>middleware</code> for server-side logging.
|
|
72
|
+
</p>
|
|
73
|
+
</div>
|
|
74
|
+
|
|
75
|
+
<div class="about-grid">
|
|
76
|
+
<div class="card about-stat">
|
|
77
|
+
<div class="stat-value">12.8k</div>
|
|
78
|
+
<div class="stat-label">Page views</div>
|
|
79
|
+
</div>
|
|
80
|
+
<div class="card about-stat">
|
|
81
|
+
<div class="stat-value">6</div>
|
|
82
|
+
<div class="stat-label">Routes</div>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="card about-stat">
|
|
85
|
+
<div class="stat-value">1.2s</div>
|
|
86
|
+
<div class="stat-label">Build time</div>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div
|
|
91
|
+
class="code-block"
|
|
92
|
+
style="max-width: 520px; margin: var(--space-2xl) auto 0;"
|
|
93
|
+
>
|
|
94
|
+
<div class="code-block-header">
|
|
95
|
+
<span>dashboard.tsx</span>
|
|
96
|
+
</div>
|
|
97
|
+
<pre>
|
|
98
|
+
<code>
|
|
99
|
+
<span class="cm">{'// Navigation guard — protect routes'}</span>
|
|
100
|
+
<span class="kw">export function</span>{' '}
|
|
101
|
+
<span class="fn">guard</span>() {'{'}
|
|
102
|
+
<span class="kw">if</span> (!isAuthenticated) {'{'}
|
|
103
|
+
<span class="kw">return</span> <span class="str">"/login"</span>{' '}
|
|
104
|
+
<span class="cm">{'// redirect'}</span>
|
|
105
|
+
{'}'}
|
|
106
|
+
<span class="kw">return</span> <span class="str">true</span>{' '}
|
|
107
|
+
<span class="cm">{'// allow'}</span>
|
|
108
|
+
{'}'}
|
|
109
|
+
<span class="cm">{'// Per-route server middleware'}</span>
|
|
110
|
+
<span class="kw">export const</span>{' '}
|
|
111
|
+
<span class="fn">middleware</span> = (req, next) => {'{'}
|
|
112
|
+
<span class="cm">{'// logging, auth, rate limiting...'}</span>
|
|
113
|
+
<span class="kw">return</span> <span class="fn">next</span>(req)
|
|
114
|
+
{'}'}
|
|
115
|
+
</code>
|
|
116
|
+
</pre>
|
|
117
|
+
</div>
|
|
118
|
+
|
|
119
|
+
<div style="text-align: center; margin-top: var(--space-2xl);">
|
|
120
|
+
<button
|
|
121
|
+
type="button"
|
|
122
|
+
class="btn btn-secondary"
|
|
123
|
+
onclick={() => {
|
|
124
|
+
localStorage.removeItem('zero-demo-auth')
|
|
125
|
+
window.location.href = '/about'
|
|
126
|
+
}}
|
|
127
|
+
>
|
|
128
|
+
Log out (clear demo auth)
|
|
129
|
+
</button>
|
|
130
|
+
</div>
|
|
131
|
+
</>
|
|
132
|
+
)
|
|
133
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { useHead } from '@pyreon/head'
|
|
2
|
+
import { Link } from '@pyreon/zero/link'
|
|
3
|
+
|
|
4
|
+
export default function ErrorPage() {
|
|
5
|
+
useHead({ title: 'Something went wrong — Zero' })
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div class="error-page">
|
|
9
|
+
<div class="error-code">500</div>
|
|
10
|
+
<h1>Something went wrong</h1>
|
|
11
|
+
<p style="color: var(--c-text-secondary); max-width: 400px;">
|
|
12
|
+
An unexpected error occurred. Try refreshing the page or navigating back
|
|
13
|
+
home.
|
|
14
|
+
</p>
|
|
15
|
+
<Link
|
|
16
|
+
href="/"
|
|
17
|
+
class="btn btn-primary"
|
|
18
|
+
style="margin-top: var(--space-md);"
|
|
19
|
+
>
|
|
20
|
+
Back to Home
|
|
21
|
+
</Link>
|
|
22
|
+
</div>
|
|
23
|
+
)
|
|
24
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Link } from '@pyreon/zero/link'
|
|
2
|
+
import { ThemeToggle } from '@pyreon/zero/theme'
|
|
3
|
+
|
|
4
|
+
export function layout(props: { children: any }) {
|
|
5
|
+
return (
|
|
6
|
+
<>
|
|
7
|
+
<header class="app-header">
|
|
8
|
+
<div class="app-header-inner">
|
|
9
|
+
<Link href="/" class="app-logo">
|
|
10
|
+
<span class="logo-mark">Z</span>
|
|
11
|
+
<span>Zero</span>
|
|
12
|
+
</Link>
|
|
13
|
+
<nav class="app-nav">
|
|
14
|
+
<Link href="/" prefetch="viewport" exactActiveClass="nav-active">
|
|
15
|
+
Home
|
|
16
|
+
</Link>
|
|
17
|
+
<Link
|
|
18
|
+
href="/counter"
|
|
19
|
+
prefetch="hover"
|
|
20
|
+
exactActiveClass="nav-active"
|
|
21
|
+
>
|
|
22
|
+
Counter
|
|
23
|
+
</Link>
|
|
24
|
+
<Link href="/posts" prefetch="hover" activeClass="nav-active">
|
|
25
|
+
Posts
|
|
26
|
+
</Link>
|
|
27
|
+
<Link href="/about" prefetch="hover" exactActiveClass="nav-active">
|
|
28
|
+
About
|
|
29
|
+
</Link>
|
|
30
|
+
<Link
|
|
31
|
+
href="/dashboard"
|
|
32
|
+
prefetch="hover"
|
|
33
|
+
exactActiveClass="nav-active"
|
|
34
|
+
>
|
|
35
|
+
Dashboard
|
|
36
|
+
</Link>
|
|
37
|
+
<ThemeToggle class="theme-toggle" />
|
|
38
|
+
</nav>
|
|
39
|
+
</div>
|
|
40
|
+
</header>
|
|
41
|
+
|
|
42
|
+
<main class="app-main">{props.children}</main>
|
|
43
|
+
|
|
44
|
+
<footer class="app-footer">
|
|
45
|
+
Built with Pyreon Zero — signal-based, blazing fast.
|
|
46
|
+
</footer>
|
|
47
|
+
</>
|
|
48
|
+
)
|
|
49
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { useHead } from '@pyreon/head'
|
|
2
|
+
import { Link } from '@pyreon/zero/link'
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
title: 'About — Pyreon Zero',
|
|
6
|
+
description: 'Learn about the Pyreon Zero meta-framework.',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function About() {
|
|
10
|
+
useHead({
|
|
11
|
+
title: meta.title,
|
|
12
|
+
meta: [{ name: 'description', content: meta.description }],
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<div class="page-header">
|
|
18
|
+
<h1>About Zero</h1>
|
|
19
|
+
<p>
|
|
20
|
+
A signal-based meta-framework built on Vite — designed for developers
|
|
21
|
+
who care about performance and simplicity.
|
|
22
|
+
</p>
|
|
23
|
+
</div>
|
|
24
|
+
|
|
25
|
+
<div class="about-grid">
|
|
26
|
+
<div class="card about-stat">
|
|
27
|
+
<div class="stat-value">0</div>
|
|
28
|
+
<div class="stat-label">Virtual DOM overhead</div>
|
|
29
|
+
</div>
|
|
30
|
+
<div class="card about-stat">
|
|
31
|
+
<div class="stat-value">4</div>
|
|
32
|
+
<div class="stat-label">Rendering strategies</div>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="card about-stat">
|
|
35
|
+
<div class="stat-value">5+</div>
|
|
36
|
+
<div class="stat-label">Deploy adapters</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<section style="margin-top: var(--space-3xl); max-width: 680px;">
|
|
41
|
+
<h2 style="font-size: 1.3rem; font-weight: 700; margin-bottom: var(--space-md);">
|
|
42
|
+
Why Zero?
|
|
43
|
+
</h2>
|
|
44
|
+
<div style="color: var(--c-text-secondary); line-height: 1.8; display: flex; flex-direction: column; gap: var(--space-lg);">
|
|
45
|
+
<p>
|
|
46
|
+
Most frameworks make you choose between developer experience and
|
|
47
|
+
runtime performance. Zero doesn't. Pyreon's signal-based reactivity
|
|
48
|
+
means your components compile to surgical DOM updates — no diffing,
|
|
49
|
+
no reconciliation, no wasted work.
|
|
50
|
+
</p>
|
|
51
|
+
<p>
|
|
52
|
+
On top of that, Zero gives you file-based routing, server-side
|
|
53
|
+
rendering, static generation, incremental regeneration, font
|
|
54
|
+
optimization, image optimization, smart caching, link prefetching,
|
|
55
|
+
and SEO utilities — all built in, all zero-config.
|
|
56
|
+
</p>
|
|
57
|
+
<p>
|
|
58
|
+
Deploy to Node, Bun, Vercel, Cloudflare, Netlify, or export as a
|
|
59
|
+
static site. One codebase, any target.
|
|
60
|
+
</p>
|
|
61
|
+
</div>
|
|
62
|
+
</section>
|
|
63
|
+
|
|
64
|
+
<section style="margin-top: var(--space-3xl); max-width: 680px;">
|
|
65
|
+
<h2 style="font-size: 1.3rem; font-weight: 700; margin-bottom: var(--space-md);">
|
|
66
|
+
The Stack
|
|
67
|
+
</h2>
|
|
68
|
+
<div style="display: grid; gap: var(--space-sm);">
|
|
69
|
+
{[
|
|
70
|
+
['Pyreon', 'Signal-based UI framework with JSX'],
|
|
71
|
+
['Vite', 'Lightning-fast dev server and optimized builds'],
|
|
72
|
+
[
|
|
73
|
+
'File Router',
|
|
74
|
+
'Drop a file, get a route — layouts, guards, loaders',
|
|
75
|
+
],
|
|
76
|
+
[
|
|
77
|
+
'SSR / SSG / ISR',
|
|
78
|
+
'Every rendering strategy, per-route overrides',
|
|
79
|
+
],
|
|
80
|
+
[
|
|
81
|
+
'<Image>',
|
|
82
|
+
'Lazy loading, responsive srcset, blur-up placeholders',
|
|
83
|
+
],
|
|
84
|
+
['<Link>', 'Prefetch on hover or viewport entry for instant nav'],
|
|
85
|
+
[
|
|
86
|
+
'Font Plugin',
|
|
87
|
+
'Google Fonts optimization, preconnect, font-display:swap',
|
|
88
|
+
],
|
|
89
|
+
[
|
|
90
|
+
'Cache MW',
|
|
91
|
+
'Immutable hashed assets, stale-while-revalidate pages',
|
|
92
|
+
],
|
|
93
|
+
['SEO Tools', 'Sitemap, robots.txt, JSON-LD structured data'],
|
|
94
|
+
].map(([name, desc]) => (
|
|
95
|
+
<div
|
|
96
|
+
class="card"
|
|
97
|
+
style="display: flex; align-items: center; gap: var(--space-md); padding: var(--space-md) var(--space-lg);"
|
|
98
|
+
>
|
|
99
|
+
<code style="min-width: 130px; font-weight: 600; color: var(--c-accent);">
|
|
100
|
+
{name}
|
|
101
|
+
</code>
|
|
102
|
+
<span style="color: var(--c-text-secondary); font-size: 0.9rem;">
|
|
103
|
+
{desc}
|
|
104
|
+
</span>
|
|
105
|
+
</div>
|
|
106
|
+
))}
|
|
107
|
+
</div>
|
|
108
|
+
</section>
|
|
109
|
+
|
|
110
|
+
<section style="text-align: center; margin-top: var(--space-3xl); padding-bottom: var(--space-xl);">
|
|
111
|
+
<h2 style="font-size: 1.3rem; font-weight: 700; margin-bottom: var(--space-md);">
|
|
112
|
+
Ready to build something?
|
|
113
|
+
</h2>
|
|
114
|
+
<div style="display: flex; gap: var(--space-md); justify-content: center;">
|
|
115
|
+
<Link href="/" class="btn btn-primary">
|
|
116
|
+
Get Started
|
|
117
|
+
</Link>
|
|
118
|
+
<Link href="/counter" class="btn btn-secondary">
|
|
119
|
+
Try the Demo
|
|
120
|
+
</Link>
|
|
121
|
+
</div>
|
|
122
|
+
</section>
|
|
123
|
+
</>
|
|
124
|
+
)
|
|
125
|
+
}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { useHead } from '@pyreon/head'
|
|
2
|
+
import { computed, signal } from '@pyreon/reactivity'
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
title: 'Counter — Pyreon Zero',
|
|
6
|
+
description: "See Pyreon's signal-based reactivity in action.",
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function Counter() {
|
|
10
|
+
useHead({ title: meta.title })
|
|
11
|
+
|
|
12
|
+
const count = signal(0)
|
|
13
|
+
const doubled = computed(() => count() * 2)
|
|
14
|
+
const isEven = computed(() => count() % 2 === 0)
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<>
|
|
18
|
+
<div class="page-header" style="text-align: center;">
|
|
19
|
+
<span class="badge">Interactive Demo</span>
|
|
20
|
+
<h1 style="margin-top: var(--space-md);">Signal Reactivity</h1>
|
|
21
|
+
<p>
|
|
22
|
+
Fine-grained reactivity with zero virtual DOM. Only the exact text
|
|
23
|
+
nodes that display these values are updated — nothing else re-renders.
|
|
24
|
+
</p>
|
|
25
|
+
</div>
|
|
26
|
+
|
|
27
|
+
<div class="counter-demo">
|
|
28
|
+
<div class="counter-display">{count}</div>
|
|
29
|
+
|
|
30
|
+
<div class="counter-controls">
|
|
31
|
+
<button
|
|
32
|
+
type="button"
|
|
33
|
+
class="btn btn-secondary"
|
|
34
|
+
onclick={() => count(count() - 1)}
|
|
35
|
+
>
|
|
36
|
+
-
|
|
37
|
+
</button>
|
|
38
|
+
<button
|
|
39
|
+
type="button"
|
|
40
|
+
class="btn btn-primary"
|
|
41
|
+
onclick={() => count(0)}
|
|
42
|
+
>
|
|
43
|
+
Reset
|
|
44
|
+
</button>
|
|
45
|
+
<button
|
|
46
|
+
type="button"
|
|
47
|
+
class="btn btn-secondary"
|
|
48
|
+
onclick={() => count(count() + 1)}
|
|
49
|
+
>
|
|
50
|
+
+
|
|
51
|
+
</button>
|
|
52
|
+
</div>
|
|
53
|
+
|
|
54
|
+
<div class="counter-meta">
|
|
55
|
+
<div>
|
|
56
|
+
count() → <strong>{count}</strong>
|
|
57
|
+
</div>
|
|
58
|
+
<div>
|
|
59
|
+
doubled() → <strong>{doubled}</strong>
|
|
60
|
+
</div>
|
|
61
|
+
<div>
|
|
62
|
+
isEven() → <strong>{() => (isEven() ? 'true' : 'false')}</strong>
|
|
63
|
+
</div>
|
|
64
|
+
</div>
|
|
65
|
+
</div>
|
|
66
|
+
|
|
67
|
+
<div
|
|
68
|
+
class="code-block"
|
|
69
|
+
style="max-width: 520px; margin: var(--space-2xl) auto 0;"
|
|
70
|
+
>
|
|
71
|
+
<div class="code-block-header">
|
|
72
|
+
<span>counter.tsx</span>
|
|
73
|
+
</div>
|
|
74
|
+
<pre>
|
|
75
|
+
<code>
|
|
76
|
+
<span class="kw">import</span> {'{'} signal, computed {'}'}{' '}
|
|
77
|
+
<span class="kw">from</span>{' '}
|
|
78
|
+
<span class="str">"@pyreon/reactivity"</span>
|
|
79
|
+
<span class="kw">const</span> <span class="fn">count</span> ={' '}
|
|
80
|
+
<span class="fn">signal</span>(<span class="str">0</span>)
|
|
81
|
+
<span class="kw">const</span> <span class="fn">doubled</span> ={' '}
|
|
82
|
+
<span class="fn">computed</span>(() =>{' '}
|
|
83
|
+
<span class="fn">count</span>() * <span class="str">2</span>)
|
|
84
|
+
<span class="cm">{'// Just reference the signal in JSX —'}</span>
|
|
85
|
+
<span class="cm">{'// only this text node updates'}</span>
|
|
86
|
+
<span class="tag"><span></span>
|
|
87
|
+
{'{'}count{'}'}
|
|
88
|
+
<span class="tag"></span></span>
|
|
89
|
+
</code>
|
|
90
|
+
</pre>
|
|
91
|
+
</div>
|
|
92
|
+
</>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
import { useHead } from '@pyreon/head'
|
|
2
|
+
import { Link } from '@pyreon/zero/link'
|
|
3
|
+
|
|
4
|
+
export const meta = {
|
|
5
|
+
title: 'Pyreon Zero',
|
|
6
|
+
description: 'The signal-based meta-framework. Build fast, stay fast.',
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export default function Home() {
|
|
10
|
+
useHead({
|
|
11
|
+
title: 'Pyreon Zero — The Signal-Based Meta-Framework',
|
|
12
|
+
meta: [{ name: 'description', content: meta.description }],
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
return (
|
|
16
|
+
<>
|
|
17
|
+
<section class="hero">
|
|
18
|
+
<span class="badge">Open Source</span>
|
|
19
|
+
<h1>
|
|
20
|
+
Build fast.
|
|
21
|
+
<br />
|
|
22
|
+
<span class="gradient">Stay fast.</span>
|
|
23
|
+
</h1>
|
|
24
|
+
<p>
|
|
25
|
+
Pyreon Zero is a signal-based full-stack framework powered by Vite.
|
|
26
|
+
File-based routing, SSR, SSG, ISR — everything you need, nothing you
|
|
27
|
+
don't.
|
|
28
|
+
</p>
|
|
29
|
+
<div class="hero-actions">
|
|
30
|
+
<Link href="/counter" class="btn btn-primary">
|
|
31
|
+
See it in action
|
|
32
|
+
</Link>
|
|
33
|
+
<a
|
|
34
|
+
href="https://github.com/pyreon/zero"
|
|
35
|
+
class="btn btn-secondary"
|
|
36
|
+
target="_blank"
|
|
37
|
+
rel="noopener noreferrer"
|
|
38
|
+
>
|
|
39
|
+
GitHub
|
|
40
|
+
</a>
|
|
41
|
+
</div>
|
|
42
|
+
|
|
43
|
+
<div class="code-block">
|
|
44
|
+
<div class="code-block-header">
|
|
45
|
+
<span>terminal</span>
|
|
46
|
+
</div>
|
|
47
|
+
<pre>
|
|
48
|
+
<code>
|
|
49
|
+
<span class="cm"># Create a new project in seconds</span>
|
|
50
|
+
<span class="fn">bun</span> create zero my-app
|
|
51
|
+
<span class="fn">cd</span> my-app
|
|
52
|
+
<span class="fn">bun</span> install
|
|
53
|
+
<span class="fn">bun</span> run dev
|
|
54
|
+
</code>
|
|
55
|
+
</pre>
|
|
56
|
+
</div>
|
|
57
|
+
</section>
|
|
58
|
+
|
|
59
|
+
<section class="features">
|
|
60
|
+
<div class="card feature">
|
|
61
|
+
<div class="feature-icon">
|
|
62
|
+
<svg
|
|
63
|
+
width="24"
|
|
64
|
+
height="24"
|
|
65
|
+
viewBox="0 0 24 24"
|
|
66
|
+
fill="none"
|
|
67
|
+
stroke="currentColor"
|
|
68
|
+
stroke-width="2"
|
|
69
|
+
stroke-linecap="round"
|
|
70
|
+
stroke-linejoin="round"
|
|
71
|
+
aria-hidden="true"
|
|
72
|
+
>
|
|
73
|
+
<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2" />
|
|
74
|
+
</svg>
|
|
75
|
+
</div>
|
|
76
|
+
<h3>Signal-Based Reactivity</h3>
|
|
77
|
+
<p>
|
|
78
|
+
Fine-grained reactivity with zero virtual DOM overhead. Only the
|
|
79
|
+
exact DOM nodes that need updating are touched.
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
|
|
83
|
+
<div class="card feature">
|
|
84
|
+
<div class="feature-icon">
|
|
85
|
+
<svg
|
|
86
|
+
width="24"
|
|
87
|
+
height="24"
|
|
88
|
+
viewBox="0 0 24 24"
|
|
89
|
+
fill="none"
|
|
90
|
+
stroke="currentColor"
|
|
91
|
+
stroke-width="2"
|
|
92
|
+
stroke-linecap="round"
|
|
93
|
+
stroke-linejoin="round"
|
|
94
|
+
aria-hidden="true"
|
|
95
|
+
>
|
|
96
|
+
<path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z" />
|
|
97
|
+
</svg>
|
|
98
|
+
</div>
|
|
99
|
+
<h3>File-Based Routing</h3>
|
|
100
|
+
<p>
|
|
101
|
+
Drop a file in <code>src/routes/</code> and it's a route. Layouts,
|
|
102
|
+
dynamic params, catch-alls, and route groups built in.
|
|
103
|
+
</p>
|
|
104
|
+
</div>
|
|
105
|
+
|
|
106
|
+
<div class="card feature">
|
|
107
|
+
<div class="feature-icon">
|
|
108
|
+
<svg
|
|
109
|
+
width="24"
|
|
110
|
+
height="24"
|
|
111
|
+
viewBox="0 0 24 24"
|
|
112
|
+
fill="none"
|
|
113
|
+
stroke="currentColor"
|
|
114
|
+
stroke-width="2"
|
|
115
|
+
stroke-linecap="round"
|
|
116
|
+
stroke-linejoin="round"
|
|
117
|
+
aria-hidden="true"
|
|
118
|
+
>
|
|
119
|
+
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
|
|
120
|
+
<line x1="8" y1="21" x2="16" y2="21" />
|
|
121
|
+
<line x1="12" y1="17" x2="12" y2="21" />
|
|
122
|
+
</svg>
|
|
123
|
+
</div>
|
|
124
|
+
<h3>SSR / SSG / ISR / SPA</h3>
|
|
125
|
+
<p>
|
|
126
|
+
Every rendering strategy out of the box. Per-route overrides let you
|
|
127
|
+
mix SSR pages with static marketing pages.
|
|
128
|
+
</p>
|
|
129
|
+
</div>
|
|
130
|
+
|
|
131
|
+
<div class="card feature">
|
|
132
|
+
<div class="feature-icon">
|
|
133
|
+
<svg
|
|
134
|
+
width="24"
|
|
135
|
+
height="24"
|
|
136
|
+
viewBox="0 0 24 24"
|
|
137
|
+
fill="none"
|
|
138
|
+
stroke="currentColor"
|
|
139
|
+
stroke-width="2"
|
|
140
|
+
stroke-linecap="round"
|
|
141
|
+
stroke-linejoin="round"
|
|
142
|
+
aria-hidden="true"
|
|
143
|
+
>
|
|
144
|
+
<path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z" />
|
|
145
|
+
</svg>
|
|
146
|
+
</div>
|
|
147
|
+
<h3>Font & Image Optimization</h3>
|
|
148
|
+
<p>
|
|
149
|
+
Automatic Google Fonts inlining, font-display swap, and an{' '}
|
|
150
|
+
<code>{'<Image>'}</code> component with lazy loading and blur-up
|
|
151
|
+
placeholders.
|
|
152
|
+
</p>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<div class="card feature">
|
|
156
|
+
<div class="feature-icon">
|
|
157
|
+
<svg
|
|
158
|
+
width="24"
|
|
159
|
+
height="24"
|
|
160
|
+
viewBox="0 0 24 24"
|
|
161
|
+
fill="none"
|
|
162
|
+
stroke="currentColor"
|
|
163
|
+
stroke-width="2"
|
|
164
|
+
stroke-linecap="round"
|
|
165
|
+
stroke-linejoin="round"
|
|
166
|
+
aria-hidden="true"
|
|
167
|
+
>
|
|
168
|
+
<path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z" />
|
|
169
|
+
</svg>
|
|
170
|
+
</div>
|
|
171
|
+
<h3>Smart Caching & Security</h3>
|
|
172
|
+
<p>
|
|
173
|
+
Built-in middleware for immutable asset caching,
|
|
174
|
+
stale-while-revalidate, security headers, and compression hints.
|
|
175
|
+
</p>
|
|
176
|
+
</div>
|
|
177
|
+
|
|
178
|
+
<div class="card feature">
|
|
179
|
+
<div class="feature-icon">
|
|
180
|
+
<svg
|
|
181
|
+
width="24"
|
|
182
|
+
height="24"
|
|
183
|
+
viewBox="0 0 24 24"
|
|
184
|
+
fill="none"
|
|
185
|
+
stroke="currentColor"
|
|
186
|
+
stroke-width="2"
|
|
187
|
+
stroke-linecap="round"
|
|
188
|
+
stroke-linejoin="round"
|
|
189
|
+
aria-hidden="true"
|
|
190
|
+
>
|
|
191
|
+
<circle cx="12" cy="12" r="10" />
|
|
192
|
+
<line x1="2" y1="12" x2="22" y2="12" />
|
|
193
|
+
<path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z" />
|
|
194
|
+
</svg>
|
|
195
|
+
</div>
|
|
196
|
+
<h3>Deploy Anywhere</h3>
|
|
197
|
+
<p>
|
|
198
|
+
Adapters for Node, Bun, Vercel, Cloudflare, and Netlify. Or export
|
|
199
|
+
as a fully static site.
|
|
200
|
+
</p>
|
|
201
|
+
</div>
|
|
202
|
+
</section>
|
|
203
|
+
|
|
204
|
+
<section style="text-align: center; margin-top: var(--space-3xl); padding-bottom: var(--space-xl);">
|
|
205
|
+
<h2 style="font-size: 1.5rem; font-weight: 700; letter-spacing: -0.02em; margin-bottom: var(--space-sm);">
|
|
206
|
+
Ready to build?
|
|
207
|
+
</h2>
|
|
208
|
+
<p style="color: var(--c-text-secondary); margin-bottom: var(--space-xl);">
|
|
209
|
+
Explore the demo pages to see Zero's features in action.
|
|
210
|
+
</p>
|
|
211
|
+
<div style="display: flex; gap: var(--space-md); justify-content: center; flex-wrap: wrap;">
|
|
212
|
+
<Link href="/counter" class="btn btn-secondary">
|
|
213
|
+
Signal Reactivity
|
|
214
|
+
</Link>
|
|
215
|
+
<Link href="/posts" class="btn btn-secondary">
|
|
216
|
+
Data Loading
|
|
217
|
+
</Link>
|
|
218
|
+
<Link href="/about" class="btn btn-secondary">
|
|
219
|
+
About Zero
|
|
220
|
+
</Link>
|
|
221
|
+
</div>
|
|
222
|
+
</section>
|
|
223
|
+
</>
|
|
224
|
+
)
|
|
225
|
+
}
|