palace-ui 0.10.0 → 0.12.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 CHANGED
@@ -9,7 +9,7 @@
9
9
  - **Retro-technical.** Hard edges (border-radius: 0 everywhere), thick 2px borders, chunky offset box-shadows. Gradients on text, thin decorative stripes (≤3px), and narrow fixed bars (e.g. sticky CTAs) only — never on large surfaces or card backgrounds.
10
10
  - **Three-color accent system.** Hot pink `#ff2d9b`, electric purple `#5b4cff`, cyan `#00e5ff`. These rotate across card lists and provide visual rhythm.
11
11
  - **Snappy interactions.** All transitions are `0.1s`. Hovers lift elements up-left and cast a hard shadow down-right. Fast, mechanical feedback.
12
- - **Uppercase everything.** All headings, labels, buttons, and status text are `text-transform: uppercase` with tracked letter-spacing. Body text is the exception.
12
+ - **Uppercase everything.** All headings, labels and buttons are `text-transform: uppercase` with tracked letter-spacing. Body text and status messages, which are sentences, are the exception.
13
13
  - **Dark on light.** `#0a0a0a` text on `#f0f0f0` background. Cards are white `#fff`. Code blocks invert to dark `#0a0a0a` backgrounds.
14
14
  - **No border radius.** Ever. `border-radius: 0` is the rule, not the exception.
15
15
  - **Content-width containers.** Max-width `640px` for main content, `420px` for auth/narrow forms.
@@ -53,7 +53,8 @@ Or if consuming via npm with a build tool:
53
53
  | `--p-surface` | `#fff` | Card/container backgrounds |
54
54
  | `--p-surface-inset` | `#f7f7f7` | Inset/recessed surfaces |
55
55
  | `--p-dark` | `#0a0a0a` | Dark backgrounds (navbar, code, hover fills) |
56
- | `--p-error` | `#ff4444` | Error states, danger zones |
56
+ | `--p-error` | `#ff4444` | Error states, danger zones: fills, borders, the error box |
57
+ | `--p-error-text` | `#c40000` | Red text (danger buttons, error text): 6.3:1 on white, where `#ff4444` is 3.4:1 |
57
58
  | `--p-success` | `#00ff88` | Success states |
58
59
  | `--p-warning` | `#ffcc00` | Warning states |
59
60
  | `--p-info` | `var(--p-accent-3)` | Informational states (semantic alias for cyan) |
@@ -137,6 +138,27 @@ Every page follows this structure:
137
138
  </nav>
138
139
  ```
139
140
 
141
+ A navbar menu for the signed-in user is a `<details class="navbar-menu">`: the summary looks like `.navbar-user`, and the panel's links and buttons are its items. A form in the panel (sign out) sits under a thin rule. It works without JavaScript; a script can add Escape and closing from outside.
142
+
143
+ ```html
144
+ <nav class="navbar">
145
+ <a href="/" class="navbar-brand">App Name</a>
146
+ <div class="navbar-right">
147
+ <details class="navbar-menu">
148
+ <summary><span class="visually-hidden">Account: </span>Username</summary>
149
+ <div class="navbar-menu-panel">
150
+ <p class="navbar-menu-label" aria-hidden="true">Your teams</p>
151
+ <ul aria-label="Your teams"><li><a href="/a" aria-current="true">Team A</a></li></ul>
152
+ <a href="/account">Account</a>
153
+ <form method="post" action="/logout"><button type="submit">Sign out</button></form>
154
+ </div>
155
+ </details>
156
+ </div>
157
+ </nav>
158
+ ```
159
+
160
+ `.visually-hidden` hides text visually and keeps it for screen readers.
161
+
140
162
  With an icon beside the brand name (`.navbar-brand` uses `inline-flex` layout with `gap: 10px`; images/SVGs are auto-sized to 24×24):
141
163
 
142
164
  ```html
@@ -321,6 +343,13 @@ Size modifiers compose with any color variant:
321
343
  </div>
322
344
  ```
323
345
 
346
+ A `<select>` is styled like the text inputs, with a drawn chevron:
347
+
348
+ ```html
349
+ <label for="direction">Direction</label>
350
+ <select id="direction"><option>Anticlockwise</option><option>Clockwise</option></select>
351
+ ```
352
+
324
353
  Inline form (input + button side by side):
325
354
 
326
355
  ```html
@@ -697,12 +726,12 @@ Full container, no navbar. Hero section, then card list, then footer.
697
726
  - **accent-3 (cyan):** Navbar user name, code highlights, key reveal borders, third card
698
727
  - **Never mix** accent colors in a single element (except the gradient on h1 text)
699
728
  - **The 3-color rotation** applies to any list of sibling cards: 1=pink, 2=purple, 3=cyan, 4=pink...
