htmlboard 0.0.0-stage → 0.2.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.
@@ -0,0 +1,95 @@
1
+ ---
2
+ name: htmlboard
3
+ description: Design UI in plain HTML files and show them side by side on an infinite, live-reloading canvas with the htmlboard CLI. Use when the user asks for mockups, screens, design variants or explorations, components, flyers or any visual proposal, or mentions htmlboard, artboards or designs.json.
4
+ ---
5
+
6
+ # HtmlBoard
7
+
8
+ HtmlBoard serves a folder of HTML files as artboards on a zoomable canvas, like a Figma page. Every design is one ordinary HTML file, so you design by writing HTML and CSS, and the user sees the result update live while you work.
9
+
10
+ ## Start the canvas
11
+
12
+ Run it in the background, pointed at the designs folder (it is created if missing):
13
+
14
+ ```sh
15
+ npx htmlboard designs --open
16
+ ```
17
+
18
+ - It prints the URL (default `http://localhost:3000`; if the port is busy it takes the next free one). Give the URL to the user.
19
+ - `-p <port>` picks the port. `--init` adds three sample artboards and a `designs.json`; skip it unless the user wants a starter.
20
+ - If the artboards link to files above the designs folder (`../css/theme.css`), serve from the project root: `npx htmlboard design --root .`
21
+ - An empty folder gets the samples automatically, so write your first artboard before starting the server.
22
+ - If a server is already running for this folder, reuse it. `GET /api/health` answers `{"status":"ok","dir":…}`.
23
+
24
+ ## Write an artboard
25
+
26
+ One design per file. Keep each file self-contained so it renders on its own:
27
+
28
+ ```html
29
+ <!DOCTYPE html>
30
+ <html lang="en">
31
+ <head>
32
+ <meta charset="UTF-8">
33
+ <title>Checkout · variant B</title> <!-- the artboard's title on the canvas -->
34
+ <style>
35
+ body { margin: 0; }
36
+ .screen { width: 390px; min-height: 844px; /* … */ }
37
+ </style>
38
+ </head>
39
+ <body>
40
+ <main class="screen">…</main>
41
+ </body>
42
+ </html>
43
+ ```
44
+
45
+ - Design at the real size of the target: phone 390×844, tablet 768×1024, desktop 1280×800 (or 1440×900), and the exact size for print or social formats (e.g. 1080×1350).
46
+ - Use real content in the user's language, not lorem ipsum.
47
+ - Relative links to shared CSS, images, scripts and fonts work (`assets/logo.svg`; `../css/theme.css` with `--root`). Reuse the project's own stylesheet and tokens when there is one.
48
+ - No build step: plain HTML, CSS and browser JavaScript (ES modules and CDN scripts work).
49
+
50
+ ## How the canvas lays out files
51
+
52
+ **Without `designs.json`** (zero config), every `.html` under the folder is an artboard:
53
+
54
+ - Each subfolder is a section (row); files at the top level go to "Root Designs".
55
+ - The size comes from words in the file's path: `mobile`, `phone`, `iphone` → 390×844; `tablet`, `ipad` → 768×1024; `component`, `ui`, `atomic` → 720×600; `desktop`, `laptop`, `screen` → 1280×800; anything else → 1200×800. Name files accordingly, e.g. `checkout/mobile-variant-b.html`.
56
+ - Hidden folders, `node_modules`, `dist` and `build` are skipped.
57
+
58
+ **With `designs.json`** in the designs folder, the manifest replaces discovery completely: a file not listed in it doesn't show. Use it to choose order, sizes and grouping:
59
+
60
+ ```json
61
+ {
62
+ "title": "Checkout redesign",
63
+ "sections": [
64
+ {
65
+ "title": "Checkout · mobile",
66
+ "description": "Three directions for the payment step",
67
+ "designs": [
68
+ { "file": "checkout/a.html", "title": "A · current", "width": 390, "height": 844 },
69
+ { "file": "checkout/b.html", "title": "B · one page", "width": 390, "tags": ["proposal"] },
70
+ { "file": "checkout/b.html?theme=dark", "title": "B · dark", "width": 390, "height": 844 }
71
+ ]
72
+ }
73
+ ]
74
+ }
75
+ ```
76
+
77
+ - Sections are rows, top to bottom; designs run left to right.
78
+ - `file` is relative to the designs folder and may carry a query string, so one file can show several states.
79
+ - Omit `height` to let the frame grow to the page's full height (long pages, boards, docs).
80
+ - Optional: `description` (shown in the list), `tags` and `category` (searchable), `id` (derived from `file`).
81
+ - `name` and `artboards` are accepted as synonyms of a section's `title` and `designs`.
82
+
83
+ When the project already has a `designs.json`, add every new file to it.
84
+
85
+ ## Live reload
86
+
87
+ - Saving an artboard's HTML reloads just that frame; the user's view stays put.
88
+ - Saving a file artboards use (a stylesheet, a script, an image, data) reloads the artboards that loaded it.
89
+ - Saving `designs.json`, or adding or removing artboards, rebuilds the canvas.
90
+
91
+ ## Working with the user
92
+
93
+ - Put variants in separate files next to each other (`hero-a.html`, `hero-b.html`) instead of overwriting the current design, so they can be compared on the canvas. Keep the current one as the reference until the user picks.
94
+ - Say which files you created or changed and what to look at; the user is watching the canvas, not the code. Link straight to an artboard with `http://localhost:3000/#<id>` (the id is the file's path without `.html`, slashes and other symbols turned into `-`: `checkout/b.html` → `checkout-b`).
95
+ - If you can browse or take screenshots, open the artboard directly (`http://localhost:3000/<file>`) to check it at its real size before reporting back. Fix overflow, clipped text and broken images first.
@@ -0,0 +1,372 @@
1
+ /* HtmlBoard explorer. Its own dark chrome, so the designs stand out on the canvas. */
2
+
3
+ @font-face {
4
+ font-family: 'Inter';
5
+ font-style: normal;
6
+ font-weight: 400 700;
7
+ font-display: swap;
8
+ src: url(fonts/inter.woff2) format('woff2');
9
+ }
10
+
11
+ @font-face {
12
+ font-family: 'Bricolage Grotesque';
13
+ font-style: normal;
14
+ font-weight: 500 800;
15
+ font-display: swap;
16
+ src: url(fonts/bricolage-grotesque.woff2) format('woff2');
17
+ }
18
+
19
+ :root {
20
+ --x-canvas: #1C1C1E;
21
+ --x-dot: rgb(255 255 255 / 0.09);
22
+ --x-panel: #252527;
23
+ --x-raised: #2E2E31;
24
+ --x-line: #3A3A3E;
25
+ --x-text: #EDEDEB;
26
+ --x-text-soft: #9D9DA3;
27
+ --x-accent: #3D9DFF;
28
+ --x-wash: rgb(255 255 255 / 0.07);
29
+ --x-live: #3DD68C;
30
+ --font-ui: 'Inter', system-ui, sans-serif;
31
+ --font-display: 'Bricolage Grotesque', 'Inter', system-ui, sans-serif;
32
+ color-scheme: dark;
33
+ }
34
+
35
+ *, ::before, ::after { box-sizing: border-box; }
36
+
37
+ html, body { height: 100%; margin: 0; overflow: hidden; }
38
+
39
+ body {
40
+ font-family: var(--font-ui);
41
+ font-weight: 500;
42
+ color: var(--x-text);
43
+ background: var(--x-canvas);
44
+ }
45
+
46
+ :focus-visible { outline: 2px solid var(--x-accent); outline-offset: 2px; }
47
+
48
+ [hidden] { display: none !important; }
49
+
50
+ kbd {
51
+ display: inline-block;
52
+ min-width: 1.6em;
53
+ padding: 0 4px;
54
+ border: 1px solid var(--x-line);
55
+ border-radius: 4px;
56
+ background: var(--x-raised);
57
+ font: inherit;
58
+ font-size: 0.92em;
59
+ line-height: 1.5;
60
+ text-align: center;
61
+ }
62
+
63
+ .app { display: flex; height: 100%; }
64
+
65
+ /* ---------- The list ---------- */
66
+
67
+ .sidebar {
68
+ display: flex;
69
+ flex-direction: column;
70
+ flex: none;
71
+ width: 272px;
72
+ border-right: 1px solid var(--x-line);
73
+ background: var(--x-panel);
74
+ }
75
+
76
+ .app.is-collapsed .sidebar { display: none; }
77
+
78
+ /* htmlboard's own row: the logo (a link to the project) and the version */
79
+ .brand { display: flex; align-items: center; justify-content: space-between; gap: 8px; padding: 14px 16px 12px; border-bottom: 1px solid var(--x-line); }
80
+ .brand-logo { display: block; border-radius: 4px; }
81
+ .brand-logo img { display: block; width: auto; height: 18px; }
82
+ .version { font-size: 11px; color: var(--x-text-soft); font-variant-numeric: tabular-nums; }
83
+
84
+ .sidebar-head { display: flex; align-items: baseline; gap: 8px; padding: 16px 16px 10px; }
85
+ .sidebar-head h1 { min-width: 0; margin: 0; font: 800 24px/1.1 var(--font-display); overflow-wrap: anywhere; }
86
+ .count { flex: none; font-size: 13px; color: var(--x-text-soft); font-variant-numeric: tabular-nums; }
87
+
88
+ .search { position: relative; margin: 0 12px 4px; }
89
+
90
+ .search svg {
91
+ position: absolute;
92
+ left: 9px;
93
+ top: 50%;
94
+ width: 15px;
95
+ height: 15px;
96
+ fill: none;
97
+ stroke: var(--x-text-soft);
98
+ stroke-width: 2;
99
+ stroke-linecap: round;
100
+ transform: translateY(-50%);
101
+ pointer-events: none;
102
+ }
103
+
104
+ .search input {
105
+ width: 100%;
106
+ height: 32px;
107
+ padding: 0 30px;
108
+ border: 1px solid var(--x-line);
109
+ border-radius: 6px;
110
+ background: var(--x-canvas);
111
+ color: var(--x-text);
112
+ font: inherit;
113
+ font-size: 13px;
114
+ }
115
+
116
+ .search input::placeholder { color: var(--x-text-soft); opacity: 1; }
117
+ .search input:focus { border-color: var(--x-accent); outline: none; }
118
+ .search input::-webkit-search-cancel-button { appearance: none; }
119
+
120
+ .search kbd {
121
+ position: absolute;
122
+ right: 7px;
123
+ top: 50%;
124
+ font-size: 11px;
125
+ color: var(--x-text-soft);
126
+ transform: translateY(-50%);
127
+ pointer-events: none;
128
+ }
129
+
130
+ .search input:focus ~ kbd, .search input:not(:placeholder-shown) ~ kbd { display: none; }
131
+
132
+ .list { flex: 1; overflow-y: auto; padding: 0 8px 16px; }
133
+
134
+ .list h2 {
135
+ margin: 18px 8px 4px;
136
+ font-size: 12px;
137
+ font-weight: 700;
138
+ letter-spacing: 0.06em;
139
+ text-transform: uppercase;
140
+ color: var(--x-text-soft);
141
+ }
142
+
143
+ .list .section-desc { margin: -2px 8px 6px; font-size: 12px; line-height: 1.45; color: var(--x-text-soft); }
144
+
145
+ .list ul { margin: 0; padding: 0; list-style: none; }
146
+
147
+ .item {
148
+ display: flex;
149
+ align-items: baseline;
150
+ justify-content: space-between;
151
+ gap: 12px;
152
+ width: 100%;
153
+ padding: 6px 8px;
154
+ border: 0;
155
+ border-radius: 5px;
156
+ background: none;
157
+ color: inherit;
158
+ font: inherit;
159
+ font-size: 14px;
160
+ line-height: 20px;
161
+ text-align: left;
162
+ cursor: pointer;
163
+ }
164
+
165
+ .item:hover { background: var(--x-wash); }
166
+ .item[aria-current='true'] { background: color-mix(in srgb, var(--x-accent) 24%, transparent); color: #fff; }
167
+ .item .dims { flex: none; font-size: 12px; color: var(--x-text-soft); font-variant-numeric: tabular-nums; }
168
+
169
+ .no-match { margin: 0; padding: 12px 16px 0; font-size: 13px; color: var(--x-text-soft); }
170
+
171
+ .sidebar-hint {
172
+ margin: 0;
173
+ padding: 12px 16px 16px;
174
+ border-top: 1px solid var(--x-line);
175
+ font-size: 12px;
176
+ line-height: 1.6;
177
+ color: var(--x-text-soft);
178
+ }
179
+
180
+ /* ---------- The canvas ---------- */
181
+
182
+ .viewport {
183
+ position: relative;
184
+ flex: 1;
185
+ overflow: hidden;
186
+ touch-action: none;
187
+ background-color: var(--x-canvas);
188
+ background-image: radial-gradient(circle at 1px 1px, var(--x-dot) 1px, transparent 0);
189
+ background-size: var(--grid, 32px) var(--grid, 32px);
190
+ outline: none;
191
+ }
192
+
193
+ .viewport.is-panning, .viewport.is-panning * { cursor: grabbing; }
194
+ .viewport.is-space { cursor: grab; }
195
+
196
+ .world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
197
+
198
+ .frame {
199
+ position: absolute;
200
+ background: #FFFFFF;
201
+ box-shadow: 0 1px 3px rgb(0 0 0 / 0.45), 0 10px 40px rgb(0 0 0 / 0.35);
202
+ }
203
+
204
+ /* The canvas takes every pointer event (wheel included, so Ctrl + scroll never zooms the page):
205
+ designs are pictures here, and Open shows one on its own. */
206
+ .frame iframe { display: block; width: 100%; height: 100%; border: 0; pointer-events: none; }
207
+
208
+ /* Designs the search leaves out stay in place, faded. */
209
+ .frame.is-dimmed { opacity: 0.2; }
210
+
211
+ .overlay { position: absolute; inset: 0; overflow: hidden; pointer-events: none; }
212
+
213
+ .label, .section-title, .outline, .size { position: absolute; left: 0; top: 0; }
214
+
215
+ .label {
216
+ overflow: hidden;
217
+ font-size: 12px;
218
+ font-weight: 600;
219
+ line-height: 16px;
220
+ white-space: nowrap;
221
+ text-overflow: ellipsis;
222
+ color: var(--x-text-soft);
223
+ }
224
+
225
+ .label.is-hover { color: var(--x-text); }
226
+ .label.is-selected { color: var(--x-accent); }
227
+ .label.is-dimmed { opacity: 0.35; }
228
+
229
+ .section-title { font: 700 20px/24px var(--font-display); letter-spacing: 0.01em; white-space: nowrap; color: var(--x-text); }
230
+
231
+ .outline--hover { box-shadow: 0 0 0 1px var(--x-accent); }
232
+ .outline--selected { box-shadow: 0 0 0 2px var(--x-accent); }
233
+
234
+ .size {
235
+ padding: 2px 6px;
236
+ border-radius: 4px;
237
+ background: var(--x-accent);
238
+ color: #FFFFFF;
239
+ font-size: 11px;
240
+ font-weight: 600;
241
+ line-height: 14px;
242
+ white-space: nowrap;
243
+ font-variant-numeric: tabular-nums;
244
+ }
245
+
246
+ /* ---------- Toolbar, shortcuts, messages ---------- */
247
+
248
+ .toolbar {
249
+ position: absolute;
250
+ bottom: 16px;
251
+ left: 50%;
252
+ display: flex;
253
+ align-items: center;
254
+ gap: 2px;
255
+ padding: 4px;
256
+ border: 1px solid var(--x-line);
257
+ border-radius: 10px;
258
+ background: var(--x-raised);
259
+ box-shadow: 0 8px 28px rgb(0 0 0 / 0.4);
260
+ transform: translateX(-50%);
261
+ }
262
+
263
+ .tool {
264
+ display: inline-flex;
265
+ align-items: center;
266
+ justify-content: center;
267
+ gap: 6px;
268
+ min-width: 34px;
269
+ height: 34px;
270
+ padding: 0 8px;
271
+ border: 0;
272
+ border-radius: 6px;
273
+ background: none;
274
+ color: var(--x-text);
275
+ font: inherit;
276
+ font-size: 13px;
277
+ text-decoration: none;
278
+ cursor: pointer;
279
+ }
280
+
281
+ .tool:hover { background: var(--x-wash); }
282
+ .tool[aria-pressed='true'] { color: var(--x-accent); }
283
+ .tool[aria-disabled='true'] { opacity: 0.4; pointer-events: none; }
284
+
285
+ .tool svg {
286
+ width: 17px;
287
+ height: 17px;
288
+ fill: none;
289
+ stroke: currentColor;
290
+ stroke-width: 2;
291
+ stroke-linecap: round;
292
+ stroke-linejoin: round;
293
+ }
294
+
295
+ .zoom { min-width: 58px; font-variant-numeric: tabular-nums; }
296
+
297
+ .sep { width: 1px; height: 20px; margin: 0 4px; background: var(--x-line); }
298
+
299
+ .live { display: inline-flex; align-items: center; gap: 6px; padding: 0 8px; font-size: 12px; color: var(--x-text-soft); }
300
+ .live::before { content: ''; width: 7px; height: 7px; border-radius: 50%; background: var(--x-live); }
301
+ .live.is-off::before { background: var(--x-text-soft); }
302
+
303
+ .help {
304
+ position: absolute;
305
+ right: 16px;
306
+ bottom: 72px;
307
+ width: min(340px, calc(100% - 32px));
308
+ padding: 14px 16px 10px;
309
+ border: 1px solid var(--x-line);
310
+ border-radius: 10px;
311
+ background: var(--x-raised);
312
+ box-shadow: 0 8px 28px rgb(0 0 0 / 0.4);
313
+ font-size: 13px;
314
+ }
315
+
316
+ .help h2 { margin: 0 0 8px; font: 700 18px/1.2 var(--font-display); }
317
+ .help dl { margin: 0; }
318
+ .help dl > div { display: flex; justify-content: space-between; gap: 12px; padding: 5px 0; border-top: 1px solid var(--x-line); }
319
+ .help dt { color: var(--x-text-soft); }
320
+ .help dd { margin: 0; text-align: right; }
321
+
322
+ .toast {
323
+ position: absolute;
324
+ top: 16px;
325
+ left: 50%;
326
+ max-width: calc(100% - 32px);
327
+ padding: 8px 14px;
328
+ border: 1px solid var(--x-line);
329
+ border-radius: 8px;
330
+ background: var(--x-raised);
331
+ font-size: 13px;
332
+ white-space: nowrap;
333
+ overflow: hidden;
334
+ text-overflow: ellipsis;
335
+ opacity: 0;
336
+ pointer-events: none;
337
+ transform: translateX(-50%);
338
+ transition: opacity 0.2s;
339
+ }
340
+
341
+ .toast.is-on { opacity: 1; }
342
+
343
+ .notice {
344
+ position: absolute;
345
+ inset: 0;
346
+ display: grid;
347
+ place-content: center;
348
+ gap: 8px;
349
+ padding: 32px;
350
+ background: var(--x-canvas);
351
+ font-size: 15px;
352
+ line-height: 1.6;
353
+ text-align: center;
354
+ }
355
+
356
+ .notice-mark { justify-self: center; width: auto; height: 72px; margin-bottom: 8px; }
357
+ .notice strong { font: 800 24px/1.2 var(--font-display); }
358
+ .notice p { max-width: 34em; margin: 0; color: var(--x-text-soft); }
359
+ .notice code { padding: 2px 6px; border-radius: 4px; background: var(--x-raised); color: var(--x-text); font-size: 14px; }
360
+
361
+ /* Phones: the toolbar keeps its icons only. */
362
+ @media (max-width: 520px) {
363
+ .toolbar { bottom: 10px; }
364
+ .tool span { display: none; }
365
+ .zoom { min-width: 48px; }
366
+ .live { padding: 0 6px; font-size: 0; }
367
+ .help { bottom: 64px; }
368
+ }
369
+
370
+ @media (prefers-reduced-motion: reduce) {
371
+ .toast { transition: none; }
372
+ }