Переглянути джерело

Pad bare cards and dialogs by default; add .prose utility

Bare containers previously rendered content flush against their frame:
.card carried no padding (only .card-header/.card-body/.card-footer did)
and dialog was hardcoded padding: 0, so any card or dialog without
structural children needed hand-written padding utilities to look right.

- .card now pads with --spry-card-padding by default via :where(), and
  drops it again when built from card-header/card-body/card-footer
  children (:has guard). Both rules sit at zero specificity like the
  reset, so .p-0/.p-2 and friends override them cleanly.
- New --spry-popover-padding token (1.1rem 1.25rem) replaces the
  hardcoded [popover] padding; themes can retune it like every other
  token.
- A dialog without .dialog-titlebar/.dialog-body/.dialog-footer or card
  sections now pads with --spry-popover-padding instead of leaving its
  content glued to the frame.
- New .prose utility for long-form content (markdown, docs) inside cards
  and dialogs: 65ch measure, heading scale stepped down to 1.5/1.25/1.1/
  1rem, flow rhythm on --spry-space-*, first/last children flush.
- README documents the card default + opt-out, bare-dialog padding and
  .prose.

Verified: ninja rebuild (SpryCss.vala regenerated), 88/88 tests, and
headless-chromium computed-style probes under the default and Airy
themes (bare card 20px/17.6px, structured 0, .p-0 opt-out 0, bare
dialog padded, structural 0, prose h1 24px).
saddle-ClankerSaddle 6 годин тому
батько
коміт
5dad376040
2 змінених файлів з 51 додано та 3 видалено
  1. 3 2
      README.md
  2. 48 1
      css/spry.css

+ 3 - 2
README.md

@@ -92,6 +92,7 @@ Typography and color:
 `.fw-{light,normal,medium,semibold,bold}`, `.fs-italic .fs-normal`,
 `.uppercase .lowercase .capitalize`, `.lead .small .mono .num`,
 `.truncate .clamp-2 .clamp-3 .measure .measure-sm .text-nowrap`,
+`.prose` (long-form container: 65ch measure, reduced heading scale),
 `.bg-surface .bg-alt .bg-inset .bg-accent`, `.border .border-0 .rounded
 .rounded-sm .rounded-lg .rounded-pill .shadow .shadow-lg .shadow-none`.
 
@@ -105,7 +106,7 @@ Components:
 | Buttons | `.btn` + `.btn-primary .btn-outline .btn-ghost .btn-link .btn-danger`, `.btn-sm .btn-lg .btn-block`, `.btn-group` |
 | Forms | `.field` `.label` `.hint` `.input`, `.is-valid .is-invalid`, `fieldset`/`legend` defaults |
 | Checks | `.choice`, `.choices` / `.choices-inline`, `.switch` with `<span class="track">` |
-| Cards | `.card` (+ `.card-flat`), `.card-header .card-body .card-footer .card-title .card-subtitle` |
+| Cards | `.card` (+ `.card-flat`) pads with `--spry-card-padding` by default; `.card-header .card-body .card-footer .card-title .card-subtitle` run flush (the card drops its padding when built from them; opt out with `.p-0`) |
 | Alerts / badges | `.alert` + `.alert-{danger,success,warning,info}`, `.badge` + `.badge-*` |
 | Hero | `.hero` (+ `.hero-compact`), `.hero-eyebrow .hero-title .hero-sub .hero-actions` |
 | Avatars | `.avatar` (+ `-sm/-lg`), `.avatar-group` |
@@ -115,7 +116,7 @@ Components:
 | Tables | bare `table` + `.table-striped .table-hover .table-compact .table-borderless`, `.table-wrap`, `.num` |
 | Progress | bare `<progress>` (determinate + indeterminate pulse), `.progress-thin`, `<meter>` |
 | Spinners | `.spinner` `.spinner-sm` `.spinner-lg` |
