create-neutron 0.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.
Files changed (95) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +25 -0
  3. package/dist/index.d.ts +3 -0
  4. package/dist/index.d.ts.map +1 -0
  5. package/dist/index.js +201 -0
  6. package/dist/index.js.map +1 -0
  7. package/package.json +45 -0
  8. package/templates/app/_gitignore +3 -0
  9. package/templates/app/index.html +12 -0
  10. package/templates/app/neutron.config.ts +5 -0
  11. package/templates/app/package.json +24 -0
  12. package/templates/app/src/main.tsx +5 -0
  13. package/templates/app/src/routes/_layout.tsx +18 -0
  14. package/templates/app/src/routes/api/session/refresh.tsx +28 -0
  15. package/templates/app/src/routes/app/dashboard.tsx +39 -0
  16. package/templates/app/src/routes/app/settings.tsx +68 -0
  17. package/templates/app/src/routes/index.tsx +26 -0
  18. package/templates/app/src/routes/login.tsx +14 -0
  19. package/templates/app/src/routes/protected.tsx +53 -0
  20. package/templates/app/src/routes/users/[id].tsx +26 -0
  21. package/templates/app/tsconfig.json +14 -0
  22. package/templates/app/vite.config.ts +7 -0
  23. package/templates/basic/_gitignore +3 -0
  24. package/templates/basic/index.html +12 -0
  25. package/templates/basic/neutron.config.ts +5 -0
  26. package/templates/basic/package.json +24 -0
  27. package/templates/basic/src/main.tsx +5 -0
  28. package/templates/basic/src/routes/_layout.tsx +16 -0
  29. package/templates/basic/src/routes/index.tsx +19 -0
  30. package/templates/basic/src/routes/users/[id].tsx +26 -0
  31. package/templates/basic/tsconfig.json +14 -0
  32. package/templates/basic/vite.config.ts +7 -0
  33. package/templates/docs/_gitignore +3 -0
  34. package/templates/docs/index.html +12 -0
  35. package/templates/docs/neutron.config.ts +8 -0
  36. package/templates/docs/package.json +22 -0
  37. package/templates/docs/public/favicon.svg +1 -0
  38. package/templates/docs/src/components/Breadcrumbs.tsx +47 -0
  39. package/templates/docs/src/components/Callout.tsx +40 -0
  40. package/templates/docs/src/components/Card.tsx +31 -0
  41. package/templates/docs/src/components/CopyButton.tsx +35 -0
  42. package/templates/docs/src/components/Footer.tsx +72 -0
  43. package/templates/docs/src/components/Search.tsx +139 -0
  44. package/templates/docs/src/components/Sidebar.tsx +59 -0
  45. package/templates/docs/src/components/SidebarToggle.tsx +47 -0
  46. package/templates/docs/src/components/Steps.tsx +9 -0
  47. package/templates/docs/src/components/Tabs.tsx +35 -0
  48. package/templates/docs/src/components/ThemeToggle.tsx +45 -0
  49. package/templates/docs/src/components/Toc.tsx +36 -0
  50. package/templates/docs/src/components/TocTracker.tsx +35 -0
  51. package/templates/docs/src/content/config.ts +13 -0
  52. package/templates/docs/src/content/docs/getting-started/installation.mdx +18 -0
  53. package/templates/docs/src/content/docs/getting-started/quick-start.mdx +19 -0
  54. package/templates/docs/src/content/docs/index.mdx +13 -0
  55. package/templates/docs/src/lib/pagination.ts +39 -0
  56. package/templates/docs/src/lib/sidebar.ts +100 -0
  57. package/templates/docs/src/main.tsx +8 -0
  58. package/templates/docs/src/routes/_layout.tsx +27 -0
  59. package/templates/docs/src/routes/docs/[...slug].tsx +85 -0
  60. package/templates/docs/src/routes/docs/_layout.tsx +47 -0
  61. package/templates/docs/src/styles/code.css +188 -0
  62. package/templates/docs/src/styles/components.css +264 -0
  63. package/templates/docs/src/styles/docs.css +416 -0
  64. package/templates/docs/src/styles/prose.css +224 -0
  65. package/templates/docs/src/styles/search.css +225 -0
  66. package/templates/docs/tsconfig.json +19 -0
  67. package/templates/docs/vite.config.ts +6 -0
  68. package/templates/full/_gitignore +3 -0
  69. package/templates/full/index.html +12 -0
  70. package/templates/full/neutron.config.ts +5 -0
  71. package/templates/full/package.json +24 -0
  72. package/templates/full/src/components/Counter.tsx +13 -0
  73. package/templates/full/src/main.tsx +5 -0
  74. package/templates/full/src/routes/(marketing)/pricing.tsx +15 -0
  75. package/templates/full/src/routes/_layout.tsx +17 -0
  76. package/templates/full/src/routes/app/dashboard.tsx +28 -0
  77. package/templates/full/src/routes/app/settings.tsx +42 -0
  78. package/templates/full/src/routes/index.tsx +31 -0
  79. package/templates/full/src/routes/users/[id].tsx +26 -0
  80. package/templates/full/tsconfig.json +14 -0
  81. package/templates/full/vite.config.ts +7 -0
  82. package/templates/marketing/_gitignore +3 -0
  83. package/templates/marketing/index.html +12 -0
  84. package/templates/marketing/neutron.config.ts +5 -0
  85. package/templates/marketing/package.json +24 -0
  86. package/templates/marketing/src/components/Counter.tsx +14 -0
  87. package/templates/marketing/src/main.tsx +5 -0
  88. package/templates/marketing/src/routes/_layout.tsx +16 -0
  89. package/templates/marketing/src/routes/about.tsx +10 -0
  90. package/templates/marketing/src/routes/blog/[slug].tsx +34 -0
  91. package/templates/marketing/src/routes/blog/index.tsx +27 -0
  92. package/templates/marketing/src/routes/index.tsx +26 -0
  93. package/templates/marketing/src/routes/users/index.tsx +10 -0
  94. package/templates/marketing/tsconfig.json +14 -0
  95. package/templates/marketing/vite.config.ts +7 -0
