palace-ui 0.13.0 → 0.15.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.
Files changed (4) hide show
  1. package/SPEC.md +197 -63
  2. package/catalog.html +892 -674
  3. package/package.json +8 -2
  4. package/palace.css +138 -53
package/SPEC.md CHANGED
@@ -75,25 +75,49 @@ Or if consuming via npm with a build tool:
75
75
  | Token | Value | Usage |
76
76
  |---|---|---|
77
77
  | `--p-space-xs` | `4px` | Tight gaps |
78
- | `--p-space-sm` | `8px` | Button gaps, small margins |
79
- | `--p-space-md` | `16px` | Card margins, standard padding |
78
+ | `--p-space-sm` | `8px` | Button gaps, label to control |
79
+ | `--p-space-md` | `16px` | Space between cards and fields, standard padding |
80
80
  | `--p-space-lg` | `24px` | Card padding, section spacing |
81
81
  | `--p-space-xl` | `32px` | Intermediate section gaps |
82
82
  | `--p-space-2xl` | `48px` | Hero padding, major section gaps |
83
83
 
84
84
  ### Spacing Rules
85
85
 
86
- Three mechanisms for spacing, each with a clear use case:
86
+ Spacing comes from the container, never from the component inside it.
87
87
 
88
- 1. **`margin-bottom`** — spacing between sibling elements. All vertical inter-element spacing uses `margin-bottom` only (single-direction margins). Elements push the next sibling down; never pull themselves away with `margin-top`. This keeps spacing predictable, portable, and avoids margin-collapsing surprises.
88
+ 1. **`space-*` between children.** A container spaces its direct children vertically with the owl selector (`> * + *` gets `margin-top`). Use it for any column: paragraphs, fields, cards, sections.
89
89
 
90
- 2. **`gap`** — spacing inside flex/grid containers. Preferred when the parent is `display: flex` or `display: grid`. No margin needed on children.
90
+ 2. **`gap-*` in rows and grids.** A flex or grid container sets its `gap`. Its children get no margin.
91
91
 
92
- 3. **`.stack-*`** — spacing between children of a non-flex/grid container. Apply to the parent; children get uniform vertical spacing via the owl selector (`> * + *`). Available sizes: `stack-xs` (4px), `stack-sm` (8px), `stack-md` (16px), `stack-lg` (24px), `stack-xl` (32px), `stack-2xl` (48px).
92
+ 3. **Margin only for a single exception.** One element that needs more or less space than its container gives it gets its own margin, with a comment saying why.
93
93
 
94
- **Never** use `padding` to create distance between sibling elements — padding is for internal space within an element.
94
+ 4. **Padding is the inside of a box**, never the space between things.
95
95
 
