@refrakt-md/lumina 0.22.0 → 0.24.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 (132) hide show
  1. package/base.css +7 -1
  2. package/contracts/structures.json +537 -0
  3. package/dist/config.d.ts.map +1 -1
  4. package/dist/config.js +7 -0
  5. package/dist/config.js.map +1 -1
  6. package/dist/tokens.d.ts.map +1 -1
  7. package/dist/tokens.js +13 -0
  8. package/dist/tokens.js.map +1 -1
  9. package/index.css +11 -1
  10. package/package.json +5 -4
  11. package/styles/base/attributes.css +6 -7
  12. package/styles/dimensions/checklist.css +6 -35
  13. package/styles/dimensions/cover.css +13 -95
  14. package/styles/dimensions/density.css +3 -0
  15. package/styles/dimensions/frame.css +3 -0
  16. package/styles/dimensions/media.css +14 -61
  17. package/styles/dimensions/metadata.css +24 -74
  18. package/styles/dimensions/motion.css +102 -0
  19. package/styles/dimensions/sections.css +10 -24
  20. package/styles/dimensions/sequence.css +14 -79
  21. package/styles/dimensions/state.css +39 -56
  22. package/styles/dimensions/substrate.css +3 -0
  23. package/styles/dimensions/surfaces.css +73 -113
  24. package/styles/elements/blockquote.css +3 -2
  25. package/styles/elements/code.css +3 -0
  26. package/styles/elements/table.css +3 -0
  27. package/styles/global.css +9 -48
  28. package/styles/layouts/blog.css +3 -64
  29. package/styles/layouts/default.css +3 -77
  30. package/styles/layouts/docs.css +3 -153
  31. package/styles/layouts/mobile.css +1 -50
  32. package/styles/layouts/on-this-page.css +3 -2
  33. package/styles/layouts/plan.css +3 -134
  34. package/styles/layouts/search.css +3 -68
  35. package/styles/layouts/split.css +24 -169
  36. package/styles/layouts/theme-toggle.css +3 -29
  37. package/styles/layouts/version-switcher.css +3 -4
  38. package/styles/runes/accordion.css +15 -58
  39. package/styles/runes/aggregate.css +3 -12
  40. package/styles/runes/annotate.css +6 -35
  41. package/styles/runes/api.css +3 -0
  42. package/styles/runes/audio.css +3 -41
  43. package/styles/runes/badge.css +3 -0
  44. package/styles/runes/bar.css +3 -0
  45. package/styles/runes/bento.css +16 -159
  46. package/styles/runes/bg.css +3 -37
  47. package/styles/runes/blog.css +3 -5
  48. package/styles/runes/bond.css +3 -23
  49. package/styles/runes/breadcrumb.css +5 -13
  50. package/styles/runes/budget.css +3 -25
  51. package/styles/runes/bug.css +3 -0
  52. package/styles/runes/card.css +24 -92
  53. package/styles/runes/cast.css +5 -22
  54. package/styles/runes/changelog.css +5 -9
  55. package/styles/runes/character.css +3 -17
  56. package/styles/runes/chart.css +35 -53
  57. package/styles/runes/codegroup.css +15 -23
  58. package/styles/runes/collection.css +5 -82
  59. package/styles/runes/compare.css +3 -14
  60. package/styles/runes/comparison.css +7 -34
  61. package/styles/runes/conversation.css +5 -27
  62. package/styles/runes/cta.css +3 -26
  63. package/styles/runes/datatable.css +25 -40
  64. package/styles/runes/decision.css +3 -0
  65. package/styles/runes/design-context.css +3 -2
  66. package/styles/runes/details.css +5 -13
  67. package/styles/runes/diagram.css +5 -13
  68. package/styles/runes/diff.css +5 -88
  69. package/styles/runes/drawer.css +1 -105
  70. package/styles/runes/embed.css +4 -12
  71. package/styles/runes/event.css +3 -1
  72. package/styles/runes/expand.css +5 -40
  73. package/styles/runes/faction.css +3 -9
  74. package/styles/runes/feature.css +4 -32
  75. package/styles/runes/figure.css +5 -24
  76. package/styles/runes/file-ref.css +3 -18
  77. package/styles/runes/form.css +3 -32
  78. package/styles/runes/gallery.css +3 -135
  79. package/styles/runes/grid.css +4 -56
  80. package/styles/runes/hero.css +13 -126
  81. package/styles/runes/hint.css +16 -41
  82. package/styles/runes/howto.css +3 -25
  83. package/styles/runes/itinerary.css +3 -32
  84. package/styles/runes/juxtapose.css +12 -87
  85. package/styles/runes/lore.css +3 -10
  86. package/styles/runes/map.css +3 -36
  87. package/styles/runes/mediatext.css +3 -44
  88. package/styles/runes/milestone.css +3 -16
  89. package/styles/runes/mockup.css +15 -108
  90. package/styles/runes/nav.css +3 -178
  91. package/styles/runes/organization.css +3 -2
  92. package/styles/runes/page-section.css +3 -4
  93. package/styles/runes/pagination.css +5 -37
  94. package/styles/runes/palette.css +3 -22
  95. package/styles/runes/placeholder.css +3 -3
  96. package/styles/runes/plan-history.css +3 -23
  97. package/styles/runes/plan-progress.css +3 -4
  98. package/styles/runes/plan-ref.css +3 -0
  99. package/styles/runes/playlist.css +2 -36
  100. package/styles/runes/plot.css +3 -19
  101. package/styles/runes/preview.css +7 -25
  102. package/styles/runes/pricing.css +7 -25
  103. package/styles/runes/progress.css +6 -25
  104. package/styles/runes/pullquote.css +3 -26
  105. package/styles/runes/realm.css +3 -9
  106. package/styles/runes/recipe.css +3 -27
  107. package/styles/runes/relationships.css +3 -34
  108. package/styles/runes/reveal.css +7 -12
  109. package/styles/runes/sandbox.css +6 -36
  110. package/styles/runes/section.css +4 -18
  111. package/styles/runes/showcase.css +3 -20
  112. package/styles/runes/sidenote.css +3 -2
  113. package/styles/runes/snippet.css +3 -0
  114. package/styles/runes/spacing.css +3 -22
  115. package/styles/runes/spec.css +3 -0
  116. package/styles/runes/steps.css +4 -36
  117. package/styles/runes/storyboard.css +2 -17
  118. package/styles/runes/swatch.css +3 -6
  119. package/styles/runes/symbol.css +6 -4
  120. package/styles/runes/tabs.css +6 -9
  121. package/styles/runes/testimonial.css +5 -6
  122. package/styles/runes/textblock.css +2 -20
  123. package/styles/runes/timeline.css +3 -21
  124. package/styles/runes/tint.css +3 -0
  125. package/styles/runes/toc.css +5 -3
  126. package/styles/runes/track.css +2 -31
  127. package/styles/runes/typography.css +3 -15
  128. package/styles/runes/work.css +3 -0
  129. package/styles/runes/xref.css +3 -1
  130. package/tokens/base.css +19 -0
  131. package/tokens/dark.css +3 -0
  132. package/styles/dimensions/guest-posture.css +0 -27
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@refrakt-md/lumina",
3
3
  "description": "Lumina theme for refrakt.md — design tokens, CSS, identity transform, and layout configs",
