create-shibumi 0.2.3 → 0.2.7

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.
@@ -0,0 +1,418 @@
1
+ /* shibumi.css — canonical shared layer for shibumistack.dev, shibumi-server, shibumi-forms.
2
+ Source of truth: shibumistack.dev repo public/shibumi.css. Do not edit vendored copies. */
3
+
4
+ /* shared.css: common styles for all pages */
5
+
6
+ :root {
7
+ color-scheme: light dark;
8
+ /* palette copied from shibumi-server site/styles.css */
9
+ --paper: light-dark(#f5f0e4, #1b130f);
10
+ --ink: light-dark(#272016, #eee5d7);
11
+ --muted: light-dark(#746b5d, #aaa092);
12
+ --faint: light-dark(rgba(39, 32, 22, 0.055), rgba(238, 229, 215, 0.07));
13
+ --line: light-dark(rgba(39, 32, 22, 0.14), rgba(238, 229, 215, 0.16));
14
+ --orange: light-dark(#e95f19, #ff8648);
15
+ --orange-soft: light-dark(#f8dfcd, #3c2117);
16
+ --green: light-dark(#26723e, #75d58a);
17
+ --danger: light-dark(#a13b2c, #e36c59);
18
+ --code: light-dark(#201c16, #120e0b);
19
+ --code-ink: #e9e2d5;
20
+ /* aliases so existing components pick up the server palette */
21
+ --bg: var(--paper);
22
+ --text: var(--ink);
23
+ --quiet: var(--faint);
24
+ --accent: var(--orange);
25
+ --accent-soft: var(--orange-soft);
26
+ --accent-hover: light-dark(#d15515, #ff9a66);
27
+ --success: var(--green);
28
+ --success-text: var(--green);
29
+ --success-ink: #17331f;
30
+ --terminal-success: light-dark(#3d8f50, #78e88f);
31
+ --terminal-active: light-dark(#16839a, #62dff0);
32
+ --terminal-info: light-dark(#397fc0, #74b6ff);
33
+ --max: 72.5rem;
34
+ --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", sans-serif;
35
+ --font-serif: ui-serif, Georgia, "Iowan Old Style", "Apple Garamond", Cambria, serif;
36
+ --font-mono: ui-monospace, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;
37
+ --text-xs: 0.75rem; /* 12px tiny */
38
+ --text-sm: 0.875rem; /* 14px small */
39
+ --text-md: 1rem; /* 16px normal / UI */
40
+ --text-base: 1.125rem; /* 18px body */
41
+ --text-lg: 1.3125rem; /* 21px */
42
+ --text-xl: 1.563rem; /* 25px */
43
+ --text-2xl: 1.953rem;
44
+ }
45
+
46
+ [data-theme="dark"] { color-scheme: dark; }
47
+ [data-theme="light"] { color-scheme: light; }
48
+
49
+ *, *::before, *::after { box-sizing: border-box; }
50
+ html { min-height: 100%; }
51
+
52
+ body {
53
+ margin: 0;
54
+ min-height: 100svh;
55
+ font-family: var(--font-sans);
56
+ font-size: var(--text-base);
57
+ font-weight: 400;
58
+ line-height: 1.65;
59
+ color: var(--text);
60
+ background: var(--bg);
61
+ }
62
+
63
+ h1, h2, h3 {
64
+ font-family: var(--font-serif);
65
+ }
66
+
67
+ /* theme-independent noise texture, copied from shibumi-server */
68
+ body::before {
69
+ content: "";
70
+ position: fixed;
71
+ inset: 0;
72
+ z-index: -1;
73
+ pointer-events: none;
74
+ opacity: 0.33;
75
+ background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 180 180' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.88' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.08'/%3E%3C/svg%3E");
76
+ }
77
+
78
+ a { color: inherit; }
79
+
80
+ main a:not(.btn):not(.button) {
81
+ text-decoration: underline;
82
+ text-decoration-color: color-mix(in srgb, var(--accent) 30%, transparent);
83
+ text-underline-offset: 0.18em;
84
+ transition: text-decoration-color 160ms ease;
85
+ }
86
+
87
+ main a:not(.btn):not(.button):hover,
88
+ main a:not(.btn):not(.button):focus-visible {
89
+ text-decoration-color: var(--accent);
90
+ }
91
+
92
+ .shell {
93
+ width: min(var(--max), calc(100% - 2rem));
94
+ margin: 0 auto;
95
+ padding: 5.5rem 0 0;
96
+ position: relative;
97
+ }
98
+
99
+ /* ---- header / nav ---- */
100
+
101
+ .shell > header,
102
+ .site-header {
103
+ position: fixed;
104
+ top: 0;
105
+ left: 50%;
106
+ z-index: 20;
107
+ width: min(var(--max), calc(100% - 2rem));
108
+ display: flex;
109
+ justify-content: space-between;
110
+ align-items: center;
111
+ gap: 1rem;
112
+ padding: 1rem 0;
113
+ transform: translateX(-50%);
114
+ }
115
+
116
+ .shell > header::before,
117
+ .site-header::before {
118
+ content: "";
119
+ position: fixed;
120
+ top: 0;
121
+ left: 50%;
122
+ z-index: -1;
123
+ width: 100vw;
124
+ height: 100%;
125
+ transform: translateX(-50%);
126
+ background: light-dark(rgb(245 240 228 / 78%), rgb(27 19 15 / 72%));
127
+ backdrop-filter: blur(1.125rem);
128
+ -webkit-backdrop-filter: blur(1.125rem);
129
+ }
130
+
131
+ .mark {
132
+ display: inline-flex;
133
+ align-items: center;
134
+ gap: 0.75rem;
135
+ color: var(--muted);
136
+ text-decoration: none;
137
+ font-family: var(--font-sans);
138
+ font-size: 1.125rem;
139
+ font-weight: 400;
140
+ letter-spacing: 0.03rem;
141
+ }
142
+
143
+ .mark-tld {
144
+ color: light-dark(rgba(92, 88, 82, 0.5), rgba(189, 183, 173, 0.62));
145
+ font-weight: 300;
146
+ }
147
+
148
+ .mark img,
149
+ img.mark-logo {
150
+ width: 2.125rem;
151
+ height: 2.125rem;
152
+ border-radius: 0.5rem;
153
+ }
154
+
155
+ [data-theme="dark"] .mark .logo-light,
156
+ [data-theme="light"] .mark .logo-dark { display: none; }
157
+
158
+ main, .page { min-width: 0; }
159
+
160
+ .eyebrow {
161
+ margin: 0 0 1rem;
162
+ color: var(--accent);
163
+ font: 300 var(--text-md)/1.2 var(--font-mono);
164
+ letter-spacing: 0.14em;
165
+ text-transform: uppercase;
166
+ }
167
+
168
+ nav {
169
+ display: flex;
170
+ align-items: center;
171
+ gap: 1rem;
172
+ font-family: var(--font-sans);
173
+ font-size: var(--text-md);
174
+ color: var(--muted);
175
+ }
176
+
177
+ nav a {
178
+ text-decoration: none;
179
+ border-bottom: 1px solid transparent;
180
+ }
181
+
182
+ nav a:hover,
183
+ nav a[aria-current="page"] {
184
+ border-bottom-color: var(--accent);
185
+ color: var(--text);
186
+ }
187
+
188
+ .nav-links {
189
+ display: flex;
190
+ align-items: center;
191
+ gap: 1rem;
192
+ }
193
+
194
+ .nav-actions {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 1rem;
198
+ }
199
+
200
+ .nav-icons {
201
+ display: flex;
202
+ align-items: center;
203
+ gap: 0.75rem;
204
+ }
205
+
206
+ .github-link {
207
+ display: inline-flex;
208
+ align-items: center;
209
+ border-bottom: 0;
210
+ color: var(--muted);
211
+ }
212
+
213
+ .github-link svg {
214
+ width: 1.05rem;
215
+ height: 1.05rem;
216
+ fill: currentColor;
217
+ }
218
+
219
+ .github-link:hover {
220
+ color: var(--text);
221
+ border-bottom: 0;
222
+ }
223
+
224
+ .stack-menu { position: relative; }
225
+
226
+ .stack-menu summary {
227
+ cursor: pointer;
228
+ list-style: none;
229
+ transition: color 160ms ease;
230
+ }
231
+
232
+ .stack-menu summary::-webkit-details-marker { display: none; }
233
+ .stack-menu summary:hover,
234
+ .stack-menu summary:focus-visible,
235
+ .stack-menu[open] summary,
236
+ .stack-menu:has(a[aria-current="page"]) summary { color: var(--text); }
237
+
238
+ .stack-menu summary:focus-visible {
239
+ border-radius: 0.2rem;
240
+ outline: 2px solid var(--accent);
241
+ outline-offset: 4px;
242
+ }
243
+
244
+ .stack-popover {
245
+ position: absolute;
246
+ top: calc(100% + 0.75rem);
247
+ right: 0;
248
+ z-index: 30;
249
+ width: 14rem;
250
+ padding: 0.45rem;
251
+ border-radius: 0.5rem;
252
+ background: light-dark(rgb(255 250 240 / 78%), rgb(48 35 29 / 72%));
253
+ -webkit-backdrop-filter: blur(0.5rem);
254
+ backdrop-filter: blur(0.5rem);
255
+ box-shadow: 0 0.8rem 2.5rem light-dark(rgb(20 14 9 / 18%), rgb(0 0 0 / 58%));
256
+ }
257
+
258
+ .stack-popover a {
259
+ display: block;
260
+ padding: 0.65rem 0.75rem;
261
+ border: 0;
262
+ border-radius: 0.3rem;
263
+ color: var(--muted);
264
+ }
265
+
266
+ .stack-popover a:hover,
267
+ .stack-popover a:focus-visible {
268
+ border: 0;
269
+ outline: none;
270
+ background: var(--quiet);
271
+ color: var(--text);
272
+ }
273
+
274
+ .stack-popover a[aria-current="page"] { color: var(--accent); }
275
+
276
+ /* first menu in nav (About) opens aligned to its left edge */
277
+ .nav-links > .stack-menu:first-child .stack-popover { left: 0; right: auto; }
278
+ .stack-popover span,
279
+ .stack-popover small { display: block; }
280
+ .stack-popover span { color: inherit; font-weight: 700; }
281
+ .stack-popover small { margin-top: 0.1rem; color: var(--muted); font-size: var(--text-xs); }
282
+
283
+ /* ---- theme toggle ---- */
284
+
285
+ .theme-toggle {
286
+ appearance: none;
287
+ display: inline-grid;
288
+ place-items: center;
289
+ width: 1.7rem;
290
+ height: 1.7rem;
291
+ border: 0;
292
+ background: transparent;
293
+ color: var(--muted);
294
+ cursor: pointer;
295
+ padding: 0;
296
+ }
297
+
298
+ .theme-toggle:hover { color: var(--text); }
299
+
300
+ .theme-toggle svg {
301
+ width: 1.1rem;
302
+ height: 1.1rem;
303
+ fill: none;
304
+ stroke: currentColor;
305
+ stroke-width: 1.8;
306
+ }
307
+
308
+ [data-theme="dark"] .theme-toggle .icon-sun,
309
+ [data-theme="light"] .theme-toggle .icon-moon { display: none; }
310
+ [data-theme="dark"] .theme-toggle .icon-moon,
311
+ [data-theme="light"] .theme-toggle .icon-sun { display: block; }
312
+ /* alias var names used by server/forms stylesheets */
313
+ :root {
314
+ --sans: var(--font-sans);
315
+ --serif: var(--font-serif);
316
+ --mono: var(--font-mono);
317
+ --fs-xs: var(--text-xs);
318
+ --fs-sm: var(--text-sm);
319
+ --fs-md: var(--text-md);
320
+ --fs-base: var(--text-base);
321
+ --fs-lg: var(--text-lg);
322
+ }
323
+
324
+ /* ---- footer ---- */
325
+
326
+ .site-footer {
327
+ margin-top: clamp(4rem, 8vw, 6rem);
328
+ padding: 1.5rem 0;
329
+ border-top: 1px solid color-mix(in srgb, var(--line) 50%, transparent);
330
+ font-family: var(--font-sans);
331
+ font-size: var(--text-sm);
332
+ color: var(--muted);
333
+ }
334
+
335
+ .footer-line {
336
+ display: flex;
337
+ align-items: center;
338
+ justify-content: space-between;
339
+ gap: 1rem;
340
+ }
341
+
342
+ .footer-line nav {
343
+ display: flex;
344
+ align-items: center;
345
+ gap: 1.25rem;
346
+ font: inherit;
347
+ }
348
+
349
+ .site-footer .stack-popover {
350
+ top: auto;
351
+ right: auto;
352
+ bottom: calc(100% + 0.75rem);
353
+ left: 0;
354
+ }
355
+
356
+ .footer-meta {
357
+ display: flex;
358
+ align-items: center;
359
+ gap: 0.3rem;
360
+ margin: 0.75rem 0 0;
361
+ font-size: var(--text-xs);
362
+ opacity: 0.75;
363
+ }
364
+
365
+ .site-footer a {
366
+ text-decoration: none;
367
+ color: var(--muted);
368
+ }
369
+
370
+ .site-footer a:hover {
371
+ color: var(--accent);
372
+ }
373
+
374
+ .footer-heart {
375
+ width: 0.875rem;
376
+ height: 0.875rem;
377
+ fill: currentColor;
378
+ color: var(--danger);
379
+ opacity: 0.5;
380
+ transition: opacity 150ms;
381
+ }
382
+
383
+ .footer-meta:hover .footer-heart {
384
+ opacity: 1;
385
+ animation: heart-pulse 1s ease-in-out infinite;
386
+ }
387
+
388
+ @keyframes heart-pulse {
389
+ 0%, 100% { transform: scale(1); }
390
+ 50% { transform: scale(1.3); }
391
+ }
392
+
393
+ .sr-only {
394
+ position: absolute;
395
+ width: 1px;
396
+ height: 1px;
397
+ margin: -1px;
398
+ padding: 0;
399
+ overflow: hidden;
400
+ clip: rect(0 0 0 0);
401
+ white-space: nowrap;
402
+ border: 0;
403
+ }
404
+
405
+
406
+ /* green pulse on the box around a just-used copy button */
407
+ @keyframes copy-glow {
408
+ 0%, 100% { box-shadow: 0 0 0 0 transparent; }
409
+ 35% {
410
+ box-shadow:
411
+ 0 0 0 3px color-mix(in srgb, var(--success) 30%, transparent),
412
+ 0 0 1.5rem color-mix(in srgb, var(--success) 40%, transparent);
413
+ }
414
+ }
415
+
416
+ :has(> .copied), :has(> .is-copied) {
417
+ animation: copy-glow 1.2s ease-in-out;
418
+ }
@@ -62,7 +62,8 @@ const page = `<!doctype html>
62
62
  <head>
63
63
  <meta charset="utf-8" />
64
64
  <meta name="viewport" content="width=device-width, initial-scale=1" />
65
- <title>Shibumi web app</title>
65
+ <title>Web app · shibumi</title>
66
+ <link rel="stylesheet" href="/public/vendor/shibumi.css" />
66
67
  <link rel="stylesheet" href="/public/style.css" />
67
68
  <!-- app.js must load before Alpine so the alpine:init listener exists
68
69
  when Alpine starts; both defer, so document order is execution order. -->
@@ -70,14 +71,23 @@ const page = `<!doctype html>
70
71
  <script src="/public/vendor/alpine-csp-3.16.2.min.js" defer></script>
71
72
  </head>
72
73
  <body>
73
- <main>
74
- <h1>It runs.</h1>
75
- <p>Hono routes, Zod validation, Alpine behavior. To get started, open <code>src/app.ts</code>; this page lives there.</p>
76
- <section x-data="counter">
74
+ <main class="scaffold">
75
+ <p class="masthead"><span class="masthead-mark">渋み</span> shibumi web</p>
76
+ <h1>Web app</h1>
77
+ <p class="lede">Hono serves the routes, Zod validates every input, and Alpine runs the client behavior. Deploy to your own server with one command.</p>
78
+ <section class="demo" x-data="counter" aria-label="Alpine counter demo">
77
79
  <button x-on:click="inc" type="button">Count</button>
78
- <output x-text="count"></output>
80
+ <output x-text="count">0</output>
81
+ <span class="demo-hint">client state without a build step</span>
79
82
  </section>
80
- <p><a href="/api/hello?name=you">/api/hello</a> · <a href="/healthz">/healthz</a></p>
83
+ <ul class="endpoints">
84
+ <li><a href="/api/hello?name=you"><span class="endpoint-path">GET /api/hello</span><span>query validated with Zod</span></a></li>
85
+ <li><a href="/healthz"><span class="endpoint-path">GET /healthz</span><span>the check every deploy waits for</span></a></li>
86
+ </ul>
87
+ <footer class="colophon">
88
+ <p>This page lives in <code>src/app.ts</code>. House rules for coding agents are in <code>agents.md</code>. Add features with <code>bun shi add email</code>.</p>
89
+ <p class="colophon-links"><a href="https://shibumistack.dev/docs" rel="noreferrer">shibumi docs</a> · <a href="https://server.shibumistack.dev/docs" rel="noreferrer">server docs</a> · <a href="https://shibumistack.dev/docs/cli/extensions" rel="noreferrer">extensions</a></p>
90
+ </footer>
81
91
  </main>
82
92
  </body>
83
93
  </html>
@@ -29,7 +29,7 @@ describe("routes", () => {
29
29
  const res = await req("/");
30
30
  expect(res.status).toBe(200);
31
31
  const html = await res.text();
32
- expect(html).toContain("It runs.");
32
+ expect(html).toContain("Web app");
33
33
  // app.js must come before Alpine so the alpine:init listener registers
34
34
  // before Alpine starts.
35
35
  expect(html.indexOf("/public/app.js")).toBeLessThan(
@@ -73,9 +73,17 @@ describe("routes", () => {
73
73
  probe.get("/__boom", () => {
74
74
  throw new Error("test explosion");
75
75
  });
76
- const res = await probe.fetch(new Request("http://localhost/__boom"));
77
- expect(res.status).toBe(500);
78
- expectSecurityHeaders(res);
76
+ // onError logs the exception before answering; silence it so the
77
+ // deliberate explosion does not read as a failure in test output.
78
+ const errorLog = console.error;
79
+ console.error = () => {};
80
+ try {
81
+ const res = await probe.fetch(new Request("http://localhost/__boom"));
82
+ expect(res.status).toBe(500);
83
+ expectSecurityHeaders(res);
84
+ } finally {
85
+ console.error = errorLog;
86
+ }
79
87
  });
80
88
  });
81
89