@refrakt-md/lumina 0.33.0 → 0.34.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 (141) hide show
  1. package/base.css +29 -29
  2. package/dist/config.d.ts.map +1 -1
  3. package/dist/config.js.map +1 -1
  4. package/dist/icons.d.ts.map +1 -1
  5. package/dist/icons.js +57 -57
  6. package/dist/icons.js.map +1 -1
  7. package/dist/presets/catppuccin.js.map +1 -1
  8. package/dist/presets/dracula.js.map +1 -1
  9. package/dist/presets/gruvbox.js.map +1 -1
  10. package/dist/presets/niwaki.js.map +1 -1
  11. package/dist/presets/nord.js.map +1 -1
  12. package/dist/presets/one-dark.js.map +1 -1
  13. package/dist/presets/solarized.js.map +1 -1
  14. package/dist/presets/tideline.d.ts.map +1 -1
  15. package/dist/presets/tideline.js +20 -4
  16. package/dist/presets/tideline.js.map +1 -1
  17. package/dist/presets/tokyo-night.js.map +1 -1
  18. package/dist/tokens.js.map +1 -1
  19. package/index.css +124 -124
  20. package/package.json +5 -5
  21. package/presets.json +61 -13
  22. package/styles/base/attributes.css +32 -13
  23. package/styles/dimensions/checklist.css +45 -46
  24. package/styles/dimensions/cover.css +71 -51
  25. package/styles/dimensions/density.css +69 -65
  26. package/styles/dimensions/frame.css +116 -57
  27. package/styles/dimensions/metadata.css +110 -103
  28. package/styles/dimensions/motion.css +42 -13
  29. package/styles/dimensions/reading.css +27 -27
  30. package/styles/dimensions/sections.css +44 -45
  31. package/styles/dimensions/sequence.css +56 -57
  32. package/styles/dimensions/state.css +8 -2
  33. package/styles/dimensions/substrate.css +82 -58
  34. package/styles/dimensions/surfaces.css +65 -62
  35. package/styles/elements/blockquote.css +24 -25
  36. package/styles/elements/code.css +72 -71
  37. package/styles/elements/table.css +36 -37
  38. package/styles/global.css +163 -121
  39. package/styles/layouts/blog.css +219 -218
  40. package/styles/layouts/default.css +168 -167
  41. package/styles/layouts/docs.css +121 -122
  42. package/styles/layouts/mobile.css +54 -55
  43. package/styles/layouts/on-this-page.css +46 -47
  44. package/styles/layouts/plan.css +248 -247
  45. package/styles/layouts/search.css +142 -140
  46. package/styles/layouts/split.css +45 -45
  47. package/styles/layouts/theme-toggle.css +33 -31
  48. package/styles/layouts/version-switcher.css +38 -37
  49. package/styles/runes/accordion.css +90 -81
  50. package/styles/runes/aggregate.css +50 -39
  51. package/styles/runes/annotate.css +46 -45
  52. package/styles/runes/api.css +22 -23
  53. package/styles/runes/audio.css +140 -141
  54. package/styles/runes/badge.css +12 -13
  55. package/styles/runes/bar.css +4 -5
  56. package/styles/runes/bento.css +73 -62
  57. package/styles/runes/bg.css +62 -47
  58. package/styles/runes/blog.css +89 -90
  59. package/styles/runes/bond.css +49 -50
  60. package/styles/runes/breadcrumb.css +34 -35
  61. package/styles/runes/budget.css +126 -127
  62. package/styles/runes/bug.css +28 -27
  63. package/styles/runes/card.css +63 -60
  64. package/styles/runes/carousel.css +15 -15
  65. package/styles/runes/cast.css +50 -51
  66. package/styles/runes/changelog.css +45 -46
  67. package/styles/runes/character.css +31 -32
  68. package/styles/runes/chart.css +194 -106
  69. package/styles/runes/codegroup.css +109 -100
  70. package/styles/runes/collection.css +87 -86
  71. package/styles/runes/compare.css +38 -39
  72. package/styles/runes/comparison.css +205 -179
  73. package/styles/runes/conversation.css +36 -33
  74. package/styles/runes/cta.css +135 -132
  75. package/styles/runes/datatable.css +84 -83
  76. package/styles/runes/decision.css +25 -26
  77. package/styles/runes/design-context.css +21 -23
  78. package/styles/runes/details.css +26 -27
  79. package/styles/runes/diagram.css +28 -29
  80. package/styles/runes/diff.css +75 -72
  81. package/styles/runes/drawer.css +276 -198
  82. package/styles/runes/embed.css +20 -21
  83. package/styles/runes/event.css +35 -36
  84. package/styles/runes/expand.css +43 -34
  85. package/styles/runes/faction.css +47 -48
  86. package/styles/runes/feature.css +119 -112
  87. package/styles/runes/figure.css +11 -12
  88. package/styles/runes/file-ref.css +11 -13
  89. package/styles/runes/form.css +155 -154
  90. package/styles/runes/gallery.css +59 -60
  91. package/styles/runes/grid.css +10 -7
  92. package/styles/runes/hero.css +189 -185
  93. package/styles/runes/hint.css +57 -58
  94. package/styles/runes/howto.css +62 -64
  95. package/styles/runes/itinerary.css +133 -134
  96. package/styles/runes/juxtapose.css +83 -82
  97. package/styles/runes/lore.css +32 -34
  98. package/styles/runes/map.css +106 -99
  99. package/styles/runes/mediatext.css +30 -27
  100. package/styles/runes/milestone.css +98 -97
  101. package/styles/runes/mockup.css +338 -335
  102. package/styles/runes/nav.css +465 -450
  103. package/styles/runes/organization.css +23 -20
  104. package/styles/runes/page-section.css +14 -15
  105. package/styles/runes/pagination.css +45 -44
  106. package/styles/runes/palette.css +78 -80
  107. package/styles/runes/placeholder.css +8 -9
  108. package/styles/runes/plan-history.css +160 -161
  109. package/styles/runes/plan-progress.css +15 -16
  110. package/styles/runes/plan-ref.css +32 -23
  111. package/styles/runes/playlist.css +106 -107
  112. package/styles/runes/plot.css +92 -91
  113. package/styles/runes/preview.css +152 -149
  114. package/styles/runes/pricing.css +133 -123
  115. package/styles/runes/progress.css +32 -27
  116. package/styles/runes/pullquote.css +89 -90
  117. package/styles/runes/realm.css +47 -48
  118. package/styles/runes/recipe.css +112 -114
  119. package/styles/runes/relationships.css +77 -76
  120. package/styles/runes/reveal.css +90 -79
  121. package/styles/runes/sandbox.css +59 -60
  122. package/styles/runes/section.css +57 -50
  123. package/styles/runes/showcase.css +35 -16
  124. package/styles/runes/sidenote.css +30 -27
  125. package/styles/runes/snippet.css +8 -9
  126. package/styles/runes/spacing.css +86 -88
  127. package/styles/runes/spec.css +24 -25
  128. package/styles/runes/steps.css +89 -80
  129. package/styles/runes/storyboard.css +46 -45
  130. package/styles/runes/swatch.css +22 -23
  131. package/styles/runes/symbol.css +117 -118
  132. package/styles/runes/tabs.css +88 -79
  133. package/styles/runes/testimonial.css +43 -44
  134. package/styles/runes/textblock.css +32 -28
  135. package/styles/runes/timeline.css +63 -64
  136. package/styles/runes/tint.css +214 -215
  137. package/styles/runes/toc.css +40 -41
  138. package/styles/runes/track.css +89 -90
  139. package/styles/runes/typography.css +89 -91
  140. package/styles/runes/work.css +47 -36
  141. package/styles/runes/xref.css +21 -22