4
- "version": "0.22.0",
4
+ "version": "0.24.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -84,9 +84,10 @@
84
84
  "generate-tokens": "node scripts/generate-tokens.mjs"
85
85
  },
86
86
  "dependencies": {
87
- "@refrakt-md/runes": "0.22.0",
88
- "@refrakt-md/transform": "0.22.0",
89
- "@refrakt-md/types": "0.22.0"
87
+ "@refrakt-md/runes": "0.24.0",
88
+ "@refrakt-md/skeleton": "0.24.0",
89
+ "@refrakt-md/transform": "0.24.0",
90
+ "@refrakt-md/types": "0.24.0"
90
91
  },
91
92
  "devDependencies": {
92
93
  "postcss": "^8.4.0"
@@ -1,3 +1,4 @@
1
+ @layer skin {
1
2
  /* Universal Rune Attributes — spacing & inset
2
3
  *
3
4
  * Compound attribute selectors [data-rune][data-*] give specificity (0,2,0):
@@ -19,11 +20,9 @@
19
20
  [data-rune][data-inset="loose"] { padding-inline: var(--rf-inset-loose, 4rem); }
20
21
  [data-rune][data-inset="breathe"] { padding-inline: var(--rf-inset-breathe, 8rem); }
21
22
 
22
- /* ── Elevationdrop shadow (box-shadow) ──────────────────────────────
23
- * Universal `elevation` attribute (SPEC-086). Maps to the shared --rf-shadow-*
24
- * token scale; `none` explicitly flattens a rune's default shadow. */
23
+ /* The `elevation` axis (SPEC-107) depth-ladder chrome (fill / border / radius
24
+ * / resting shadow) is mapped by value in dimensions/surfaces.css. The old
25
+ * SPEC-086 shadow scale (none/sm/md/lg) is superseded; the engine maps those
26
+ * deprecated aliases onto the ladder before they reach the DOM. */
25
27
 
26
- [data-rune][data-elevation="none"] { box-shadow: var(--rf-shadow-none); }
27
- [data-rune][data-elevation="sm"] { box-shadow: var(--rf-shadow-sm); }
28
- [data-rune][data-elevation="md"] { box-shadow: var(--rf-shadow-md); }
29
- [data-rune][data-elevation="lg"] { box-shadow: var(--rf-shadow-lg); }
28
+ }
@@ -1,21 +1,11 @@
1
- /* === Checklist: universal checkbox item styling === */
1
+ @layer skin {
2
+ /* === Checklist — skin ===
3
+ * The indicator chrome (fill, border, radius, box-shadow) and the item text
4
+ * treatment per state. The indicator gutter + box placement/size live in
5
+ * @refrakt-md/skeleton (dimensions/checklist.css). */
2
6
 
3
- /* All checklist items get left padding for the indicator */
4
- [data-checked] {
5
- position: relative;
6
- padding-left: 1.75rem;
7
- list-style: none;
8
- margin-left: -1.5rem;
9
- }
10
-
11
- /* Indicator base — positioned left of text */
7
+ /* Indicator base chrome. */
12
8
  [data-checked]::before {
13
- content: '';
14
- position: absolute;
15
- left: 0.125rem;
16
- top: 0.5em;
17
- width: 1rem;
18
- height: 1rem;
19
9
  border-radius: var(--rf-radius-sm, 0.25rem);
20
10
  border: 2px solid var(--rf-color-border);
21
11
  background: transparent;
@@ -59,23 +49,4 @@
59
49
  background: transparent;
60
50
  }
61
51
 
62
- /* === Density interaction === */
63
-
64
- /* Compact: tighter spacing */
65
- [data-density="compact"] [data-checked] {
66
- padding-left: 1.5rem;
67
- }
68
-
69
- [data-density="compact"] [data-checked]::before {
70
- width: 0.75rem;
71
- height: 0.75rem;
72
- }
73
-
74
- /* Minimal: indicators only, no text */
75
- [data-density="minimal"] [data-checked] {
76
- font-size: 0;
77
- padding-left: 0;
78
- display: inline-block;
79
- width: 1rem;
80
- height: 1rem;
81
52
  }
@@ -1,116 +1,32 @@
1
- /* Cover layout (SPEC-089) — `media-position="cover"`.
1
+ @layer skin {
2
+ /* Cover layout (SPEC-089) — skin.
2
3
  *
3
- * The media well fills the rune interior and the content overlays it (the poster
4
- * / cover card). The media stays a media guest — the thin-edge frame and
5
- * `--rf-radius-media` are preserved with content floated on top via grid
6
- * stacking (media + content share one grid cell). No overlay primitive in the
7
- * layout config; the variant supplies the structure (SPEC-091), CSS positions.
8
- *
9
- * Height authority: an external grid track (bento) wins; else the media aspect
10
- * (`frame-aspect`, default portrait); a card height/aspect knob overrides via the
11
- * cascade. Cover supersedes the media-vs-content split knobs (content-height /
12
- * media-ratio), which have no meaning when there is no split. */
4
+ * The grid-overlay *structure* (media well + content share one grid cell, the
5
+ * well fills/crops its guest, the overlay positions via content-place) lives in
6
+ * @refrakt-md/skeleton (dimensions/cover.css). What remains here is skin: the
7
+ * media radii, the overlay's surface padding, and the legibility scrim
8
+ * (gradient / frost) painted over the media. */
13
9
 
14
- /* ── full scope — the whole content overlays the media well ──────────── */
15
- [data-media-position="cover"]:not([data-cover-scope="header"]) {
16
- display: grid;
17
- grid-template: minmax(0, 1fr) / minmax(0, 1fr);
18
- aspect-ratio: var(--frame-aspect, var(--cover-aspect, 3 / 4));
19
- container-type: size;
20
- overflow: hidden;
21
- isolation: isolate;
22
- }
23
- [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-section="media"],
24
- [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-name="content"] {
25
- grid-area: 1 / 1;
26
- margin: 0;
27
- min-width: 0;
28
- }
29
-
30
- /* ── header scope — only the cover-band overlays; body flows below ────── */
31
10
  [data-cover-scope="header"] > [data-name="cover-band"] {
32
- display: grid;
33
- grid-template: minmax(0, 1fr) / minmax(0, 1fr);
34
- aspect-ratio: var(--frame-aspect, 16 / 9);
35
11
  border-radius: var(--rf-radius-media);
36
- overflow: hidden;
37
- isolation: isolate;
38
- container-type: size;
39
- }
40
- /* On narrow screens a 16/9 band is too short to seat the preamble over the
41
- * scrim, so default it to a taller (≥1:1) poster shape. An explicit
42
- * `frame-aspect` still wins. */
43
- @media (max-width: 40rem) {
44
- [data-cover-scope="header"] > [data-name="cover-band"] {
45
- aspect-ratio: var(--frame-aspect, 4 / 5);
46
- }
47
- }
48
- [data-cover-scope="header"] > [data-name="cover-band"] > [data-section="media"],
49
- [data-cover-scope="header"] > [data-name="cover-band"] > [data-name="preamble"] {
50
- grid-area: 1 / 1;
51
- margin: 0;
52
12
  }
53
13
 
54
- /* ── shared: the media well fills, the overlaid box positions ─────────── */
55
14
  [data-media-position="cover"] [data-section="media"] {
56
- position: relative;
57
- height: 100%;
58
15
  border-radius: var(--rf-radius-media);
59
- overflow: hidden;
60
- }
61
- [data-media-position="cover"] [data-section="media"] > :is(img, video) {
62
- width: 100%;
63
- height: 100%;
64
- object-fit: cover;
65
- }
66
- /* SPEC-101 — non-img/video guests (a sandbox, an embed) fill the well too.
67
- * They can't be object-fit-cropped, so they get the well's box outright;
68
- * display:block covers undefined custom elements (inline by default). */
69
- [data-media-position="cover"] [data-section="media"] > :not(img, video) {
70
- display: block;
71
- width: 100%;
72
- height: 100%;
73
16
  }
17
+
74
18
  /* A backdrop sandbox sits flush in the well — the well's own radius/overflow
75
- * does the clipping. The iframe height is pinned inline by the element in
76
- * `fill` mode; this carries the fixed-height case visually until then. */
19
+ * does the clipping. */
77
20
  [data-media-position="cover"] [data-section="media"] .rf-sandbox {
78
- margin: 0;
79
21
  border-radius: 0;
80
22
  }
81
- [data-media-position="cover"] [data-section="media"] .rf-sandbox iframe {
82
- height: 100%;
83
- }
84
- /* The overlaid box (full: content; header: preamble) anchors via content-place. */
23
+
24
+ /* The overlaid box carries the surface padding (density-aware via --rune-padding). */
85
25
  [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-name="content"],
86
26
  [data-cover-scope="header"] > [data-name="cover-band"] > [data-name="preamble"] {
87
- position: relative;
88
- z-index: 1;
89
- align-self: var(--cover-place-block, end);
90
- justify-self: var(--cover-place-inline, stretch);
91
27
  padding: var(--rune-padding, var(--rf-spacing-md));
92
28
  }
93
29
 
94
- /* ── auto / unset placement ───────────────────────────────────────────
95
- * `auto` is the cover default, so an unset content-place behaves the same as an
96
- * explicit `auto`. A header band is always a caption strip — the preamble sits at
97
- * the block-end over the scrim regardless of the band's orientation. A full-scope
98
- * overlay adapts to the cover region's orientation (portrait → block-end caption;
99
- * landscape → inline-start side panel). An explicit value (e.g. "center center")
100
- * sets the `--cover-place-*` vars on the base rule above and does NOT match here,
101
- * so it pins regardless of orientation. */
102
- [data-media-position="cover"]:not([data-cover-scope="header"]):is([data-content-place="auto"], :not([data-content-place])) > [data-name="content"],
103
- [data-cover-scope="header"]:is([data-content-place="auto"], :not([data-content-place])) > [data-name="cover-band"] > [data-name="preamble"] {
104
- align-self: end;
105
- justify-self: stretch;
106
- }
107
- @container (min-aspect-ratio: 1 / 1) {
108
- [data-media-position="cover"]:not([data-cover-scope="header"]):is([data-content-place="auto"], :not([data-content-place])) > [data-name="content"] {
109
- align-self: center;
110
- justify-self: start;
111
- }
112
- }
113
-
114
30
  /* ── default cover scrim (SPEC-088 scrim, on the media surface) ────────
115
31
  * Overlaying text on an arbitrary image without a scrim is a legibility footgun,
116
32
  * so cover applies a default gradient scrim weighted toward the content edge.
@@ -169,3 +85,5 @@
169
85
  * reads at full foreground strength — the same colour as the headline. */
170
86
  --rf-color-muted: var(--rf-color-text);
171
87
  }
88
+
89
+ }
@@ -1,3 +1,4 @@
1
+ @layer skin {
1
2
  /* ─── Density Dimension ─────────────────────────────────────────────────
2
3
  * Controls spacing, visibility, and detail level across all runes.
3
4
  * Three levels: full (dedicated page), compact (grid/card), minimal (list).
@@ -74,3 +75,5 @@
74
75
  [data-density="minimal"] [data-density="full"] [data-meta-rank="secondary"] {
75
76
  display: revert;
76
77
  }
78
+
79
+ }
@@ -1,3 +1,4 @@
1
+ @layer skin {
1
2
  /* Frame chrome (SPEC-086) — media-surface presentation.
2
3
  *
3
4
  * The engine lands the frame contract on the frame-target element: a rune's
@@ -102,3 +103,5 @@
102
103
  * clipped by the zone's `overflow: hidden` regardless of width, so the peek
103
104
  * reads consistently from desktop to mobile. For self-target (showcase) the
104
105
  * negative-margin spill stays on too — that's the intended breakout. */
106
+
107
+ }
@@ -1,62 +1,15 @@
1
- /* ─── Media Slots Dimension ─────────────────────────────────────────────
2
- * Generic treatment for 5 media slot types: portrait, cover, thumbnail,
3
- * hero, icon. Applied via data-media attribute on image/media elements.
4
- *
5
- * Includes density × media interactions (compact shrinks, minimal hides).
6
- * ────────────────────────────────────────────────────────────────────── */
7
-
8
- /* ─── Portrait: circular crop ─────────────────────────────────────── */
9
-
10
- [data-media="portrait"] {
11
- border-radius: var(--rf-radius-full);
12
- aspect-ratio: 1 / 1;
13
- object-fit: cover;
14
- width: var(--media-portrait-size, 5rem);
15
- height: var(--media-portrait-size, 5rem);
16
- }
17
-
18
- /* ─── Cover: full-width banner image ──────────────────────────────── */
19
-
20
- [data-media="cover"] {
21
- width: 100%;
22
- object-fit: cover;
23
- border-radius: var(--rf-radius-md);
24
- }
25
-
26
- /* ─── Thumbnail: small fixed preview ──────────────────────────────── */
27
-
28
- [data-media="thumbnail"] {
29
- width: var(--media-thumbnail-size, 3rem);
30
- height: var(--media-thumbnail-size, 3rem);
31
- border-radius: var(--rf-radius-sm);
32
- object-fit: cover;
33
- flex-shrink: 0;
34
- }
35
-
36
- /* ─── Hero: large responsive image ────────────────────────────────── */
37
-
38
- [data-media="hero"] {
39
- width: 100%;
40
- object-fit: cover;
41
- }
42
-
43
- /* ─── Icon: small square, no crop ─────────────────────────────────── */
44
-
45
- [data-media="icon"] {
46
- width: var(--media-icon-size, 2rem);
47
- height: var(--media-icon-size, 2rem);
48
- object-fit: contain;
49
- flex-shrink: 0;
50
- }
51
-
52
- /* ─── Density × Media ─────────────────────────────────────────────── */
53
-
54
- /* Compact: smaller portraits */
55
- [data-density="compact"] [data-media="portrait"] {
56
- --media-portrait-size: 3rem;
57
- }
58
-
59
- /* Minimal: hide all media */
60
- [data-density="minimal"] [data-media] {
61
- display: none;
1
+ /* ─── Media Slots Dimension — skin ──────────────────────────────────────
2
+ * The rounded corners of each slot type. Slot sizing/fit (the geometry) is
3
+ * structure and lives in `@refrakt-md/skeleton` (styles/dimensions/media.css). */
4
+
5
+ @layer skin {
6
+ [data-media="portrait"] {
7
+ border-radius: var(--rf-radius-full);
8
+ }
9
+ [data-media="cover"] {
10
+ border-radius: var(--rf-radius-md);
11
+ }
12
+ [data-media="thumbnail"] {
13
+ border-radius: var(--rf-radius-sm);
14
+ }
62
15
  }
@@ -1,13 +1,12 @@
1
- /* ─── Metadata Dimensions ──────────────────────────────────────────────
2
- * SPEC-080 typography / geometry split:
3
- * - `[data-meta-type=…]` selectors carry ONLY typography hints
4
- * (monospace, tabular-nums). No padding, no border, no chip geometry.
5
- * - Geometry comes from `[data-zone-layout=…]` selectors (`bar`,
6
- * `definition-list`) and from the universal `.rf-badge` class, which
7
- * is emitted by chip-type fields AND by the standalone `{% badge %}`
8
- * rune.
9
- * - Sentiment rules drive `--meta-color`, which the chip and any
10
- * sentiment-tinted text inherits.
1
+ @layer skin {
2
+ /* ─── Metadata Dimensions skin ───────────────────────────────────────
3
+ * SPEC-080 typography / geometry split. The zone-layout *structure* (bar +
4
+ * definition-list display/grid, the rating flex row, the sr-only label
5
+ * mechanism) lives in @refrakt-md/skeleton (dimensions/metadata.css). What
6
+ * remains here is skin: sentiment colours, meta-type typography, the rating
7
+ * glyph + colours, the chip/def-list spacing + borders + radius, and labels.
8
+ * - `[data-meta-type=…]` selectors carry ONLY typography hints.
9
+ * - Sentiment rules drive `--meta-color`, inherited by chips + tinted text.
11
10
  * ────────────────────────────────────────────────────────────────────── */
12
11
 
13
12
  /* ─── Sentiment ────────────────────────────────────────────────────── */
@@ -44,9 +43,10 @@
44
43
  }
