@velox0/cerver 0.4.2 → 0.5.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.
@@ -17,7 +17,7 @@ function newProject(name) {
17
17
  console.log(`\n Creating cerver project: ${name}\n`);
18
18
 
19
19
  // Create directory structure
20
- const dirs = ["", "app", "app/routes", "public", "dist"];
20
+ const dirs = ["", "routes", "public", "public/about", "dist"];
21
21
 
22
22
  for (const dir of dirs) {
23
23
  fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
@@ -35,7 +35,7 @@ function newProject(name) {
35
35
  // Default route
36
36
  fs.copyFileSync(
37
37
  path.join(templatesDir, "index.route.js"),
38
- path.join(projectDir, "app", "routes", "index.js"),
38
+ path.join(projectDir, "routes", "index.js"),
39
39
  );
40
40
 
41
41
  // Default public/index.html
@@ -48,264 +48,13 @@ function newProject(name) {
48
48
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
49
49
  <title>${name}</title>
50
50
  <link rel="icon" href="/favicon.ico" type="image/x-icon">
51
- <link rel="preconnect" href="https://fonts.googleapis.com">
52
- <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
53
- <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Unbounded:wght@500;700&display=swap" rel="stylesheet">
54
- <style>
55
- :root {
56
- --ink: #121316;
57
- --muted: #4a4f57;
58
- --paper: #f7f4f1;
59
- --glass: rgba(255, 255, 255, 0.7);
60
- --edge: rgba(255, 255, 255, 0.6);
61
- --pink: #ff7abf;
62
- --peach: #ffb380;
63
- --mint: #7de3c9;
64
- --blue: #6aa9ff;
65
- --shadow: rgba(18, 19, 22, 0.18);
66
- }
67
- * {
68
- box-sizing: border-box;
69
- }
70
- body {
71
- margin: 0;
72
- font-family: "Space Grotesk", "Segoe UI", sans-serif;
73
- color: var(--ink);
74
- min-height: 100vh;
75
- display: grid;
76
- place-items: center;
77
- background-color: var(--paper);
78
- background-image:
79
- radial-gradient(circle at 15% 10%, rgba(255, 122, 191, 0.35), transparent 45%),
80
- radial-gradient(circle at 85% 12%, rgba(255, 179, 128, 0.4), transparent 50%),
81
- radial-gradient(circle at 82% 82%, rgba(122, 214, 255, 0.35), transparent 55%),
82
- radial-gradient(circle at 20% 80%, rgba(125, 227, 201, 0.35), transparent 55%),
83
- linear-gradient(120deg, #f7f4f1 0%, #f2f7ff 100%);
84
- }
85
- body::before,
86
- body::after {
87
- content: "";
88
- position: fixed;
89
- inset: -20% -10%;
90
- pointer-events: none;
91
- }
92
- body::before {
93
- background:
94
- conic-gradient(from 200deg at 50% 50%, rgba(255, 122, 191, 0.08), rgba(122, 214, 255, 0.08), rgba(125, 227, 201, 0.08), rgba(255, 179, 128, 0.08));
95
- filter: blur(60px);
96
- opacity: 0.6;
97
- }
98
- body::after {
99
- background-image: radial-gradient(circle, rgba(18, 19, 22, 0.04) 1px, transparent 1px);
100
- background-size: 24px 24px;
101
- opacity: 0.6;
102
- }
103
- .stage {
104
- width: min(1100px, 92vw);
105
- padding: 4rem 0;
106
- }
107
- .frame {
108
- position: relative;
109
- display: grid;
110
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
111
- gap: 2.5rem;
112
- align-items: center;
113
- padding: clamp(2.5rem, 4vw, 4rem);
114
- border-radius: 32px;
115
- background: var(--glass);
116
- border: 1px solid var(--edge);
117
- box-shadow:
118
- 0 40px 90px -40px var(--shadow),
119
- inset 0 1px 0 rgba(255, 255, 255, 0.7);
120
- backdrop-filter: blur(18px);
121
- -webkit-backdrop-filter: blur(18px);
122
- animation: rise 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
123
- }
124
- h1 {
125
- margin: 0.6rem 0 1rem;
126
- font-family: "Unbounded", "Space Grotesk", sans-serif;
127
- font-size: clamp(2.2rem, 4vw, 4.4rem);
128
- letter-spacing: 0.08em;
129
- text-transform: uppercase;
130
- }
131
- p {
132
- margin: 0 0 1.5rem;
133
- font-size: 1.1rem;
134
- line-height: 1.7;
135
- color: var(--muted);
136
- max-width: 32rem;
137
- }
138
- .steps {
139
- margin: 0 0 1.8rem;
140
- }
141
- .steps-box {
142
- padding: 1rem 1.2rem;
143
- border-radius: 18px;
144
- background: rgba(255, 255, 255, 0.35);
145
- border: 1px solid rgba(18, 19, 22, 0.08);
146
- backdrop-filter: blur(8px);
147
- -webkit-backdrop-filter: blur(8px);
148
- }
149
- .steps-title {
150
- margin: 0 0 0.7rem;
151
- font-size: 0.75rem;
152
- text-transform: uppercase;
153
- letter-spacing: 0.18em;
154
- color: var(--muted);
155
- }
156
- .steps-list {
157
- margin: 0;
158
- padding-left: 1.1rem;
159
- display: grid;
160
- gap: 0.55rem;
161
- color: var(--muted);
162
- }
163
- .steps-list li::marker {
164
- color: var(--muted);
165
- }
166
- .steps-list strong {
167
- color: #2c3138;
168
- font-weight: 600;
169
- }
170
- .meta {
171
- display: inline-flex;
172
- align-items: center;
173
- gap: 0.6rem;
174
- font-weight: 600;
175
- color: #2c3138;
176
- }
177
- .dot {
178
- width: 10px;
179
- height: 10px;
180
- border-radius: 50%;
181
- background: linear-gradient(135deg, var(--pink), var(--blue));
182
- box-shadow: 0 0 12px rgba(122, 214, 255, 0.8);
183
- }
184
- .footer {
185
- margin-top: 1.8rem;
186
- text-align: center;
187
- font-size: 0.9rem;
188
- color: var(--muted);
189
- }
190
- .footer a {
191
- color: inherit;
192
- text-decoration: none;
193
- font-weight: 600;
194
- }
195
- .footer a:hover {
196
- text-decoration: underline;
197
- }
198
- .art {
199
- position: relative;
200
- display: grid;
201
- place-items: center;
202
- min-height: 280px;
203
- }
204
- .art::before {
205
- content: "";
206
- position: absolute;
207
- width: min(320px, 70vw);
208
- aspect-ratio: 1;
209
- border-radius: 28%;
210
- background: linear-gradient(140deg, rgba(255, 122, 191, 0.25), rgba(122, 214, 255, 0.2), rgba(125, 227, 201, 0.25));
211
- filter: blur(10px);
212
- transform: rotate(18deg);
213
- }
214
- .art img {
215
- width: min(300px, 65vw);
216
- height: auto;
217
- border-radius: 18%;
218
- filter: drop-shadow(0 25px 40px rgba(18, 19, 22, 0.25));
219
- animation: float 6s ease-in-out infinite;
220
- }
221
- @keyframes float {
222
- 0%, 100% { transform: translateY(0px) rotate(-2deg); }
223
- 50% { transform: translateY(-12px) rotate(2deg); }
224
- }
225
- @keyframes rise {
226
- from { opacity: 0; transform: translateY(18px) scale(0.98); }
227
- to { opacity: 1; transform: translateY(0) scale(1); }
228
- }
229
- @media (max-width: 760px) {
230
- .stage {
231
- padding: 2.5rem 0;
232
- }
233
- .frame {
234
- padding: 2.2rem;
235
- }
236
- h1 {
237
- letter-spacing: 0.05em;
238
- }
239
- .art {
240
- order: -1;
241
- }
242
- }
243
- @media (prefers-color-scheme: dark) {
244
- :root {
245
- --ink: #f7f8fb;
246
- --muted: #c2c8d0;
247
- --paper: #0b0e13;
248
- --glass: rgba(15, 19, 28, 0.78);
249
- --edge: rgba(255, 255, 255, 0.12);
250
- --shadow: rgba(0, 0, 0, 0.65);
251
- }
252
- body {
253
- background-image:
254
- radial-gradient(circle at 15% 10%, rgba(255, 122, 191, 0.45), transparent 45%),
255
- radial-gradient(circle at 85% 12%, rgba(255, 179, 128, 0.45), transparent 50%),
256
- radial-gradient(circle at 82% 82%, rgba(106, 169, 255, 0.45), transparent 55%),
257
- radial-gradient(circle at 20% 80%, rgba(125, 227, 201, 0.4), transparent 55%),
258
- linear-gradient(120deg, #0b0e13 0%, #121826 100%);
259
- }
260
- body::before {
261
- opacity: 0.95;
262
- filter: blur(90px);
263
- }
264
- body::after {
265
- opacity: 0.2;
266
- }
267
- .frame {
268
- box-shadow:
269
- 0 50px 120px -40px rgba(0, 0, 0, 0.75),
270
- 0 0 120px rgba(106, 169, 255, 0.25),
271
- inset 0 1px 0 rgba(255, 255, 255, 0.08);
272
- }
273
- .steps-box {
274
- background: rgba(6, 8, 14, 0.45);
275
- border: 1px solid rgba(255, 255, 255, 0.12);
276
- }
277
- .steps-list strong {
278
- color: #eef1f6;
279
- }
280
- .meta {
281
- color: #e6e9ef;
282
- }
283
- .dot {
284
- box-shadow: 0 0 18px rgba(255, 122, 191, 0.9);
285
- }
286
- .art::before {
287
- filter: blur(18px);
288
- }
289
- .art img {
290
- filter: drop-shadow(0 30px 60px rgba(106, 169, 255, 0.45));
291
- }
292
- .footer {
293
- color: #c2c8d0;
294
- }
295
- }
296
- @media (prefers-reduced-motion: reduce) {
297
- .frame,
298
- .art img {
299
- animation: none;
300
- }
301
- }
302
- </style>
51
+ <link rel="stylesheet" href="/style.css">
303
52
  </head>
304
53
  <body>
305
54
  <main class="stage">
306
55
  <section class="frame">
307
56
  <div class="copy">
308
- <h1>${name}</h1>
57
+ <h1 class="home-title">${name}</h1>
309
58
  <p>Native-speed web apps with a glassy sheen. Your new cerver project is wired, built, and ready to ship.</p>
310
59
  <div class="steps">
311
60
  <div class="steps-box">
@@ -313,6 +62,7 @@ function newProject(name) {
313
62
  <ul class="steps-list">
314
63
  <li>Edit <strong>public/index.html</strong> to change this page.</li>
315
64
  <li>Config lives in <strong>cerver.config.js</strong> at the project root.</li>
65
+ <li>Learn more <strong><a href="/about">about cerver</a></strong>.</li>
316
66
  </ul>
317
67
  </div>
318
68
  </div>
@@ -325,7 +75,7 @@ function newProject(name) {
325
75
  <img src="/cerver.png" alt="cerver logo">
326
76
  </div>
327
77
  </section>
328
- <footer class="footer">
78
+ <footer class="footer-home">
329
79
  <a href="https://github.com/velox0/cerver" target="_blank" rel="noreferrer">GitHub: github.com/velox0/cerver</a>
330
80
  </footer>
331
81
  </main>
@@ -334,7 +84,519 @@ function newProject(name) {
334
84
  `,
335
85
  );
336
86
 
337
- // Copy standard static assets
87
+ // Default public/style.css
88
+ fs.writeFileSync(
89
+ path.join(projectDir, "public", "style.css"),
90
+ `@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Unbounded:wght@500;700&family=JetBrains+Mono:wght@400;500&display=swap');
91
+
92
+ :root {
93
+ --ink: #121316;
94
+ --muted: #4a4f57;
95
+ --paper: #f7f4f1;
96
+ --glass: rgba(255, 255, 255, 0.75);
97
+ --edge: rgba(255, 255, 255, 0.6);
98
+ --pink: #ff7abf;
99
+ --peach: #ffb380;
100
+ --mint: #7de3c9;
101
+ --blue: #6aa9ff;
102
+ --shadow: rgba(18, 19, 22, 0.12);
103
+ }
104
+
105
+ @media (prefers-color-scheme: dark) {
106
+ :root {
107
+ --ink: #f7f8fb;
108
+ --muted: #c2c8d0;
109
+ --paper: #0b0e13;
110
+ --glass: rgba(15, 19, 28, 0.82);
111
+ --edge: rgba(255, 255, 255, 0.08);
112
+ --shadow: rgba(0, 0, 0, 0.5);
113
+ --blue: #58a6ff;
114
+ }
115
+ }
116
+
117
+ * {
118
+ box-sizing: border-box;
119
+ }
120
+
121
+ body {
122
+ margin: 0;
123
+ font-family: "Space Grotesk", "Segoe UI", sans-serif;
124
+ color: var(--ink);
125
+ height: 100vh;
126
+ overflow: hidden;
127
+ display: grid;
128
+ place-items: center;
129
+ background-color: var(--paper);
130
+ background-image:
131
+ radial-gradient(circle at 15% 10%, rgba(255, 122, 191, 0.25), transparent 45%),
132
+ radial-gradient(circle at 85% 12%, rgba(255, 179, 128, 0.3), transparent 50%),
133
+ radial-gradient(circle at 82% 82%, rgba(122, 214, 255, 0.25), transparent 55%),
134
+ radial-gradient(circle at 20% 80%, rgba(125, 227, 201, 0.35), transparent 55%),
135
+ linear-gradient(120deg, #f7f4f1 0%, #f2f7ff 100%);
136
+ }
137
+
138
+ @media (prefers-color-scheme: dark) {
139
+ body {
140
+ background-image:
141
+ radial-gradient(circle at 15% 10%, rgba(255, 122, 191, 0.45), transparent 45%),
142
+ radial-gradient(circle at 85% 12%, rgba(255, 179, 128, 0.45), transparent 50%),
143
+ radial-gradient(circle at 82% 82%, rgba(106, 169, 255, 0.45), transparent 55%),
144
+ radial-gradient(circle at 20% 80%, rgba(125, 227, 201, 0.4), transparent 55%),
145
+ linear-gradient(120deg, #0b0e13 0%, #121826 100%);
146
+ }
147
+ }
148
+
149
+ body::before,
150
+ body::after {
151
+ content: "";
152
+ position: fixed;
153
+ inset: -20% -10%;
154
+ pointer-events: none;
155
+ z-index: -1;
156
+ }
157
+
158
+ body::before {
159
+ background:
160
+ conic-gradient(from 200deg at 50% 50%, rgba(255, 122, 191, 0.08), rgba(122, 214, 255, 0.08), rgba(125, 227, 201, 0.08), rgba(255, 179, 128, 0.08));
161
+ filter: blur(60px);
162
+ opacity: 0.6;
163
+ }
164
+
165
+ body::after {
166
+ background-image: radial-gradient(circle, rgba(18, 19, 22, 0.04) 1px, transparent 1px);
167
+ background-size: 24px 24px;
168
+ opacity: 0.6;
169
+ }
170
+
171
+ @media (prefers-color-scheme: dark) {
172
+ body::before {
173
+ opacity: 0.95;
174
+ filter: blur(90px);
175
+ }
176
+ body::after {
177
+ background-image: radial-gradient(circle, rgba(255, 255, 255, 0.02) 1px, transparent 1px);
178
+ opacity: 0.2;
179
+ }
180
+ }
181
+
182
+ /* Visited / Non-visited links: Pink / Green hue */
183
+ a, a:visited {
184
+ color: #ff57a0;
185
+ text-decoration: none;
186
+ font-weight: 600;
187
+ transition: color 0.2s ease, border-color 0.2s ease;
188
+ border-bottom: 1px solid rgba(255, 87, 160, 0.2);
189
+ }
190
+
191
+ a:hover, a:active {
192
+ color: #10b981;
193
+ border-bottom-color: rgba(16, 185, 129, 0.6);
194
+ }
195
+
196
+ @media (prefers-color-scheme: dark) {
197
+ a, a:visited {
198
+ color: #ff7abf;
199
+ border-bottom-color: rgba(255, 122, 191, 0.2);
200
+ }
201
+ a:hover, a:active {
202
+ color: #7de3c9;
203
+ border-bottom-color: rgba(125, 227, 201, 0.6);
204
+ }
205
+ }
206
+
207
+ /* Home template specific styles */
208
+ .stage {
209
+ width: min(1100px, 92vw);
210
+ padding: 4rem 0;
211
+ }
212
+
213
+ .frame {
214
+ position: relative;
215
+ display: grid;
216
+ grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
217
+ gap: 2.5rem;
218
+ align-items: center;
219
+ padding: clamp(2.5rem, 4vw, 4rem);
220
+ border-radius: 32px;
221
+ background: var(--glass);
222
+ border: 1px solid var(--edge);
223
+ box-shadow:
224
+ 0 40px 90px -40px var(--shadow),
225
+ inset 0 1px 0 rgba(255, 255, 255, 0.7);
226
+ backdrop-filter: blur(18px);
227
+ -webkit-backdrop-filter: blur(18px);
228
+ animation: rise 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
229
+ }
230
+
231
+ @media (prefers-color-scheme: dark) {
232
+ .frame {
233
+ box-shadow:
234
+ 0 50px 120px -40px rgba(0, 0, 0, 0.75),
235
+ 0 0 120px rgba(106, 169, 255, 0.25),
236
+ inset 0 1px 0 rgba(255, 255, 255, 0.08);
237
+ }
238
+ }
239
+
240
+ h1.home-title {
241
+ margin: 0.6rem 0 1rem;
242
+ font-family: "Unbounded", "Space Grotesk", sans-serif;
243
+ font-size: clamp(2.2rem, 4vw, 4.4rem);
244
+ letter-spacing: 0.08em;
245
+ text-transform: uppercase;
246
+ }
247
+
248
+ .steps {
249
+ margin: 0 0 1.8rem;
250
+ }
251
+
252
+ .steps-box {
253
+ padding: 1rem 1.2rem;
254
+ border-radius: 18px;
255
+ background: rgba(255, 255, 255, 0.35);
256
+ border: 1px solid rgba(18, 19, 22, 0.08);
257
+ backdrop-filter: blur(8px);
258
+ -webkit-backdrop-filter: blur(8px);
259
+ }
260
+
261
+ @media (prefers-color-scheme: dark) {
262
+ .steps-box {
263
+ background: rgba(6, 8, 14, 0.45);
264
+ border: 1px solid rgba(255, 255, 255, 0.12);
265
+ }
266
+ }
267
+
268
+ .steps-title {
269
+ margin: 0 0 0.7rem;
270
+ font-size: 0.75rem;
271
+ text-transform: uppercase;
272
+ letter-spacing: 0.18em;
273
+ color: var(--muted);
274
+ }
275
+
276
+ .steps-list {
277
+ margin: 0;
278
+ padding-left: 1.1rem;
279
+ display: grid;
280
+ gap: 0.55rem;
281
+ color: var(--muted);
282
+ }
283
+
284
+ .steps-list li::marker {
285
+ color: var(--muted);
286
+ }
287
+
288
+ .steps-list strong {
289
+ color: var(--ink);
290
+ font-weight: 600;
291
+ }
292
+
293
+ .meta {
294
+ display: inline-flex;
295
+ align-items: center;
296
+ gap: 0.6rem;
297
+ font-weight: 600;
298
+ color: var(--ink);
299
+ }
300
+
301
+ .dot {
302
+ width: 10px;
303
+ height: 10px;
304
+ border-radius: 50%;
305
+ background: linear-gradient(135deg, var(--pink), var(--blue));
306
+ box-shadow: 0 0 12px rgba(122, 214, 255, 0.8);
307
+ }
308
+
309
+ @media (prefers-color-scheme: dark) {
310
+ .dot {
311
+ box-shadow: 0 0 18px rgba(255, 122, 191, 0.9);
312
+ }
313
+ }
314
+
315
+ .footer-home {
316
+ margin-top: 1.8rem;
317
+ text-align: center;
318
+ font-size: 0.9rem;
319
+ color: var(--muted);
320
+ }
321
+
322
+ .footer-home a {
323
+ color: inherit;
324
+ text-decoration: none;
325
+ font-weight: 600;
326
+ }
327
+
328
+ .footer-home a:hover {
329
+ text-decoration: underline;
330
+ }
331
+
332
+ .art {
333
+ position: relative;
334
+ display: grid;
335
+ place-items: center;
336
+ min-height: 280px;
337
+ }
338
+
339
+ .art::before {
340
+ content: "";
341
+ position: absolute;
342
+ width: min(320px, 70vw);
343
+ aspect-ratio: 1;
344
+ border-radius: 28%;
345
+ background: linear-gradient(140deg, rgba(255, 122, 191, 0.25), rgba(122, 214, 255, 0.2), rgba(125, 227, 201, 0.25));
346
+ filter: blur(10px);
347
+ transform: rotate(18deg);
348
+ }
349
+
350
+ @media (prefers-color-scheme: dark) {
351
+ .art::before {
352
+ filter: blur(18px);
353
+ }
354
+ }
355
+
356
+ .art img {
357
+ width: min(300px, 65vw);
358
+ height: auto;
359
+ border-radius: 18%;
360
+ filter: drop-shadow(0 25px 40px rgba(18, 19, 22, 0.25));
361
+ animation: float 6s ease-in-out infinite;
362
+ }
363
+
364
+ @media (prefers-color-scheme: dark) {
365
+ .art img {
366
+ filter: drop-shadow(0 30px 60px rgba(106, 169, 255, 0.45));
367
+ }
368
+ }
369
+
370
+ @keyframes float {
371
+ 0%, 100% { transform: translateY(0px) rotate(-2deg); }
372
+ 50% { transform: translateY(-12px) rotate(2deg); }
373
+ }
374
+
375
+ @keyframes rise {
376
+ from { opacity: 0; transform: translateY(18px) scale(0.98); }
377
+ to { opacity: 1; transform: translateY(0) scale(1); }
378
+ }
379
+
380
+ @media (max-width: 760px) {
381
+ .stage {
382
+ padding: 2.5rem 0;
383
+ }
384
+ .frame {
385
+ padding: 2.2rem;
386
+ }
387
+ h1.home-title {
388
+ letter-spacing: 0.05em;
389
+ }
390
+ .art {
391
+ order: -1;
392
+ }
393
+ }
394
+
395
+ @media (prefers-reduced-motion: reduce) {
396
+ .frame,
397
+ .art img {
398
+ animation: none;
399
+ }
400
+ }
401
+
402
+ /* Docs template specific styles */
403
+ .container {
404
+ width: min(840px, 95vw);
405
+ max-height: 85vh;
406
+ display: flex;
407
+ flex-direction: column;
408
+ padding: 2.5rem clamp(1.5rem, 5vw, 3.5rem);
409
+ border-radius: 24px;
410
+ background: var(--glass);
411
+ border: 1px solid var(--edge);
412
+ box-shadow: 0 30px 70px -30px var(--shadow);
413
+ backdrop-filter: blur(20px);
414
+ -webkit-backdrop-filter: blur(20px);
415
+ }
416
+
417
+ .docs-content {
418
+ overflow-y: auto;
419
+ flex-grow: 1;
420
+ padding-right: 1.5rem;
421
+ }
422
+
423
+ .docs-content::-webkit-scrollbar {
424
+ width: 8px;
425
+ }
426
+
427
+ .docs-content::-webkit-scrollbar-track {
428
+ background: transparent;
429
+ }
430
+
431
+ .docs-content::-webkit-scrollbar-thumb {
432
+ background: rgba(18, 19, 22, 0.1);
433
+ border-radius: 4px;
434
+ }
435
+
436
+ .docs-content::-webkit-scrollbar-thumb:hover {
437
+ background: rgba(18, 19, 22, 0.2);
438
+ }
439
+
440
+ @media (prefers-color-scheme: dark) {
441
+ .docs-content::-webkit-scrollbar-thumb {
442
+ background: rgba(255, 255, 255, 0.15);
443
+ }
444
+ .docs-content::-webkit-scrollbar-thumb:hover {
445
+ background: rgba(255, 255, 255, 0.25);
446
+ }
447
+ }
448
+
449
+ .back-link {
450
+ display: inline-flex;
451
+ align-items: center;
452
+ gap: 0.5rem;
453
+ margin-bottom: 2rem;
454
+ color: var(--muted);
455
+ text-decoration: none;
456
+ font-weight: 600;
457
+ font-size: 0.95rem;
458
+ transition: color 0.2s ease;
459
+ flex-shrink: 0;
460
+ border-bottom: none !important;
461
+ }
462
+
463
+ .back-link:hover {
464
+ color: var(--ink);
465
+ }
466
+
467
+ .markdown-body h1 {
468
+ font-family: "Unbounded", "Space Grotesk", sans-serif;
469
+ font-size: clamp(1.8rem, 3.5vw, 2.8rem);
470
+ margin-top: 0;
471
+ margin-bottom: 1.5rem;
472
+ letter-spacing: -0.02em;
473
+ }
474
+
475
+ .markdown-body h2 {
476
+ font-family: "Unbounded", "Space Grotesk", sans-serif;
477
+ font-size: clamp(1.3rem, 2.5vw, 1.8rem);
478
+ margin-top: 2.5rem;
479
+ margin-bottom: 1rem;
480
+ border-bottom: 2px solid rgba(18, 19, 22, 0.08);
481
+ padding-bottom: 0.4rem;
482
+ }
483
+
484
+ @media (prefers-color-scheme: dark) {
485
+ .markdown-body h2 {
486
+ border-bottom-color: rgba(255, 255, 255, 0.1);
487
+ }
488
+ }
489
+
490
+ .markdown-body h3 {
491
+ font-size: 1.25rem;
492
+ margin-top: 1.8rem;
493
+ margin-bottom: 0.8rem;
494
+ }
495
+
496
+ .markdown-body p,
497
+ .markdown-body li {
498
+ font-size: 1.05rem;
499
+ line-height: 1.7;
500
+ color: var(--muted);
501
+ }
502
+
503
+ .markdown-body p {
504
+ margin: 0 0 1.2rem;
505
+ }
506
+
507
+ .markdown-body ul,
508
+ .markdown-body ol {
509
+ margin: 0 0 1.5rem;
510
+ padding-left: 1.5rem;
511
+ }
512
+
513
+ .markdown-body li {
514
+ margin-bottom: 0.6rem;
515
+ }
516
+
517
+ .markdown-body code {
518
+ font-family: "JetBrains Mono", monospace;
519
+ background: rgba(0, 0, 0, 0.05);
520
+ padding: 0.2rem 0.4rem;
521
+ border-radius: 6px;
522
+ font-size: 0.9rem;
523
+ color: #d13d7a;
524
+ }
525
+
526
+ @media (prefers-color-scheme: dark) {
527
+ .markdown-body code {
528
+ background: rgba(255, 255, 255, 0.08);
529
+ color: #ff7abf;
530
+ }
531
+ }
532
+
533
+ .markdown-body pre {
534
+ background: #181a1f;
535
+ padding: 1.2rem;
536
+ border-radius: 12px;
537
+ overflow-x: auto;
538
+ margin: 1.5rem 0;
539
+ border: 1px solid rgba(255, 255, 255, 0.05);
540
+ }
541
+
542
+ .markdown-body pre code {
543
+ background: transparent;
544
+ color: #abb2bf;
545
+ padding: 0;
546
+ font-size: 0.9rem;
547
+ }
548
+
549
+ .markdown-body table {
550
+ width: 100%;
551
+ border-collapse: collapse;
552
+ margin: 1.5rem 0;
553
+ font-size: 0.95rem;
554
+ }
555
+
556
+ .markdown-body th,
557
+ .markdown-body td {
558
+ padding: 0.8rem 1rem;
559
+ text-align: left;
560
+ border-bottom: 1px solid rgba(18, 19, 22, 0.08);
561
+ }
562
+
563
+ @media (prefers-color-scheme: dark) {
564
+ .markdown-body th,
565
+ .markdown-body td {
566
+ border-bottom-color: rgba(255, 255, 255, 0.1);
567
+ }
568
+ }
569
+
570
+ .markdown-body th {
571
+ font-weight: 600;
572
+ background: rgba(0, 0, 0, 0.02);
573
+ }
574
+
575
+ @media (prefers-color-scheme: dark) {
576
+ .markdown-body th {
577
+ background: rgba(255, 255, 255, 0.04);
578
+ }
579
+ }
580
+
581
+ .markdown-body tr:nth-child(even) {
582
+ background: rgba(0, 0, 0, 0.01);
583
+ }
584
+
585
+ @media (prefers-color-scheme: dark) {
586
+ .markdown-body tr:nth-child(even) {
587
+ background: rgba(255, 255, 255, 0.02);
588
+ }
589
+ }
590
+
591
+ .footer-docs {
592
+ text-align: center;
593
+ font-size: 0.9rem;
594
+ color: var(--muted);
595
+ }
596
+ `,
597
+ );
598
+
599
+ // Copy static assets
338
600
  fs.copyFileSync(
339
601
  path.join(templatesDir, "cerver.png"),
340
602
  path.join(projectDir, "public", "cerver.png"),
@@ -362,9 +624,54 @@ function newProject(name) {
362
624
  ) + "\n",
363
625
  );
364
626
 
627
+ // Read README.md from the cerver project root
628
+ const readmePath = path.join(__dirname, "..", "..", "README.md");
629
+ let readmeContent = "";
630
+ if (fs.existsSync(readmePath)) {
631
+ readmeContent = fs.readFileSync(readmePath, "utf8");
632
+ } else {
633
+ readmeContent = `# Cerver
634
+
635
+ Cerver is a lightweight compile-time web framework.
636
+ `;
637
+ }
638
+
639
+ const parsedReadme = parseMarkdown(readmeContent);
640
+
641
+ // Generate public/about/about.html
642
+ fs.writeFileSync(
643
+ path.join(projectDir, "public", "about", "about.html"),
644
+ `<!doctype html>
645
+ <html lang="en">
646
+ <head>
647
+ <meta charset="utf-8">
648
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
649
+ <title>About ${name}</title>
650
+ <link rel="icon" href="/favicon.ico" type="image/x-icon">
651
+ <link rel="stylesheet" href="/style.css">
652
+ </head>
653
+ <body>
654
+ <div class="container">
655
+ <a href="/" class="back-link">&larr; Back to Home</a>
656
+ <div class="docs-content">
657
+ <article class="markdown-body">
658
+ ${parsedReadme}
659
+ </article>
660
+ </div>
661
+ <footer class="footer-docs" style="margin-top: 1.5rem; flex-shrink: 0;">
662
+ Powered by cerver
663
+ </footer>
664
+ </div>
665
+ </body>
666
+ </html>
667
+ `,
668
+ );
669
+
365
670
  console.log(" Created:");
366
- console.log(" app/routes/index.js");
671
+ console.log(" routes/index.js");
367
672
  console.log(" public/index.html");
673
+ console.log(" public/style.css");
674
+ console.log(" public/about/about.html");
368
675
  console.log(" public/cerver.png");
369
676
  console.log(" public/favicon.ico");
370
677
  console.log(" cerver.config.js");
@@ -377,4 +684,106 @@ function newProject(name) {
377
684
  console.log("");
378
685
  }
379
686
 
687
+ function parseMarkdown(md) {
688
+ const lines = md.split("\n");
689
+ const htmlLines = [];
690
+ let inList = false;
691
+ let inCode = false;
692
+ let codeBlock = [];
693
+
694
+ for (let i = 0; i < lines.length; i++) {
695
+ const line = lines[i];
696
+
697
+ if (line.startsWith("```")) {
698
+ if (inCode) {
699
+ inCode = false;
700
+ htmlLines.push(`<pre><code>${codeBlock.join("\n")}</code></pre>`);
701
+ codeBlock = [];
702
+ } else {
703
+ inCode = true;
704
+ }
705
+ continue;
706
+ }
707
+
708
+ if (inCode) {
709
+ codeBlock.push(line.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;"));
710
+ continue;
711
+ }
712
+
713
+ if (line.trim().startsWith("- ")) {
714
+ if (!inList) {
715
+ htmlLines.push("<ul>");
716
+ inList = true;
717
+ }
718
+ const itemText = line.trim().slice(2);
719
+ htmlLines.push(` <li>${inlineFormatting(itemText)}</li>`);
720
+ continue;
721
+ } else if (line.trim().startsWith("|") || line.trim() === "") {
722
+ if (inList) {
723
+ htmlLines.push("</ul>");
724
+ inList = false;
725
+ }
726
+ }
727
+
728
+ if (line.startsWith("# ")) {
729
+ htmlLines.push(`<h1>${inlineFormatting(line.slice(2))}</h1>`);
730
+ continue;
731
+ }
732
+ if (line.startsWith("## ")) {
733
+ htmlLines.push(`<h2>${inlineFormatting(line.slice(3))}</h2>`);
734
+ continue;
735
+ }
736
+ if (line.startsWith("### ")) {
737
+ htmlLines.push(`<h3>${inlineFormatting(line.slice(4))}</h3>`);
738
+ continue;
739
+ }
740
+
741
+ if (line.trim().startsWith("|")) {
742
+ if (line.includes("---")) {
743
+ continue;
744
+ }
745
+ const cols = line.split("|").map(c => c.trim()).filter((c, idx, arr) => idx > 0 && idx < arr.length - 1);
746
+ htmlLines.push(`<tr>${cols.map(c => `<td>${inlineFormatting(c)}</td>`).join("")}</tr>`);
747
+ continue;
748
+ }
749
+
750
+ if (line.trim() !== "") {
751
+ htmlLines.push(`<p>${inlineFormatting(line)}</p>`);
752
+ }
753
+ }
754
+
755
+ if (inList) {
756
+ htmlLines.push("</ul>");
757
+ }
758
+
759
+ let joined = htmlLines.join("\n");
760
+ joined = joined.replace(/(<tr>.*?<\/tr>)+/gs, "<table>\n$&\n</table>");
761
+
762
+ return joined;
763
+ }
764
+
765
+ function inlineFormatting(text) {
766
+ let escaped = text
767
+ .replace(/&/g, "&amp;")
768
+ .replace(/</g, "&lt;")
769
+ .replace(/>/g, "&gt;");
770
+
771
+ // Map raw Cerver Logo html tag from templates/cerver.png to /cerver.png
772
+ escaped = escaped.replace(/&lt;img src="templates\/cerver\.png" alt="Cerver Logo" width="200px" align="right" \/&gt;/g, '<img src="/cerver.png" alt="Cerver Logo" width="100" style="float: right; margin-left: 1.5rem; border-radius: 12px; max-width: 100%; height: auto;" />');
773
+
774
+ // Handle Markdown images: ![alt](url)
775
+ escaped = escaped.replace(/!\[([^\]]*?)\]\(([^)]+?)\)/g, '<img src="$2" alt="$1" style="height: 20px; vertical-align: middle; display: inline-block; margin: 2px;" />');
776
+
777
+ // Handle Markdown links: [text](url)
778
+ escaped = escaped.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>');
779
+
780
+ // Handle bold: **text**
781
+ escaped = escaped.replace(/\*\*([^*]+)\*\*/g, "<strong>$1</strong>");
782
+
783
+ // Handle inline code: `code`
784
+ escaped = escaped.replace(/`([^`]+)`/g, "<code>$1</code>");
785
+
786
+ return escaped;
787
+ }
788
+
380
789
  module.exports = { newProject };