@refrakt-md/lumina 0.18.0 → 0.20.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 (58) hide show
  1. package/base.css +4 -0
  2. package/contracts/structures.json +509 -223
  3. package/dist/config.js +5 -5
  4. package/dist/config.js.map +1 -1
  5. package/dist/presets/tideline.d.ts.map +1 -1
  6. package/dist/presets/tideline.js +0 -14
  7. package/dist/presets/tideline.js.map +1 -1
  8. package/dist/tokens.d.ts.map +1 -1
  9. package/dist/tokens.js +20 -23
  10. package/dist/tokens.js.map +1 -1
  11. package/index.css +5 -1
  12. package/package.json +4 -4
  13. package/styles/base/attributes.css +9 -0
  14. package/styles/dimensions/cover.css +140 -0
  15. package/styles/dimensions/frame.css +54 -0
  16. package/styles/dimensions/guest-posture.css +27 -0
  17. package/styles/dimensions/media.css +1 -1
  18. package/styles/dimensions/sections.css +4 -1
  19. package/styles/dimensions/sequence.css +1 -1
  20. package/styles/dimensions/state.css +1 -1
  21. package/styles/dimensions/substrate.css +101 -0
  22. package/styles/dimensions/surfaces.css +35 -2
  23. package/styles/global.css +68 -0
  24. package/styles/layouts/on-this-page.css +2 -2
  25. package/styles/layouts/search.css +2 -2
  26. package/styles/layouts/split.css +134 -183
  27. package/styles/layouts/version-switcher.css +1 -1
  28. package/styles/runes/audio.css +1 -1
  29. package/styles/runes/bento.css +193 -99
  30. package/styles/runes/bg.css +24 -0
  31. package/styles/runes/bond.css +1 -1
  32. package/styles/runes/budget.css +1 -1
  33. package/styles/runes/card.css +77 -9
  34. package/styles/runes/character.css +1 -1
  35. package/styles/runes/chart.css +69 -0
  36. package/styles/runes/design-context.css +7 -5
  37. package/styles/runes/event.css +1 -1
  38. package/styles/runes/faction.css +32 -6
  39. package/styles/runes/feature.css +21 -16
  40. package/styles/runes/figure.css +1 -2
  41. package/styles/runes/gallery.css +39 -8
  42. package/styles/runes/itinerary.css +2 -2
  43. package/styles/runes/lore.css +1 -1
  44. package/styles/runes/palette.css +3 -3
  45. package/styles/runes/plan-progress.css +15 -62
  46. package/styles/runes/playlist.css +32 -0
  47. package/styles/runes/plot.css +2 -2
  48. package/styles/runes/realm.css +32 -6
  49. package/styles/runes/recipe.css +38 -2
  50. package/styles/runes/sandbox.css +1 -1
  51. package/styles/runes/showcase.css +7 -63
  52. package/styles/runes/spacing.css +4 -4
  53. package/styles/runes/swatch.css +1 -1
  54. package/styles/runes/tint.css +7 -7
  55. package/styles/runes/typography.css +7 -7
  56. package/styles/runes/xref.css +1 -1
  57. package/tokens/base.css +14 -14
  58. package/tokens/dark.css +18 -14
@@ -4,6 +4,17 @@
4
4
  * Padding scales with density via --rune-padding custom property.
5
5
  * ────────────────────────────────────────────────────────────────────── */
6
6
 
7
+ /* Semantic surface/media aliases over the raw token scale (these reference
8
+ * tokens rather than holding raw values, so they live in the styles layer, not
9
+ * the generated token contract). Outer surfaces round at --rf-radius-container;
10
+ * media guests nested inside them round at the smaller --rf-radius-media so they
11
+ * never read as more rounded than their container. SPEC-086's `frame` will
12
+ * override these per-instance. */
13
+ :root {
14
+ --rf-radius-container: var(--rf-radius-lg);
15
+ --rf-radius-media: var(--rf-radius-md);
16
+ }
17
+
7
18
  /* ─── Rune Spacing ────────────────────────────────────────────────── */
