htmlboard 0.6.0 → 0.8.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.
- package/README.md +12 -2
- package/package.json +1 -1
- package/src/client/explorer.css +180 -2
- package/src/client/explorer.js +602 -46
- package/src/client/index.html +33 -0
- package/src/client/picture.js +375 -0
package/README.md
CHANGED
|
@@ -39,6 +39,9 @@ Modern AI agents and developers generate dozens of HTML mockups, layout variatio
|
|
|
39
39
|
- 📐 **Optional `designs.json`**: Curates when you want it (section titles, order, overrides); designs it doesn't list still show at the end, or don't (`"unlisted": "hide"`). `htmlboard migrate` moves an existing one into the files.
|
|
40
40
|
- 📱 **Device Filters**: **Mobile**, **Tablet** and **Desktop** (and **Components**, or your own categories) lay out just those designs, side by side, to compare them.
|
|
41
41
|
- 🔎 **Search**: Find designs by name, file, tags or section (case and accents don't matter); the others fade out in place.
|
|
42
|
+
- 👁️ **Sections You Can Fold and Hide**: Fold a section in the list, or hide it from the canvas: its designs unload, so a canvas of hundreds of designs stays light. <kbd>Alt</kbd> + click its eye to see only that one; the list remembers what you folded and hid.
|
|
43
|
+
- 🖼️ **Light When Zoomed Out**: Below 25%, each design shows as a picture of its page, made in the background, and the page under it isn't drawn: hundreds of designs pan and zoom smoothly, and their animations don't keep your computer busy. Zoom in and they're live again.
|
|
44
|
+
- 📸 **Copy or Save as an Image**: Hover a design for two buttons: copy it to the clipboard as a PNG of its full size (twice that on a Retina screen), or save it in a folder you choose once. <kbd>C</kbd> and <kbd>S</kbd> do the same for the selected design.
|
|
42
45
|
- 🎯 **Focus & Zoom-to-Fit**: Click a design in the list or double-click it on the canvas to fit it, <kbd>Shift</kbd> + <kbd>1</kbd> fits everything, <kbd>←</kbd> <kbd>→</kbd> step through the designs.
|
|
43
46
|
- 🔗 **Deep Links**: The URL follows the selected design (`http://localhost:3000/#checkout-b`), so you can share or reload a view of one design.
|
|
44
47
|
- ⌨️ **Keyboard-First Workflow**: Figma-like shortcuts; <kbd>?</kbd> lists them.
|
|
@@ -240,7 +243,8 @@ Checked 6 designs in designs (the canvas at http://localhost:3000): 2 with probl
|
|
|
240
243
|
|
|
241
244
|
Every `.html` file under the folder is a design (hidden folders, `node_modules`, `dist`, `build` and the [components](#-components) in `components/` aside):
|
|
242
245
|
|
|
243
|
-
- **Sections are folders.** Each folder is a row on the canvas, named after the folder; the files at the top of the folder make a first row without a title.
|
|
246
|
+
- **Sections are folders.** Each folder is a row on the canvas, named after the folder; the files at the top of the folder make a first row without a title ("Top level" in the list).
|
|
247
|
+
- **In the list, a section folds** under its title, and **its eye hides it from the canvas**: its designs unload and the rows below move up, which keeps a canvas of many designs light. <kbd>Alt</kbd> + click an eye shows only that section (again: all of them); the buttons above the sections fold, unfold, show or hide them all. A hidden section's designs stay in the list, faded, and the search still finds them: clicking one (or a link to it) shows its section. The list remembers what you folded and hid, for each project.
|
|
244
248
|
- **Number prefixes set the order** of files and folders: `01-…`, `02-…`, `10-…` (up to three digits; `2` comes before `10`). They don't show in names or in links, so reordering breaks nothing.
|
|
245
249
|
- **A file describes itself** in its `<head>`:
|
|
246
250
|
|
|
@@ -366,6 +370,7 @@ It writes each file's size, category, label, variants and description as tags af
|
|
|
366
370
|
| <kbd>Shift</kbd> + <kbd>2</kbd>, double-click | Fit the design |
|
|
367
371
|
| <kbd>←</kbd> / <kbd>→</kbd> | Previous / next design |
|
|
368
372
|
| <kbd>Enter</kbd> | Open the selected design on its own |
|
|
373
|
+
| <kbd>C</kbd> / <kbd>S</kbd> | Copy / save the selected design as an image (<kbd>Shift</kbd> + <kbd>S</kbd>: choose the folder) |
|
|
369
374
|
| <kbd>/</kbd> | Search (<kbd>Enter</kbd> fits the first match, <kbd>Esc</kbd> clears) |
|
|
370
375
|
| <kbd>1</kbd> – <kbd>4</kbd> | Show all, mobile, tablet, desktop (<kbd>5</kbd> on: the other categories) |
|
|
371
376
|
| <kbd>R</kbd> | Reload the designs |
|
|
@@ -373,7 +378,11 @@ It writes each file's size, category, label, variants and description as tags af
|
|
|
373
378
|
| <kbd>?</kbd> | Show the shortcuts |
|
|
374
379
|
| <kbd>Esc</kbd> | Deselect, then clear the search, then the filter |
|
|
375
380
|
|
|
376
|
-
On the canvas the designs
|
|
381
|
+
On the canvas the designs don't take clicks: the canvas takes every click and scroll, so <kbd>Ctrl</kbd> + scroll never zooms the page itself. Use **Open** (or <kbd>Enter</kbd>) to try a design's links and hover states on its own.
|
|
382
|
+
|
|
383
|
+
**Zoomed out, pictures.** Below 25% zoom, each design shows a picture of its page instead of the page itself, so a canvas of hundreds of designs stays light: its animations stop, and nothing is drawn again until the view changes. The pictures are made in the background, a few at a time, nearest the middle of the screen first, once a page has loaded and built itself; until then, and while a saved design's picture is made again, its page shows as before. Zoom in past 25% and the designs come back to life once the canvas stops. A picture has the limits of an image of a design (below).
|
|
384
|
+
|
|
385
|
+
**Images of a design.** Hovering a design shows two buttons over its top right corner. **Copy** puts it in the clipboard as a PNG, to paste anywhere; **Save** writes it in a folder you pick the first time (the canvas remembers it for the project, <kbd>Shift</kbd> + click picks another), as `<design id>.png`, then `-2`, `-3`… so it never replaces an image. Browsers without folder access (Firefox, Safari) download it instead. Your browser draws the image from the page as its frame shows it, with its stylesheets, fonts and images, at the frame's size and your screen's pixel density. A few things can't be in it: images from other sites that don't allow it, pages inside the page (iframes), and shadow DOM.
|
|
377
386
|
|
|
378
387
|
---
|
|
379
388
|
|
|
@@ -467,6 +476,7 @@ htmlboard/
|
|
|
467
476
|
│ │ ├── explorer.css # Dark chrome, dot grid
|
|
468
477
|
│ │ ├── explorer.js # Layout, pan/zoom, search, live reload
|
|
469
478
|
│ │ ├── check.js # Checks the designs in frames out of sight, for `htmlboard check`
|
|
479
|
+
│ │ ├── picture.js # A design as an image, drawn by the browser (copy, save, zoomed out)
|
|
470
480
|
│ │ ├── probe.js # Notes script errors and failed loads in the frames the check loads
|
|
471
481
|
│ │ ├── favicon.svg, logo.svg, mark.svg
|
|
472
482
|
│ │ └── fonts/ # Inter and Bricolage Grotesque (OFL)
|
package/package.json
CHANGED
package/src/client/explorer.css
CHANGED
|
@@ -158,6 +158,49 @@ kbd {
|
|
|
158
158
|
|
|
159
159
|
.search input:focus ~ kbd, .search input:not(:placeholder-shown) ~ kbd { display: none; }
|
|
160
160
|
|
|
161
|
+
/* All the sections at once: unfold or fold them in the list, show or hide them on the canvas */
|
|
162
|
+
.sections-bar {
|
|
163
|
+
display: flex;
|
|
164
|
+
align-items: center;
|
|
165
|
+
justify-content: space-between;
|
|
166
|
+
gap: 8px;
|
|
167
|
+
margin: 10px 12px 0;
|
|
168
|
+
padding: 6px 0 0 4px;
|
|
169
|
+
border-top: 1px solid var(--x-line-soft);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
.sections-label { font-size: 11px; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--x-text-faint); }
|
|
173
|
+
.sections-tools { display: flex; align-items: center; gap: 2px; }
|
|
174
|
+
.mini-gap { width: 6px; }
|
|
175
|
+
|
|
176
|
+
/* A small icon button: the sections bar, a section's eye */
|
|
177
|
+
.mini {
|
|
178
|
+
display: inline-grid;
|
|
179
|
+
flex: none;
|
|
180
|
+
place-items: center;
|
|
181
|
+
width: 24px;
|
|
182
|
+
height: 24px;
|
|
183
|
+
padding: 0;
|
|
184
|
+
border: 0;
|
|
185
|
+
border-radius: 5px;
|
|
186
|
+
background: none;
|
|
187
|
+
color: var(--x-text-soft);
|
|
188
|
+
cursor: pointer;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
.mini:hover:not(:disabled) { background: var(--x-wash); color: var(--x-text); }
|
|
192
|
+
.mini:disabled { opacity: 0.35; cursor: default; }
|
|
193
|
+
|
|
194
|
+
.mini svg {
|
|
195
|
+
width: 15px;
|
|
196
|
+
height: 15px;
|
|
197
|
+
fill: none;
|
|
198
|
+
stroke: currentColor;
|
|
199
|
+
stroke-width: 2;
|
|
200
|
+
stroke-linecap: round;
|
|
201
|
+
stroke-linejoin: round;
|
|
202
|
+
}
|
|
203
|
+
|
|
161
204
|
.list { flex: 1; overflow-y: auto; padding: 0 8px 16px; scrollbar-width: thin; scrollbar-color: var(--x-line) transparent; }
|
|
162
205
|
|
|
163
206
|
.list h2 {
|
|
@@ -174,6 +217,67 @@ kbd {
|
|
|
174
217
|
.list ul { margin: 0; padding: 0; list-style: none; }
|
|
175
218
|
.list section > ul:first-child { margin-top: 12px; } /* a section without a title (the files at the top) */
|
|
176
219
|
|
|
220
|
+
/* A section of several: its title (and arrow) folds it, its eye hides it from the canvas. The eye
|
|
221
|
+
shows on hover, and stays while the section is hidden. */
|
|
222
|
+
.group-head { display: flex; align-items: center; gap: 2px; margin: 12px 0 2px; border-radius: 5px; }
|
|
223
|
+
.group-head:hover { background: var(--x-wash); }
|
|
224
|
+
|
|
225
|
+
.group-toggle {
|
|
226
|
+
display: flex;
|
|
227
|
+
flex: 1;
|
|
228
|
+
align-items: center;
|
|
229
|
+
gap: 4px;
|
|
230
|
+
min-width: 0;
|
|
231
|
+
padding: 4px 6px 4px 2px;
|
|
232
|
+
border: 0;
|
|
233
|
+
border-radius: 5px;
|
|
234
|
+
background: none;
|
|
235
|
+
color: var(--x-text-soft);
|
|
236
|
+
font: inherit;
|
|
237
|
+
text-align: left;
|
|
238
|
+
cursor: pointer;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.group-toggle svg {
|
|
242
|
+
flex: none;
|
|
243
|
+
width: 14px;
|
|
244
|
+
height: 14px;
|
|
245
|
+
fill: none;
|
|
246
|
+
stroke: var(--x-text-faint);
|
|
247
|
+
stroke-width: 2;
|
|
248
|
+
stroke-linecap: round;
|
|
249
|
+
stroke-linejoin: round;
|
|
250
|
+
transition: transform 0.12s;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
.group-name {
|
|
254
|
+
flex: 0 1 auto;
|
|
255
|
+
min-width: 0;
|
|
256
|
+
overflow: hidden;
|
|
257
|
+
font-size: 12px;
|
|
258
|
+
font-weight: 700;
|
|
259
|
+
letter-spacing: 0.06em;
|
|
260
|
+
text-transform: uppercase;
|
|
261
|
+
white-space: nowrap;
|
|
262
|
+
text-overflow: ellipsis;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
.group-count { order: 1; flex: none; margin-left: auto; padding-left: 8px; font-size: 12px; color: var(--x-text-faint); font-variant-numeric: tabular-nums; }
|
|
266
|
+
.group-eye { visibility: hidden; }
|
|
267
|
+
.group-head:hover .group-eye, .group-eye:focus-visible, .group.is-hidden .group-eye { visibility: visible; }
|
|
268
|
+
|
|
269
|
+
.group.is-folded .group-toggle svg { transform: rotate(-90deg); }
|
|
270
|
+
.group.is-folded > ul, .group.is-folded > .section-desc { display: none; }
|
|
271
|
+
.group-head ~ ul .item { padding-left: 20px; }
|
|
272
|
+
.list .group-head ~ .section-desc { margin-left: 20px; }
|
|
273
|
+
|
|
274
|
+
.group.is-hidden .group-name, .group.is-hidden .group-count, .group.is-hidden .item { opacity: 0.45; }
|
|
275
|
+
.group.is-hidden .item:hover { opacity: 0.8; }
|
|
276
|
+
|
|
277
|
+
/* A folded section holding the selected design says so */
|
|
278
|
+
.group.has-selection .group-name { color: var(--x-accent-text); }
|
|
279
|
+
.group.has-selection .group-toggle::after { content: ''; flex: none; width: 6px; height: 6px; margin-left: 3px; border-radius: 50%; background: var(--x-accent); }
|
|
280
|
+
|
|
177
281
|
.item {
|
|
178
282
|
display: flex;
|
|
179
283
|
align-items: baseline;
|
|
@@ -210,10 +314,14 @@ kbd {
|
|
|
210
314
|
|
|
211
315
|
/* ---------- The canvas ---------- */
|
|
212
316
|
|
|
317
|
+
/* clip, not just hidden: a hidden box can still be scrolled, and a design calling scrollIntoView()
|
|
318
|
+
or focus() scrolls every box around its frame, which would shift the canvas, its toolbar and
|
|
319
|
+
where the pointer lands. A clipped box can't scroll. */
|
|
213
320
|
.viewport {
|
|
214
321
|
position: relative;
|
|
215
322
|
flex: 1;
|
|
216
323
|
overflow: hidden;
|
|
324
|
+
overflow: clip;
|
|
217
325
|
touch-action: none;
|
|
218
326
|
background-color: var(--x-canvas);
|
|
219
327
|
background-image: radial-gradient(circle at 1px 1px, var(--x-dot) 1px, transparent 0);
|
|
@@ -225,6 +333,7 @@ kbd {
|
|
|
225
333
|
.viewport.is-space { cursor: grab; }
|
|
226
334
|
|
|
227
335
|
.world { position: absolute; left: 0; top: 0; transform-origin: 0 0; }
|
|
336
|
+
.world.is-moving { will-change: transform; } /* see setView in explorer.js */
|
|
228
337
|
|
|
229
338
|
.frame {
|
|
230
339
|
position: absolute;
|
|
@@ -239,7 +348,17 @@ kbd {
|
|
|
239
348
|
/* Designs the search leaves out stay in place, faded. */
|
|
240
349
|
.frame.is-dimmed { opacity: 0.2; }
|
|
241
350
|
|
|
242
|
-
|
|
351
|
+
/* Well off screen: not drawn (see paint in explorer.js) */
|
|
352
|
+
.frame.is-away { content-visibility: hidden; }
|
|
353
|
+
|
|
354
|
+
/* Zoomed out, a picture of the page over it, and the page not drawn while the picture is up to date
|
|
355
|
+
(see pictures in explorer.js) */
|
|
356
|
+
.page { position: absolute; inset: 0; }
|
|
357
|
+
.frame.is-still .page { content-visibility: hidden; }
|
|
358
|
+
.picture { position: absolute; inset: 0; display: none; width: 100%; height: 100%; pointer-events: none; user-select: none; }
|
|
359
|
+
.frame.is-picture .picture { display: block; }
|
|
360
|
+
|
|
361
|
+
.overlay { position: absolute; inset: 0; overflow: hidden; overflow: clip; pointer-events: none; }
|
|
243
362
|
|
|
244
363
|
.label, .section-title, .outline, .size { position: absolute; left: 0; top: 0; }
|
|
245
364
|
|
|
@@ -259,9 +378,38 @@ kbd {
|
|
|
259
378
|
|
|
260
379
|
.section-title { font: 700 20px/24px var(--font-display); letter-spacing: 0.01em; white-space: nowrap; color: var(--x-text); }
|
|
261
380
|
|
|
381
|
+
/* Zoomed far out, a title goes over the top of its designs: on a backing, so it reads over them */
|
|
382
|
+
.section-title.is-over {
|
|
383
|
+
margin-left: -5px;
|
|
384
|
+
padding: 0 5px;
|
|
385
|
+
border-radius: 4px;
|
|
386
|
+
background: color-mix(in srgb, var(--x-canvas) 85%, transparent);
|
|
387
|
+
}
|
|
388
|
+
|
|
262
389
|
.outline--hover { box-shadow: 0 0 0 1px var(--x-accent); }
|
|
263
390
|
.outline--selected { box-shadow: 0 0 0 2px var(--x-accent); }
|
|
264
391
|
|
|
392
|
+
/* The buttons of the design under the pointer, over its top right corner: copy it, save it */
|
|
393
|
+
.actions {
|
|
394
|
+
position: absolute;
|
|
395
|
+
left: 0;
|
|
396
|
+
top: 0;
|
|
397
|
+
display: flex;
|
|
398
|
+
gap: 2px;
|
|
399
|
+
padding: 2px;
|
|
400
|
+
border: 1px solid var(--x-line);
|
|
401
|
+
border-radius: 8px;
|
|
402
|
+
background: var(--x-glass);
|
|
403
|
+
backdrop-filter: blur(16px) saturate(1.4);
|
|
404
|
+
box-shadow: 0 4px 16px rgb(0 0 0 / 0.4);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
/* Bridges the gap to the design, so the pointer stays on it on the way to the buttons */
|
|
408
|
+
.actions::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 8px; }
|
|
409
|
+
.actions.is-below::after { top: auto; bottom: 100%; }
|
|
410
|
+
.actions .mini { color: var(--x-text); }
|
|
411
|
+
.actions[aria-busy='true'] .mini { opacity: 0.5; cursor: progress; }
|
|
412
|
+
|
|
265
413
|
.size {
|
|
266
414
|
padding: 2px 6px;
|
|
267
415
|
border-radius: 4px;
|
|
@@ -375,6 +523,36 @@ kbd {
|
|
|
375
523
|
|
|
376
524
|
.toast.is-on { opacity: 1; }
|
|
377
525
|
|
|
526
|
+
/* The canvas with nothing left to show (every section hidden), and the way back */
|
|
527
|
+
.empty {
|
|
528
|
+
position: absolute;
|
|
529
|
+
inset: 0 0 64px;
|
|
530
|
+
display: grid;
|
|
531
|
+
place-content: center;
|
|
532
|
+
justify-items: center;
|
|
533
|
+
gap: 12px;
|
|
534
|
+
font-size: 14px;
|
|
535
|
+
color: var(--x-text-soft);
|
|
536
|
+
pointer-events: none;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
.empty p { margin: 0; }
|
|
540
|
+
|
|
541
|
+
.empty-show {
|
|
542
|
+
height: 30px;
|
|
543
|
+
padding: 0 14px;
|
|
544
|
+
border: 1px solid var(--x-line);
|
|
545
|
+
border-radius: 15px;
|
|
546
|
+
background: var(--x-raised);
|
|
547
|
+
color: var(--x-text);
|
|
548
|
+
font: inherit;
|
|
549
|
+
font-size: 13px;
|
|
550
|
+
cursor: pointer;
|
|
551
|
+
pointer-events: auto;
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
.empty-show:hover { border-color: transparent; background: var(--x-accent-wash); color: var(--x-accent-text); }
|
|
555
|
+
|
|
378
556
|
.notice {
|
|
379
557
|
position: absolute;
|
|
380
558
|
inset: 0;
|
|
@@ -403,5 +581,5 @@ kbd {
|
|
|
403
581
|
}
|
|
404
582
|
|
|
405
583
|
@media (prefers-reduced-motion: reduce) {
|
|
406
|
-
.toast { transition: none; }
|
|
584
|
+
.toast, .group-toggle svg { transition: none; }
|
|
407
585
|
}
|