-| Dialogs | `<dialog>` + `.dialog-sm .dialog-lg`, `.dialog-titlebar .dialog-title .dialog-close .dialog-body .dialog-footer` |
+| Dialogs | `<dialog>` + `.dialog-sm .dialog-lg`, `.dialog-titlebar .dialog-title .dialog-close .dialog-body .dialog-footer`; a dialog without those parts pads with `--spry-popover-padding` |
 | Popovers | any `[popover]` element + `popovertarget` buttons; `.anchor` on the trigger + `.popover-anchor` on the popover + a shared `--anchor-name`; `.popover-title .popover-menu .popover-footer` |
 | Expanders | `<details>`/`<summary>` defaults, `.expander-group` |
 

+ 48 - 1
css/spry.css

@@ -116,6 +116,7 @@
   --spry-popover-bg: var(--spry-surface);
   --spry-popover-border: var(--spry-border);
   --spry-popover-shadow: var(--spry-shadow-lg);
+  --spry-popover-padding: 1.1rem 1.25rem;
 
   accent-color: var(--spry-accent);
 }
@@ -805,6 +806,32 @@ abbr[title] {
 .shadow-lg { box-shadow: var(--spry-shadow-lg); }
 .shadow-none { box-shadow: none; }
 
+/* Long-form content (markdown, docs, help text) inside cards and dialogs.
+   The page-scale headings are oversized in those containers, so .prose caps
+   the measure and steps headings down to a document scale; spacing rides
+   --spry-space-* so themes and dark mode compose unchanged. */
+.prose {
+  max-width: 65ch;
+}
+
+.prose h1 { font-size: 1.5rem; margin-block: var(--spry-space-4) var(--spry-space-2); }
+.prose h2 { font-size: 1.25rem; margin-block: var(--spry-space-4) var(--spry-space-2); }
+.prose h3 { font-size: 1.1rem; margin-block: var(--spry-space-3) var(--spry-space-2); }
+.prose h4 { font-size: 1rem; margin-block: var(--spry-space-3) var(--spry-space-2); }
+
+.prose p,
+.prose ul,
+.prose ol,
+.prose dl,
+.prose blockquote {
+  margin-block-end: var(--spry-space-3);
+}
+
+/* Flush at both ends — trailing/leading flow margins would sit on top of the
+   card or dialog padding and make one side look heavier than the other. */
+.prose > :first-child { margin-block-start: 0; }
+.prose > :last-child { margin-block-end: 0; }
+
 /* ==========================================================================
    9. Components — topbar & navigation
    ========================================================================== */
@@ -1447,6 +1474,19 @@ input[type="radio"] {
   backdrop-filter: var(--spry-glass);
 }
 
+/* Padding comes with the card, so a bare <div class="card"> already looks
+   right; structural sections carry their own padding flush to the frame, so
+   a card built from them drops the default. Both rules sit at zero
+   specificity (:where), the same trick the reset uses — padding utilities
+   (.p-0, .px-2, …) override them without a specificity fight. */
+:where(.card) {
+  padding: var(--spry-card-padding);
+}
+
+:where(.card:has(> .card-header, > .card-body, > .card-footer)) {
+  padding: 0;
+}
+
 .card-flat {
   box-shadow: none;
 }
@@ -1729,6 +1769,13 @@ dialog.sm,
 dialog.lg,
 .dialog-lg { width: min(92vw, 56rem); }
 
+/* A dialog without structural parts (titlebar/body/footer/card sections)
+   would render its content flush against the frame, so pad it like a
+   popover. Structural dialogs keep padding: 0 above. */
+dialog:not(:has(.dialog-titlebar, .dialog-body, .dialog-footer, .card-header, .card-body, .card-footer)) {
+  padding: var(--spry-popover-padding);
+}
+
 dialog::backdrop {
   background: var(--spry-backdrop);
 }
@@ -1863,7 +1910,7 @@ dialog[open]::backdrop,
      the reset above zeroes margins, which parks popovers in the top-left */
   margin: auto;
   width: min(92vw, 26rem);
-  padding: 1.1rem 1.25rem;
+  padding: var(--spry-popover-padding);
   border: var(--spry-border-width) solid var(--spry-popover-border);
   border-radius: var(--spry-radius-lg);
   background: var(--spry-popover-bg);