8
19
  /* Only top-level runes get vertical margin; child runes (nav items,
9
20
  accordion items, grid cells, etc.) inherit spacing from their parent.
@@ -42,7 +53,8 @@
42
53
  .rf-howto,
43
54
  .rf-form {
44
55
  background: var(--rf-color-surface);
45
- border-radius: var(--rf-radius-md);
56
+ border: 1px solid var(--rf-color-border);
57
+ border-radius: var(--rf-radius-container);
46
58
  padding: var(--rune-padding, var(--rf-spacing-md));
47
59
  }
48
60
 
@@ -86,6 +98,27 @@
86
98
  .rf-figure,
87
99
  .rf-reveal {
88
100
  background: var(--rf-color-surface);
89
- border-radius: var(--rf-radius-md);
101
+ border: 1px solid var(--rf-color-border);
102
+ border-radius: var(--rf-radius-container);
90
103
  padding: var(--rune-padding, var(--rf-spacing-md));
91
104
  }
105
+
106
+ /* ─── Inset surface (SPEC-087) — tint-tracking recessed fill ──────────
107
+ * Derived at use-site via relative-color (lower L, keep C+H) so it recomputes from a tinted
108
+ * `--rf-color-surface` automatically (a static inset-colour token would
109
+ * freeze to the untinted :root). Writes `background` only — never re-bases
110
+ * `--rf-color-surface` — so insets don't compound under nesting; depth is
111
+ * conveyed by border/elevation. `--rf-surface-inset-shift: 0` flushes it. */
112
+
113
+ /* chart / diagram self surface: the standalone "darker surface". */
114
+ .rf-chart,
115
+ .rf-diagram {
116
+ background: oklch(from var(--rf-color-surface) calc(l - var(--rf-surface-inset-shift)) c h);
117
+ }
118
+
119
+ /* Media wells of media-bearing runes: a recessed sub-surface that tracks the
120
+ * (possibly tinted) container colour — invisible under a full-bleed guest,
121
+ * visible in the gaps (transparent, displaced, or absent guest). */
122
+ :is(.rf-card, .rf-bento-cell, .rf-recipe, .rf-realm, .rf-faction, .rf-playlist) [data-section="media"] {
123
+ background: oklch(from var(--rf-color-surface) calc(l - var(--rf-surface-inset-shift)) c h);
124
+ }
package/styles/global.css CHANGED
@@ -28,6 +28,74 @@ body {
28
28
  margin: 0;
29
29
  }
30
30
 