96
- **Exceptions:** dividers (`.divider`, `.divider-danger`, `.gradient-stripe`) use top+bottom margin because they are structural separators by nature. Tight optical adjustments inside components (e.g. `.stat-label` 2px nudge) are exempt.
96
+ 5. **Components set no outer margin.** A component styles its inside only: `.card`, `.card-static`, `label`, `.stat-row`, `.page-header` and dialog parts have no margin around them. An outer margin would add to a `gap` or fight a `space-*`. Components that space their own children (`.page-header`, `.code-reveal`, `dialog`, list items in a container) do it with `:where()` rules, so a `space-*` utility on them always wins.
97
+
98
+ 6. **Missing values become tokens.** When a value is missing, add it to palace as a token instead of writing a raw number in an app. A raw value is only for a genuine one-off, with a comment saying why.
99
+
100
+ Sizes for both: `xs` (4px), `sm` (8px), `md` (16px), `lg` (24px), `xl` (32px), `2xl` (48px), the `--p-space-*` scale.
101
+
102
+ **Exceptions:** dividers (`.divider`, `.divider-danger`, `.gradient-stripe`) use top and bottom margin because they are separators by nature. `margin: auto` centres containers and dialogs. The `.stat-label` 2px optical nudge and the rules between the navbar menu's groups are part of those components.
103
+
104
+ ### Shadows
105
+
106
+ Offsets only; combine with a colour: `box-shadow: var(--p-shadow-md) var(--p-accent-2)`. Pick a size by role, never write a raw offset. Inset indicator lines (`inset 3px 0 0`) are not offset shadows and stay as they are.
107
+
108
+ | Token | Usage |
109
+ |---|---|
110
+ | `--p-shadow-xs` | Keys (`kbd`) |
111
+ | `--p-shadow-sm` | Small chips |
112
+ | `--p-shadow-md` | Controls: hover and focus |
113
+ | `--p-shadow-lg` | Boxes and cards: status boxes, cards, dialogs, the navbar menu panel |
114
+
115
+ Boxes, by component:
116
+
117
+ - **`lg`:** `.card-static` (`--p-text`, at rest and on hover; it never lifts), `.status` (`--p-text`), `dialog` and `.navbar-menu-panel` (`--p-accent-2`; the panel floats above the page like a dialog), and `.card` on hover (the lift casts its accent; at rest a `.card` is flat, so a list of cards shows the shadow where the pointer is).
118
+ - **Deliberately none:** tables (rules between rows, no outer box), `pre` and `.code-reveal` (they scroll sideways, which would clip a shadow), `.tag-sticker` and other tags (small chips, flat), and the disabled state of any control.
119
+
120
+ A shadow reaches 4px right and below its box. Leave that room inside a container that clips (`overflow: hidden`, `auto` or `scroll`); palace's own layouts clip nothing a box sits in.
97
121
 
98
122
  ### Layout
99
123
 
@@ -103,6 +127,14 @@ Three mechanisms for spacing, each with a clear use case:
103
127
  | `--p-container-narrow` | `420px` |
104
128
  | `--p-navbar-height` | `52px` |
105
129
 
130
+ ### Controls
131
+
132
+ | Token | Value | Usage |
133
+ |---|---|---|
134
+ | `--p-control-height` | `44px` | Text inputs, selects, number input, buttons |
135
+ | `--p-control-height-sm` | `32px` | `.btn-sm`, a button inside `.input-action` |
136
+ | `--p-control-height-lg` | `52px` | `.btn-lg`, `.cta` |
137
+
106
138
  ## Component Catalog
107
139
 
108
140
  ### Page Shell
@@ -198,7 +230,7 @@ Light variant for pages that need a white navbar:
198
230
 
199
231
  ### Page Header
200
232
 
201
- Standard spacing for non-hero pages (after navbar). Provides `48px` top padding and consistent gaps between h1, subtitle, and content.
233
+ Standard spacing for non-hero pages (after navbar). Provides `48px` top padding and `8px` between its children.
202
234
 
203
235
  ```html
204
236
  <div class="container page-header">
@@ -218,18 +250,30 @@ Combines with any container class:
218
250
  </div>
219
251
  ```
220
252
 
221
- Spacing: `48px` top padding, `8px` below h1, `32px` below `.subtitle`. Do **not** use on landing/hero pages (those use custom hero spacing).
253
+ Spacing: `48px` top padding and `8px` between its children; it has no outer margin. To give the content more room than the heading, group the heading and subtitle and put a `space-*` class on the header:
254
+
255
+ ```html
256
+ <div class="container page-header space-xl">
257
+ <div class="space-sm">
258
+ <h1>Page Title</h1>
259
+ <p class="subtitle">Helpful subtitle</p>
260
+ </div>
261
+ <!-- content -->
262
+ </div>
263
+ ```
264
+
265
+ Do **not** use on landing/hero pages (those use custom hero spacing).
222
266
 
223
267
  ### Hero Section (Landing Pages)
224
268
 
