htmlboard 0.5.0 → 0.7.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 +29 -9
- package/bin/htmlboard.js +24 -4
- package/package.json +1 -1
- package/skills/htmlboard/SKILL.md +21 -60
- package/skills/htmlboard-components/SKILL.md +36 -0
- package/skills/htmlboard-designs-json/SKILL.md +25 -0
- package/src/canvases.js +38 -0
- package/src/check.js +1 -31
- package/src/client/explorer.css +173 -2
- package/src/client/explorer.js +436 -36
- package/src/client/index.html +33 -0
- package/src/client/picture.js +281 -0
- package/src/designs.js +44 -8
- package/src/migrate.js +2 -2
- package/src/server.js +1 -1
- package/src/skills.js +2 -1
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,10 @@ 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
|
+
.overlay { position: absolute; inset: 0; overflow: hidden; overflow: clip; pointer-events: none; }
|
|
243
355
|
|
|
244
356
|
.label, .section-title, .outline, .size { position: absolute; left: 0; top: 0; }
|
|
245
357
|
|
|
@@ -259,9 +371,38 @@ kbd {
|
|
|
259
371
|
|
|
260
372
|
.section-title { font: 700 20px/24px var(--font-display); letter-spacing: 0.01em; white-space: nowrap; color: var(--x-text); }
|
|
261
373
|
|
|
374
|
+
/* Zoomed far out, a title goes over the top of its designs: on a backing, so it reads over them */
|
|
375
|
+
.section-title.is-over {
|
|
376
|
+
margin-left: -5px;
|
|
377
|
+
padding: 0 5px;
|
|
378
|
+
border-radius: 4px;
|
|
379
|
+
background: color-mix(in srgb, var(--x-canvas) 85%, transparent);
|
|
380
|
+
}
|
|
381
|
+
|
|
262
382
|
.outline--hover { box-shadow: 0 0 0 1px var(--x-accent); }
|
|
263
383
|
.outline--selected { box-shadow: 0 0 0 2px var(--x-accent); }
|
|
264
384
|
|
|
385
|
+
/* The buttons of the design under the pointer, over its top right corner: copy it, save it */
|
|
386
|
+
.actions {
|
|
387
|
+
position: absolute;
|
|
388
|
+
left: 0;
|
|
389
|
+
top: 0;
|
|
390
|
+
display: flex;
|
|
391
|
+
gap: 2px;
|
|
392
|
+
padding: 2px;
|
|
393
|
+
border: 1px solid var(--x-line);
|
|
394
|
+
border-radius: 8px;
|
|
395
|
+
background: var(--x-glass);
|
|
396
|
+
backdrop-filter: blur(16px) saturate(1.4);
|
|
397
|
+
box-shadow: 0 4px 16px rgb(0 0 0 / 0.4);
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
/* Bridges the gap to the design, so the pointer stays on it on the way to the buttons */
|
|
401
|
+
.actions::after { content: ''; position: absolute; left: 0; right: 0; top: 100%; height: 8px; }
|
|
402
|
+
.actions.is-below::after { top: auto; bottom: 100%; }
|
|
403
|
+
.actions .mini { color: var(--x-text); }
|
|
404
|
+
.actions[aria-busy='true'] .mini { opacity: 0.5; cursor: progress; }
|
|
405
|
+
|
|
265
406
|
.size {
|
|
266
407
|
padding: 2px 6px;
|
|
267
408
|
border-radius: 4px;
|
|
@@ -375,6 +516,36 @@ kbd {
|
|
|
375
516
|
|
|
376
517
|
.toast.is-on { opacity: 1; }
|
|
377
518
|
|
|
519
|
+
/* The canvas with nothing left to show (every section hidden), and the way back */
|
|
520
|
+
.empty {
|
|
521
|
+
position: absolute;
|
|
522
|
+
inset: 0 0 64px;
|
|
523
|
+
display: grid;
|
|
524
|
+
place-content: center;
|
|
525
|
+
justify-items: center;
|
|
526
|
+
gap: 12px;
|
|
527
|
+
font-size: 14px;
|
|
528
|
+
color: var(--x-text-soft);
|
|
529
|
+
pointer-events: none;
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
.empty p { margin: 0; }
|
|
533
|
+
|
|
534
|
+
.empty-show {
|
|
535
|
+
height: 30px;
|
|
536
|
+
padding: 0 14px;
|
|
537
|
+
border: 1px solid var(--x-line);
|
|
538
|
+
border-radius: 15px;
|
|
539
|
+
background: var(--x-raised);
|
|
540
|
+
color: var(--x-text);
|
|
541
|
+
font: inherit;
|
|
542
|
+
font-size: 13px;
|
|
543
|
+
cursor: pointer;
|
|
544
|
+
pointer-events: auto;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
.empty-show:hover { border-color: transparent; background: var(--x-accent-wash); color: var(--x-accent-text); }
|
|
548
|
+
|
|
378
549
|
.notice {
|
|
379
550
|
position: absolute;
|
|
380
551
|
inset: 0;
|
|
@@ -403,5 +574,5 @@ kbd {
|
|
|
403
574
|
}
|
|
404
575
|
|
|
405
576
|
@media (prefers-reduced-motion: reduce) {
|
|
406
|
-
.toast { transition: none; }
|
|
577
|
+
.toast, .group-toggle svg { transition: none; }
|
|
407
578
|
}
|