45
44
 
46
45
  /* Rating — `value` filled marks out of `total`; each mark carries
47
- * `data-filled`. Stars via colour (filled = primary, empty = border). */
46
+ * `data-filled`. Stars via colour (filled = primary, empty = border). The
47
+ * inline-flex row is structure (skeleton); the glyph + spacing + colours are
48
+ * skin. */
48
49
  [data-meta-type="rating"] {
49
- display: inline-flex;
50
50
  gap: 0.0625rem;
51
51
  line-height: 1;
52
52
  }
@@ -73,50 +73,22 @@
73
73
  text-underline-offset: 0.15em;
74
74
  }
75
75
 
76
- /* ─── Layout: bar (SPEC-080) ───────────────────────────────────────── */
77
-
78
- /* Horizontal flex row of fields, each in its own intrinsic shape (chip or
79
- * bare). Wraps by default; `data-wrap="false"` keeps it on one line. A field
80
- * tagged `data-align="end"` (and everything after it) is pushed to the right
81
- * edge. */
76
+ /* ─── Layout: bar spacing ────────────────────────────────────────── */
77
+ /* The flex row + end-push are structure (skeleton); the gap is skin. */
82
78
  [data-zone-layout="bar"] {
83
- display: flex;
84
- flex-wrap: wrap;
85
79
  gap: 0.5rem;
86
- align-items: center;
87
- }
88
- [data-zone-layout="bar"][data-wrap="false"] {
89
- flex-wrap: nowrap;
90
- }
91
- [data-zone-layout="bar"] [data-align="end"] {
92
- margin-left: auto;
93
80
  }
