zero-query 0.9.7 → 0.9.9

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 (72) hide show
  1. package/README.md +55 -4
  2. package/cli/commands/build.js +50 -3
  3. package/cli/commands/create.js +58 -11
  4. package/cli/help.js +4 -0
  5. package/cli/scaffold/default/app/app.js +211 -0
  6. package/cli/scaffold/default/app/components/about.js +201 -0
  7. package/cli/scaffold/default/app/components/api-demo.js +143 -0
  8. package/cli/scaffold/default/app/components/contact-card.js +231 -0
  9. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -0
  10. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -0
  11. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -0
  12. package/cli/scaffold/default/app/components/counter.js +127 -0
  13. package/cli/scaffold/default/app/components/home.js +249 -0
  14. package/cli/scaffold/{app → default/app}/components/not-found.js +2 -2
  15. package/cli/scaffold/default/app/components/playground/playground.css +116 -0
  16. package/cli/scaffold/default/app/components/playground/playground.html +162 -0
  17. package/cli/scaffold/default/app/components/playground/playground.js +117 -0
  18. package/cli/scaffold/default/app/components/todos.js +225 -0
  19. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -0
  20. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -0
  21. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -0
  22. package/cli/scaffold/default/app/routes.js +15 -0
  23. package/cli/scaffold/{app → default/app}/store.js +15 -10
  24. package/cli/scaffold/{global.css → default/global.css} +238 -252
  25. package/cli/scaffold/{index.html → default/index.html} +35 -0
  26. package/cli/scaffold/{app → minimal/app}/app.js +37 -39
  27. package/cli/scaffold/minimal/app/components/about.js +68 -0
  28. package/cli/scaffold/minimal/app/components/counter.js +122 -0
  29. package/cli/scaffold/minimal/app/components/home.js +68 -0
  30. package/cli/scaffold/minimal/app/components/not-found.js +16 -0
  31. package/cli/scaffold/minimal/app/routes.js +9 -0
  32. package/cli/scaffold/minimal/app/store.js +36 -0
  33. package/cli/scaffold/minimal/assets/.gitkeep +0 -0
  34. package/cli/scaffold/minimal/global.css +291 -0
  35. package/cli/scaffold/minimal/index.html +44 -0
  36. package/cli/scaffold/ssr/app/app.js +30 -0
  37. package/cli/scaffold/ssr/app/components/about.js +28 -0
  38. package/cli/scaffold/ssr/app/components/home.js +37 -0
  39. package/cli/scaffold/ssr/app/components/not-found.js +15 -0
  40. package/cli/scaffold/ssr/app/routes.js +6 -0
  41. package/cli/scaffold/ssr/global.css +113 -0
  42. package/cli/scaffold/ssr/index.html +31 -0
  43. package/cli/scaffold/ssr/package.json +8 -0
  44. package/cli/scaffold/ssr/server/index.js +118 -0
  45. package/dist/zquery.dist.zip +0 -0
  46. package/dist/zquery.js +2006 -1933
  47. package/dist/zquery.min.js +2 -2
  48. package/index.d.ts +20 -1
  49. package/index.js +8 -5
  50. package/package.json +8 -2
  51. package/src/component.js +6 -3
  52. package/src/diff.js +15 -2
  53. package/src/errors.js +59 -5
  54. package/src/package.json +1 -0
  55. package/src/ssr.js +116 -22
  56. package/tests/cli.test.js +304 -0
  57. package/tests/errors.test.js +423 -145
  58. package/tests/ssr.test.js +435 -3
  59. package/types/errors.d.ts +34 -2
  60. package/types/ssr.d.ts +21 -1
  61. package/cli/scaffold/app/components/about.js +0 -131
  62. package/cli/scaffold/app/components/api-demo.js +0 -103
  63. package/cli/scaffold/app/components/contacts/contacts.css +0 -246
  64. package/cli/scaffold/app/components/contacts/contacts.html +0 -140
  65. package/cli/scaffold/app/components/contacts/contacts.js +0 -153
  66. package/cli/scaffold/app/components/counter.js +0 -85
  67. package/cli/scaffold/app/components/home.js +0 -137
  68. package/cli/scaffold/app/components/todos.js +0 -131
  69. package/cli/scaffold/app/routes.js +0 -13
  70. /package/cli/scaffold/{LICENSE → default/LICENSE} +0 -0
  71. /package/cli/scaffold/{assets → default/assets}/.gitkeep +0 -0
  72. /package/cli/scaffold/{favicon.ico → default/favicon.ico} +0 -0