@@ -0,0 +1,225 @@
1
+ /* Search dialog overlay */
2
+
3
+ .search-overlay {
4
+ position: fixed;
5
+ inset: 0;
6
+ z-index: 100;
7
+ display: flex;
8
+ align-items: flex-start;
9
+ justify-content: center;
10
+ padding-top: min(20vh, 8rem);
11
+ background: hsla(0, 0%, 0%, 0.6);
12
+ backdrop-filter: blur(4px);
13
+ -webkit-backdrop-filter: blur(4px);
14
+ opacity: 0;
15
+ visibility: hidden;
16
+ transition: opacity 0.15s ease, visibility 0.15s ease;
17
+ }
18
+
19
+ .search-overlay.is-open {
20
+ opacity: 1;
21
+ visibility: visible;
22
+ }
23
+
24
+ /* Search modal */
25
+ .search-modal {
26
+ width: 100%;
27
+ max-width: 640px;
28
+ margin: 0 1rem;
29
+ background: var(--nd-bg);
30
+ border: 1px solid var(--nd-border);
31
+ border-radius: var(--nd-radius);
32
+ box-shadow: 0 16px 48px hsla(0, 0%, 0%, 0.25);
33
+ overflow: hidden;
34
+ transform: translateY(-0.5rem) scale(0.98);
35
+ transition: transform 0.15s ease;
36
+ }
37
+
38
+ .search-overlay.is-open .search-modal {
39
+ transform: translateY(0) scale(1);
40
+ }
41
+
42
+ /* Search input area */
43
+ .search-input-wrap {
44
+ display: flex;
45
+ align-items: center;
46
+ gap: 0.75rem;
47
+ padding: 0.875rem 1.25rem;
48
+ border-bottom: 1px solid var(--nd-border);
49
+ }
50
+
51
+ .search-input-icon {
52
+ flex-shrink: 0;
53
+ width: 1.125rem;
54
+ height: 1.125rem;
55
+ color: var(--nd-muted);
56
+ }
57
+
58
+ .search-input {
59
+ flex: 1;
60
+ background: none;
61
+ border: none;
62
+ outline: none;
63
+ font-family: inherit;
64
+ font-size: 1rem;
65
+ color: var(--nd-fg);
66
+ }
67
+
68
+ .search-input::placeholder {
69
+ color: var(--nd-muted);
70
+ }
71
+
72
+ .search-input-kbd {
73
+ flex-shrink: 0;
74
+ display: flex;
75
+ align-items: center;
76
+ gap: 0.25rem;
77
+ font-family: 'Inter', system-ui, sans-serif;
78
+ font-size: 0.75rem;
79
+ color: var(--nd-muted);
80
+ padding: 0.15rem 0.4rem;
81
+ border: 1px solid var(--nd-border);
82
+ border-radius: 0.25rem;
83
+ background: var(--nd-card-bg);
84
+ }
85
+
86
+ /* Results list */
87
+ .search-results {
88
+ max-height: 24rem;
89
+ overflow-y: auto;
90
+ padding: 0.5rem;
91
+ scrollbar-width: thin;
92
+ scrollbar-color: var(--nd-border) transparent;
93
+ }
94
+
95
+ .search-results::-webkit-scrollbar {
96
+ width: 4px;
97
+ }
98
+
99
+ .search-results::-webkit-scrollbar-track {
100
+ background: transparent;
101
+ }
102
+
103
+ .search-results::-webkit-scrollbar-thumb {
104
+ background: var(--nd-border);
105
+ border-radius: 2px;
106
+ }
107
+
108
+ .search-results:empty::after {
109
+ content: '';
110
+ display: none;
111
+ }
112
+
113
+ .search-no-results {
114
+ padding: 2rem 1rem;
115
+ text-align: center;
116
+ font-size: 0.875rem;
117
+ color: var(--nd-muted);
118
+ }
119
+
120
+ .search-result {
121
+ display: flex;
122
+ flex-direction: column;
123
+ gap: 0.125rem;
124
+ padding: 0.625rem 0.75rem;
125
+ border-radius: calc(var(--nd-radius) - 2px);
126
+ cursor: pointer;
127
+ transition: background 0.1s ease;
128
+ }
129
+
130
+ .search-result:hover,
131
+ .search-result[aria-selected="true"] {
132
+ background: var(--nd-card-hover);
133
+ }
134
+
135
+ .search-result-title {
136
+ font-size: 0.875rem;
137
+ font-weight: 500;
138
+ color: var(--nd-fg);
139
+ }
140
+
141
+ .search-result-path {
142
+ font-size: 0.75rem;
143
+ color: var(--nd-muted);
144
+ }
145
+
146
+ .search-result mark {
147
+ background: hsla(250, 80%, 67%, 0.25);
148
+ color: var(--nd-accent);
149
+ border-radius: 2px;
150
+ padding: 0 1px;
151
+ }
152
+
153
+ /* Search footer */
154
+ .search-footer {
155
+ display: flex;
156
+ align-items: center;
157
+ justify-content: space-between;
158
+ padding: 0.5rem 1.25rem;
159
+ border-top: 1px solid var(--nd-border);
160
+ font-size: 0.75rem;
161
+ color: var(--nd-muted);
162
+ }
163
+
164
+ .search-footer-keys {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 0.75rem;
168
+ }
169
+
170
+ .search-footer-key {
171
+ display: flex;
172
+ align-items: center;
173
+ gap: 0.25rem;
174
+ }
175
+
176
+ .search-footer-key kbd {
177
+ display: inline-flex;
178
+ align-items: center;
179
+ justify-content: center;
180
+ min-width: 1.25rem;
181
+ height: 1.25rem;
182
+ padding: 0 0.25rem;
183
+ font-family: inherit;
184
+ font-size: 0.625rem;
185
+ border: 1px solid var(--nd-border);
186
+ border-radius: 0.2rem;
187
+ background: var(--nd-card-bg);
188
+ }
189
+
190
+ /* Trigger button (in header) */
191
+ .search-trigger {
192
+ display: flex;
193
+ align-items: center;
194
+ gap: 0.5rem;
195
+ padding: 0.375rem 0.75rem;
196
+ border: 1px solid var(--nd-border);
197
+ border-radius: var(--nd-radius);
198
+ background: var(--nd-card-bg);
199
+ color: var(--nd-muted);
200
+ font-family: inherit;
201
+ font-size: 0.875rem;
202
+ cursor: pointer;
203
+ transition: border-color 0.15s ease, color 0.15s ease;
204
+ min-width: 12rem;
205
+ }
206
+
207
+ .search-trigger:hover {
208
+ border-color: var(--nd-muted);
209
+ color: var(--nd-fg);
210
+ }
211
+
212
+ .search-trigger-icon {
213
+ width: 0.875rem;
214
+ height: 0.875rem;
215
+ }
216
+
217
+ .search-trigger-text {
218
+ flex: 1;
219
+ text-align: left;
220
+ }
221
+
222
+ .search-trigger-kbd {
223
+ font-size: 0.75rem;
224
+ opacity: 0.6;
225
+ }
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "forceConsistentCasingInFileNames": true,
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "noEmit": true,
15
+ "jsx": "react-jsx",
16
+ "jsxImportSource": "preact"
17
+ },
18
+ "include": ["src/**/*"]
19
+ }
@@ -0,0 +1,6 @@
1
+ import { defineConfig } from "vite";
2
+ import preact from "@preact/preset-vite";
3
+
4
+ export default defineConfig({
5
+ plugins: [preact()],
6
+ });
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ .neutron
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__PROJECT_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from "@neutron-build/core";
2
+
3
+ export default defineConfig({
4
+ runtime: "__RUNTIME__",
5
+ });
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "neutron dev",
8
+ "build": "neutron build",
9
+ "build:vercel": "neutron build --preset vercel",
10
+ "start": "neutron start",
11
+ "preview": "neutron preview",
12
+ "release:check": "neutron release-check --preset vercel"
13
+ },
14
+ "dependencies": {
15
+ "@neutron-build/core": "__NEUTRON_VERSION__",
16
+ "@neutron-build/cli": "__NEUTRON_CLI_VERSION__",
17
+ "preact": "^10.25.4"
18
+ },
19
+ "devDependencies": {
20
+ "@preact/preset-vite": "^2.9.4",
21
+ "typescript": "^5.7.2",
22
+ "vite": "^6.0.7"
23
+ }
24
+ }
@@ -0,0 +1,13 @@
1
+ import { useState } from "preact/hooks";
2
+
3
+ export function Counter(props: { start?: number }) {
4
+ const [count, setCount] = useState(props.start || 0);
5
+
6
+ return (
7
+ <div style="display: inline-flex; align-items: center; gap: 0.5rem;">
8
+ <button onClick={() => setCount((value) => value - 1)}>-</button>
9
+ <span>{count}</span>
10
+ <button onClick={() => setCount((value) => value + 1)}>+</button>
11
+ </div>
12
+ );
13
+ }
@@ -0,0 +1,5 @@
1
+ import { init, registerRoutes } from "@neutron-build/core/client";
2
+ import { routes } from "virtual:neutron/routes";
3
+
4
+ registerRoutes(routes);
5
+ void init();
@@ -0,0 +1,15 @@
1
+ export const config = { mode: "static" };
2
+
3
+ export default function Pricing() {
4
+ return (
5
+ <section>
6
+ <h2>Pricing</h2>
7
+ <p>This route lives in a <code>(marketing)</code> route group and renders at <code>/pricing</code>.</p>
8
+ <ul>
9
+ <li>Starter: $0</li>
10
+ <li>Pro: $29</li>
11
+ <li>Scale: Contact us</li>
12
+ </ul>
13
+ </section>
14
+ );
15
+ }
@@ -0,0 +1,17 @@
1
+ export default function Layout(props: { children?: unknown }) {
2
+ return (
3
+ <div style="max-width: 900px; margin: 0 auto; padding: 2rem; font-family: system-ui, sans-serif;">
4
+ <header style="margin-bottom: 1.5rem;">
5
+ <h1 style="margin: 0;">__PROJECT_NAME__</h1>
6
+ <p style="color: #666;">Neutron full-stack template</p>
7
+ </header>
8
+ <nav style="display: flex; gap: 0.75rem; margin-bottom: 1.5rem;">
9
+ <a href="/">Home</a>
10
+ <a href="/pricing">Pricing</a>
11
+ <a href="/app/dashboard">Dashboard</a>
12
+ <a href="/app/settings">Settings</a>
13
+ </nav>
14
+ <main>{props.children}</main>
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,28 @@
1
+ import type { LoaderArgs } from "@neutron-build/core";
2
+
3
+ export const config = { mode: "app", cache: { maxAge: 30, loaderMaxAge: 10 } };
4
+
5
+ export async function loader({ request }: LoaderArgs) {
6
+ const url = new URL(request.url);
7
+ const region = url.searchParams.get("region") || "us-east-1";
8
+ return {
9
+ region,
10
+ rps: 1200,
11
+ p95: 28,
12
+ loadedAt: new Date().toISOString(),
13
+ };
14
+ }
15
+
16
+ export default function Dashboard(props: {
17
+ data?: { region: string; rps: number; p95: number; loadedAt: string };
18
+ }) {
19
+ return (
20
+ <section>
21
+ <h2>Dashboard</h2>
22
+ <p>Region: {props.data?.region}</p>
23
+ <p>RPS: {props.data?.rps}</p>
24
+ <p>p95: {props.data?.p95} ms</p>
25
+ <p>Loaded at {props.data?.loadedAt}</p>
26
+ </section>
27
+ );
28
+ }
@@ -0,0 +1,42 @@
1
+ import { Form } from "@neutron-build/core/client";
2
+ import type { ActionArgs } from "@neutron-build/core";
3
+
4
+ export const config = { mode: "app" };
5
+
6
+ export async function loader() {
7
+ return { alerts: "all" };
8
+ }
9
+
10
+ export async function action({ request }: ActionArgs) {
11
+ const formData = await request.formData();
12
+ const alerts = String(formData.get("alerts") || "all");
13
+ return Response.json({ ok: true, alerts, savedAt: new Date().toISOString() });
14
+ }
15
+
16
+ export default function Settings(props: {
17
+ data?: { alerts: string };
18
+ actionData?: { ok: boolean; alerts: string; savedAt: string };
19
+ }) {
20
+ return (
21
+ <section>
22
+ <h2>Settings</h2>
23
+ <Form method="post" style={{ display: "grid", gap: "0.75rem", maxWidth: "360px" }}>
24
+ <label>
25
+ Alerts
26
+ <select name="alerts" defaultValue={props.data?.alerts || "all"}>
27
+ <option value="all">All</option>
28
+ <option value="critical">Critical only</option>
29
+ <option value="none">None</option>
30
+ </select>
31
+ </label>
32
+ <button type="submit">Save</button>
33
+ </Form>
34
+
35
+ {props.actionData?.ok ? (
36
+ <p style={{ marginTop: "1rem" }}>
37
+ Saved <strong>{props.actionData.alerts}</strong> at {props.actionData.savedAt}
38
+ </p>
39
+ ) : null}
40
+ </section>
41
+ );
42
+ }
@@ -0,0 +1,31 @@
1
+ import { Island } from "@neutron-build/core/client";
2
+ import { Counter } from "../components/Counter";
3
+
4
+ export const config = { mode: "static" };
5
+
6
+ export async function loader() {
7
+ return {
8
+ title: "__PROJECT_NAME__",
9
+ generatedAt: new Date().toISOString().slice(0, 19).replace("T", " "),
10
+ };
11
+ }
12
+
13
+ export default function Home(props: { data?: { title: string; generatedAt: string } }) {
14
+ return (
15
+ <section>
16
+ <h2>{props.data?.title}</h2>
17
+ <p>
18
+ Static landing page with optional islands + app route links.
19
+ </p>
20
+ <ul>
21
+ <li><a href="/pricing">Pricing (route group)</a></li>
22
+ <li><a href="/app/dashboard">Dashboard (SSR app route)</a></li>
23
+ <li><a href="/app/settings">Settings (action + Form)</a></li>
24
+ </ul>
25
+ <Island component={Counter} client="visible" id="full-counter" start={2} />
26
+ <p style={{ marginTop: "1rem" }}>
27
+ Generated at <strong>{props.data?.generatedAt}</strong>.
28
+ </p>
29
+ </section>
30
+ );
31
+ }
@@ -0,0 +1,26 @@
1
+ import type { LoaderArgs } from "@neutron-build/core";
2
+
3
+ export const config = { mode: "app", cache: { maxAge: 30 } };
4
+
5
+ export async function loader({ params }: LoaderArgs) {
6
+ const id = params.id || "1";
7
+ return {
8
+ user: {
9
+ id,
10
+ name: `User ${id}`,
11
+ email: `user${id}@example.com`,
12
+ },
13
+ };
14
+ }
15
+
16
+ export default function UserRoute(props: {
17
+ data?: { user: { id: string; name: string; email: string } };
18
+ }) {
19
+ return (
20
+ <section>
21
+ <h2>{props.data?.user.name}</h2>
22
+ <p>ID: {props.data?.user.id}</p>
23
+ <p>Email: {props.data?.user.email}</p>
24
+ </section>
25
+ );
26
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM"],
7
+ "jsx": "react-jsx",
8
+ "jsxImportSource": "preact",
9
+ "strict": true,
10
+ "skipLibCheck": true,
11
+ "types": ["vite/client"]
12
+ },
13
+ "include": ["src"]
14
+ }
@@ -0,0 +1,7 @@
1
+ import { defineConfig } from "vite";
2
+ import preact from "@preact/preset-vite";
3
+ import { neutronPlugin } from "@neutron-build/core/vite";
4
+
5
+ export default defineConfig({
6
+ plugins: [preact(), neutronPlugin()],
7
+ });
@@ -0,0 +1,3 @@
1
+ node_modules
2
+ dist
3
+ .neutron
@@ -0,0 +1,12 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <title>__PROJECT_NAME__</title>
7
+ </head>
8
+ <body>
9
+ <div id="app"></div>
10
+ <script type="module" src="/src/main.tsx"></script>
11
+ </body>
12
+ </html>
@@ -0,0 +1,5 @@
1
+ import { defineConfig } from "@neutron-build/core";
2
+
3
+ export default defineConfig({
4
+ runtime: "__RUNTIME__",
5
+ });
@@ -0,0 +1,24 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "private": true,
4
+ "version": "0.0.1",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "neutron dev",
8
+ "build": "neutron build",
9
+ "build:static": "neutron build --preset static",
10
+ "start": "neutron start",
11
+ "preview": "neutron preview",
12
+ "release:check": "neutron release-check --preset static"
13
+ },
14
+ "dependencies": {
15
+ "@neutron-build/core": "__NEUTRON_VERSION__",
16
+ "@neutron-build/cli": "__NEUTRON_CLI_VERSION__",
17
+ "preact": "^10.25.4"
18
+ },
19
+ "devDependencies": {
20
+ "@preact/preset-vite": "^2.9.4",
21
+ "typescript": "^5.7.2",
22
+ "vite": "^6.0.7"
23
+ }
24
+ }
@@ -0,0 +1,14 @@
1
+ import { useState } from "preact/hooks";
2
+
3
+ export function Counter(props: { start?: number }) {
4
+ const [count, setCount] = useState(props.start || 0);
5
+
6
+ return (
7
+ <div style="margin-top: 1rem; display: inline-flex; gap: 0.5rem; align-items: center;">
8
+ <strong>Island Counter:</strong>
9
+ <button onClick={() => setCount((value) => value - 1)}>-</button>
10
+ <span>{count}</span>
11
+ <button onClick={() => setCount((value) => value + 1)}>+</button>
12
+ </div>
13
+ );
14
+ }
@@ -0,0 +1,5 @@
1
+ import { init, registerRoutes } from "@neutron-build/core/client";
2
+ import { routes } from "virtual:neutron/routes";
3
+
4
+ registerRoutes(routes);
5
+ void init();
@@ -0,0 +1,16 @@
1
+ export default function Layout(props: { children?: unknown }) {
2
+ return (
3
+ <div style="max-width: 920px; margin: 0 auto; padding: 2rem; font-family: system-ui, sans-serif;">
4
+ <header style="margin-bottom: 1.5rem;">
5
+ <h1 style="margin: 0;">__PROJECT_NAME__</h1>
6
+ <p style="color: #666; margin-top: 0.5rem;">Neutron marketing template</p>
7
+ </header>
8
+ <nav style="display: flex; gap: 0.75rem; margin-bottom: 1.5rem;">
9
+ <a href="/">Home</a>
10
+ <a href="/about">About</a>
11
+ <a href="/blog">Blog</a>
12
+ </nav>
13
+ <main>{props.children}</main>
14
+ </div>
15
+ );
16
+ }
@@ -0,0 +1,10 @@
1
+ export const config = { mode: "static" };
2
+
3
+ export default function About() {
4
+ return (
5
+ <section>
6
+ <h2>About</h2>
7
+ <p>This route ships as static HTML by default.</p>
8
+ </section>
9
+ );
10
+ }
@@ -0,0 +1,34 @@
1
+ const posts = [
2
+ { slug: "launch", title: "Launch Day", body: "We launched a static-first site." },
3
+ { slug: "roadmap", title: "Roadmap", body: "Next we add app routes where needed." },
4
+ ];
5
+
6
+ export const config = { mode: "static" };
7
+
8
+ export async function getStaticPaths() {
9
+ return {
10
+ paths: posts.map((post) => ({
11
+ params: { slug: post.slug },
12
+ props: { post },
13
+ })),
14
+ };
15
+ }
16
+
17
+ export async function loader({ params }: { params: { slug?: string } }) {
18
+ const post = posts.find((entry) => entry.slug === params.slug) || posts[0];
19
+ return { post };
20
+ }
21
+
22
+ export default function BlogPost(props: {
23
+ data?: { post: { title: string; body: string } };
24
+ }) {
25
+ return (
26
+ <article>
27
+ <h2>{props.data?.post?.title}</h2>
28
+ <p>{props.data?.post?.body}</p>
29
+ <p>
30
+ <a href="/blog">Back to blog</a>
31
+ </p>
32
+ </article>
33
+ );
34
+ }
@@ -0,0 +1,27 @@
1
+ const posts = [
2
+ { slug: "launch", title: "Launch Day" },
3
+ { slug: "roadmap", title: "Roadmap" },
4
+ ];
5
+
6
+ export const config = { mode: "static" };
7
+
8
+ export async function loader() {
9
+ return { posts };
10
+ }
11
+
12
+ export default function BlogIndex(props: {
13
+ data?: { posts: Array<{ slug: string; title: string }> };
14
+ }) {
15
+ return (
16
+ <section>
17
+ <h2>Blog</h2>
18
+ <ul>
19
+ {props.data?.posts.map((post) => (
20
+ <li key={post.slug}>
21
+ <a href={"/blog/" + post.slug}>{post.title}</a>
22
+ </li>
23
+ ))}
24
+ </ul>
25
+ </section>
26
+ );
27
+ }