palace-ui 0.13.0 → 0.14.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/SPEC.md +175 -59
- package/catalog.html +887 -674
- package/package.json +8 -2
- package/palace.css +132 -49
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "palace-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Retro-technical design system. Hard edges, vibrant gradients, chunky shadows.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -21,5 +21,11 @@
|
|
|
21
21
|
"design-system",
|
|
22
22
|
"design-tokens",
|
|
23
23
|
"ui"
|
|
24
|
-
]
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"test": "node --test 'test/*.test.js'"
|
|
27
|
+
},
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"playwright-core": "^1.64.1"
|
|
30
|
+
}
|
|
25
31
|
}
|
package/palace.css
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/*! palace-ui v0.
|
|
1
|
+
/*! palace-ui v0.14.0 | MIT License | fonts: SIL OFL 1.1, see fonts/ */
|
|
2
2
|
|
|
3
3
|
/* ══════════════════════════════════════════
|
|
4
4
|
FONTS (self-hosted; no third-party requests)
|
|
@@ -69,6 +69,11 @@
|
|
|
69
69
|
--p-space-xl: 32px;
|
|
70
70
|
--p-space-2xl: 48px;
|
|
71
71
|
|
|
72
|
+
/* ── Controls ── */
|
|
73
|
+
--p-control-height: 44px;
|
|
74
|
+
--p-control-height-sm: 32px;
|
|
75
|
+
--p-control-height-lg: 52px;
|
|
76
|
+
|
|
72
77
|
/* ── Layout ── */
|
|
73
78
|
--p-container-width: 640px;
|
|
74
79
|
--p-container-narrow: 420px;
|
|
@@ -239,27 +244,31 @@ h3, .h3 {
|
|
|
239
244
|
padding: 32px 16px;
|
|
240
245
|
}
|
|
241
246
|
|
|
242
|
-
/* Page header —
|
|
247
|
+
/* Page header — top padding for non-hero pages; no outer margin, the
|
|
248
|
+
space after it comes from the parent's space-* or gap-* */
|
|
243
249
|
.page-header {
|
|
244
250
|
padding-top: var(--p-space-2xl);
|
|
245
251
|
}
|
|
246
252
|
|
|
247
|
-
.page-header
|
|
248
|
-
.page-header .h1 {
|
|
249
|
-
margin-bottom: var(--p-space-sm);
|
|
250
|
-
}
|
|
253
|
+
:where(.page-header) > * + * { margin-top: var(--p-space-sm); }
|
|
251
254
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
+
/* ── Space and gap (the spacing scale between children) ──
|
|
256
|
+
space-*: vertical rhythm between children, with the owl selector.
|
|
257
|
+
gap-*: the gap of a flex or grid container.
|
|
258
|
+
stack-*: deprecated alias of space-*, kept so existing markup works. */
|
|
259
|
+
.space-xs > * + *, .stack-xs > * + * { margin-top: var(--p-space-xs); }
|
|
260
|
+
.space-sm > * + *, .stack-sm > * + * { margin-top: var(--p-space-sm); }
|
|
261
|
+
.space-md > * + *, .stack-md > * + * { margin-top: var(--p-space-md); }
|
|
262
|
+
.space-lg > * + *, .stack-lg > * + * { margin-top: var(--p-space-lg); }
|
|
263
|
+
.space-xl > * + *, .stack-xl > * + * { margin-top: var(--p-space-xl); }
|
|
264
|
+
.space-2xl > * + *, .stack-2xl > * + * { margin-top: var(--p-space-2xl); }
|
|
255
265
|
|
|
256
|
-
|
|
257
|
-
.
|
|
258
|
-
.
|
|
259
|
-
.
|
|
260
|
-
.
|
|
261
|
-
.
|
|
262
|
-
.stack-2xl > * + * { margin-top: var(--p-space-2xl); }
|
|
266
|
+
.gap-xs { gap: var(--p-space-xs); }
|
|
267
|
+
.gap-sm { gap: var(--p-space-sm); }
|
|
268
|
+
.gap-md { gap: var(--p-space-md); }
|
|
269
|
+
.gap-lg { gap: var(--p-space-lg); }
|
|
270
|
+
.gap-xl { gap: var(--p-space-xl); }
|
|
271
|
+
.gap-2xl { gap: var(--p-space-2xl); }
|
|
263
272
|
|
|
264
273
|
/* ── Lists (restore styling inside containers) ── */
|
|
265
274
|
.container ul,
|
|
@@ -281,11 +290,13 @@ h3, .h3 {
|
|
|
281
290
|
|
|
282
291
|
.container li,
|
|
283
292
|
.container-narrow li {
|
|
284
|
-
margin-bottom: 4px;
|
|
285
293
|
font-size: 14px;
|
|
286
294
|
line-height: 1.5;
|
|
287
295
|
}
|
|
288
296
|
|
|
297
|
+
/* Items of a list, never items laid out by a component (a dialog's <menu>). */
|
|
298
|
+
:where(.container, .container-narrow) :where(ul, ol) > :where(li + li) { margin-top: var(--p-space-xs); }
|
|
299
|
+
|
|
289
300
|
|
|
290
301
|
/* ══════════════════════════════════════════
|
|
291
302
|
LINKS
|
|
@@ -314,8 +325,15 @@ a:focus-visible {
|
|
|
314
325
|
BUTTONS
|
|
315
326
|
══════════════════════════════════════════ */
|
|
316
327
|
|
|
328
|
+
/* Buttons are inline blocks, so the spaces between inline children (a label
|
|
329
|
+
and its <kbd>, <strong>) stay. One line of label is centred by padding
|
|
330
|
+
derived from the control height: (height - one line - both borders) / 2.
|
|
331
|
+
A label that wraps grows the button past min-height. */
|
|
317
332
|
button, .btn {
|
|
318
|
-
|
|
333
|
+
min-height: var(--p-control-height);
|
|
334
|
+
padding: calc((var(--p-control-height) - 1.3em - 2 * var(--p-border-width)) / 2) 16px;
|
|
335
|
+
/* One line height for <button> and <a class="btn">, so a long label wraps the same in both. */
|
|
336
|
+
line-height: 1.3;
|
|
319
337
|
font-family: var(--p-font);
|
|
320
338
|
font-size: 13px;
|
|
321
339
|
font-weight: 700;
|
|
@@ -331,25 +349,22 @@ button, .btn {
|
|
|
331
349
|
transition-property: background-color, border-color, color, box-shadow, transform, outline-color;
|
|
332
350
|
text-decoration: none;
|
|
333
351
|
display: inline-block;
|
|
334
|
-
/* One line height for <button> and <a class="btn">, so both are the same
|
|
335
|
-
height: a button does not inherit the body's line height, a link does. */
|
|
336
|
-
line-height: calc(17 / 13);
|
|
337
352
|
text-align: center;
|
|
338
353
|
}
|
|
339
354
|
|
|
340
355
|
/* Size modifiers */
|
|
341
356
|
.btn-sm {
|
|
342
|
-
|
|
357
|
+
min-height: var(--p-control-height-sm);
|
|
358
|
+
padding: calc((var(--p-control-height-sm) - 1.3em - 2 * var(--p-border-thin)) / 2) 14px;
|
|
343
359
|
font-size: 11px;
|
|
344
|
-
line-height: calc(14 / 11);
|
|
345
360
|
letter-spacing: 1px;
|
|
346
361
|
border-width: var(--p-border-thin);
|
|
347
362
|
}
|
|
348
363
|
|
|
349
364
|
.btn-lg {
|
|
350
|
-
|
|
365
|
+
min-height: var(--p-control-height-lg);
|
|
366
|
+
padding: calc((var(--p-control-height-lg) - 1.3em - 2 * var(--p-border-width)) / 2) 28px;
|
|
351
367
|
font-size: 15px;
|
|
352
|
-
line-height: calc(19 / 15);
|
|
353
368
|
letter-spacing: 1px;
|
|
354
369
|
}
|
|
355
370
|
|
|
@@ -409,11 +424,13 @@ button, .btn {
|
|
|
409
424
|
|
|
410
425
|
/* CTA buttons (larger, for hero sections) */
|
|
411
426
|
.cta {
|
|
427
|
+
/* An inline block like .btn, centred by padding from the large control height. */
|
|
412
428
|
display: inline-block;
|
|
413
|
-
|
|
429
|
+
min-height: var(--p-control-height-lg);
|
|
430
|
+
padding: calc((var(--p-control-height-lg) - 1.3em - 2 * var(--p-border-width)) / 2) 28px;
|
|
431
|
+
line-height: 1.3;
|
|
414
432
|
font-family: var(--p-font);
|
|
415
433
|
font-size: 15px;
|
|
416
|
-
line-height: calc(19 / 15);
|
|
417
434
|
text-align: center;
|
|
418
435
|
font-weight: 700;
|
|
419
436
|
text-transform: uppercase;
|
|
@@ -474,7 +491,6 @@ button:disabled, .btn:disabled, .cta:disabled {
|
|
|
474
491
|
background: var(--p-surface);
|
|
475
492
|
border: var(--p-border-width) solid var(--p-text);
|
|
476
493
|
padding: var(--p-space-lg);
|
|
477
|
-
margin-bottom: var(--p-space-md);
|
|
478
494
|
transition: var(--p-transition);
|
|
479
495
|
}
|
|
480
496
|
|
|
@@ -515,7 +531,6 @@ button:disabled, .btn:disabled, .cta:disabled {
|
|
|
515
531
|
background: var(--p-surface);
|
|
516
532
|
border: var(--p-border-width) solid var(--p-text);
|
|
517
533
|
padding: var(--p-space-lg);
|
|
518
|
-
margin-bottom: var(--p-space-md);
|
|
519
534
|
}
|
|
520
535
|
|
|
521
536
|
.card-static:hover {
|
|
@@ -535,7 +550,6 @@ label {
|
|
|
535
550
|
text-transform: uppercase;
|
|
536
551
|
letter-spacing: 1px;
|
|
537
552
|
color: var(--p-text-muted);
|
|
538
|
-
margin-bottom: 6px;
|
|
539
553
|
}
|
|
540
554
|
|
|
541
555
|
input[type="text"],
|
|
@@ -545,7 +559,8 @@ input[type="search"],
|
|
|
545
559
|
input[type="url"],
|
|
546
560
|
input[type="tel"] {
|
|
547
561
|
width: 100%;
|
|
548
|
-
|
|
562
|
+
height: var(--p-control-height);
|
|
563
|
+
padding: 0 12px;
|
|
549
564
|
border: var(--p-border-width) solid var(--p-text);
|
|
550
565
|
border-radius: var(--p-radius);
|
|
551
566
|
font-family: var(--p-font);
|
|
@@ -569,7 +584,8 @@ input[type="tel"]:focus {
|
|
|
569
584
|
select {
|
|
570
585
|
width: 100%;
|
|
571
586
|
appearance: none;
|
|
572
|
-
|
|
587
|
+
height: var(--p-control-height);
|
|
588
|
+
padding: 0 2.5rem 0 12px;
|
|
573
589
|
border: var(--p-border-width) solid var(--p-text);
|
|
574
590
|
border-radius: var(--p-radius);
|
|
575
591
|
background-color: var(--p-surface);
|
|
@@ -584,7 +600,7 @@ select {
|
|
|
584
600
|
outline: none;
|
|
585
601
|
transition: var(--p-transition);
|
|
586
602
|
}
|
|
587
|
-
select[multiple], select[size]:not([size="1"]) { padding
|
|
603
|
+
select[multiple], select[size]:not([size="1"]) { height: auto; padding: 12px; background-image: none; }
|
|
588
604
|
|
|
589
605
|
select:focus {
|
|
590
606
|
border-color: var(--p-accent-2);
|
|
@@ -604,7 +620,8 @@ input[type="number"] {
|
|
|
604
620
|
text-align: center;
|
|
605
621
|
border: var(--p-border-width) solid var(--p-text);
|
|
606
622
|
border-radius: var(--p-radius);
|
|
607
|
-
|
|
623
|
+
height: var(--p-control-height);
|
|
624
|
+
padding: 0 4px;
|
|
608
625
|
outline: none;
|
|
609
626
|
background: var(--p-surface);
|
|
610
627
|
transition: var(--p-transition);
|
|
@@ -624,7 +641,7 @@ textarea {
|
|
|
624
641
|
border-radius: var(--p-radius);
|
|
625
642
|
padding: var(--p-space-sm);
|
|
626
643
|
resize: vertical;
|
|
627
|
-
min-height:
|
|
644
|
+
min-height: var(--p-control-height);
|
|
628
645
|
background: var(--p-surface);
|
|
629
646
|
outline: none;
|
|
630
647
|
transition: var(--p-transition);
|
|
@@ -637,28 +654,74 @@ textarea:focus {
|
|
|
637
654
|
}
|
|
638
655
|
|
|
639
656
|
/* A field with a button inside, at its right end (show/hide a password, paste,
|
|
640
|
-
copy). The input reserves the button's width plus its
|
|
657
|
+
copy). The input reserves the button's width plus its --p-space-sm inset; the button is centred in the
|
|
641
658
|
input's box and lifts with it, so the two stay aligned on focus. Set
|
|
642
659
|
--input-action-width to the button's width when it is wider. */
|
|
643
660
|
.input-action {
|
|
644
661
|
position: relative;
|
|
645
662
|
--input-action-width: 4.75rem;
|
|
646
663
|
}
|
|
647
|
-
.input-action > input[type] { padding-inline-end: calc(var(--input-action-width) +
|
|
664
|
+
.input-action > input[type] { padding-inline-end: calc(var(--input-action-width) + var(--p-space-sm)); }
|
|
648
665
|
.input-action > .btn,
|
|
649
666
|
.input-action > button {
|
|
650
667
|
position: absolute;
|
|
651
668
|
inset-block: 0;
|
|
652
|
-
inset-inline-end:
|
|
653
|
-
height:
|
|
669
|
+
inset-inline-end: var(--p-space-sm);
|
|
670
|
+
min-height: 0;
|
|
671
|
+
height: var(--p-control-height-sm);
|
|
654
672
|
white-space: nowrap;
|
|
655
673
|
margin-block: auto;
|
|
674
|
+
/* One line centred in the small height, as .btn centres it in its own. */
|
|
675
|
+
padding-block: calc((var(--p-control-height-sm) - 1.3em - 2 * var(--p-border-width)) / 2);
|
|
656
676
|
}
|
|
677
|
+
.input-action > .btn-sm { padding-block: calc((var(--p-control-height-sm) - 1.3em - 2 * var(--p-border-thin)) / 2); }
|
|
657
678
|
.input-action > input:focus ~ .btn,
|
|
658
679
|
.input-action > input:focus ~ button {
|
|
659
680
|
transform: var(--p-lift-sm);
|
|
660
681
|
}
|
|
661
682
|
|
|
683
|
+
/* A label, its control and an optional hint or error, spaced by --p-space-sm. */
|
|
684
|
+
.field {
|
|
685
|
+
display: grid;
|
|
686
|
+
gap: var(--p-space-sm);
|
|
687
|
+
}
|
|
688
|
+
.field-hint,
|
|
689
|
+
.field-error {
|
|
690
|
+
/* palace has no font-size tokens; 12px matches the file's other small text */
|
|
691
|
+
font-size: 12px;
|
|
692
|
+
}
|
|
693
|
+
.field-hint { color: var(--p-text-muted); }
|
|
694
|
+
.field-error {
|
|
695
|
+
color: var(--p-error-text);
|
|
696
|
+
font-weight: 600;
|
|
697
|
+
}
|
|
698
|
+
:is(input, select, textarea)[aria-invalid="true"],
|
|
699
|
+
:is(input, select, textarea)[aria-invalid="true"]:focus {
|
|
700
|
+
border-color: var(--p-error-text);
|
|
701
|
+
}
|
|
702
|
+
:is(input:not([type="checkbox"], [type="radio"]), select, textarea)[aria-invalid="true"]:focus {
|
|
703
|
+
box-shadow: var(--p-shadow-md) var(--p-error-text);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
/* Fields and buttons in one row, bottoms aligned; the row wraps when narrow.
|
|
707
|
+
Fields, inputs, selects and textareas grow from --control-row-min; buttons keep their
|
|
708
|
+
natural width but may shrink and wrap their label. */
|
|
709
|
+
.control-row {
|
|
710
|
+
--control-row-min: 12rem;
|
|
711
|
+
display: flex;
|
|
712
|
+
flex-wrap: wrap;
|
|
713
|
+
align-items: end;
|
|
714
|
+
gap: var(--p-space-sm);
|
|
715
|
+
}
|
|
716
|
+
.control-row > :is(.field, .input-action, input, select, textarea) {
|
|
717
|
+
flex: 1 1 var(--control-row-min);
|
|
718
|
+
min-width: 0;
|
|
719
|
+
}
|
|
720
|
+
.control-row > :is(button, .btn) {
|
|
721
|
+
flex: 0 1 auto;
|
|
722
|
+
max-width: 100%;
|
|
723
|
+
}
|
|
724
|
+
|
|
662
725
|
/* Checkboxes, radio buttons and disclosure summaries: the focus ring of links
|
|
663
726
|
and buttons, without the lift. */
|
|
664
727
|
input[type="checkbox"]:focus-visible,
|
|
@@ -850,9 +913,11 @@ summary:focus-visible {
|
|
|
850
913
|
border: var(--p-border-width) solid var(--p-text);
|
|
851
914
|
box-shadow: var(--p-shadow-md) var(--p-accent-2);
|
|
852
915
|
}
|
|
853
|
-
|
|
916
|
+
/* The margin resets sit in :where() so a space-* utility on the panel beats them. */
|
|
917
|
+
:where(.navbar-menu-panel ul) { margin: 0; }
|
|
918
|
+
.navbar-menu-panel ul { list-style: none; padding: 0; display: grid; }
|
|
919
|
+
:where(.navbar-menu-label) { margin: 0; }
|
|
854
920
|
.navbar-menu-label {
|
|
855
|
-
margin: 0;
|
|
856
921
|
padding: var(--p-space-sm) var(--p-space-md) var(--p-space-xs);
|
|
857
922
|
font-size: 11px;
|
|
858
923
|
font-weight: 600;
|
|
@@ -864,6 +929,8 @@ summary:focus-visible {
|
|
|
864
929
|
.navbar-menu-panel button {
|
|
865
930
|
display: block;
|
|
866
931
|
width: 100%;
|
|
932
|
+
height: auto;
|
|
933
|
+
min-height: 0;
|
|
867
934
|
box-sizing: border-box;
|
|
868
935
|
padding: var(--p-space-sm) var(--p-space-md);
|
|
869
936
|
border: 0;
|
|
@@ -988,7 +1055,6 @@ pre {
|
|
|
988
1055
|
.code-reveal code {
|
|
989
1056
|
word-break: break-all;
|
|
990
1057
|
display: block;
|
|
991
|
-
margin-bottom: 12px;
|
|
992
1058
|
background: var(--p-code-bg);
|
|
993
1059
|
padding: 12px;
|
|
994
1060
|
border: var(--p-border-thin) solid var(--p-border-dark);
|
|
@@ -996,6 +1062,8 @@ pre {
|
|
|
996
1062
|
user-select: all;
|
|
997
1063
|
}
|
|
998
1064
|
|
|
1065
|
+
:where(.code-reveal) > * + * { margin-top: var(--p-space-sm); }
|
|
1066
|
+
|
|
999
1067
|
|
|
1000
1068
|
/* ══════════════════════════════════════════
|
|
1001
1069
|
TAGS & BADGES
|
|
@@ -1213,14 +1281,14 @@ pre {
|
|
|
1213
1281
|
text-transform: uppercase;
|
|
1214
1282
|
letter-spacing: 1.5px;
|
|
1215
1283
|
color: var(--p-text-faint);
|
|
1216
|
-
margin-top: 2px;
|
|
1217
1284
|
}
|
|
1285
|
+
/* Optical: 2px off the stat above. In :where() so space-* wins. */
|
|
1286
|
+
:where(.stat-label) { margin-top: 2px; }
|
|
1218
1287
|
|
|
1219
1288
|
.stat-row {
|
|
1220
1289
|
display: flex;
|
|
1221
1290
|
flex-wrap: wrap;
|
|
1222
1291
|
gap: var(--p-space-lg);
|
|
1223
|
-
margin-bottom: 12px;
|
|
1224
1292
|
}
|
|
1225
1293
|
|
|
1226
1294
|
|
|
@@ -1264,21 +1332,30 @@ dialog {
|
|
|
1264
1332
|
which is what centres a modal dialog. Restored here. */
|
|
1265
1333
|
margin: auto;
|
|
1266
1334
|
}
|
|
1335
|
+
/* An open modal stays centred even in a space-* container, whose
|
|
1336
|
+
`> * + *` margin is more specific than `dialog`. */
|
|
1337
|
+
dialog:modal { margin: auto; }
|
|
1267
1338
|
|
|
1268
1339
|
dialog::backdrop { background: var(--p-overlay); }
|
|
1269
|
-
dialog
|
|
1270
|
-
|
|
1271
|
-
|
|
1340
|
+
/* The dialog spaces its own children, or those of a direct <form>.
|
|
1341
|
+
Specificity decides first: `.subtitle + *` has a class and beats the
|
|
1342
|
+
element rules. Among those (p, h2, menu), all equally specific, the later
|
|
1343
|
+
one in this file wins, so a <menu> after a <p> still gets lg. */
|
|
1344
|
+
:where(dialog, dialog > form) > p + * { margin-top: var(--p-space-sm); }
|
|
1345
|
+
:where(dialog, dialog > form) > h2 + * { margin-top: var(--p-space-xs); }
|
|
1346
|
+
:where(dialog, dialog > form) > .subtitle + *,
|
|
1347
|
+
:where(dialog, dialog > form) > menu { margin-top: var(--p-space-lg); }
|
|
1272
1348
|
|
|
1273
1349
|
dialog menu {
|
|
1274
1350
|
display: flex;
|
|
1275
1351
|
gap: var(--p-space-sm);
|
|
1276
1352
|
padding: 0;
|
|
1277
|
-
margin: var(--p-space-lg) 0 0;
|
|
1278
1353
|
list-style: none;
|
|
1279
1354
|
}
|
|
1280
1355
|
|
|
1281
|
-
|
|
1356
|
+
/* The buttons share the row's width. */
|
|
1357
|
+
dialog menu > li { flex: 1; }
|
|
1358
|
+
dialog menu > li > :is(button, .btn) { width: 100%; }
|
|
1282
1359
|
|
|
1283
1360
|
/* ══════════════════════════════════════════
|
|
1284
1361
|
KBD
|
|
@@ -1290,6 +1367,12 @@ kbd {
|
|
|
1290
1367
|
border: var(--p-border-thin) solid var(--p-text);
|
|
1291
1368
|
box-shadow: 1px 1px 0 var(--p-accent-1);
|
|
1292
1369
|
background: var(--p-surface-inset);
|
|
1370
|
+
/* Its own text colour: on its light background, a key must not take a button's white. */
|
|
1371
|
+
color: var(--p-text);
|
|
1293
1372
|
font: 600 11px/1.5 var(--p-font-mono);
|
|
1294
1373
|
text-transform: uppercase;
|
|
1295
1374
|
}
|
|
1375
|
+
/* A key in a button's label stays inside the button's one line, so the button
|
|
1376
|
+
keeps the control height: sized from the button's font (11px in a 13px
|
|
1377
|
+
button), with no padding or line height of its own above it. */
|
|
1378
|
+
:where(button, .btn, .cta) kbd { padding-block: 0; font-size: 0.85em; line-height: 1; vertical-align: middle; }
|