225
269
  ```html
226
- <div class="container" style="padding-top: 80px; padding-bottom: 48px;">
270
+ <div class="container space-lg" style="padding-top: 80px; padding-bottom: 48px;">
227
271
  <p class="brand">App Name</p>
228
272
  <h1 class="h1-hero">Main Headline Here</h1>
229
273
  <p class="tagline">
230
274
  A sentence or two explaining what this does. Keep it short.
231
275
  </p>
232
- <div style="display: flex; gap: 12px; flex-wrap: wrap; margin-top: 32px;">
276
+ <div class="gap-sm" style="display: flex; flex-wrap: wrap;">
233
277
  <a href="/signup" class="cta cta-primary">Sign Up</a>
234
278
  <a href="/login" class="cta cta-secondary">Log In</a>
235
279
  </div>
@@ -239,8 +283,8 @@ Spacing: `48px` top padding, `8px` below h1, `32px` below `.subtitle`. Do **not*
239
283
  When the app has a visual mark/icon, use `.brand-lockup` instead of a standalone `.brand`:
240
284
 
241
285
  ```html
242
- <div class="container" style="padding-top: 80px; padding-bottom: 48px;">
243
- <div class="brand-lockup" style="margin-bottom: 32px;">
286
+ <div class="container space-xl" style="padding-top: 80px; padding-bottom: 48px;">
287
+ <div class="brand-lockup">
244
288
  <img src="/icons/app-icon.svg" alt="">
245
289
  <div>
246
290
  <p class="brand" style="font-size: 17px; letter-spacing: 3px;">App Name</p>
@@ -270,10 +314,10 @@ Use in hero sections and landing pages where the app has a visual mark. The `.br
270
314
 
271
315
  ### Card List (3-Color Rotation)
272
316
 
273
- When you have a list of cards, the accent color auto-rotates via `nth-child`:
317
+ When you have a list of cards, the accent color auto-rotates via `nth-child`. Cards have no margin; the list spaces them with `space-md` (or `gap-md` in a grid):
274
318
 
275
319
  ```html
276
- <div class="container">
320
+ <div class="container space-md">
277
321
  <div class="card">
278
322
  <h2>First Card</h2>
279
323
  <p>Description goes here. Color will be accent-1 (pink).</p>
@@ -299,12 +343,14 @@ For explicit accent control (e.g. server-rendered cards), use the `--accent` cus
299
343
 
300
344
  ### Static Card
301
345
 
302
- For cards used as form containers or static content that should not lift on hover:
346
+ For cards used as form containers or static content that should not lift on hover. It keeps its `--p-shadow-lg` shadow in `--p-text`, at rest and on hover:
303
347
 
304
348
  ```html
305
- <div class="card-static">
306
- <label for="name">Name</label>
307
- <input type="text" id="name" placeholder="Enter your name">
349
+ <div class="card-static space-md">
350
+ <div class="field">
351
+ <label for="name">Name</label>
352
+ <input type="text" id="name" placeholder="Enter your name">
353
+ </div>
308
354
  </div>
309
355
  ```
310
356
 