700
- - **Error is always `#ff4444`**, success is always `#00ff88`, warning is `#ffcc00`, info is `#00e5ff`. Status colors are not accent colors.
729
+ - **Error is always `#ff4444`**, success is always `#00ff88`, warning is `#ffcc00`, info is `#00e5ff`. Status colors are not accent colors. Text on a status color is always dark `--p-text`; red text uses `--p-error-text`, never `#ff4444`.
701
730
 
702
731
  ## Do's and Don'ts
703
732
 
704
733
  ### Do
705
- - Use uppercase for headings, labels, buttons, badges, and status messages
734
+ - Use uppercase for headings, labels, buttons and badges; status messages are sentences in sentence case
706
735
  - Use the 3-color card rotation for any list of cards
707
736
  - Keep `border-radius: 0` everywhere
708
737
  - Use `2px solid` borders on cards, buttons, inputs
package/catalog.html CHANGED
@@ -424,7 +424,7 @@
424
424
  <div class="container">
425
425
  <p class="eyebrow" style="color: var(--p-accent-1); margin-bottom: 8px;">Components</p>
426
426
  <h2 id="forms">Forms</h2>
427
- <p class="subtitle" style="margin-bottom: 24px;">Labels, text inputs, number input, textarea.</p>
427
+ <p class="subtitle" style="margin-bottom: 24px;">Labels, text inputs, select, number input, textarea.</p>
428
428
 
429
429
  <div data-source class="card-static" style="margin-bottom: 16px;">
430
430
  <div class="stack-md">
@@ -444,6 +444,10 @@
444
444
  <label for="cat-search">Search</label>
445
445
  <input type="search" id="cat-search" placeholder="Search...">
446
446
  </div>
447
+ <div>
448
+ <label for="cat-direction">Direction</label>
449
+ <select id="cat-direction"><option>Anticlockwise</option><option>Clockwise</option></select>
450
+ </div>
447
451
  <div>
448
452
  <label for="cat-num">Quantity</label>
449
453
  <input type="number" id="cat-num" value="5" min="0" max="99">
@@ -789,6 +793,24 @@
789
793
  </nav>
790
794
  </div>
791
795
 
796
+ <h3 style="margin-bottom: 12px;">With a navbar menu (open it)</h3>
797
+ <div data-source style="margin-bottom: 24px; padding-bottom: 180px;">
798
+ <nav class="navbar">
799
+ <a href="#" class="navbar-brand">App Name</a>
800
+ <div class="navbar-right">
801
+ <details class="navbar-menu">
802
+ <summary><span class="visually-hidden">Account: </span>Username</summary>
803
+ <div class="navbar-menu-panel">
804
+ <p class="navbar-menu-label" aria-hidden="true">Your teams</p>
805
+ <ul aria-label="Your teams"><li><a href="#" aria-current="true">Team A</a></li><li><a href="#">Team B</a></li></ul>
806
+ <a href="#">Account</a>
807
+ <form><button type="button">Sign out</button></form>
808
+ </div>
809
+ </details>
810
+ </div>
811
+ </nav>
812
+ </div>
813
+
792
814
  <h3 style="margin-bottom: 12px;">Light (.navbar-light)</h3>
793
815
  <div data-source>
794
816
  <nav class="navbar navbar-light" style="border: 2px solid var(--p-border);">
package/package.json CHANGED
@@ -1,8 +1,12 @@
1
1
  {
2
2
  "name": "palace-ui",
3
- "version": "0.10.0",
3
+ "version": "0.12.0",
4
4
  "description": "Retro-technical design system. Hard edges, vibrant gradients, chunky shadows.",
5
5
  "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/jostsg/palace-ui.git"
9
+ },
6
10
  "type": "module",
7
11
  "main": "palace.css",
8
12
  "style": "palace.css",
package/palace.css CHANGED
@@ -1,4 +1,4 @@
1
- /*! palace-ui v0.10.0 | MIT License | fonts: SIL OFL 1.1, see fonts/ */
1
+ /*! palace-ui v0.12.0 | MIT License | fonts: SIL OFL 1.1, see fonts/ */
2
2
 
3
3
  /* ══════════════════════════════════════════
4
4
  FONTS (self-hosted; no third-party requests)
@@ -41,6 +41,7 @@
41
41
 
42
42
  /* ── Status ── */
43
43
  --p-error: #ff4444;
44
+ --p-error-text: #c40000; /* red for text: 6.3:1 on white, where #ff4444 is 3.4:1 */
44
45
  --p-success: #00ff88;
45
46
  --p-warning: #ffcc00;
46
47
  --p-info: var(--p-accent-3);