@@ -1,5 +1,5 @@
1
1
  @layer skin {
2
- /* Cover layout (SPEC-089) — skin.
2
+ /* Cover layout (SPEC-089) — skin.
3
3
  *
4
4
  * The grid-overlay *structure* (media well + content share one grid cell, the
5
5
  * well fills/crops its guest, the overlay positions via content-place) lives in
@@ -7,83 +7,103 @@
7
7
  * media radii, the overlay's surface padding, and the legibility scrim
8
8
  * (gradient / frost) painted over the media. */
9
9
 
10
- [data-cover-scope="header"] > [data-name="cover-band"] {
11
- border-radius: var(--rf-radius-media);
12
- }
10
+ [data-cover-scope="header"] > [data-name="cover-band"] {
11
+ border-radius: var(--rf-radius-media);
12
+ }
13
13
 
14
- [data-media-position="cover"] [data-section="media"] {
15
- border-radius: var(--rf-radius-media);
16
- }
14
+ [data-media-position="cover"] [data-section="media"] {
15
+ border-radius: var(--rf-radius-media);
16
+ }
17
17
 
18
- /* A backdrop sandbox sits flush in the well — the well's own radius/overflow
18
+ /* A backdrop sandbox sits flush in the well — the well's own radius/overflow
19
19
  * does the clipping. */
20
- [data-media-position="cover"] [data-section="media"] .rf-sandbox {
21
- border-radius: 0;
22
- }
20
+ [data-media-position="cover"] [data-section="media"] .rf-sandbox {
21
+ border-radius: 0;
22
+ }
23
23
 
24
- /* The overlaid box carries the surface padding (density-aware via --rune-padding). */
25
- [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-name="content"],
26
- [data-cover-scope="header"] > [data-name="cover-band"] > [data-name="preamble"] {
27
- padding: var(--rune-padding, var(--rf-spacing-md));
28
- }
24
+ /* The overlaid box carries the surface padding (density-aware via --rune-padding). */
25
+ [data-media-position="cover"]:not([data-cover-scope="header"]) > [data-name="content"],
26
+ [data-cover-scope="header"] > [data-name="cover-band"] > [data-name="preamble"] {
27
+ padding: var(--rune-padding, var(--rf-spacing-md));
28
+ }
29
29
 
30
- /* ── default cover scrim (SPEC-088 scrim, on the media surface) ────────
30
+ /* ── default cover scrim (SPEC-088 scrim, on the media surface) ────────
31
31
  * Overlaying text on an arbitrary image without a scrim is a legibility footgun,
32
32
  * so cover applies a default gradient scrim weighted toward the content edge.
33
33
  * `scrim="none"` opts out. The overlaid foreground is set light by the engine
34
34
  * (data-color-scheme) so text reads against the darkened media. */
35
- [data-media-position="cover"]:not([data-scrim="none"]) [data-section="media"]::after {
36
- content: "";
37
- position: absolute;
38
- inset: 0;
39
- pointer-events: none;
40
- /* `inherit` matches the media zone's `--rf-radius-media`, so a frost scrim's
35
+ [data-media-position="cover"]:not([data-scrim="none"]) [data-section="media"]::after {
36
+ content: "";
37
+ position: absolute;
38
+ inset: 0;
39
+ pointer-events: none;
40
+ /* `inherit` matches the media zone's `--rf-radius-media`, so a frost scrim's
41
41
  * `backdrop-filter` region clips at the same rounded corners — `overflow:
42
42
  * hidden` on the media zone doesn't reliably clip backdrop-filter past the
43
43
  * rounded edge in WebKit, so the pseudo carries its own radius. (The
44
44
  * gradient variant doesn't strictly need this, but it's harmless and keeps
45
45
  * the two scrim variants visually congruent.) */
46
- border-radius: inherit;
47
- /* The scrim shape is `--cover-scrim-image` (custom property override) when
46
+ border-radius: inherit;
47
+ /* The scrim shape is `--cover-scrim-image` (custom property override) when
48
48
  * the engine emits one — for `content-place="center …"` it emits a radial
49
49
  * ellipse so the dark falls under the centred overlay. For the directional
50
50
  * cases (`start`/`end` block-axis) the engine sets `--cover-scrim-dir` and
51
51
  * the linear fallback resolves to the right edge. */
52
- background-image: var(--cover-scrim-image, linear-gradient(var(--cover-scrim-dir, to top), rgb(0 0 0 / 0.55), transparent 62%));
53
- }
52
+ background-image: var(
53
+ --cover-scrim-image,
54
+ linear-gradient(var(--cover-scrim-dir, to top), rgb(0 0 0 / 0.55), transparent 62%)
55
+ );
56
+ }
54
57
 