94
81
 
95
- /* ─── Layout: definition-list ──────────────────────────────────────── */
96
-
97
- /* Single source of truth for the `definition-list` primitive, shared by
98
- * both the projected metadata zone (`<dl data-zone="…">`) and the authored
99
- * `{% deflist %}` rune (`<dl data-rune="deflist">`) — they emit the same
100
- * `data-zone-layout` so they render identically (geometry, borders, margin).
101
- *
102
- * Each row stacks `dt` above `dd`. Rows flow into multiple columns
103
- * via `auto-fit` — CSS grid naturally collapses to a single column
104
- * when the container is narrower than the minimum, so the same rule
105
- * reads fine on mobile and packs dense on wider screens without a
106
- * media-query step. The 8rem minimum keeps short label / value pairs
107
- * like "Prep" + "15m" tight while leaving room for the per-item border. */
82
+ /* ─── Layout: definition-list spacing + chrome ───────────────────── */
83
+ /* The grid + row stacking are structure (skeleton); the gaps, the per-row
84
+ * border/padding/radius card chrome, and the dt/dd type are skin. */
108
85
  [data-zone-layout="definition-list"] {
109
- display: grid;
110
- grid-template-columns: repeat(auto-fit, minmax(8rem, 1fr));
111
86
  gap: 0.75rem;
112
87
  margin: 1.25rem 0;
113
88
  }