@@ -110,6 +111,19 @@
110
111
  padding: 0;
111
112
  }
112
113
 
114
+ /* The hidden attribute always hides, even on elements palace gives a display (buttons, .btn). */
115
+ [hidden] { display: none !important; }
116
+
117
+ /* Text for screen readers only: off screen, still read. */
118
+ .visually-hidden {
119
+ position: absolute;
120
+ width: 1px;
121
+ height: 1px;
122
+ overflow: hidden;
123
+ clip-path: inset(50%);
124
+ white-space: nowrap;
125
+ }
126
+
113
127
  body {
114
128
  font-family: var(--p-font);
115
129
  color: var(--p-text);
@@ -367,12 +381,12 @@ button, .btn {
367
381
 
368
382
  .btn-danger {
369
383
  background: transparent;
370
- color: var(--p-error);
384
+ color: var(--p-error-text);
371
385
  border-color: var(--p-error);
372
386
  }
373
387
  .btn-danger:hover {
374
388
  background: var(--p-error);
375
- color: #fff;
389
+ color: var(--p-text);
376
390
  transform: var(--p-lift-sm);
377
391
  box-shadow: var(--p-shadow-sm) var(--p-dark);
378
392
  }
@@ -534,6 +548,32 @@ input[type="tel"]:focus {
534
548
  transform: var(--p-lift-sm);
535
549
  }
536
550
 
551
+ select {
552
+ width: 100%;
553
+ appearance: none;
554
+ padding: 12px 2.5rem 12px 12px;
555
+ border: var(--p-border-width) solid var(--p-text);
556
+ border-radius: var(--p-radius);
557
+ background-color: var(--p-surface);
558
+ /* A chevron drawn with two gradients: no image, no font. */
559
+ background-image: linear-gradient(45deg, transparent 50%, var(--p-text) 50%), linear-gradient(135deg, var(--p-text) 50%, transparent 50%);
560
+ background-position: calc(100% - 1.25rem) 50%, calc(100% - 0.9rem) 50%;
561
+ background-size: 0.35rem 0.35rem;
562
+ background-repeat: no-repeat;
563
+ font-family: var(--p-font);
564
+ font-size: 1rem;
565
+ color: var(--p-text);
566
+ outline: none;
567
+ transition: var(--p-transition);
568
+ }
569
+ select[multiple], select[size]:not([size="1"]) { padding-right: 12px; background-image: none; }
570
+
571
+ select:focus {
572
+ border-color: var(--p-accent-2);
573
+ box-shadow: var(--p-shadow-md) var(--p-accent-2);
574
+ transform: var(--p-lift-sm);
575
+ }
576
+
537
577
  input::placeholder {
538
578
  color: var(--p-border-dashed);
539
579
  }
@@ -583,12 +623,11 @@ textarea:focus {
583
623
  STATUS MESSAGES
584
624
  ══════════════════════════════════════════ */
585
625
 
626
+ /* A status message is a sentence, so it reads in sentence case, like body text. */
586
627
  .status {
587
628
  padding: 12px;
588
629
  font-size: 13px;
589
630
  font-weight: 600;
590
- text-transform: uppercase;
591
- letter-spacing: 0.5px;
592
631
  border: var(--p-border-width) solid var(--p-text);
593
632
  }
594
633
 
@@ -600,7 +639,7 @@ textarea:focus {
600
639
 
601
640
  .status-error {
602
641
  background: var(--p-error);
603
- color: #fff;
642
+ color: var(--p-text); /* 5.8:1 on #ff4444; white would be 3.4:1 */
604
643
  border-color: var(--p-text);
605
644
  }
606
645
 
@@ -691,6 +730,11 @@ textarea:focus {
691
730
  color: var(--p-accent-2);
692
731
  }
693
732
 
733
+ .navbar-light .navbar-menu > summary {
734
+ color: var(--p-accent-2);
735
+ border-color: var(--p-border);
736
+ }
737
+
694
738
  .navbar-light .btn {
695
739
  color: var(--p-text-secondary);
696
740
  border-color: var(--p-border);
@@ -702,6 +746,108 @@ textarea:focus {
702
746
  }
703
747
 
704
748
 
749
+ /* Navbar menu: a <details> whose summary looks like .navbar-user and opens a
750
+ panel in the dialog card language. Works without JavaScript; a script may
751
+ add Escape and closing from outside. Links and buttons in the panel are its
752
+ items; a form in it (e.g. Sign out) sits under a thin rule. */
753
+ .navbar-menu { position: relative; }
754
+ .navbar-menu > summary {
755
+ position: relative;
756
+ display: block;
757
+ box-sizing: border-box;
758
+ max-width: min(14rem, 40vw);
759
+ /* The label truncates; the caret after it, in the right padding, never does. */
760
+ padding: 6px 28px 6px 14px;
761
+ overflow: hidden;
762
+ text-overflow: ellipsis;
763
+ white-space: nowrap;
764
+ border: var(--p-border-thin) solid var(--p-border-dark);
765
+ font: 600 11px/1.4 var(--p-font);
766
+ letter-spacing: 1px;
767
+ text-transform: uppercase;
768
+ color: var(--p-accent-3);
769
+ list-style: none;
770
+ cursor: pointer;
771
+ }
772
+ .navbar-menu > summary::-webkit-details-marker { display: none; }
773
+ .navbar-menu > summary::after {
774
+ content: '';
775
+ position: absolute;
776
+ right: 12px;
777
+ top: 50%;
778
+ margin-top: -2px;
779
+ border: 4px solid transparent;
780
+ border-top-color: currentColor;
781
+ border-bottom: 0;
782
+ }
783
+ .navbar-menu > summary:hover,
784
+ .navbar-menu[open] > summary { color: var(--p-accent-1); border-color: var(--p-accent-1); }
785
+ .navbar-menu > summary:focus-visible { outline: 2px solid var(--p-accent-3); outline-offset: 2px; }
786
+
787
+ /* Right-aligned under the summary: a navbar menu sits at the navbar's right. */
788
+ .navbar-menu-panel {
789
+ position: absolute;
790
+ right: 0;
791
+ top: calc(100% + 6px);
792
+ z-index: 100;
793
+ min-width: 14rem;
794
+ max-width: calc(100vw - 2 * var(--p-space-md));
795
+ display: grid;
796
+ padding: var(--p-space-xs) 0;
797
+ background: var(--p-surface);
798
+ color: var(--p-text);
799
+ border: var(--p-border-width) solid var(--p-text);
800
+ box-shadow: var(--p-shadow-md) var(--p-accent-2);
801
+ }
802
+ .navbar-menu-panel ul { list-style: none; margin: 0; padding: 0; display: grid; }
803
+ .navbar-menu-label {
804
+ margin: 0;
805
+ padding: var(--p-space-sm) var(--p-space-md) var(--p-space-xs);
806
+ font-size: 11px;
807
+ font-weight: 600;
808
+ letter-spacing: 1px;
809
+ text-transform: uppercase;
810
+ color: var(--p-text-faint);
811
+ }
812
+ .navbar-menu-panel a,
813
+ .navbar-menu-panel button {
814
+ display: block;
815
+ width: 100%;
816
+ box-sizing: border-box;
817
+ padding: var(--p-space-sm) var(--p-space-md);
818
+ border: 0;
819
+ background: none;
820
+ font: 600 13px/1.4 var(--p-font);
821
+ text-transform: none;
822
+ letter-spacing: normal;
823
+ color: var(--p-text);
824
+ text-align: left;
825
+ text-decoration: none;
826
+ cursor: pointer;
827
+ /* No transition: a closed <details> does not render its content, so a
828
+ transition started there would never finish. */
829
+ transition: none;
830
+ }
831
+ .navbar-menu-panel a:hover,
832
+ .navbar-menu-panel button:hover { background: var(--p-hover-bg); transform: none; box-shadow: none; }
833
+ /* One focus style for links and buttons: the button lift would spill past the panel. */
834
+ .navbar-menu-panel a:focus-visible,
835
+ .navbar-menu-panel button:focus-visible {
836
+ outline: 2px solid var(--p-accent-2);
837
+ outline-offset: -2px;
838
+ color: var(--p-accent-2);
839
+ transform: none;
840
+ box-shadow: none;
841
+ }
842
+ /* The current item: a pink bar, beside any focus outline, never instead of it. */
843
+ .navbar-menu-panel a[aria-current],
844
+ .navbar-menu-panel a[aria-current]:focus-visible { box-shadow: inset 3px 0 0 var(--p-accent-1); }
845
+ .navbar-menu-panel form {
846
+ margin: var(--p-space-xs) 0 0;
847
+ padding-top: var(--p-space-xs);
848
+ border-top: var(--p-border-thin) solid var(--p-border);
849
+ }
850
+
705
851
  /* ══════════════════════════════════════════
706
852
  TABLES
707
853
  ══════════════════════════════════════════ */
@@ -878,7 +1024,7 @@ pre {
878
1024
  DANGER ZONE
879
1025
  ══════════════════════════════════════════ */
880
1026
 
881
- .danger-zone h2 { color: var(--p-error); }
1027
+ .danger-zone h2 { color: var(--p-error-text); }
882
1028
  .danger-zone p {
883
1029
  color: var(--p-text-muted);
884
1030
  font-size: 13px;