55
- /* Frost treatment (`scrim-type="frost"`) — a frosted-glass blur over the media
58
+ /* Frost treatment (`scrim-type="frost"`) — a frosted-glass blur over the media
56
59
  * instead of a gradient. `scrim-blur` (named scale) sets the blur radius; the
57
60
  * tint follows the cover scheme (a dark scheme → a dark frost for light text).
58
61
  * The frost is masked to the content edge (following `--cover-scrim-dir`, default
59
62
  * bottom) so it reads as a band behind the text, not a blur over the whole
60
63
  * image. `--cover-scrim-mask` is the radial equivalent for centred content. */
61
- [data-media-position="cover"][data-scrim-blur="none"] { --cover-scrim-blur: 0px; }
62
- [data-media-position="cover"][data-scrim-blur="sm"] { --cover-scrim-blur: 4px; }
63
- [data-media-position="cover"][data-scrim-blur="md"] { --cover-scrim-blur: 8px; }
64
- [data-media-position="cover"][data-scrim-blur="lg"] { --cover-scrim-blur: 16px; }
65
- [data-media-position="cover"][data-scrim-type="frost"]:not([data-scrim="none"]) [data-section="media"]::after {
66
- background-image: none;
67
- background-color: rgb(0 0 0 / 0.18);
68
- -webkit-backdrop-filter: blur(var(--cover-scrim-blur, 8px));
69
- backdrop-filter: blur(var(--cover-scrim-blur, 8px));
70
- -webkit-mask-image: var(--cover-scrim-mask, linear-gradient(var(--cover-scrim-dir, to top), #000 30%, transparent 72%));
71
- mask-image: var(--cover-scrim-mask, linear-gradient(var(--cover-scrim-dir, to top), #000 30%, transparent 72%));
72
- }
73
- [data-media-position="cover"][data-scrim-type="frost"][data-color-scheme="light"]:not([data-scrim="none"]) [data-section="media"]::after {
74
- background-color: rgb(255 255 255 / 0.25);
75
- }
64
+ [data-media-position="cover"][data-scrim-blur="none"] {
65
+ --cover-scrim-blur: 0px;
66
+ }
67
+ [data-media-position="cover"][data-scrim-blur="sm"] {
68
+ --cover-scrim-blur: 4px;
69
+ }
70
+ [data-media-position="cover"][data-scrim-blur="md"] {
71
+ --cover-scrim-blur: 8px;
72
+ }
73
+ [data-media-position="cover"][data-scrim-blur="lg"] {
74
+ --cover-scrim-blur: 16px;
75
+ }
76
+ [data-media-position="cover"][data-scrim-type="frost"]:not([data-scrim="none"])
77
+ [data-section="media"]::after {
78
+ background-image: none;
79
+ background-color: rgb(0 0 0 / 0.18);
80
+ -webkit-backdrop-filter: blur(var(--cover-scrim-blur, 8px));
81
+ backdrop-filter: blur(var(--cover-scrim-blur, 8px));
82
+ -webkit-mask-image: var(
83
+ --cover-scrim-mask,
84
+ linear-gradient(var(--cover-scrim-dir, to top), #000 30%, transparent 72%)
85
+ );
86
+ mask-image: var(
87
+ --cover-scrim-mask,
88
+ linear-gradient(var(--cover-scrim-dir, to top), #000 30%, transparent 72%)
89
+ );
90
+ }
91
+ [data-media-position="cover"][data-scrim-type="frost"][data-color-scheme="light"]:not(
92
+ [data-scrim="none"]
93
+ )
94
+ [data-section="media"]::after {
95
+ background-color: rgb(255 255 255 / 0.25);
96
+ }
76
97
 
77
- /* The overlay carries the cover colour-scheme (set by the engine) so its text
98
+ /* The overlay carries the cover colour-scheme (set by the engine) so its text
78
99
  * reads against the darkened media. The standalone `[data-color-scheme]` rule
79
100
  * also paints a `background-color`; null it out here so the overlay stays
80
101
  * see-through over the media (the scrim, not a solid box, does the legibility). */
81
- [data-media-position="cover"] [data-name="content"][data-color-scheme],
82
- [data-cover-scope="header"] > [data-name="cover-band"][data-color-scheme] {
83
- background-color: transparent;
84
- /* Muted text (the blurb) is barely legible over a photo, so on the overlay it
102
+ [data-media-position="cover"] [data-name="content"][data-color-scheme],
103
+ [data-cover-scope="header"] > [data-name="cover-band"][data-color-scheme] {
104
+ background-color: transparent;
105
+ /* Muted text (the blurb) is barely legible over a photo, so on the overlay it
85
106
  * reads at full foreground strength — the same colour as the headline. */
86
- --rf-color-muted: var(--rf-color-text);
87
- }
88
-
107
+ --rf-color-muted: var(--rf-color-text);
108
+ }
89
109
  }
@@ -1,103 +1,107 @@
1
1
  @layer skin {
2
- /* ─── Density Dimension ─────────────────────────────────────────────────
2
+ /* ─── Density Dimension ─────────────────────────────────────────────────
3
3
  * Controls spacing, visibility, and detail level across all runes.
4
4
  * Three levels: full (dedicated page), compact (grid/card), minimal (list).
5
5
  *
6
6
  * Sets --rune-padding which surfaces consume for container padding.
7
7
  * ────────────────────────────────────────────────────────────────────── */
8
8
 
9
- /* ─── Full: all content visible, generous spacing ─────────────────── */
9
+ /* ─── Full: all content visible, generous spacing ─────────────────── */
10
10
 
11
- [data-density="full"] {
12
- --rune-padding: var(--rf-spacing-md);
13
- --rf-title-size: var(--rf-text-2xl);
14
- }
11
+ [data-density="full"] {
12
+ --rune-padding: var(--rf-spacing-md);
13
+ --rf-title-size: var(--rf-text-2xl);
14
+ }
15
15
 
16
- /* ─── Compact: truncated descriptions, tight spacing ──────────────── */
16
+ /* ─── Compact: truncated descriptions, tight spacing ──────────────── */
17
17
 
18
- [data-density="compact"] {
19
- --rune-padding: var(--rf-spacing-sm);
20
- --rf-title-size: var(--rf-text-xl);
21
- }
18
+ [data-density="compact"] {
19
+ --rune-padding: var(--rf-spacing-sm);
20
+ --rf-title-size: var(--rf-text-xl);
21
+ }
22
22
 
23
- [data-density="compact"] [data-section="description"] {
24
- display: -webkit-box;
25
- -webkit-line-clamp: 2;
26
- -webkit-box-orient: vertical;
27
- overflow: hidden;
28
- }
23
+ [data-density="compact"] [data-section="description"] {
24
+ display: -webkit-box;
25
+ -webkit-line-clamp: 2;
26
+ -webkit-box-orient: vertical;
27
+ overflow: hidden;
28
+ }
29
29
 
30
- [data-density="compact"] [data-meta-rank="secondary"] {
31
- display: none;
32
- }
30
+ [data-density="compact"] [data-meta-rank="secondary"] {
31
+ display: none;
32
+ }
33
33
 
34
- /* ─── Minimal: title and primary metadata only ────────────────────── */
34
+ /* ─── Minimal: title and primary metadata only ────────────────────── */
35
35
 
36
- [data-density="minimal"] {
37
- --rune-padding: var(--rf-spacing-xs);
38
- --rf-title-size: var(--rf-text);
39
- }
36
+ [data-density="minimal"] {
37
+ --rune-padding: var(--rf-spacing-xs);
38
+ --rf-title-size: var(--rf-text);
39
+ }
40
40
 
41
- /* Every rune root clears the prominence override so an ancestor's `prominence=`
41
+ /* Every rune root clears the prominence override so an ancestor's `prominence=`
42
42
  * cannot leak into a nested rune's title — the same protection the density
43
43
  * declarations above already give --rf-title-size. Declared here because this
44
44
  * file is imported before dimensions/surfaces.css, so a rune carrying both
45
45
  * `data-density` and `data-prominence` still keeps its own. */
46
- [data-density] {
47
- --rf-prominence-size: initial;
48
- }
46
+ [data-density] {
47
+ --rf-prominence-size: initial;
48
+ }
49
49
 
50
- [data-density="minimal"] [data-section="description"] {
51
- display: none;
52
- }
50
+ [data-density="minimal"] [data-section="description"] {
51
+ display: none;
52
+ }
53
53
 
54
- [data-density="minimal"] [data-section="body"] {
55
- display: none;
56
- }
54
+ [data-density="minimal"] [data-section="body"] {
55
+ display: none;
56
+ }
57
57
 
58
- /* Most runes put the title beside the body; card puts its leading heading
58
+ /* Most runes put the title beside the body; card puts its leading heading
59
59
  * *inside* the body slot. The blanket hide above would take the title with it
60
60
  * and leave an empty surface, so where a title is nested in the body, keep the
61
61
  * body box and hide only its other children — "title and primary metadata only"
62
62
  * then means what it says on those runes too. (SPEC-125 Phase 1: card only
63
63
  * gained a `body` role in v0.31.0, so this case could not arise before.) */
64
- [data-density="minimal"] [data-section="body"]:has(> [data-name="title"]) {
65
- display: revert;
66
- }
64
+ [data-density="minimal"] [data-section="body"]:has(> [data-name="title"]) {
65
+ display: revert;
66
+ }
67
67
 
68
- [data-density="minimal"] [data-section="body"]:has(> [data-name="title"]) > :not([data-name="title"]) {
69
- display: none;
70
- }
68
+ [data-density="minimal"]
69
+ [data-section="body"]:has(> [data-name="title"])
70
+ > :not([data-name="title"]) {
71
+ display: none;
72
+ }
71
73
 
72
- [data-density="minimal"] [data-section="footer"] {
73
- display: none;
74
- }
74
+ [data-density="minimal"] [data-section="footer"] {
75
+ display: none;
76
+ }
75
77
 
76
- [data-density="minimal"] [data-meta-rank="secondary"] {
77
- display: none;
78
- }
78
+ [data-density="minimal"] [data-meta-rank="secondary"] {
79
+ display: none;
80
+ }
79
81
 
80
- /* ─── Nested density scoping ─────────────────────────────────────── *
82
+ /* ─── Nested density scoping ─────────────────────────────────────── *
81
83
  * Density rules use descendant selectors that leak into nested runes
82
84
  * with their own density. These resets ensure each rune's density is
83
85
  * self-contained — e.g. a full-density recipe inside a compact-density
84
86
  * preview should not have its description truncated.
85
87
  * ──────────────────────────────────────────────────────────────────── */
86
88
 
87
- [data-density="compact"] [data-density="full"] [data-section="description"] {
88
- display: block;
89
- -webkit-line-clamp: unset;
90
- -webkit-box-orient: initial;
91
- overflow: visible;
92
- }
93
-
94
- [data-density="compact"] [data-density="full"] [data-meta-rank="secondary"],
95
- [data-density="minimal"] [data-density="full"] [data-section="description"],
96
- [data-density="minimal"] [data-density="full"] [data-section="body"],
97
- [data-density="minimal"] [data-density="full"] [data-section="footer"],
98
- [data-density="minimal"] [data-density="full"] [data-meta-rank="secondary"],
99
- [data-density="minimal"] [data-density="full"] [data-section="body"]:has(> [data-name="title"]) > :not([data-name="title"]) {
100
- display: revert;
101
- }
102
-
89
+ [data-density="compact"] [data-density="full"] [data-section="description"] {
90
+ display: block;
91
+ -webkit-line-clamp: unset;
92
+ -webkit-box-orient: initial;
93
+ overflow: visible;
94
+ }
95
+
96
+ [data-density="compact"] [data-density="full"] [data-meta-rank="secondary"],
97
+ [data-density="minimal"] [data-density="full"] [data-section="description"],
98
+ [data-density="minimal"] [data-density="full"] [data-section="body"],
99
+ [data-density="minimal"] [data-density="full"] [data-section="footer"],
100
+ [data-density="minimal"] [data-density="full"] [data-meta-rank="secondary"],
101
+ [data-density="minimal"]
102
+ [data-density="full"]
103
+ [data-section="body"]:has(> [data-name="title"])
104
+ > :not([data-name="title"]) {
105
+ display: revert;
106
+ }
103
107
  }
@@ -1,5 +1,5 @@
1
1
  @layer skin {
2
- /* Frame chrome (SPEC-086) — media-surface presentation.
2
+ /* Frame chrome (SPEC-086) — media-surface presentation.
3
3
  *
4
4
  * The engine lands the frame contract on the frame-target element: a rune's
5
5
  * `[data-section="media"]` zone (frameTarget: 'media') or the rune's own root
@@ -21,48 +21,66 @@
21
21
  * `frame-shadow` (drop-shadow silhouette here) never collide — different property,
22
22
  * different surface. */
23
23
 
24
- /* ── Silhouette drop-shadow ────────────────────────────────────────────
24
+ /* ── Silhouette drop-shadow ────────────────────────────────────────────
25
25
  * Same media-target / self-target split as `frame-displace`. Media-target
26
26
  * frames land `data-frame-shadow` on the media zone wrapper, but the shadow
27
27
  * should ride the inner guest so it (a) shifts with `frame-displace` and
28
28
  * (b) actually casts onto exposed slot interior when the guest is inset
29
29
  * from the wrapper. Self-target frames (showcase, figure) keep the filter
30
30
  * on the root itself, where it traces the rune's own painted silhouette. */
31
- [data-frame-shadow="none"] { --frame-shadow: none; }
32
- [data-frame-shadow="sm"] { --frame-shadow: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.10)); }
33
- [data-frame-shadow="md"] { --frame-shadow: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.25)); }
34
- [data-frame-shadow="lg"] { --frame-shadow: drop-shadow(0 12px 40px rgba(0, 0, 0, 0.20)); }
31
+ [data-frame-shadow="none"] {
32
+ --frame-shadow: none;
33
+ }
34
+ [data-frame-shadow="sm"] {
35
+ --frame-shadow: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.1));
36
+ }
37
+ [data-frame-shadow="md"] {
38
+ --frame-shadow: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.25));
39
+ }
40
+ [data-frame-shadow="lg"] {
41
+ --frame-shadow: drop-shadow(0 12px 40px rgba(0, 0, 0, 0.2));
42
+ }
35
43
 