31
+ /* ─── Theme-aware browser chrome (WORK-351) ─────────────────────────
32
+ * Make native chrome — scrollbars, form controls, the main viewport
33
+ * scrollbar — follow the active theme instead of always rendering in
34
+ * the OS default scheme. */
35
+
36
+ /* `color-scheme` so the UA paints native widgets/scrollbars per mode.
37
+ * Mirrors the dark-token selectors (explicit toggle + system preference
38
+ * unless forced light). */
39
+ :root { color-scheme: light; }
40
+ [data-theme="dark"] { color-scheme: dark; }
41
+ @media (prefers-color-scheme: dark) {
42
+ :root:not([data-theme="light"]) { color-scheme: dark; }
43
+ }
44
+
45
+ /* Branded text selection — a subtle primary wash (the native/OS blue
46
+ * otherwise leaks through). Background only, so the element's own text
47
+ * colour stays legible under any theme. */
48
+ ::selection {
49
+ background: color-mix(in oklch, var(--rf-color-primary) 22%, transparent);
50
+ }
51
+
52
+ /* Token-driven scrollbars. `scrollbar-color` / `scrollbar-width` inherit,
53
+ * so setting them on the root reaches every overflow container (codegroup,
54
+ * datatable, drawer body, nav/sidebar) without per-rune rules; the WebKit
55
+ * pseudo-elements refine the same colours. More specific per-container
56
+ * rules (e.g. the hidden docs sidebar) still win. */
57
+ :root {
58
+ scrollbar-width: thin;
59
+ scrollbar-color: var(--rf-color-muted) transparent;
60
+ }
61
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
62
+ ::-webkit-scrollbar-track { background: transparent; }
63
+ ::-webkit-scrollbar-thumb {
64
+ background: var(--rf-color-muted);
65
+ border-radius: var(--rf-radius-full);
66
+ border: 2px solid transparent;
67
+ background-clip: padding-box;
68
+ }
69
+ ::-webkit-scrollbar-thumb:hover { background: var(--rf-color-text); }
70
+
71
+ /* ─── Keyboard focus (WORK-352) ─────────────────────────────────────
72
+ * A uniform, token-driven focus ring on every interactive element, at
73
+ * zero specificity (`:where`) so a rune's own `:focus-visible` overrides
74
+ * it. An outline (not box-shadow) follows border-radius and never shifts
75
+ * layout; `outline-color` uses `primary`, so it is visible in both modes. */
76
+ :where(
77
+ a[href], button, input, select, textarea, summary,
78
+ [tabindex]:not([tabindex="-1"]),
79
+ [role="button"], [role="tab"], [role="link"], [role="menuitem"], [role="menuitemradio"]
80
+ ):focus-visible {
81
+ outline: 2px solid var(--rf-color-primary);
82
+ outline-offset: 2px;
83
+ }
84
+
85
+ /* ─── Reduced motion (WORK-352) ─────────────────────────────────────
86
+ * Honour the user's motion preference across every animated rune
87
+ * (chart, reveal, juxtapose, accordion, drawer, …) in one place. Uses a
88
+ * near-zero duration rather than `none` so animation/transition end
89
+ * events still fire for behaviours that await them. */
90
+ @media (prefers-reduced-motion: reduce) {
91
+ *, *::before, *::after {
92
+ animation-duration: 0.01ms !important;
93
+ animation-iteration-count: 1 !important;
94
+ transition-duration: 0.01ms !important;
95
+ scroll-behavior: auto !important;
96
+ }
97
+ }
98
+
31
99
  h1, h2, h3, h4, h5, h6 {
32
100
  line-height: 1.25;
33
101
  margin-top: 2em;
@@ -40,9 +40,9 @@
40
40
  }
41
41
 