@@ -312,7 +358,7 @@ Use `.card-static` instead of `.card` when the card is a container (e.g. auth fo
312
358
 
313
359
  ### Buttons
314
360
 
315
- Size modifiers compose with any color variant. `.btn` works on `<button>` and on `<a>` alike: each size sets its own line height, so a button and a link button of the same size are the same height (41px default, 28px `.btn-sm`, 51px `.btn-lg` and `.cta`) and line up side by side.
361
+ Size modifiers compose with any color variant. `.btn` works on `<button>` and on `<a>` alike: a button and a link button of the same size are the same height (44px default, 32px `.btn-sm`, 52px `.btn-lg` and `.cta`, from the control height tokens) and line up side by side. The height is a minimum: a button grows when its label wraps.
316
362
 
317
363
  ```html
318
364
  <!-- Size modifiers -->
@@ -345,37 +391,86 @@ Size modifiers compose with any color variant. `.btn` works on `<button>` and on
345
391
 
346
392
  ### Forms
347
393
 
394
+ A `.field` holds a label, its control and an optional hint or error, with 8px between them. Labels have no margin of their own; always put a label and its control in a `.field`. Space a column of fields with `space-md`.
395
+
348
396
  ```html
349
- <div class="card">
350
- <label for="name">Your Name</label>
351
- <input type="text" id="name" placeholder="Enter your name" style="margin-bottom: 16px;">
352
- <button class="btn-primary" style="width: 100%;">Submit</button>
353
- </div>
397
+ <form class="card-static space-md">
398
+ <div class="field">
399
+ <label for="name">Your Name</label>
400
+ <input type="text" id="name" placeholder="Enter your name">
401
+ </div>
402
+ <div class="field">
403
+ <label for="email">Email</label>
404
+ <input type="email" id="email" aria-invalid="true" aria-describedby="email-error">
405
+ <p class="field-error" id="email-error">Enter an email address with a domain, like you@example.com.</p>
406
+ </div>
407
+ <div class="field">
408
+ <label for="pass">Password</label>
409
+ <input type="password" id="pass" aria-describedby="pass-hint">
410
+ <p class="field-hint" id="pass-hint">At least 8 characters.</p>
411
+ </div>
412
+ <button class="btn btn-primary btn-block">Submit</button>
413
+ </form>
354
414
  ```
355
415
 
416
+ `.field-hint` is muted small text; `.field-error` is small red text. Connect either to the control with `aria-describedby`. `aria-invalid="true"` on an input, select or textarea gives it a red border, and a red shadow on focus.
417
+
418
+ All controls share one height. Text inputs, selects and the number input are exactly `--p-control-height` (44px) tall; buttons have it as a minimum and grow when a label wraps. A textarea starts at the control height. A field and a button beside it line up by construction.
419
+
356
420
  A `<select>` is styled like the text inputs, with a drawn chevron:
357
421
 
358
422
  ```html
359
- <label for="direction">Direction</label>
360
- <select id="direction"><option>Anticlockwise</option><option>Clockwise</option></select>
423
+ <div class="field">
424
+ <label for="direction">Direction</label>
425
+ <select id="direction"><option>Anticlockwise</option><option>Clockwise</option></select>
426
+ </div>
361
427
  ```
362
428
 
363
- Inline form (input + button side by side):
429
+ A `.control-row` puts inputs, fields and buttons on one line, aligned to the bottom, 8px apart. Inputs, selects, textareas, `.input-action`s and fields grow and share the width, starting from `--control-row-min` (12rem); buttons keep their natural width, may wrap their label, and the row wraps when it is narrow.
430
+
431
+ The row aligns its items to the bottom of each `.field`, so a field in a row holds only its label and control. Put the row's hint or error below the row, not inside the `.field`, or the button no longer lines up with the control. Space them with `space-sm` on a wrapper.
364
432
 
365
433
  ```html
366
- <div style="display: flex; gap: 8px;">
367
- <input type="text" placeholder="Label" style="flex: 1;">
368
- <button class="btn-primary">Create</button>
434
+ <div class="control-row">
435
+ <input type="text" placeholder="Label" aria-label="Label">
436
+ <button class="btn btn-primary">Create</button>
437
+ </div>
438
+
439
+ <div class="control-row">
440
+ <div class="field">
441
+ <label for="driver">Driver</label>
442
+ <input type="text" id="driver">
443
+ </div>
444
+ <div class="field">
445
+ <label for="car">Car</label>
446
+ <select id="car"><option>Red</option><option>Blue</option></select>
447
+ </div>
448
+ <button class="btn btn-primary">Add Driver</button>
449
+ </div>
450
+
451
+ <div class="space-sm">
452
+ <div class="control-row">
453
+ <div class="field">
454
+ <label for="code">Transponder</label>
455
+ <input type="text" id="code" aria-invalid="true" aria-describedby="code-error">
456
+ </div>
457
+ <button class="btn btn-primary">Assign</button>
458
+ </div>
459
+ <p class="field-error" id="code-error">That transponder is already assigned.</p>
369
460
  </div>
370
461
  ```
371
462
 
372
- A field with a button inside, at its right end (show/hide a password, paste, copy). The input reserves the button's width plus its 8px inset from the edge (`--input-action-width` is the button's width, default `4.75rem`); the button is centred in the input's box and lifts with the input on focus, so they stay aligned. Use `.btn-sm`, a real `<button type="button">`, and for a toggle let the visible text and the accessible name change together ("Show" / `aria-label="Show password"` and "Hide" / "Hide password"). Pick one way to say the state: a changing name, or a fixed name with `aria-pressed` — never both.
463
+ Form grids and column layouts are not part of palace: build them with `gap-*` and `space-*`.
464
+
465
+ A field with a button inside, at its right end (show/hide a password, paste, copy). The input reserves the button's width plus its 8px inset from the edge (`--input-action-width` is the button's width, default `4.75rem`); the button is centred in the input's box and lifts with the input on focus, so they stay aligned. The button is the small control height. Use `.btn-sm`, a real `<button type="button">`, and for a toggle let the visible text and the accessible name change together ("Show" / `aria-label="Show password"` and "Hide" / "Hide password"). Pick one way to say the state: a changing name, or a fixed name with `aria-pressed` — never both.
373
466
 
374
467
  ```html
375
- <label for="pw">Password</label>
376
- <div class="input-action">
377
- <input type="password" id="pw" autocomplete="off">
378
- <button type="button" class="btn btn-sm btn-outline" aria-label="Show password">Show</button>
468
+ <div class="field">
469
+ <label for="pw">Password</label>
470
+ <div class="input-action">
471
+ <input type="password" id="pw" autocomplete="off">
472
+ <button type="button" class="btn btn-sm btn-outline" aria-label="Show password">Show</button>
473
+ </div>
379
474
  </div>
380
475
  ```
381
476
 
@@ -441,14 +536,14 @@ Standard code block:
441
536
  };</pre>
442
537
  ```
443
538
 
444
- Dark reveal panel (for secrets, API keys, terminal output):
539
+ Dark reveal panel (for secrets, API keys, terminal output). It puts 8px between its children:
445
540
  ```html
446
541
  <div class="code-reveal">
447
- <p class="eyebrow" style="color: var(--p-accent-3); margin-bottom: 10px;">
542
+ <p class="eyebrow" style="color: var(--p-accent-3);">
448
543
  Copy this — you won't see it again
449
544
  </p>
450
545
  <code>sk_live_abc123def456</code>
451
- <div style="display: flex; gap: 8px; margin-top: 12px;">
546
+ <div class="gap-sm" style="display: flex;">
452
547
  <button class="btn-secondary">Copy</button>
453
548
  </div>
454
549
  </div>
@@ -483,21 +578,30 @@ Dark reveal panel (for secrets, API keys, terminal output):
483
578
  </div>
484
579
  ```
485
580
 
486
- ### Stack Spacing
581
+ ### Space and Gap
487
582
 
488
- Uniform vertical spacing between direct children. Apply to the parent element.
583
+ `space-*` puts the same vertical space between the direct children of a container. Apply it to the parent.
489
584
 
490
585
  ```html
491
- <div class="stack-md">
586
+ <div class="space-md">
492
587
  <p>First item</p>
493
- <p>16px gap</p>
494
- <p>16px gap</p>
588
+ <p>16px above</p>
589
+ <p>16px above</p>
495
590
  </div>
496
591
  ```
497
592
 
498
- Available: `stack-xs` (4px) · `stack-sm` (8px) · `stack-md` (16px) · `stack-lg` (24px) · `stack-xl` (32px) · `stack-2xl` (48px)
593
+ `gap-*` sets the gap of a flex or grid container, for rows and grids.
499
594
 
500
- For flex/grid parents, prefer `gap` instead. `.stack-*` is for flow-layout contexts where children are block-level elements stacked vertically.
595
+ ```html
596
+ <div class="gap-sm" style="display: flex; flex-wrap: wrap;">
597
+ <span class="tag">One</span>
598
+ <span class="tag">Two</span>
599
+ </div>
600
+ ```
601
+
602
+ Available: `xs` (4px) · `sm` (8px) · `md` (16px) · `lg` (24px) · `xl` (32px) · `2xl` (48px), as `space-md`, `gap-md` and so on.
603
+
604
+ `.stack-*` is a deprecated alias of `space-*` and will be removed in a later release.
501
605
 
502
606
  ### Dividers
503
607
 
@@ -513,7 +617,7 @@ For flex/grid parents, prefer `gap` instead. `.stack-*` is for flow-layout conte
513
617
 
514
618
  ```html
515
619
  <hr class="divider-danger">
516
- <div class="danger-zone">
620
+ <div class="danger-zone space-sm">
517
621
  <h2>Danger Zone</h2>
518
622
  <p>This will permanently delete your account. This cannot be undone.</p>
519
623
  <button class="btn btn-danger btn-block">Delete My Account</button>
@@ -626,9 +730,22 @@ Lists inside `.container` or `.container-narrow` are automatically styled with p
626
730
 
627
731
  ### Dialog
628
732
 
629
- A native `<dialog>` styled as a card: border, purple offset shadow, overlay backdrop. Actions go in a `<menu>`; its buttons share the width equally.
733
+ A native `<dialog>` styled as a card: border, purple offset shadow, overlay backdrop. It spaces its own children — its direct children, or children of a direct `<form>` (`<form method="dialog">`): 4px below the heading, 8px after a paragraph, 24px after the subtitle and before the actions. Actions go in a `<menu>`, one `<li>` per button; the buttons share the width equally. A modal dialog stays centred in any container.
630
734
 
631
735
  ```html
736
+ <dialog>
737
+ <form method="dialog">
738
+ <h2>Title</h2>
739
+ <p class="subtitle">Supporting line</p>
740
+ <p>Body text.</p>
741
+ <menu>
742
+ <li><button class="btn" value="cancel">Cancel</button></li>
743
+ <li><button class="btn btn-primary" value="confirm">Confirm</button></li>
744
+ </menu>
745
+ </form>
746
+ </dialog>
747
+
748
+ <!-- Without a form, the same rhythm on its direct children -->
632
749
  <dialog>
633
750
  <h2>Title</h2>
634
751
  <p class="subtitle">Supporting line</p>
@@ -642,7 +759,7 @@ A native `<dialog>` styled as a card: border, purple offset shadow, overlay back
642
759
 
643
760
  ### Kbd
644
761
 
645
- Inline keyboard key: bordered, uppercase mono, with a small pink offset shadow.
762
+ Inline keyboard key: bordered, uppercase mono, with a pink `--p-shadow-xs` shadow.
646
763
 
647
764
  ```html
648
765
  <kbd>Ctrl</kbd> + <kbd>Z</kbd>
@@ -668,7 +785,7 @@ It is absolutely positioned where the text would have been, with no `top` or `le
668
785
 
669
786
  ### Focus Visible
670
787
 
671
- All interactive elements (buttons, links, inputs, checkboxes, radio buttons, `<summary>`) have `:focus-visible` styles with a `2px` outline in accent-2 (purple) and `2px` offset. Buttons also get a lift + shadow on focus. Text inputs show focus with a purple border and shadow instead. The navbar menu's summary keeps its own cyan ring.
788
+ All interactive elements (buttons, links, inputs, checkboxes, radio buttons, `<summary>`) have `:focus-visible` styles with a `2px` outline in accent-2 (purple) and `2px` offset. Buttons also get a lift + shadow on focus. Text inputs show focus with a purple border and shadow instead; an input with `aria-invalid="true"` shows a red border and shadow. The navbar menu's summary keeps its own cyan ring.
672
789
 
673
790
  ### Reduced Motion
674
791
 
@@ -696,14 +813,16 @@ Use `container-narrow page-header` (420px). No navbar. Centered with top padding
696
813
 
697
814
  ```html
698
815
  <body>
699
- <div class="container-narrow page-header">
700
- <p class="brand">App Name</p>
701
- <h1>Page Title</h1>
702
- <p class="subtitle">Helpful subtitle</p>
703
- <div class="card">
704
- <!-- form contents -->
816
+ <div class="container-narrow page-header space-lg">
817
+ <div class="space-sm">
818
+ <p class="brand">App Name</p>
819
+ <h1>Page Title</h1>
820
+ <p class="subtitle">Helpful subtitle</p>
705
821
  </div>
706
- <p style="margin-top: 20px; text-align: center; font-size: 13px; color: var(--p-text-muted);">
822
+ <form class="card-static space-md">
823
+ <!-- fields -->
824
+ </form>
825
+ <p style="text-align: center; font-size: 13px; color: var(--p-text-muted);">
707
826
  Already in? <a href="/login">Log in</a>
708
827
  </p>
709
828
  </div>
@@ -717,9 +836,11 @@ Navbar + container with `page-header`. Use `divider` between major sections.
717
836
  ```html
718
837
  <body>
719
838
  <nav class="navbar">...</nav>
720
- <div class="container page-header">
721
- <h1>Dashboard</h1>
722
- <p class="subtitle">Manage your stuff</p>
839
+ <div class="container page-header space-xl">
840
+ <div class="space-sm">
841
+ <h1>Dashboard</h1>
842
+ <p class="subtitle">Manage your stuff</p>
843
+ </div>
723
844
  <!-- content sections -->
724
845
  <hr class="divider">
725
846
  <!-- more sections -->
@@ -750,7 +871,7 @@ Full container, no navbar. Hero section, then card list, then footer.
750
871
  ## Interaction Rules
751
872
 
752
873
  1. **Hover = lift + shadow.** Elements move `translate(-1px, -1px)` or `translate(-2px, -2px)` and cast a hard shadow in the opposite direction. Shadow color matches the element's accent.
753
- 2. **Focus = border-color + shadow.** Input focus changes border to accent-2 (purple) and adds a `3px 3px 0` shadow. Number inputs focus to accent-1 (pink).
874
+ 2. **Focus = border-color + shadow.** Input focus changes border to accent-2 (purple) and adds the `--p-shadow-md` shadow. Number inputs focus to accent-1 (pink).
754
875
  3. **Disabled = flat gray.** `#ddd` background, `#999` text, `#ccc` border. No transform, no shadow, `cursor: not-allowed`.
755
876
  4. **Transitions are 0.1s.** Never slower. The UI should feel mechanical and responsive.
756
877
  5. **Links underline with border-bottom**, not `text-decoration`. Hover swaps from accent-2 (purple) to accent-1 (pink).
@@ -773,7 +894,7 @@ Full container, no navbar. Hero section, then card list, then footer.
773
894
  - Use the 3-color card rotation for any list of cards
774
895
  - Keep `border-radius: 0` everywhere
775
896
  - Use `2px solid` borders on cards, buttons, inputs
776
- - Use hard shadows (`Npx Npx 0 color`) — never blurred shadows
897
+ - Use hard shadows (a `--p-shadow-*` size and a colour) — never blurred shadows
777
898
  - Use `font-weight: 700` for headings and buttons
778
899
  - Use `letter-spacing` on all uppercase text (0.5px to 2px)
779
900
  - Keep containers at 640px max width
@@ -808,3 +929,16 @@ Palace CSS handles h1, container, and card adjustments automatically. For page-s
808
929
  .cta-row { flex-direction: column; }
809
930
  }