36
- /* Self-target — figure, showcase, anything else where the rune root is the
44
+ /* Self-target — figure, showcase, anything else where the rune root is the
37
45
  * frame target. The filter applies to the painted root. */
38
- [data-frame-shadow]:not([data-section="media"]) {
39
- filter: var(--frame-shadow);
40
- }
46
+ [data-frame-shadow]:not([data-section="media"]) {
47
+ filter: var(--frame-shadow);
48
+ }
41
49
 
42
- /* Media-target — card, bento-cell, recipe, etc. The filter rides the inner
50
+ /* Media-target — card, bento-cell, recipe, etc. The filter rides the inner
43
51
  * guest so a displaced image's shadow falls into the slot's exposed
44
52
  * interior (clipped at the slot's `overflow: hidden` boundary like any
45
53
  * other in-slot content). */
46
- [data-section="media"][data-frame-shadow] > :is(img, video, .rf-placeholder, [data-rune]) {
47
- filter: var(--frame-shadow);
48
- }
54
+ [data-section="media"][data-frame-shadow] > :is(img, video, .rf-placeholder, [data-rune]) {
55
+ filter: var(--frame-shadow);
56
+ }
49
57
 
50
- /* ── Aspect ratio ────────────────────────────────────────────────────── */
51
- [style*="--frame-aspect"] { aspect-ratio: var(--frame-aspect); }
58
+ /* ── Aspect ratio ────────────────────────────────────────────────────── */
59
+ [style*="--frame-aspect"] {
60
+ aspect-ratio: var(--frame-aspect);
61
+ }
52
62
 