42
42
  .rf-on-this-page__item[data-active] {
43
- border-left-color: var(--rf-color-primary, #0ea5e9);
43
+ border-left-color: var(--rf-color-primary);
44
44
  }
45
45
 
46
46
  .rf-on-this-page__item[data-active] a {
47
- color: var(--rf-color-primary, #0ea5e9);
47
+ color: var(--rf-color-primary);
48
48
  }
@@ -145,8 +145,8 @@
145
145
  color: var(--rf-color-muted);
146
146
  }
147
147
  .rf-search-result__excerpt mark {
148
- background: var(--rf-color-primary-100);
149
- color: var(--rf-color-primary-700);
148
+ background: var(--rf-color-primary-bg);
149
+ color: var(--rf-color-text);
150
150
  border-radius: 2px;
151
151
  padding: 0 1px;
152
152
  }
@@ -1,231 +1,182 @@
1
- /* Shared split layout — used by Feature, Hero, Step, and any rune with data-layout */
2
-
3
- [data-layout="split"],
4
- [data-layout="split-reverse"] {
1
+ /* Shared media + content layout — used by card, recipe, hero, feature, step,
2
+ * realm, faction, playlist (anything that uses the `splitLayoutAttributes`
3
+ * preset). Same vocabulary as `bento-cell`:
4
+ * • `data-media-position="top"` — media above content (default, block flow)
5
+ * • `data-media-position="bottom"` — media below content (flex column-reverse)
6
+ * • `data-media-position="start"` — media beside content on the left (grid)
7
+ * • `data-media-position="end"` — media beside content on the right (grid)
8
+ *
9
+ * Body source order is always media-first (the `splitMediaBodyFooter` helper
10
+ * splits `media --- content` zones in that order); the position dial decides
11
+ * the visual order independently. */
12
+
13
+ /* ─── Beside layouts (start / end): two-column grid ─────────────────── */
14
+
15
+ [data-media-position="start"],
16
+ [data-media-position="end"] {
5
17
  display: grid;
6
- grid-template-columns: var(--split-ratio, 1fr 1fr);
18
+ grid-template-columns: var(--media-share, 50%) 1fr;
7
19
  align-items: var(--split-valign, start);
8
- column-gap: var(--split-gap, var(--rf-spacing-lg));
9
20
  }
10
21
 
11
- [data-layout="split-reverse"] > [data-name="content"] { order: 2; }
12
- [data-layout="split-reverse"] > [data-name="media"] { order: 1; }
22
+ /* "end" flips the grid so media lands in column 2 (right) instead of column 1.
23
+ * Source order is media-first, so we re-place media via grid-column. */
24
+ [data-media-position="end"] {
25
+ grid-template-columns: 1fr var(--media-share, 50%);
26
+ }
27
+ [data-media-position="end"] > [data-section="media"],
28
+ [data-media-position="end"] > [data-name="media"] { grid-column: 2; }
29
+ [data-media-position="end"] > [data-name="content"] { grid-column: 1; }
30
+
31
+ /* `media-ratio` → media's share of the row width (shared across runes; mirrors
32
+ * the bento-cell scale at `bento.css:50-54`). When unset, columns fall back to
33
+ * the 50% default on `--media-share`. */
34
+ [data-media-ratio="1/3"] { --media-share: 33.333%; }
35
+ [data-media-ratio="2/5"] { --media-share: 40%; }
36
+ [data-media-ratio="1/2"] { --media-share: 50%; }
37
+ [data-media-ratio="3/5"] { --media-share: 60%; }
38
+ [data-media-ratio="2/3"] { --media-share: 66.667%; }
13
39
 
14
40
  /* Grid so children can use justify-self/align-self (default: stretch) */
15
- [data-layout="split"] > [data-name="media"],
16
- [data-layout="split-reverse"] > [data-name="media"],
17
- [data-layout="split"] > [data-section="media"],
18
- [data-layout="split-reverse"] > [data-section="media"] {
41
+ [data-media-position="start"] > [data-name="media"],
42
+ [data-media-position="end"] > [data-name="media"],
43
+ [data-media-position="start"] > [data-section="media"],
44
+ [data-media-position="end"] > [data-section="media"] {
19
45
  display: grid;
20
46
  }
21
47
 
22
- /* ─── 3-section grid placement (header + content + media) ─────────── */
23
- /* Only applies when a data-section="header" child exists (3-section runes).
24
- 2-section runes (e.g. Feature with display:contents on content) use the
25
- default 2-column flow from the base grid above. */
26
-
27
- /* Split: header + content in col 1, media in col 2 spanning both rows */
28
- [data-layout="split"]:has(> [data-section="header"]) > [data-section="header"] { grid-column: 1; grid-row: 1; }
29
- [data-layout="split"]:has(> [data-section="header"]) > [data-name="content"] { grid-column: 1; grid-row: 2; }
30
- [data-layout="split"]:has(> [data-section="header"]) > [data-section="media"] { grid-column: 2; grid-row: 1 / 3; }
31
-
32
- /* Split-reverse: media in col 1 spanning both rows, header + content in col 2 */
33
- [data-layout="split-reverse"]:has(> [data-section="header"]) > [data-section="header"] { grid-column: 2; grid-row: 1; }
34
- [data-layout="split-reverse"]:has(> [data-section="header"]) > [data-name="content"] { grid-column: 2; grid-row: 2; }
35
- [data-layout="split-reverse"]:has(> [data-section="header"]) > [data-section="media"] { grid-column: 1; grid-row: 1 / 3; }
36
-
37
- /* Reset margin on media zone in 3-section split layouts (header + content + media).
38
- 2-section runes (e.g. Hero) retain their per-rune media margins. */
39
- [data-layout="split"]:has(> [data-section="header"]) > [data-section="media"],
40
- [data-layout="split-reverse"]:has(> [data-section="header"]) > [data-section="media"] {
48
+ /* ─── Stacked layouts (top / bottom) ────────────────────────────────── */
49
+
50
+ /* "bottom" puts content on top, media at the bottom. Source order is media-
51
+ * first, so column-reverse flips it visually without changing the DOM. */
52
+ [data-media-position="bottom"] {
53
+ display: flex;
54
+ flex-direction: column-reverse;
55
+ }
56
+
57
+ /* ─── 3-section grid placement (header + content + media) ───────────── */
58
+ /* Applies only when a `data-section="header"` child exists. 2-section runes
59
+ * (e.g. hero, feature with the header rolled into content) fall through to
60
+ * the simple 2-column flow above. */
61
+
62
+ [data-media-position="start"]:has(> [data-section="header"]) > [data-section="header"] { grid-column: 1; grid-row: 1; }
63
+ [data-media-position="start"]:has(> [data-section="header"]) > [data-name="content"] { grid-column: 1; grid-row: 2; }
64
+ [data-media-position="start"]:has(> [data-section="header"]) > [data-section="media"] { grid-column: 2; grid-row: 1 / 3; }
65
+
66
+ [data-media-position="end"]:has(> [data-section="header"]) > [data-section="header"] { grid-column: 2; grid-row: 1; }
67
+ [data-media-position="end"]:has(> [data-section="header"]) > [data-name="content"] { grid-column: 2; grid-row: 2; }
68
+ [data-media-position="end"]:has(> [data-section="header"]) > [data-section="media"] { grid-column: 1; grid-row: 1 / 3; }
69
+
70
+ [data-media-position="start"]:has(> [data-section="header"]) > [data-section="media"],
71
+ [data-media-position="end"]:has(> [data-section="header"]) > [data-section="media"] {
41
72
  margin: 0;
42
73
  }
43
74
 
44
75
  /* ─── Media zone base styles ──────────────────────────────────────── */
45
76
 
46
- /* Clip overflow + round corners when the media zone holds media-like
47
- content: an image, or a self-contained block rune that wants the same
48
- "fills the slot" treatment (codegroup, snippet, chart, sandbox). Anything
49
- else keeps overflow visible so interactive bleed effects (preview,
50
- juxtapose) still work. Add new rune-typed media here when they should
51
- participate in the same chrome. */
52
- [data-section="media"]:where(
53
- :has(> img),
54
- :has(> [data-rune="code-group"]),
55
- :has(> [data-rune="snippet"]),
56
- :has(> [data-rune="chart"]),
57
- :has(> [data-rune="sandbox"])
58
- ) {
59
- border-radius: var(--rf-radius-lg);
77
+ /* Media-zone guest adaptation (WORK-339 / SPEC-084). Any visual guest fills the
78
+ * zone, and the zone clips, rounds, and becomes a container-query context — so
79
+ * a tall guest scales/clips instead of ballooning, and intrinsically responsive
80
+ * guests (e.g. `mockup`'s `cqi` auto-scale) resolve against the slot. Guests
81
+ * that manage their own bleed / interactive chrome (preview, juxtapose, an
82
+ * explicitly-bleeding showcase) self-declare an opt-out below so their
83
+ * displacement isn't clipped. */
84
+ [data-section="media"] {
85
+ container-type: inline-size;
60
86
  overflow: hidden;
87
+ /* Media guests use the smaller media radius tier so they never read as more
88
+ * rounded than the (larger-radius) container they sit in. */
89
+ border-radius: var(--rf-radius-media);
61
90
  }
62
- [data-section="media"] > :where(
63
- img,
64
- [data-rune="code-group"],
65
- [data-rune="snippet"],
66
- [data-rune="chart"],
67
- [data-rune="sandbox"]
68
- ) {
91
+ [data-section="media"] > * {
69
92
  display: block;
70
93
  width: 100%;
71
- height: auto;
72
- border-radius: var(--rf-radius-lg);
94
+ max-height: 100%;
95
+ border-radius: inherit;
73
96
  margin: 0;
74
97
  }
75
-
76
- /* Split layout images get subtle depth. Block runes carry their own visual
77
- weight (topbars, frames) so the shadow is image-only. */
78
- [data-layout="split"] > [data-section="media"] > img,
79
- [data-layout="split-reverse"] > [data-section="media"] > img {
80
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
98
+ [data-section="media"] > :is(img, video) {
99
+ height: 100%;
100
+ object-fit: cover;
81
101
  }
82
-
83
- /* ─── Collapse breakpoints ────────────────────────────────────────── */
84
-
85
- @media (max-width: 640px) {
86
- [data-layout^="split"][data-collapse="sm"] { grid-template-columns: 1fr; }
87
- [data-layout^="split"][data-collapse="sm"] > * { order: unset; }
102
+ [data-section="media"]:is(
103
+ :has(> [data-rune="preview"]),
104
+ :has(> [data-rune="juxtapose"]),
105
+ :has(> .rf-showcase[data-displace]:not(.rf-showcase--in-bento-cell))
106
+ ) {
107
+ overflow: visible;
108
+ container-type: normal;
109
+ border-radius: 0;
88
110
  }
89
-
90
- @media (max-width: 768px) {
91
- [data-layout^="split"][data-collapse="md"] { grid-template-columns: 1fr; }
92
- [data-layout^="split"][data-collapse="md"] > * { order: unset; }
111
+ /* …and let those self-managing guests keep their intrinsic width/sizing: the
112
+ * generic `width: 100%` above would defeat a negative-margin breakout (e.g.
113
+ * `preview--in-feature` bleeding to the viewport edge on mobile). */
114
+ [data-section="media"]:is(
115
+ :has(> [data-rune="preview"]),
116
+ :has(> [data-rune="juxtapose"]),
117
+ :has(> .rf-showcase[data-displace]:not(.rf-showcase--in-bento-cell))
118
+ ) > * {
119
+ width: auto;
120
+ max-height: none;
121
+ border-radius: 0;
93
122
  }
94
123
 
95
- @media (max-width: 1024px) {
96
- [data-layout^="split"][data-collapse="lg"] { grid-template-columns: 1fr; }
97
- [data-layout^="split"][data-collapse="lg"] > * { order: unset; }
124
+ /* (Figure-style media bleed used to live here — a negative-margin breakout that
125
+ * extended a top-position media banner past the container padding to leave a
126
+ * small consistent figure margin. Every consumer (card / recipe / realm /
127
+ * faction / playlist / bento-cell) moved to the thin-edge inset model — outer
128
+ * padding is a thin edge, the content zone fills the remaining inset — which
129
+ * achieves the same visual without the negative-margin hack. The rule was
130
+ * removed once it had no remaining matches; reintroduce a positive opt-in if
131
+ * a new rune ever wants figure bleed.) */
132
+
133
+ /* A code-group dropped into a media zone uses the media radius tier so its
134
+ * border and corners line up with the zone's clip instead of the larger
135
+ * container radius it gets as a standalone inset surface. */
136
+ [data-section="media"] .rf-codegroup {
137
+ border-radius: var(--rf-radius-media);
98
138
  }
99
139
 
100
- /* ─── Default mobile collapse for split layouts ───────────────────── */
101
- /* Runes without an explicit data-collapse always collapse at sm */
102
-
103
- @media (max-width: 640px) {
104
- [data-layout^="split"]:not([data-collapse]) {
105
- grid-template-columns: 1fr;
106
- }
107
- [data-layout^="split"]:not([data-collapse]) > [data-section="header"],
108
- [data-layout^="split"]:not([data-collapse]) > [data-name="content"],
109
- [data-layout^="split"]:not([data-collapse]) > [data-section="media"] {
110
- grid-column: auto;
111
- grid-row: auto;
112
- }
113
- [data-layout^="split"]:not([data-collapse]) > * {
114
- order: unset;
115
- }
116
-
117
- /* ─── Content-first collapse (data-media-position="top") ────────── */
118
- /* Media hoists above preamble as full-bleed card header */
119
- [data-media-position="top"] > [data-section="media"],
120
- [data-media-position="top"][data-layout="split"] > [data-section="media"],
121
- [data-media-position="top"][data-layout="split-reverse"] > [data-section="media"] {
122
- order: -1;
123
- grid-column: auto;
124
- grid-row: auto;
125
- width: calc(100% + 2 * var(--rune-padding, var(--rf-spacing-md)));
126
- margin: calc(-1 * var(--rune-padding, var(--rf-spacing-md)));
127
- margin-bottom: var(--rune-padding, var(--rf-spacing-md));
128
- border-radius: 0;
129
- }
130
- [data-media-position="top"] > [data-section="media"] > :where(
131
- img,
132
- [data-rune="code-group"],
133
- [data-rune="snippet"],
134
- [data-rune="chart"],
135
- [data-rune="sandbox"]
136
- ) {
137
- border-radius: var(--rf-radius-md) var(--rf-radius-md) 0 0;
138
- }
139
-
140
- /* Stacked layout also needs flex context for order to work */
141
- [data-media-position="top"][data-layout="stacked"] {
142
- display: flex;
143
- flex-direction: column;
144
- }
140
+ /* Beside-layout images get subtle depth. Block runes carry their own visual
141
+ * weight (topbars, frames) so the shadow is image-only. */
142
+ [data-media-position="start"] > [data-section="media"] > img,
143
+ [data-media-position="end"] > [data-section="media"] > img {
144
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
145
145
  }
146
146
 
147
- /* ─── Explicit collapse breakpoint + content-first ────────────────── */
147
+ /* ─── Collapse breakpoints ────────────────────────────────────────────
148
+ * Beside (start/end) layouts drop to a single column at the chosen breakpoint;
149
+ * the source-order (media-first) carries through, so media ends up on top of
150
+ * content after the collapse. `top` / `bottom` are already stacked and unchanged
151
+ * by collapse (their orientation is preserved). */
148
152
 
149
153
  @media (max-width: 640px) {
150
- [data-media-position="top"][data-collapse="sm"] > [data-section="media"],
151
- [data-media-position="top"][data-collapse="sm"][data-layout="split"] > [data-section="media"],
152
- [data-media-position="top"][data-collapse="sm"][data-layout="split-reverse"] > [data-section="media"] {
153
- order: -1;
154
- grid-column: auto;
155
- grid-row: auto;
156
- width: calc(100% + 2 * var(--rune-padding, var(--rf-spacing-md)));
157
- margin: calc(-1 * var(--rune-padding, var(--rf-spacing-md)));
158
- margin-bottom: var(--rune-padding, var(--rf-spacing-md));
159
- border-radius: 0;
160
- }
161
- [data-media-position="top"][data-collapse="sm"] > [data-section="media"] > :where(
162
- img,
163
- [data-rune="code-group"],
164
- [data-rune="snippet"],
165
- [data-rune="chart"],
166
- [data-rune="sandbox"]
167
- ) {
168
- border-radius: var(--rf-radius-md) var(--rf-radius-md) 0 0;
154
+ :is([data-media-position="start"], [data-media-position="end"])[data-collapse="sm"],
155
+ :is([data-media-position="start"], [data-media-position="end"]):not([data-collapse]) {
156
+ grid-template-columns: 1fr;
169
157
  }
170
- /* Reset grid placement on collapse for header/content */
171
- [data-media-position="top"][data-collapse="sm"] > [data-section="header"],
172
- [data-media-position="top"][data-collapse="sm"] > [data-name="content"] {
158
+ :is([data-media-position="start"], [data-media-position="end"])[data-collapse="sm"] > *,
159
+ :is([data-media-position="start"], [data-media-position="end"]):not([data-collapse]) > * {
173
160
  grid-column: auto;
174
161
  grid-row: auto;
175
162
  }
176
163
  }
177
164
 
178
165
  @media (max-width: 768px) {
179
- [data-media-position="top"][data-collapse="md"] > [data-section="media"],
180
- [data-media-position="top"][data-collapse="md"][data-layout="split"] > [data-section="media"],
181
- [data-media-position="top"][data-collapse="md"][data-layout="split-reverse"] > [data-section="media"] {
182
- order: -1;
183
- grid-column: auto;
184
- grid-row: auto;
185
- width: calc(100% + 2 * var(--rune-padding, var(--rf-spacing-md)));
186
- margin: calc(-1 * var(--rune-padding, var(--rf-spacing-md)));
187
- margin-bottom: var(--rune-padding, var(--rf-spacing-md));
188
- border-radius: 0;
189
- }
190
- [data-media-position="top"][data-collapse="md"] > [data-section="media"] > :where(
191
- img,
192
- [data-rune="code-group"],
193
- [data-rune="snippet"],
194
- [data-rune="chart"],
195
- [data-rune="sandbox"]
196
- ) {
197
- border-radius: var(--rf-radius-md) var(--rf-radius-md) 0 0;
166
+ :is([data-media-position="start"], [data-media-position="end"])[data-collapse="md"] {
167
+ grid-template-columns: 1fr;
198
168
  }
199
- [data-media-position="top"][data-collapse="md"] > [data-section="header"],
200
- [data-media-position="top"][data-collapse="md"] > [data-name="content"] {
169
+ :is([data-media-position="start"], [data-media-position="end"])[data-collapse="md"] > * {
201
170
  grid-column: auto;
202
171
  grid-row: auto;
203
172
  }
204
173
  }
205
174
 
206
175
  @media (max-width: 1024px) {
207
- [data-media-position="top"][data-collapse="lg"] > [data-section="media"],
208
- [data-media-position="top"][data-collapse="lg"][data-layout="split"] > [data-section="media"],
209
- [data-media-position="top"][data-collapse="lg"][data-layout="split-reverse"] > [data-section="media"] {
210
- order: -1;
211
- grid-column: auto;
212
- grid-row: auto;
213
- width: calc(100% + 2 * var(--rune-padding, var(--rf-spacing-md)));
214
- margin: calc(-1 * var(--rune-padding, var(--rf-spacing-md)));
215
- margin-bottom: var(--rune-padding, var(--rf-spacing-md));
216
- border-radius: 0;
217
- }
218
- [data-media-position="top"][data-collapse="lg"] > [data-section="media"] > :where(
219
- img,
220
- [data-rune="code-group"],
221
- [data-rune="snippet"],
222
- [data-rune="chart"],
223
- [data-rune="sandbox"]
224
- ) {
225
- border-radius: var(--rf-radius-md) var(--rf-radius-md) 0 0;
176
+ :is([data-media-position="start"], [data-media-position="end"])[data-collapse="lg"] {
177
+ grid-template-columns: 1fr;
226
178
  }
227
- [data-media-position="top"][data-collapse="lg"] > [data-section="header"],
228
- [data-media-position="top"][data-collapse="lg"] > [data-name="content"] {
179
+ :is([data-media-position="start"], [data-media-position="end"])[data-collapse="lg"] > * {
229
180
  grid-column: auto;
230
181
  grid-row: auto;
231
182
  }
@@ -41,5 +41,5 @@
41
41
  .rf-version-switcher__select:focus-visible {
42
42
  outline: none;
43
43
  border-color: var(--rf-color-primary);
44
- box-shadow: 0 0 0 2px var(--rf-color-primary-100, rgba(14, 165, 233, 0.15));
44
+ box-shadow: 0 0 0 2px var(--rf-color-primary-bg);
45
45
  }
@@ -58,7 +58,7 @@
58
58
  border-radius: var(--rf-radius-full, 9999px);
59
59
  border: none;
60
60
  background: var(--rf-color-primary);
61
- color: #fff;
61
+ color: var(--rf-color-on-primary);
62
62
  cursor: pointer;
63
63
  display: flex;
64
64
  align-items: center;