114
89
 
115
90
  [data-zone-layout="definition-list"] > [data-name="row"] {
116
- display: flex;
117
- flex-direction: column;
118
91
  gap: 0.125rem;
119
- min-width: 0;
120
92
  padding: 0.625rem 0.75rem;
121
93
  border: 1px solid var(--rf-color-border);
122
94
  border-radius: var(--rf-radius-sm);
@@ -129,15 +101,10 @@
129
101
  }
130
102
 
131
103
  [data-zone-layout="definition-list"] dd {
132
- margin: 0;
133
104
  font-size: var(--rf-text-sm);
134
105
  }
135
106
 
136
- /* Multi-value dd (fields with `splitOn`) renders a row of chips
137
- * inside the cell, flowing naturally. */
138
107
  [data-zone-layout="definition-list"] dd[data-multi-value] {
139
- display: flex;
140
- flex-wrap: wrap;
141
108
  gap: 0.25rem;
142
109
  }
143
110
 
@@ -149,19 +116,6 @@
149
116
  opacity: 0.65;
150
117
  }
151
118
 
152
- /* Visually hidden labels — accessible to screen readers only */
153
- [data-meta-label-hidden] {
154
- position: absolute;
155
- width: 1px;
156
- height: 1px;
157
- padding: 0;
158
- margin: -1px;
159
- overflow: hidden;
160
- clip: rect(0, 0, 0, 0);
161
- white-space: nowrap;
162
- border: 0;
163
- }
164
-
165
119
  /* ─── Mobile: compact ──────────────────────────────────────────────── */