53
- /* ── Crop anchor — focal point when the guest is cut ─────────────────── */
54
- [style*="--frame-anchor"] > :is(img, video, .rf-placeholder) { object-position: var(--frame-anchor); }
63
+ /* ── Crop anchor — focal point when the guest is cut ─────────────────── */
64
+ [style*="--frame-anchor"] > :is(img, video, .rf-placeholder) {
65
+ object-position: var(--frame-anchor);
66
+ }
55
67
 
56
- /* ── Placement of the framed box within its slot ─────────────────────── */
57
- [style*="--frame-place-x"] { justify-self: var(--frame-place-x, auto); align-self: var(--frame-place-y, auto); }
68
+ /* ── Placement of the framed box within its slot ─────────────────────── */
69
+ [style*="--frame-place-x"] {
70
+ justify-self: var(--frame-place-x, auto);
71
+ align-self: var(--frame-place-y, auto);
72
+ }
58
73
 
59
- /* ── Oversize — guest exceeds its slot (clipping hosts crop it) ──────── */
60
- /* `[data-rune]` extends the rule to nested rune guests (codegroup, mockup,
74
+ /* ── Oversize — guest exceeds its slot (clipping hosts crop it) ──────── */
75
+ /* `[data-rune]` extends the rule to nested rune guests (codegroup, mockup,
61
76
  * chart, etc.), so frame chrome stays guest-agnostic — the same selector
62
77
  * pattern the displace rule uses. */