810
931
  ```
932
+
933
+ ## Migrating to 0.14
934
+
935
+ 0.14 moves spacing from components to containers. Check every page:
936
+
937
+ - Rename `stack-*` to `space-*`. `.stack-*` is deprecated: it still works for now, as an alias of `space-*`, and will be removed in a later major version.
938
+ - `.card` and `.card-static` have no margin. Wrap a list of cards in `space-md`, or `gap-md` in a flex or grid container.
939
+ - Labels have no margin. Put each label and its control in a `.field`.
940
+ - `.page-header`, `.stat-row`, `.code-reveal` and dialog parts set no outer margin. Give the space after them from the parent with `space-*` or `gap-*`.
941
+ - A dialog, `.page-header` and `.code-reveal` now space only their own children (a dialog: its direct children, or those of a direct `<form>`), with `:where()` rules, so a `space-*` on them wins. Content nested deeper gets no space from them: give its wrapper a `space-*`.
942
+ - Inputs, selects and the number input are exactly 44px tall. Buttons are at least 44px (32px `.btn-sm`, 52px `.btn-lg` and `.cta`) and grow when a label wraps. Check rows of fields and buttons against them; a `.control-row` lines them up.
943
+ - In a `.control-row`, put a field's hint or error below the row, not inside the `.field`.
944
+ - Replace a margin that spaced siblings with `space-*` or `gap-*` on their parent.