166
120
 
167
121
  @media (max-width: 48rem) {
@@ -169,26 +123,22 @@
169
123
  gap: 0.375rem;
170
124
  }
171
125
 
172
- /* On narrow screens, fall back to the original label-left / value-right
173
- * list: each row's dt/dd flow directly into a two-column grid via
174
- * `display: contents`, which drops the per-item border (no box to paint).
175
- * The border + padding move to the list itself so the whole group reads
176
- * as one bordered card. */
126
+ /* The border + padding move to the list itself so the whole group reads as
127
+ * one bordered card (the per-row box is dropped by the skeleton's
128
+ * `display: contents`). */
177
129
  [data-zone-layout="definition-list"] {
178
- grid-template-columns: max-content 1fr;
179
130
  gap: 0.25rem 1rem;
180
- align-items: baseline;
181
131
  padding: 0.75rem 1rem;
182
132
  border: 1px solid var(--rf-color-border);
183
133
  border-radius: var(--rf-radius-sm);
184
134
  }
185
135
 
186
- [data-zone-layout="definition-list"] > [data-name="row"] {
187
- display: contents;
188
- }
189
-
136
+ /* Header rhythm stays skin with sections.css (deferred until the split layout
137
+ * + sections promote together). */
190
138
  [data-section="header"] {
191
139
  gap: 0.375rem;
192
140
  margin-bottom: 1.5rem;
193
141
  }