63
- [style*="--frame-oversize"] > :is(img, video, .rf-placeholder, [data-rune]) { width: calc(100% * var(--frame-oversize, 1)); max-width: none; }
78
+ [style*="--frame-oversize"] > :is(img, video, .rf-placeholder, [data-rune]) {
79
+ width: calc(100% * var(--frame-oversize, 1));
80
+ max-width: none;
81
+ }
64
82
 
65
- /* ── Displacement (peek / spill) — move the guest toward an edge/corner.
83
+ /* ── Displacement (peek / spill) — move the guest toward an edge/corner.
66
84
  * The host decides whether the guest is cropped or spills; this only moves it.
67
85
  * Two cases, distinguished by where the engine landed `data-displace`:
68
86
  *
@@ -80,45 +98,87 @@
80
98
  * We push the root with negative margins so it can spill past its
81
99
  * parent's edge for a true breakout. */
82
100
 
83
- /* — Case 1a: media-zone displace, default `peek` mode → translate the inner
101
+ /* — Case 1a: media-zone displace, default `peek` mode → translate the inner
84
102
  * guest inside the slot. The zone's `overflow: hidden` clips the displaced
85
103
  * guest into a peek (card / bento-cell behaviour). */
86
- [data-section="media"][data-displace]:not([data-displace-mode="bleed"]) > :is(img, video, .rf-placeholder, [data-rune]) {
87
- transform: translate(var(--displace-x, 0), var(--displace-y, 0));
88
- }
89
- [data-section="media"][data-displace="top"] { --displace-y: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
90
- [data-section="media"][data-displace="bottom"] { --displace-y: var(--frame-offset, var(--rf-spacing-lg)); }
91
- [data-section="media"][data-displace="end"] { --displace-x: var(--frame-offset, var(--rf-spacing-lg)); }
92
- [data-section="media"][data-displace="top-end"] { --displace-x: var(--frame-offset, var(--rf-spacing-lg)); --displace-y: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
93
- [data-section="media"][data-displace="bottom-end"] { --displace-x: var(--frame-offset, var(--rf-spacing-lg)); --displace-y: var(--frame-offset, var(--rf-spacing-lg)); }
104
+ [data-section="media"][data-displace]:not([data-displace-mode="bleed"])
105
+ > :is(img, video, .rf-placeholder, [data-rune]) {
106
+ transform: translate(var(--displace-x, 0), var(--displace-y, 0));
107
+ }
108
+ [data-section="media"][data-displace="top"] {
109
+ --displace-y: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
110
+ }
111
+ [data-section="media"][data-displace="bottom"] {
112
+ --displace-y: var(--frame-offset, var(--rf-spacing-lg));
113
+ }
114
+ [data-section="media"][data-displace="end"] {
115
+ --displace-x: var(--frame-offset, var(--rf-spacing-lg));
116
+ }
117
+ [data-section="media"][data-displace="top-end"] {
118
+ --displace-x: var(--frame-offset, var(--rf-spacing-lg));
119
+ --displace-y: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
120
+ }
121
+ [data-section="media"][data-displace="bottom-end"] {
122
+ --displace-x: var(--frame-offset, var(--rf-spacing-lg));
123
+ --displace-y: var(--frame-offset, var(--rf-spacing-lg));
124
+ }
94
125
 
95
- /* — Case 1b: media-zone displace, `bleed` mode → negative margin on the media
126
+ /* — Case 1b: media-zone displace, `bleed` mode → negative margin on the media
96
127
  * zone itself, so following layout pulls up and the guest extends past the
97
128
  * host's edge (hero / cta behaviour). The guest renders at its natural
98
129
  * position inside the zone — no translate, no artificial gap above. The
99
130
  * margin needs to exceed the host's padding for the guest to clear the host
100
131
  * edge; the offset scale tops out at `3xl` (6rem) for this. */
101
- [data-section="media"][data-displace="top"][data-displace-mode="bleed"] { margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
102
- [data-section="media"][data-displace="bottom"][data-displace-mode="bleed"] { margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
103
- [data-section="media"][data-displace="end"][data-displace-mode="bleed"] { margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
104
- [data-section="media"][data-displace="top-end"][data-displace-mode="bleed"] { margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
105
- [data-section="media"][data-displace="bottom-end"][data-displace-mode="bleed"] { margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
132
+ [data-section="media"][data-displace="top"][data-displace-mode="bleed"] {
133
+ margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
134
+ }
135
+ [data-section="media"][data-displace="bottom"][data-displace-mode="bleed"] {
136
+ margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
137
+ }
138
+ [data-section="media"][data-displace="end"][data-displace-mode="bleed"] {
139
+ margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
140
+ }
141
+ [data-section="media"][data-displace="top-end"][data-displace-mode="bleed"] {
142
+ margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
143
+ margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
144
+ }
145
+ [data-section="media"][data-displace="bottom-end"][data-displace-mode="bleed"] {
146
+ margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
147
+ margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
148
+ }
106
149
 
107
- /* — Case 2: self-target (showcase, etc.) → root moves; parent decides. */
108
- [data-displace]:not([data-section="media"]) { position: relative; z-index: 1; }
109
- [data-displace="top"]:not([data-section="media"]) { margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
110
- [data-displace="bottom"]:not([data-section="media"]) { margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
111
- [data-displace="both"]:not([data-section="media"]) { margin-block: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
112
- [data-displace="end"]:not([data-section="media"]) { margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
113
- [data-displace="bottom-end"]:not([data-section="media"]) { margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
114
- [data-displace="top-end"]:not([data-section="media"]) { margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg))); }
150
+ /* — Case 2: self-target (showcase, etc.) → root moves; parent decides. */
151
+ [data-displace]:not([data-section="media"]) {
152
+ position: relative;
153
+ z-index: 1;
154
+ }
155
+ [data-displace="top"]:not([data-section="media"]) {
156
+ margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
157
+ }
158
+ [data-displace="bottom"]:not([data-section="media"]) {
159
+ margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
160
+ }
161
+ [data-displace="both"]:not([data-section="media"]) {
162
+ margin-block: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
163
+ }
164
+ [data-displace="end"]:not([data-section="media"]) {
165
+ margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
166
+ }
167
+ [data-displace="bottom-end"]:not([data-section="media"]) {
168
+ margin-bottom: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
169
+ margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
170
+ }
171
+ [data-displace="top-end"]:not([data-section="media"]) {
172
+ margin-top: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
173
+ margin-inline-end: calc(-1 * var(--frame-offset, var(--rf-spacing-lg)));
174
+ }
115
175
 
116
- /* Displacement carries across breakpoints. For media-target the guest is
176
+ /* Displacement carries across breakpoints. For media-target the guest is
117
177
  * clipped by the zone's `overflow: hidden` regardless of width, so the peek
118
178
  * reads consistently from desktop to mobile. For self-target (showcase) the
119
179
  * negative-margin spill stays on too — that's the intended breakout. */
120
180
 
121
- /* ── frame-overflow="bleed" (SPEC-116) ───────────────────────────────────
181
+ /* ── frame-overflow="bleed" (SPEC-116) ───────────────────────────────────
122
182
  * The host declares the policy (`data-frame-overflow="bleed"` on its media
123
183
  * zone, a bleed host only); the guest signals the fact (`data-overflowing`,
124
184
  * set by its runtime when its content is wider than the frame). When both
@@ -131,13 +191,12 @@
131
191
  * at the content row). v1 is inline-end only; direction (via frame-anchor) and
132
192
  * side-by-side are deferred. The host media zone is already `overflow: visible`
133
193
  * for a rune guest (the bleed-host guestFit rule), so the guest can spill. */
134
- @media (max-width: 640px) {
135
- [data-frame-overflow="bleed"] > [data-overflowing] {
136
- width: calc(100% + var(--rf-bleed-room-end, var(--rf-content-gutter, 1.5rem)));
137
- max-width: none;
138
- border-start-end-radius: 0;
139
- border-end-end-radius: 0;
194
+ @media (max-width: 640px) {
195
+ [data-frame-overflow="bleed"] > [data-overflowing] {
196
+ width: calc(100% + var(--rf-bleed-room-end, var(--rf-content-gutter, 1.5rem)));
197
+ max-width: none;
198
+ border-start-end-radius: 0;
199
+ border-end-end-radius: 0;
200
+ }
140
201
  }
141
202
  }
142
-
143
- }