palace-ui 0.12.2 → 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 +182 -54
- package/catalog.html +886 -664
- package/package.json +8 -2
- package/palace.css +152 -45
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);
|
|
@@ -636,6 +653,75 @@ textarea:focus {
|
|
|
636
653
|
transform: var(--p-lift-sm);
|
|
637
654
|
}
|
|
638
655
|
|
|
656
|
+
/* A field with a button inside, at its right end (show/hide a password, paste,
|
|
657
|
+
copy). The input reserves the button's width plus its --p-space-sm inset; the button is centred in the
|
|
658
|
+
input's box and lifts with it, so the two stay aligned on focus. Set
|
|
659
|
+
--input-action-width to the button's width when it is wider. */
|
|
660
|
+
.input-action {
|
|
661
|
+
position: relative;
|
|
662
|
+
--input-action-width: 4.75rem;
|
|
663
|
+
}
|
|
664
|
+
.input-action > input[type] { padding-inline-end: calc(var(--input-action-width) + var(--p-space-sm)); }
|
|
665
|
+
.input-action > .btn,
|
|
666
|
+
.input-action > button {
|
|
667
|
+
position: absolute;
|
|
668
|
+
inset-block: 0;
|
|
669
|
+
inset-inline-end: var(--p-space-sm);
|
|
670
|
+
min-height: 0;
|
|
671
|
+
height: var(--p-control-height-sm);
|
|
672
|
+
white-space: nowrap;
|
|
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);
|
|
676
|
+
}
|
|
677
|
+
.input-action > .btn-sm { padding-block: calc((var(--p-control-height-sm) - 1.3em - 2 * var(--p-border-thin)) / 2); }
|
|
678
|
+
.input-action > input:focus ~ .btn,
|
|
679
|
+
.input-action > input:focus ~ button {
|
|
680
|
+
transform: var(--p-lift-sm);
|
|
681
|
+
}
|
|
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
|
+
|
|
639
725
|
/* Checkboxes, radio buttons and disclosure summaries: the focus ring of links
|
|
640
726
|
and buttons, without the lift. */
|
|
641
727
|
input[type="checkbox"]:focus-visible,
|
|
@@ -656,6 +742,7 @@ summary:focus-visible {
|
|
|
656
742
|
font-size: 13px;
|
|
657
743
|
font-weight: 600;
|
|
658
744
|
border: var(--p-border-width) solid var(--p-text);
|
|
745
|
+
box-shadow: var(--p-shadow-lg) var(--p-text);
|
|
659
746
|
}
|
|
660
747
|
|
|
661
748
|
.status-success {
|
|
@@ -826,9 +913,11 @@ summary:focus-visible {
|
|
|
826
913
|
border: var(--p-border-width) solid var(--p-text);
|
|
827
914
|
box-shadow: var(--p-shadow-md) var(--p-accent-2);
|
|
828
915
|
}
|
|
829
|
-
|
|
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; }
|
|
830
920
|
.navbar-menu-label {
|
|
831
|
-
margin: 0;
|
|
832
921
|
padding: var(--p-space-sm) var(--p-space-md) var(--p-space-xs);
|
|
833
922
|
font-size: 11px;
|
|
834
923
|
font-weight: 600;
|
|
@@ -840,6 +929,8 @@ summary:focus-visible {
|
|
|
840
929
|
.navbar-menu-panel button {
|
|
841
930
|
display: block;
|
|
842
931
|
width: 100%;
|
|
932
|
+
height: auto;
|
|
933
|
+
min-height: 0;
|
|
843
934
|
box-sizing: border-box;
|
|
844
935
|
padding: var(--p-space-sm) var(--p-space-md);
|
|
845
936
|
border: 0;
|
|
@@ -964,7 +1055,6 @@ pre {
|
|
|
964
1055
|
.code-reveal code {
|
|
965
1056
|
word-break: break-all;
|
|
966
1057
|
display: block;
|
|
967
|
-
margin-bottom: 12px;
|
|
968
1058
|
background: var(--p-code-bg);
|
|
969
1059
|
padding: 12px;
|
|
970
1060
|
border: var(--p-border-thin) solid var(--p-border-dark);
|
|
@@ -972,6 +1062,8 @@ pre {
|
|
|
972
1062
|
user-select: all;
|
|
973
1063
|
}
|
|
974
1064
|
|
|
1065
|
+
:where(.code-reveal) > * + * { margin-top: var(--p-space-sm); }
|
|
1066
|
+
|
|
975
1067
|
|
|
976
1068
|
/* ══════════════════════════════════════════
|
|
977
1069
|
TAGS & BADGES
|
|
@@ -1189,14 +1281,14 @@ pre {
|
|
|
1189
1281
|
text-transform: uppercase;
|
|
1190
1282
|
letter-spacing: 1.5px;
|
|
1191
1283
|
color: var(--p-text-faint);
|
|
1192
|
-
margin-top: 2px;
|
|
1193
1284
|
}
|
|
1285
|
+
/* Optical: 2px off the stat above. In :where() so space-* wins. */
|
|
1286
|
+
:where(.stat-label) { margin-top: 2px; }
|
|
1194
1287
|
|
|
1195
1288
|
.stat-row {
|
|
1196
1289
|
display: flex;
|
|
1197
1290
|
flex-wrap: wrap;
|
|
1198
1291
|
gap: var(--p-space-lg);
|
|
1199
|
-
margin-bottom: 12px;
|
|
1200
1292
|
}
|
|
1201
1293
|
|
|
1202
1294
|
|
|
@@ -1240,21 +1332,30 @@ dialog {
|
|
|
1240
1332
|
which is what centres a modal dialog. Restored here. */
|
|
1241
1333
|
margin: auto;
|
|
1242
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; }
|
|
1243
1338
|
|
|
1244
1339
|
dialog::backdrop { background: var(--p-overlay); }
|
|
1245
|
-
dialog
|
|
1246
|
-
|
|
1247
|
-
|
|
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); }
|
|
1248
1348
|
|
|
1249
1349
|
dialog menu {
|
|
1250
1350
|
display: flex;
|
|
1251
1351
|
gap: var(--p-space-sm);
|
|
1252
1352
|
padding: 0;
|
|
1253
|
-
margin: var(--p-space-lg) 0 0;
|
|
1254
1353
|
list-style: none;
|
|
1255
1354
|
}
|
|
1256
1355
|
|
|
1257
|
-
|
|
1356
|
+
/* The buttons share the row's width. */
|
|
1357
|
+
dialog menu > li { flex: 1; }
|
|
1358
|
+
dialog menu > li > :is(button, .btn) { width: 100%; }
|
|
1258
1359
|
|
|
1259
1360
|
/* ══════════════════════════════════════════
|
|
1260
1361
|
KBD
|
|
@@ -1266,6 +1367,12 @@ kbd {
|
|
|
1266
1367
|
border: var(--p-border-thin) solid var(--p-text);
|
|
1267
1368
|
box-shadow: 1px 1px 0 var(--p-accent-1);
|
|
1268
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);
|
|
1269
1372
|
font: 600 11px/1.5 var(--p-font-mono);
|
|
1270
1373
|
text-transform: uppercase;
|
|
1271
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; }
|