194
142
  }
143
+
144
+ }
@@ -0,0 +1,102 @@
1
+ /* ─── Motion Dimension — skin (SPEC-105) ───────────────────────────────────
2
+ * Scroll-reveal entrance choreography. The split that keeps motion from leaking
3
+ * author concerns into theme concerns:
4
+ * • Author declares *intent* — `data-reveal` / `data-stagger` (the engine, WORK-431).
5
+ * • The behaviour owns *when* — `data-in-view` + the root `data-animate` gate (WORK-433).
6
+ * • This file owns *how* — the physics, keyed on `data-reveal` × `data-in-view`.
7
+ *
8
+ * It covers every section rune from ONE stylesheet: it keys on the generic
9
+ * attributes + the `--rf-reveal-index` marker, never on a rune's structure, so no
10
+ * rune's own CSS gains a motion block. Motion is theme choreography (skin); the
11
+ * framework structure (@refrakt-md/skeleton) has none — a different theme ships its
12
+ * own motion.css. Retune calm↔punchy purely via the tokens below.
13
+ *
14
+ * Reduced motion is handled by the global reset (WORK-352, global.css), which
15
+ * neutralises transition/animation durations; the behaviour also marks everything
16
+ * in-view immediately. */
17
+
18
+ @layer skin {
19
+ /* Physics tokens (`--rf-reveal-duration` / `-easing` / `-distance` /
20
+ * `-scale-start` / `-blur` / `-stagger`) are part of the theme TokenContract
21
+ * (SPEC-105), defined in `src/tokens.ts` and emitted into `tokens/base.css`.
22
+ * A site retunes the feel via `refrakt.config.json` `theme.tokens.reveal.*`;
23
+ * this file only *consumes* them. The fallbacks below keep motion sensible if a
24
+ * theme ships this stylesheet without the reveal token group. */
25
+
26
+ /* Per-character offsets — set on the `[data-reveal]` container and inherited by
27
+ * staggered children, so the single hidden-state block below covers every
28
+ * character. `none` carries no offsets (it never animates). */
29
+ [data-reveal="fade"] { --reveal-x: 0; --reveal-y: 0; --reveal-scale: 1; --reveal-blur: 0; }
30
+ [data-reveal="slide"] { --reveal-x: 0; --reveal-y: var(--rf-reveal-distance, 1.5rem); --reveal-scale: 1; --reveal-blur: 0; }
31
+ [data-reveal="scale"] { --reveal-x: 0; --reveal-y: 0; --reveal-scale: var(--rf-reveal-scale-start, 0.95); --reveal-blur: 0; }
32
+ /* `blur` is focus-led; it animates `filter` (not compositor-cheap), so a theme
33
+ * or low-power path may downgrade it to `fade` by zeroing the blur token. */
34
+ [data-reveal="blur"] { --reveal-x: 0; --reveal-y: 0; --reveal-scale: 1; --reveal-blur: var(--rf-reveal-blur, 8px); }
35
+
36
+ /* The animated unit:
37
+ * • non-staggered → the `[data-reveal]` element itself,
38
+ * • staggered → its indexed children (`[style*="--rf-reveal-index"]`).
39
+ * Everything is gated under the root `[data-animate]` flag the behaviour sets on
40
+ * boot — no JS → no flag → fully visible, nothing hidden (SSR-complete).
41
+ *
42
+ * CRITICAL: reveal animates the INDIVIDUAL `translate`/`scale` properties, never
43
+ * the `transform` shorthand, so it composes with the rune transforms Lumina
44
+ * already uses (hover-lifts on card/cta/feature, frame displacement, drawer/nav
45
+ * slides) instead of clobbering them. */
46
+ [data-animate] :where(
47
+ [data-reveal]:not([data-reveal="none"]):not([data-stagger]),
48
+ [data-stagger]:not([data-reveal="none"]) [style*="--rf-reveal-index"]
49
+ ) {
50
+ transition:
51
+ opacity var(--rf-reveal-duration, 0.9s) var(--rf-reveal-easing, ease-out),
52
+ translate var(--rf-reveal-duration, 0.9s) var(--rf-reveal-easing, ease-out),
53
+ scale var(--rf-reveal-duration, 0.9s) var(--rf-reveal-easing, ease-out),
54
+ filter var(--rf-reveal-duration, 0.9s) var(--rf-reveal-easing, ease-out);
55
+ }
56
+
57
+ /* Pre-entrance (hidden) — until the unit's trigger fires. */
58
+ [data-animate] :where(
59
+ [data-reveal]:not([data-reveal="none"]):not([data-stagger]):not([data-in-view]),
60
+ [data-stagger]:not([data-reveal="none"]):not([data-in-view]) [style*="--rf-reveal-index"]
61
+ ) {
62
+ opacity: 0;
63
+ translate: var(--reveal-x) var(--reveal-y);
64
+ scale: var(--reveal-scale);
65
+ filter: blur(var(--reveal-blur));
66
+ }
67
+
68
+ /* Revealed (resting) — explicit IDENTITY values, never the `none` default.
69
+ * This is what makes the entrance interpolate in WebKit: Safari does not
70
+ * transition `translate`/`scale`/`filter` to-or-from `none`, so reverting to
71
+ * the default would snap (no motion on iOS) — only opacity would animate.
72
+ * Transitioning between two explicit values animates everywhere. */
73
+ [data-animate] :where(
74
+ [data-reveal]:not([data-reveal="none"]):not([data-stagger])[data-in-view],
75
+ [data-stagger][data-in-view] [style*="--rf-reveal-index"]
76
+ ) {
77
+ opacity: 1;
78
+ translate: 0 0;
79
+ scale: 1;
80
+ filter: blur(0);
81
+ }
82
+
83
+ /* Stagger — each child's entrance offset from the container's single in-view
84
+ * trigger by its engine-stamped index against the interval token. */
85
+ [data-animate] [data-stagger][data-in-view] [style*="--rf-reveal-index"] {
86
+ transition-delay: calc(var(--rf-reveal-index) * var(--rf-reveal-stagger, 140ms));
87
+ }
88
+
89
+ /* Per-part choreography (opt-in polish, not required): on a non-staggered
90
+ * sliding section, let the media arrive a beat behind the content for a subtle
91
+ * two-part entrance. Demonstrated on the named anatomy the theme already styles;
92
+ * a theme adds or drops this freely. */
93
+ [data-animate] [data-reveal="slide"]:not([data-stagger]):not([data-in-view]) > * > [data-section="media"] {
94
+ translate: 0 calc(var(--rf-reveal-distance, 1.5rem) * 1.5);
95
+ }
96
+ [data-animate] [data-reveal="slide"]:not([data-stagger])[data-in-view] > * > [data-section="media"] {
97
+ translate: 0 0; /* explicit identity end-state — see the WebKit note above */
98
+ }
99
+ [data-animate] [data-reveal="slide"]:not([data-stagger]) > * > [data-section="media"] {
100
+ transition: translate var(--rf-reveal-duration, 0.9s) var(--rf-reveal-easing, ease-out);
101
+ }
102
+ }