@@ -0,0 +1,291 @@
1
+ /* global.css — minimal scaffold styles
2
+ *
3
+ * Dark theme by default, light theme via [data-theme="light"].
4
+ * Modify the CSS variables below to customise the look and feel.
5
+ */
6
+
7
+ /* -- Theme Variables -- */
8
+ :root {
9
+ --bg: #0d1117;
10
+ --bg-surface: #161b22;
11
+ --bg-card: rgba(22, 27, 34, 0.55);
12
+ --bg-hover: #21262d;
13
+ --border: rgba(48, 54, 61, 0.45);
14
+ --text: #e6edf3;
15
+ --text-muted: #7d8590;
16
+ --accent: #58a6ff;
17
+ --accent-hover:#79c0ff;
18
+ --accent-soft: rgba(88, 166, 255, 0.10);
19
+ --accent-glow: rgba(88, 166, 255, 0.06);
20
+ --success: #3fb950;
21
+ --danger: #f85149;
22
+ --radius: 8px;
23
+ --radius-lg: 12px;
24
+ --sidebar-w: 220px;
25
+ --topbar-h: 56px;
26
+ --font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
27
+ --ease-out: cubic-bezier(0.22, 1, 0.36, 1);
28
+ }
29
+
30
+ [data-theme="light"] {
31
+ --bg: #f6f8fa;
32
+ --bg-surface: #ffffff;
33
+ --bg-card: rgba(255, 255, 255, 0.8);
34
+ --bg-hover: #eaeef2;
35
+ --border: rgba(208, 215, 222, 0.65);
36
+ --text: #1f2328;
37
+ --text-muted: #656d76;
38
+ --accent: #0969da;
39
+ --accent-hover:#0550ae;
40
+ --accent-soft: rgba(9,105,218,0.08);
41
+ --accent-glow: rgba(9,105,218,0.04);
42
+ --success: #1a7f37;
43
+ --danger: #cf222e;
44
+ }
45
+
46
+ /* -- Reset -- */
47
+ *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
48
+
49
+ body {
50
+ font-family: var(--font);
51
+ font-size: 15px;
52
+ line-height: 1.6;
53
+ color: var(--text);
54
+ background: var(--bg);
55
+ overflow-x: hidden;
56
+ }
57
+
58
+ a { color: var(--accent); text-decoration: none; transition: color .15s; }
59
+ a:hover { color: var(--accent-hover); }
60
+ code { background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-size: 0.85em; }
61
+
62
+ /* -- Sidebar -- */
63
+ .sidebar {
64
+ position: fixed;
65
+ top: 0; left: 0; bottom: 0;
66
+ width: var(--sidebar-w);
67
+ background: var(--bg-surface);
68
+ border-right: 1px solid var(--border);
69
+ display: flex;
70
+ flex-direction: column;
71
+ z-index: 100;
72
+ transition: transform 0.3s var(--ease-out);
73
+ }
74
+
75
+ .sidebar-header {
76
+ padding: 1.25rem 1.25rem 1rem;
77
+ border-bottom: 1px solid var(--border);
78
+ }
79
+
80
+ .brand {
81
+ font-size: 1.15rem;
82
+ font-weight: 700;
83
+ color: var(--text);
84
+ letter-spacing: -0.02em;
85
+ }
86
+
87
+ .sidebar-nav {
88
+ flex: 1;
89
+ padding: 0.75rem 0;
90
+ overflow-y: auto;
91
+ }
92
+
93
+ .nav-link {
94
+ display: block;
95
+ padding: 0.6rem 1.25rem;
96
+ color: var(--text-muted);
97
+ font-size: 0.9rem;
98
+ font-weight: 500;
99
+ transition: all 0.15s ease;
100
+ border-left: 3px solid transparent;
101
+ }
102
+
103
+ .nav-link:hover {
104
+ color: var(--text);
105
+ background: var(--bg-hover);
106
+ }
107
+
108
+ .nav-link.active {
109
+ color: var(--accent);
110
+ background: var(--accent-soft);
111
+ border-left-color: var(--accent);
112
+ }
113
+
114
+ .sidebar-footer {
115
+ padding: 0.75rem 1.25rem;
116
+ border-top: 1px solid var(--border);
117
+ color: var(--text-muted);
118
+ font-size: 0.8rem;
119
+ }
120
+
121
+ /* -- Mobile Top Bar -- */
122
+ .topbar {
123
+ display: none;
124
+ position: fixed;
125
+ top: 0; left: 0; right: 0;
126
+ height: var(--topbar-h);
127
+ background: var(--bg-surface);
128
+ border-bottom: 1px solid var(--border);
129
+ align-items: center;
130
+ padding: 0 1rem;
131
+ z-index: 101;
132
+ }
133
+
134
+ .topbar-brand {
135
+ font-weight: 700;
136
+ font-size: 1.05rem;
137
+ margin-left: 0.75rem;
138
+ }
139
+
140
+ /* Hamburger */
141
+ .hamburger {
142
+ display: flex;
143
+ flex-direction: column;
144
+ justify-content: center;
145
+ gap: 5px;
146
+ width: 32px;
147
+ height: 32px;
148
+ background: none;
149
+ border: none;
150
+ cursor: pointer;
151
+ padding: 4px;
152
+ }
153
+
154
+ .hamburger span {
155
+ display: block;
156
+ height: 2px;
157
+ background: var(--text);
158
+ border-radius: 2px;
159
+ transition: all 0.25s var(--ease-out);
160
+ }
161
+
162
+ .hamburger.active span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
163
+ .hamburger.active span:nth-child(2) { opacity: 0; }
164
+ .hamburger.active span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }
165
+
166
+ /* Overlay */
167
+ .overlay {
168
+ display: none;
169
+ position: fixed;
170
+ inset: 0;
171
+ background: rgba(0, 0, 0, 0.5);
172
+ z-index: 99;
173
+ opacity: 0;
174
+ transition: opacity 0.3s ease;
175
+ }
176
+
177
+ .overlay.visible { display: block; opacity: 1; }
178
+
179
+ /* -- Main Content -- */
180
+ .content {
181
+ margin-left: auto;
182
+ margin-right: auto;
183
+ padding: 2.5rem 3rem 2.5rem calc(var(--sidebar-w) + 3rem);
184
+ max-width: calc(1010px + var(--sidebar-w));
185
+ min-height: 100vh;
186
+ }
187
+
188
+ /* -- Page Header -- */
189
+ .page-header { margin-bottom: 2rem; }
190
+ .page-header h1 { font-size: 1.85rem; font-weight: 700; margin-bottom: 0.35rem; letter-spacing: -0.02em; }
191
+ .subtitle { color: var(--text-muted); font-size: 0.95rem; }
192
+
193
+ /* -- Cards -- */
194
+ .card {
195
+ background: var(--bg-card);
196
+ border: 1px solid var(--border);
197
+ border-radius: var(--radius-lg);
198
+ padding: 1.5rem;
199
+ margin-bottom: 1.25rem;
200
+ backdrop-filter: blur(8px);
201
+ -webkit-backdrop-filter: blur(8px);
202
+ transition: border-color .2s ease, box-shadow .2s ease;
203
+ }
204
+
205
+ .card:hover {
206
+ border-color: rgba(88, 166, 255, 0.15);
207
+ box-shadow: 0 4px 24px rgba(0, 0, 0, 0.15), 0 0 0 1px rgba(88, 166, 255, 0.04);
208
+ }
209
+
210
+ .card h3 { font-size: 1.1rem; margin-bottom: 0.5rem; }
211
+ .card p { color: var(--text-muted); font-size: 0.9rem; margin-bottom: 0.75rem; }
212
+
213
+ /* -- Buttons -- */
214
+ .btn {
215
+ display: inline-flex;
216
+ align-items: center;
217
+ gap: 0.4rem;
218
+ padding: 0.5rem 1.1rem;
219
+ font-size: 0.9rem;
220
+ font-weight: 500;
221
+ border: none;
222
+ border-radius: var(--radius);
223
+ cursor: pointer;
224
+ transition: all 0.15s ease;
225
+ font-family: inherit;
226
+ }
227
+
228
+ .btn:active { transform: scale(0.97); }
229
+
230
+ .btn-primary { background: var(--accent); color: #fff; }
231
+ .btn-primary:hover { background: var(--accent-hover); color: #fff; }
232
+
233
+ .btn-outline { background: transparent; border: 1px solid var(--border); color: var(--accent); }
234
+ .btn-outline:hover { border-color: var(--accent); background: var(--accent-soft); }
235
+
236
+ .btn-ghost { background: transparent; color: var(--text-muted); }
237
+ .btn-ghost:hover { color: var(--text); background: var(--bg-hover); }
238
+
239
+ .btn-sm { padding: 0.35rem 0.75rem; font-size: 0.82rem; }
240
+
241
+ /* -- Inputs -- */
242
+ .input {
243
+ width: 100%;
244
+ padding: 0.55rem 0.85rem;
245
+ background: var(--bg);
246
+ border: 1px solid var(--border);
247
+ border-radius: var(--radius);
248
+ color: var(--text);
249
+ font-size: 0.9rem;
250
+ font-family: inherit;
251
+ outline: none;
252
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
253
+ }
254
+
255
+ .input:hover { border-color: rgba(88,166,255,0.2); }
256
+ .input:focus { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(88,166,255,0.1); }
257
+ .input-sm { width: auto; padding: 0.35rem 0.6rem; font-size: 0.82rem; }
258
+
259
+ .muted { color: var(--text-muted); }
260
+
261
+ /* -- Route Transition -- */
262
+ #app { animation: fade-in 0.25s var(--ease-out); }
263
+ @keyframes fade-in { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: translateY(0); } }
264
+
265
+ /* -- Responsive: Mobile -- */
266
+ @media (max-width: 768px) {
267
+ .sidebar {
268
+ transform: translateX(-100%);
269
+ width: 260px;
270
+ }
271
+ .sidebar.open { transform: translateX(0); }
272
+
273
+ .topbar { display: flex; }
274
+
275
+ .content {
276
+ margin-left: 0;
277
+ padding: 1.25rem;
278
+ padding-top: calc(var(--topbar-h) + 1.25rem);
279
+ max-width: 100%;
280
+ }
281
+
282
+ .card { padding: 1.15rem; }
283
+ .page-header h1 { font-size: 1.4rem; }
284
+ }
285
+
286
+ @media (max-width: 480px) {
287
+ .content { padding: 0.75rem; padding-top: calc(var(--topbar-h) + 0.75rem); }
288
+ .card { padding: 1rem; }
289
+ .page-header h1 { font-size: 1.25rem; }
290
+ .subtitle { font-size: 0.85rem; }
291
+ }
@@ -0,0 +1,44 @@
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>{{NAME}}</title>
7
+ <base href="/">
8
+ <link rel="stylesheet" href="global.css">
9
+ <script src="zquery.min.js"></script>
10
+ <script type="module" src="app/app.js"></script>
11
+ </head>
12
+ <body>
13
+
14
+ <!-- Sidebar Navigation -->
15
+ <aside class="sidebar" id="sidebar">
16
+ <div class="sidebar-header">
17
+ <span class="brand">⚡ {{NAME}}</span>
18
+ </div>
19
+ <nav class="sidebar-nav">
20
+ <a z-link="/" class="nav-link">Home</a>
21
+ <a z-link="/counter" class="nav-link">Counter</a>
22
+ <a z-link="/about" class="nav-link">About</a>
23
+ </nav>
24
+ <div class="sidebar-footer">
25
+ <small>zQuery <span id="nav-version"></span></small>
26
+ </div>
27
+ </aside>
28
+
29
+ <!-- Mobile Top Bar -->
30
+ <header class="topbar" id="topbar">
31
+ <button class="hamburger" id="menu-toggle" aria-label="Toggle menu">
32
+ <span></span><span></span><span></span>
33
+ </button>
34
+ <span class="topbar-brand">⚡ {{NAME}}</span>
35
+ </header>
36
+
37
+ <!-- Overlay for mobile menu -->
38
+ <div class="overlay" id="overlay"></div>
39
+
40
+ <!-- Router Outlet -->
41
+ <main class="content" id="app"></main>
42
+
43
+ </body>
44
+ </html>
@@ -0,0 +1,30 @@
1
+ // app.js — Client entry point
2
+ //
3
+ // Imports shared component definitions and registers them with zQuery.
4
+ // The SSR server imports the same definitions via createSSRApp().
5
+
6
+ import { homePage } from './components/home.js';
7
+ import { aboutPage } from './components/about.js';
8
+ import { notFound } from './components/not-found.js';
9
+ import { routes } from './routes.js';
10
+
11
+ // Register shared component definitions on the client
12
+ $.component('home-page', homePage);
13
+ $.component('about-page', aboutPage);
14
+ $.component('not-found', notFound);
15
+
16
+ // Client-side router
17
+ const router = $.router({
18
+ el: '#app',
19
+ routes,
20
+ fallback: 'not-found',
21
+ mode: 'history'
22
+ });
23
+
24
+ // Active nav highlighting
25
+ router.onChange((to) => {
26
+ $.qsa('.nav-link').forEach(link => {
27
+ const href = link.getAttribute('z-link') || link.getAttribute('href');
28
+ link.classList.toggle('active', href === to.path);
29
+ });
30
+ });
@@ -0,0 +1,28 @@
1
+ // about.js — About page component
2
+
3
+ export const aboutPage = {
4
+ state: () => ({
5
+ features: [
6
+ 'createSSRApp() — isolated component registry for Node.js',
7
+ 'renderToString() — render a component to an HTML string',
8
+ 'renderPage() — full HTML document with meta tags',
9
+ 'renderBatch() — render multiple components in one call',
10
+ 'Hydration markers (data-zq-ssr) for client takeover',
11
+ 'SEO: description, canonical URL, Open Graph tags',
12
+ ]
13
+ }),
14
+
15
+ render() {
16
+ const list = this.state.features.map(f => `<li>${f}</li>`).join('');
17
+ return `
18
+ <div class="page-header">
19
+ <h1>About</h1>
20
+ <p class="subtitle">SSR capabilities in this scaffold.</p>
21
+ </div>
22
+ <div class="card">
23
+ <h3>SSR API</h3>
24
+ <ul style="padding-left:1.2rem; line-height:2;">${list}</ul>
25
+ </div>
26
+ `;
27
+ }
28
+ };
@@ -0,0 +1,37 @@
1
+ // home.js — Home page component
2
+ //
3
+ // Exports a plain definition object that works on both client and server.
4
+ // The client registers it with $.component(), the server with app.component().
5
+
6
+ export const homePage = {
7
+ state: () => ({
8
+ greeting: 'Hello',
9
+ timestamp: new Date().toLocaleTimeString(),
10
+ }),
11
+
12
+ // init() runs on both client and server — no DOM required
13
+ init() {
14
+ const hour = new Date().getHours();
15
+ this.state.greeting =
16
+ hour < 12 ? 'Good morning' :
17
+ hour < 18 ? 'Good afternoon' : 'Good evening';
18
+ },
19
+
20
+ render() {
21
+ return `
22
+ <div class="page-header">
23
+ <h1>${this.state.greeting} 👋</h1>
24
+ <p class="subtitle">Rendered with zQuery SSR</p>
25
+ </div>
26
+ <div class="card">
27
+ <h3>Server-Side Rendering</h3>
28
+ <p>
29
+ This page was rendered to HTML on the server and served as a complete
30
+ document. The same component definition powers both the SSR server and
31
+ the client-side SPA.
32
+ </p>
33
+ <p>Rendered at <strong>${this.state.timestamp}</strong></p>
34
+ </div>
35
+ `;
36
+ }
37
+ };
@@ -0,0 +1,15 @@
1
+ // not-found.js — 404 fallback component
2
+
3
+ export const notFound = {
4
+ render() {
5
+ return `
6
+ <div class="page-header" style="text-align:center; margin-top:4rem;">
7
+ <h1>404</h1>
8
+ <p class="subtitle">Page not found.</p>
9
+ <p style="margin-top:1rem;">
10
+ <a href="/" style="color:var(--accent);">← Home</a>
11
+ </p>
12
+ </div>
13
+ `;
14
+ }
15
+ };
@@ -0,0 +1,6 @@
1
+ // routes.js — Route definitions (shared between client and server)
2
+
3
+ export const routes = [
4
+ { path: '/', component: 'home-page' },
5
+ { path: '/about', component: 'about-page' },
6
+ ];
@@ -0,0 +1,113 @@
1
+ /* global.css — SSR Scaffold Styles */
2
+
3
+ *, *::before, *::after {
4
+ box-sizing: border-box;
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ :root {
10
+ --bg: #0f0f0f;
11
+ --surface: #1a1a2e;
12
+ --text: #e0e0e0;
13
+ --muted: #888;
14
+ --accent: #00d4ff;
15
+ --accent-hover: #00b8d9;
16
+ --radius: 8px;
17
+ --gap: 1.5rem;
18
+ }
19
+
20
+ body {
21
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
22
+ background: var(--bg);
23
+ color: var(--text);
24
+ line-height: 1.6;
25
+ min-height: 100vh;
26
+ }
27
+
28
+ /* -- Navbar -- */
29
+ .navbar {
30
+ display: flex;
31
+ align-items: center;
32
+ justify-content: space-between;
33
+ padding: 1rem 2rem;
34
+ background: var(--surface);
35
+ border-bottom: 1px solid rgba(255,255,255,0.06);
36
+ }
37
+
38
+ .brand {
39
+ font-weight: 700;
40
+ font-size: 1.2rem;
41
+ color: var(--accent);
42
+ }
43
+
44
+ .nav-links { display: flex; gap: 1rem; }
45
+
46
+ .nav-link {
47
+ color: var(--muted);
48
+ text-decoration: none;
49
+ padding: 0.4rem 0.8rem;
50
+ border-radius: var(--radius);
51
+ transition: color 0.2s, background 0.2s;
52
+ }
53
+ .nav-link:hover, .nav-link.active {
54
+ color: var(--accent);
55
+ background: rgba(0, 212, 255, 0.08);
56
+ }
57
+
58
+ /* -- Main content -- */
59
+ #app {
60
+ max-width: 800px;
61
+ margin: 2rem auto;
62
+ padding: 0 1.5rem;
63
+ }
64
+
65
+ [z-cloak] { display: none !important; }
66
+
67
+ .page-header {
68
+ margin-bottom: var(--gap);
69
+ }
70
+ .page-header h1 {
71
+ font-size: 2rem;
72
+ margin-bottom: 0.5rem;
73
+ }
74
+ .page-header .subtitle {
75
+ color: var(--muted);
76
+ }
77
+
78
+ .card {
79
+ background: var(--surface);
80
+ border: 1px solid rgba(255,255,255,0.06);
81
+ border-radius: var(--radius);
82
+ padding: 1.5rem;
83
+ margin-bottom: var(--gap);
84
+ }
85
+ .card h3 {
86
+ margin-bottom: 0.75rem;
87
+ color: var(--accent);
88
+ }
89
+ .card code {
90
+ background: rgba(255,255,255,0.06);
91
+ padding: 0.15em 0.4em;
92
+ border-radius: 4px;
93
+ font-size: 0.9em;
94
+ }
95
+
96
+ .badge {
97
+ display: inline-block;
98
+ padding: 2px 8px;
99
+ border-radius: 4px;
100
+ font-size: 0.8em;
101
+ font-weight: 600;
102
+ }
103
+ .badge-ssr { background: rgba(0,212,255,0.15); color: var(--accent); }
104
+ .badge-csr { background: rgba(255,165,0,0.15); color: #ffa500; }
105
+
106
+ /* -- Footer -- */
107
+ .footer {
108
+ text-align: center;
109
+ padding: 2rem;
110
+ color: var(--muted);
111
+ border-top: 1px solid rgba(255,255,255,0.06);
112
+ margin-top: 3rem;
113
+ }
@@ -0,0 +1,31 @@
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>{{NAME}}</title>
7
+ <base href="/">
8
+ <link rel="stylesheet" href="global.css">
9
+ <script src="zquery.min.js"></script>
10
+ <script type="module" src="app/app.js"></script>
11
+ </head>
12
+ <body>
13
+
14
+ <!-- Navigation -->
15
+ <nav class="navbar">
16
+ <span class="brand">⚡ {{NAME}}</span>
17
+ <div class="nav-links">
18
+ <a z-link="/" class="nav-link">Home</a>
19
+ <a z-link="/about" class="nav-link">About</a>
20
+ </div>
21
+ </nav>
22
+
23
+ <!-- Main content — SSR output is injected here by the server -->
24
+ <main id="app" z-cloak></main>
25
+
26
+ <footer class="footer">
27
+ <small>Built with zQuery · SSR Scaffold</small>
28
+ </footer>
29
+
30
+ </body>
31
+ </html>
@@ -0,0 +1,8 @@
1
+ {
2
+ "name": "{{NAME}}",
3
+ "private": true,
4
+ "type": "module",
5
+ "dependencies": {
6
+ "zero-query": "latest"
7
+ }
8
+ }