@refrakt-md/lumina 0.17.0 → 0.19.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 (53) hide show
  1. package/contracts/structures.json +479 -232
  2. package/dist/config.js +5 -5
  3. package/dist/config.js.map +1 -1
  4. package/dist/presets/tideline.d.ts.map +1 -1
  5. package/dist/presets/tideline.js +0 -14
  6. package/dist/presets/tideline.js.map +1 -1
  7. package/dist/tokens.d.ts.map +1 -1
  8. package/dist/tokens.js +14 -23
  9. package/dist/tokens.js.map +1 -1
  10. package/index.css +1 -1
  11. package/package.json +4 -4
  12. package/styles/dimensions/media.css +1 -1
  13. package/styles/dimensions/sections.css +4 -1
  14. package/styles/dimensions/sequence.css +1 -1
  15. package/styles/dimensions/state.css +1 -1
  16. package/styles/dimensions/surfaces.css +15 -2
  17. package/styles/global.css +68 -0
  18. package/styles/layouts/on-this-page.css +2 -2
  19. package/styles/layouts/search.css +2 -2
  20. package/styles/layouts/split.css +134 -183
  21. package/styles/layouts/version-switcher.css +1 -1
  22. package/styles/runes/audio.css +1 -1
  23. package/styles/runes/bento.css +186 -97
  24. package/styles/runes/bond.css +1 -1
  25. package/styles/runes/budget.css +1 -1
  26. package/styles/runes/card.css +64 -9
  27. package/styles/runes/character.css +1 -1
  28. package/styles/runes/chart.css +100 -0
  29. package/styles/runes/design-context.css +7 -5
  30. package/styles/runes/drawer.css +4 -0
  31. package/styles/runes/event.css +1 -1
  32. package/styles/runes/expand.css +5 -7
  33. package/styles/runes/faction.css +32 -6
  34. package/styles/runes/feature.css +21 -16
  35. package/styles/runes/figure.css +1 -2
  36. package/styles/runes/gallery.css +39 -8
  37. package/styles/runes/itinerary.css +2 -2
  38. package/styles/runes/lore.css +1 -1
  39. package/styles/runes/mockup.css +7 -7
  40. package/styles/runes/palette.css +3 -3
  41. package/styles/runes/plan-progress.css +15 -62
  42. package/styles/runes/playlist.css +32 -0
  43. package/styles/runes/plot.css +2 -2
  44. package/styles/runes/realm.css +32 -6
  45. package/styles/runes/recipe.css +38 -2
  46. package/styles/runes/sandbox.css +1 -1
  47. package/styles/runes/spacing.css +4 -4
  48. package/styles/runes/swatch.css +1 -1
  49. package/styles/runes/tint.css +7 -7
  50. package/styles/runes/typography.css +7 -7
  51. package/styles/runes/xref.css +1 -1
  52. package/tokens/base.css +10 -14
  53. package/tokens/dark.css +16 -14
@@ -1,126 +1,215 @@
1
- /* Bento */
2
- .rf-bento__preamble {
3
- flex-direction: column;
4
- align-items: flex-start;
5
- gap: 0.25rem;
6
- margin-bottom: 1.5rem;
7
- }
8
- .rf-bento__eyebrow {
9
- font-size: 0.8125rem;
10
- font-weight: 600;
11
- letter-spacing: 0.05em;
12
- text-transform: uppercase;
13
- color: var(--rf-color-primary);
14
- margin: 0 0 0.5rem;
15
- }
16
- .rf-bento__eyebrow:has(a) {
17
- display: inline-block;
18
- position: relative;
19
- padding: 0.25rem 0.875rem;
20
- border: 1px solid var(--rf-color-border);
21
- border-radius: var(--rf-radius-full);
22
- color: var(--rf-color-text);
23
- font-weight: 400;
24
- text-transform: none;
25
- letter-spacing: 0;
26
- transition: border-color 150ms ease;
27
- }
28
- .rf-bento__eyebrow:has(a):hover { border-color: var(--rf-color-muted); }
29
- .rf-bento__eyebrow:has(a) a { color: var(--rf-color-primary); font-weight: 600; text-decoration: none; }
30
- .rf-bento__eyebrow:has(a) a::before { content: ''; position: absolute; inset: 0; border-radius: inherit; }
31
- .rf-bento__headline {
32
- margin-top: 0;
33
- }
34
- .rf-bento__blurb {
35
- color: var(--rf-color-muted);
36
- margin-bottom: 0;
37
- }
38
- .rf-bento__image {
39
- margin-bottom: 1rem;
40
- }
1
+ /* Bento — a grid of cells, each a card-in-a-grid-track (SPEC-085). */
2
+
3
+ /* The grid responds to its OWN width, not the viewport, so it reduces columns and
4
+ * stacks correctly inside any container — a doc preview, a sidebar, a narrow page
5
+ * track — not just at viewport breakpoints. The progressive-reduction and collapse
6
+ * rules below are `@container rf-bento` queries against this. */
7
+ .rf-bento {
8
+ container-type: inline-size;
9
+ container-name: rf-bento;
10
+ }
11
+
41
12
  .rf-bento__grid {
13
+ /* Effective column count: the author's `--bento-columns` (default 6),
14
+ * progressively reduced at breakpoints below. A CSS variable (not the inline
15
+ * `--bento-columns`) so the media queries can override it without !important. */
16
+ --bento-cols-effective: var(--bento-columns, 6);
42
17
  display: grid;
43
- grid-template-columns: repeat(var(--bento-columns, 4), 1fr);
18
+ grid-template-columns: repeat(var(--bento-cols-effective), 1fr);
19
+ /* Uniform fixed row tracks in grid mode — never tied to column width (which
20
+ * would explode vertically on collapse). Makes row spans meaningful and bounds
21
+ * tall guests instead of letting them balloon a row. When the grid collapses to
22
+ * a single column it becomes a STACK, and `--bento-row-track` is flipped to
23
+ * `auto` (below) so cells size to their content and text is never clipped.
24
+ * `--bento-row-height` is the author's `row-height` preset (below); it falls
25
+ * back to the theme default `--rf-bento-row-height`. */
26
+ grid-auto-rows: var(--bento-row-track, var(--bento-row-height, var(--rf-bento-row-height, 24rem)));
44
27
  gap: var(--bento-gap, 1rem);
45
28
  }
29
+
30
+ /* Author row-height presets — the uniform grid row track height (grid mode). */
31
+ .rf-bento[data-row-height="sm"] { --bento-row-height: 16rem; }
32
+ .rf-bento[data-row-height="md"] { --bento-row-height: 24rem; }
33
+ .rf-bento[data-row-height="lg"] { --bento-row-height: 32rem; }
34
+ .rf-bento[data-row-height="xl"] { --bento-row-height: 40rem; }
35
+
36
+ /* content-height / media-ratio — perpendicular text-zone knobs, each settable as
37
+ * a GRID default (on .rf-bento, inherited by every cell) or a per-CELL override
38
+ * (on .rf-bento-cell, shadowing the inherited value). A cell is either a column
39
+ * cell or a beside cell, so the two never collide:
40
+ * • column cells (top/bottom/no-media): `content-height` pins the TEXT height;
41
+ * the media zone (flex:1) absorbs the rest of the row track.
42
+ * • beside cells (start/end): `media-ratio` pins the MEDIA width; the content
43
+ * (flex:1) absorbs the rest of the cell width.
44
+ * The cascade is pure CSS variable inheritance — grid sets the var on the
45
+ * section, a cell redeclares it on itself. */
46
+ .rf-bento[data-content-height="sm"], .rf-bento-cell[data-content-height="sm"] { --cell-content-height: 6rem; --cell-content-overflow: hidden; }
47
+ .rf-bento[data-content-height="md"], .rf-bento-cell[data-content-height="md"] { --cell-content-height: 10rem; --cell-content-overflow: hidden; }
48
+ .rf-bento[data-content-height="lg"], .rf-bento-cell[data-content-height="lg"] { --cell-content-height: 14rem; --cell-content-overflow: hidden; }
49
+ .rf-bento[data-content-height="xl"], .rf-bento-cell[data-content-height="xl"] { --cell-content-height: 18rem; --cell-content-overflow: hidden; }
50
+
51
+ .rf-bento[data-media-ratio="1/3"], .rf-bento-cell[data-media-ratio="1/3"] { --cell-media-ratio: 33.333%; }
52
+ .rf-bento[data-media-ratio="2/5"], .rf-bento-cell[data-media-ratio="2/5"] { --cell-media-ratio: 40%; }
53
+ .rf-bento[data-media-ratio="1/2"], .rf-bento-cell[data-media-ratio="1/2"] { --cell-media-ratio: 50%; }
54
+ .rf-bento[data-media-ratio="3/5"], .rf-bento-cell[data-media-ratio="3/5"] { --cell-media-ratio: 60%; }
55
+ .rf-bento[data-media-ratio="2/3"], .rf-bento-cell[data-media-ratio="2/3"] { --cell-media-ratio: 66.667%; }
56
+
57
+ /* content-height pins the text area on COLUMN cells only (top/bottom — and
58
+ * no-media cells, which default to `top`). Positive selectors keep this at the
59
+ * same specificity as the mobile/collapse stack resets below, so those still win
60
+ * and the text area returns to natural height when stacked. Both the basis and
61
+ * the clip are gated on content-height being set (via the cascading vars): when
62
+ * unset they fall back to `auto` / `visible`, so the content zone keeps its
63
+ * default overflow and a bleeding showcase (negative margins) still reaches the
64
+ * cell edge. The clip only engages when you actually pin the height. */
65
+ .rf-bento-cell[data-media-position="top"] > .rf-bento-cell__content,
66
+ .rf-bento-cell[data-media-position="bottom"] > .rf-bento-cell__content {
67
+ flex-basis: var(--cell-content-height, auto);
68
+ overflow: var(--cell-content-overflow, visible);
69
+ }
70
+
71
+ /* Cell footprint: resolved `cols`/`rows` spans, auto-capped to the effective
72
+ * column count so a wide cell never overflows the (reduced) grid. */
46
73
  .rf-bento-cell {
47
- border-radius: var(--rf-radius-md);
48
- padding: 1.25rem;
74
+ grid-column: span min(var(--cell-cols, 1), var(--bento-cols-effective, 6));
75
+ grid-row: span var(--cell-rows, 1);
76
+ min-width: 0;
77
+ }
78
+
79
+ /* The cell. `:where()` keeps the background at zero specificity so a per-cell
80
+ * `{% tint %}` can repaint it (tint-deferrable). */
81
+ :where(.rf-bento-cell) {
49
82
  background: var(--rf-color-surface);
50
- overflow: hidden;
51
- }
52
- .rf-bento-cell--full {
53
- grid-column: 1 / -1;
54
- }
55
- .rf-bento-cell--large {
56
- grid-column: span 2;
57
- grid-row: span 2;
58
- }
59
- .rf-bento-cell--medium {
60
- grid-column: span 2;
61
83
  }
62
- .rf-bento-cell--small {
63
- grid-column: span 1;
64
- }
65
- /* Span mode: heading level determines column span via CSS variable */
66
- .rf-bento-cell--span {
67
- grid-column: span var(--cell-span, 1);
68
- }
69
- .rf-bento-cell__icon {
70
- margin-bottom: 0.75rem;
71
- line-height: 0;
72
- }
73
- .rf-bento-cell__icon svg {
74
- width: 1.5rem;
75
- height: 1.5rem;
76
- color: var(--rf-color-accent);
84
+ .rf-bento-cell {
85
+ border-radius: var(--rf-radius-container);
86
+ /* The cell hugs its media with only a thin edge margin; the content zone adds
87
+ * the remaining inset back (below), so copy still sits at the full padding
88
+ * while media — and a media banner — sits close to the cell border.
89
+ * `--bento-media-gap` is the always-on space between media and content (in any
90
+ * `data-media-position`), regardless of cell size; the flex gap handles both
91
+ * column / column-reverse (top/bottom) and row / row-reverse (start/end). */
92
+ --container-padding: 1.25rem;
93
+ --bento-cell-edge: 0.5rem;
94
+ --bento-media-gap: var(--rf-spacing-md);
95
+ padding: var(--bento-cell-edge);
96
+ border: 1px solid var(--rf-color-border);
97
+ overflow: hidden;
98
+ position: relative; /* anchor for the stretched link */
99
+ display: flex;
100
+ flex-direction: column;
101
+ gap: var(--bento-media-gap);
102
+ min-height: 0;
103
+ }
104
+
105
+ /* Media placement (data-media-position): top stacks media above content (the
106
+ * default flow order); bottom flips it; start/end place media beside the body. */
107
+ .rf-bento-cell[data-media-position="bottom"] { flex-direction: column-reverse; }
108
+ .rf-bento-cell[data-media-position="start"] { flex-direction: row; align-items: stretch; }
109
+ .rf-bento-cell[data-media-position="end"] { flex-direction: row-reverse; align-items: stretch; }
110
+ .rf-bento-cell[data-media-position="start"] > .rf-bento-cell__media,
111
+ .rf-bento-cell[data-media-position="end"] > .rf-bento-cell__media {
112
+ flex: 0 0 var(--cell-media-ratio, 42%);
113
+ margin-bottom: 0;
114
+ align-self: stretch;
115
+ }
116
+ .rf-bento-cell[data-media-position="start"] > .rf-bento-cell__content,
117
+ .rf-bento-cell[data-media-position="end"] > .rf-bento-cell__content { flex: 1; min-width: 0; }
118
+
119
+ /* Zones. The WORK-339 media-zone selector sizes/clips any guest — no per-guest
120
+ * CSS here. */
121
+ .rf-bento-cell__media { margin-bottom: 0; }
122
+ /* Text-first: the body keeps its natural height and is never squeezed out by
123
+ * media. In a fixed grid track the media zone (top/bottom) absorbs the leftover
124
+ * height and crops (cover); once collapsed to a stack it becomes an aspect-ratio
125
+ * banner (below). `--bento-media-aspect` is the knob SPEC-086's `frame-aspect`
126
+ * will drive; `--bento-media-anchor` picks which slice shows when cropped (e.g.
127
+ * `top` to peek the top of a tall guest like a phone mockup). */
128
+ /* Add the remaining inset back on top of the cell's thin edge, so text sits at
129
+ * the full `--container-padding`. The top inset doubles as the gap between media
130
+ * and copy (media zone carries no margin of its own). */
131
+ .rf-bento-cell__content {
132
+ display: flex;
133
+ flex-direction: column;
134
+ min-height: 0;
135
+ flex: 0 0 auto;
136
+ padding: calc(var(--container-padding) - var(--bento-cell-edge));
77
137
  }
78
- .rf-bento-cell__icon .rf-icon {
79
- width: 1.5rem;
80
- height: 1.5rem;
81
- color: var(--rf-color-accent);
138
+ .rf-bento-cell[data-media-position="top"] > .rf-bento-cell__media,
139
+ .rf-bento-cell[data-media-position="bottom"] > .rf-bento-cell__media {
140
+ flex: 1 1 auto;
141
+ min-height: 0;
142
+ aspect-ratio: var(--bento-media-aspect, 16 / 9);
82
143
  }
144
+ .rf-bento-cell__media > :is(img, video) { object-position: var(--bento-media-anchor, center); }
83
145
  .rf-bento-cell__title {
84
146
  font-size: 1.125rem;
85
147
  font-weight: 600;
86
148
  margin: 0 0 0.5rem;
87
149
  }
150
+ .rf-bento-cell__body { min-height: 0; }
88
151
  .rf-bento-cell__body > span[property],
89
152
  .rf-bento-cell__body > meta { display: none; }
153
+ .rf-bento-cell__body > :first-child { margin-top: 0; }
90
154
  .rf-bento-cell__body p:last-child { margin-bottom: 0; }
91
- .rf-bento-cell img {
155
+ .rf-bento-cell__footer {
156
+ margin-top: auto;
157
+ padding-top: 0.5rem;
158
+ border-top: 1px solid var(--rf-color-border);
159
+ font-size: 0.8125em;
160
+ color: var(--rf-color-muted);
161
+ }
162
+ .rf-bento-cell__body img {
92
163
  width: 100%;
93
164
  height: auto;
94
165
  border-radius: var(--rf-radius-sm);
95
166
  margin-bottom: 0.75rem;
96
167
  }
97
- /* Remove padding on bleed edges when showcase bleeds inside a cell */
168
+
169
+ /* Whole-cell link — stretched overlay; nested links stay clickable. */
170
+ .rf-bento-cell__link { position: absolute; inset: 0; z-index: 0; }
171
+ .rf-bento-cell a:not(.rf-bento-cell__link) { position: relative; z-index: 1; }
172
+
173
+ /* Drop padding on bleed edges when a showcase bleeds (clipped to a peek by
174
+ * the media-zone + cell overflow). */
98
175
  .rf-bento-cell:has(.rf-showcase[data-bleed="bottom"]),
99
176
  .rf-bento-cell:has(.rf-showcase[data-bleed="both"]),
100
- .rf-bento-cell:has(.rf-showcase[data-bleed="bottom-end"]) {
101
- padding-bottom: 0;
102
- }
177
+ .rf-bento-cell:has(.rf-showcase[data-bleed="bottom-end"]) { padding-bottom: 0; }
103
178
  .rf-bento-cell:has(.rf-showcase[data-bleed="end"]),
104
179
  .rf-bento-cell:has(.rf-showcase[data-bleed="bottom-end"]),
105
- .rf-bento-cell:has(.rf-showcase[data-bleed="top-end"]) {
106
- padding-inline-end: 0;
107
- }
180
+ .rf-bento-cell:has(.rf-showcase[data-bleed="top-end"]) { padding-inline-end: 0; }
108
181
  .rf-bento-cell:has(.rf-showcase[data-bleed="top"]),
109
182
  .rf-bento-cell:has(.rf-showcase[data-bleed="both"]),
110
- .rf-bento-cell:has(.rf-showcase[data-bleed="top-end"]) {
111
- padding-top: 0;
112
- }
113
- @media (max-width: 768px) {
114
- .rf-bento__grid { grid-template-columns: repeat(2, 1fr) !important; }
115
- .rf-bento-cell--full { grid-column: 1 / -1; }
116
- .rf-bento-cell--large { grid-column: span 2; grid-row: span 1; }
117
- .rf-bento-cell--medium { grid-column: span 2; }
118
- .rf-bento-cell--span { grid-column: span min(var(--cell-span, 1), 2); }
119
- }
120
- @media (max-width: 480px) {
121
- .rf-bento__grid { grid-template-columns: 1fr !important; }
122
- .rf-bento-cell--full,
123
- .rf-bento-cell--large,
124
- .rf-bento-cell--medium,
125
- .rf-bento-cell--span { grid-column: span 1; }
183
+ .rf-bento-cell:has(.rf-showcase[data-bleed="top-end"]) { padding-top: 0; }
184
+
185
+ /* ─── Binary collapse ─── Above the chosen breakpoint, the grid renders exactly
186
+ * as authored: `columns` and per-cell `cols`/`rows` are honored (span auto-cap
187
+ * above still keeps wide cells inside the grid). Below it, the grid drops to a
188
+ * single stacked column with auto row tracks (cells size to content, never
189
+ * clipped). Default = `sm` (640px), so phones stack but tablet-portrait and up
190
+ * keep the grid. Authors who want graceful column reduction between desktop and
191
+ * stack can opt in via `collapse="gradual"` (not yet implemented). */
192
+ @container rf-bento (max-width: 640px) {
193
+ /* `[data-collapse=""]` matches the unset (default) case — the engine emits
194
+ * the attribute with an empty value when the author doesn't set it, so a bare
195
+ * `:not([data-collapse])` wouldn't match. */
196
+ :is(.rf-bento[data-collapse="sm"], .rf-bento[data-collapse=""]) .rf-bento__grid { --bento-cols-effective: 1; --bento-row-track: auto; }
197
+ :is(.rf-bento[data-collapse="sm"], .rf-bento[data-collapse=""]) .rf-bento-cell[data-media-position="start"],
198
+ :is(.rf-bento[data-collapse="sm"], .rf-bento[data-collapse=""]) .rf-bento-cell[data-media-position="end"] { flex-direction: column; }
199
+ :is(.rf-bento[data-collapse="sm"], .rf-bento[data-collapse=""]) .rf-bento-cell[data-media-position] > .rf-bento-cell__media { flex: 0 0 auto; aspect-ratio: var(--bento-media-aspect, 16 / 9); }
200
+ :is(.rf-bento[data-collapse="sm"], .rf-bento[data-collapse=""]) .rf-bento-cell[data-media-position] > .rf-bento-cell__content { flex: 0 0 auto; }
201
+ }
202
+ @container rf-bento (max-width: 768px) {
203
+ .rf-bento[data-collapse="md"] .rf-bento__grid { --bento-cols-effective: 1; --bento-row-track: auto; }
204
+ .rf-bento[data-collapse="md"] .rf-bento-cell[data-media-position="start"],
205
+ .rf-bento[data-collapse="md"] .rf-bento-cell[data-media-position="end"] { flex-direction: column; }
206
+ .rf-bento[data-collapse="md"] .rf-bento-cell[data-media-position] > .rf-bento-cell__media { flex: 0 0 auto; aspect-ratio: var(--bento-media-aspect, 16 / 9); }
207
+ .rf-bento[data-collapse="md"] .rf-bento-cell[data-media-position] > .rf-bento-cell__content { flex: 0 0 auto; }
208
+ }
209
+ @container rf-bento (max-width: 1024px) {
210
+ .rf-bento[data-collapse="lg"] .rf-bento__grid { --bento-cols-effective: 1; --bento-row-track: auto; }
211
+ .rf-bento[data-collapse="lg"] .rf-bento-cell[data-media-position="start"],
212
+ .rf-bento[data-collapse="lg"] .rf-bento-cell[data-media-position="end"] { flex-direction: column; }
213
+ .rf-bento[data-collapse="lg"] .rf-bento-cell[data-media-position] > .rf-bento-cell__media { flex: 0 0 auto; aspect-ratio: var(--bento-media-aspect, 16 / 9); }
214
+ .rf-bento[data-collapse="lg"] .rf-bento-cell[data-media-position] > .rf-bento-cell__content { flex: 0 0 auto; }
126
215
  }
@@ -9,7 +9,7 @@
9
9
  .rf-bond > span[property="to"] {
10
10
  font-weight: 600;
11
11
  font-size: 1rem;
12
- color: var(--rf-color-heading);
12
+ color: var(--rf-color-text);
13
13
  white-space: nowrap;
14
14
  }
15
15
  .rf-bond__connector {
@@ -86,7 +86,7 @@
86
86
  gap: 1rem;
87
87
  }
88
88
  .rf-budget-line-item + .rf-budget-line-item {
89
- border-top: 1px solid var(--rf-color-border-light, var(--rf-color-border));
89
+ border-top: 1px solid var(--rf-color-border);
90
90
  }
91
91
  .rf-budget-line-item__description {
92
92
  font-size: 0.875rem;
@@ -1,20 +1,75 @@
1
1
  /* card — generic content card (SPEC-070).
2
2
  *
3
- * Borderless (no outline) but with a soft surface fill — the surface + the
4
- * structure (media split, footer separator) carry the card, no border needed.
5
- * The media|content split + responsive collapse + mobile full-bleed media
6
- * header come from the shared layouts/split.css (keyed off data-layout /
7
- * data-section="media" / data-name="content" / data-media-position).
3
+ * A soft surface fill plus a subtle border; the surface + border + structure
4
+ * (media split, footer separator) carry the card. The media|content split +
5
+ * responsive collapse + inset media banner come from the shared
6
+ * layouts/split.css (keyed off data-layout / data-section="media" /
7
+ * data-name="content" / data-media-position).
8
8
  */
9
9
 
10
10
  .rf-card {
11
- position: relative; /* anchor for the stretched link + media bleed */
12
- padding: var(--rune-padding, var(--rf-spacing-md));
13
- border-radius: var(--rf-radius-md);
11
+ position: relative; /* anchor for the stretched link */
12
+ /* Thin-edge inset model (matches `bento-cell`): the card hugs its media with
13
+ * only a small edge margin, and the content zone adds the remaining inset
14
+ * back below — so text still sits at the full `--rune-padding` while media
15
+ * sits close to the card border, with no negative-margin bleed.
16
+ * • `--rf-card-edge` — fixed thin outer padding.
17
+ * • `--rf-card-media-gap` — generous gap when media stacks above/below
18
+ * content (stacked layout, or split collapsed
19
+ * to one column on mobile).
20
+ * • `--rf-card-split-gap` — tight gap when media sits beside content,
21
+ * so the card reads as a unified surface
22
+ * rather than two loosely-related panels. */
23
+ --rf-card-edge: 0.5rem;
24
+ --rf-card-media-gap: var(--rf-spacing-lg);
25
+ --rf-card-split-gap: var(--rf-spacing-sm);
26
+ padding: var(--rf-card-edge);
27
+ border-radius: var(--rf-radius-container);
28
+ border: 1px solid var(--rf-color-border);
14
29
  background: var(--rf-color-surface);
15
30
  }
16
31
 
17
- .rf-card:hover {
32
+ /* Content zone fills the remaining inset so total text padding still equals
33
+ * `--rune-padding`. `max(0, …)` keeps dense+ (rune-padding < edge) safe — at
34
+ * tighter densities the edge becomes the floor. */
35
+ .rf-card__content {
36
+ padding: max(0px, calc(var(--rune-padding, var(--rf-spacing-md)) - var(--rf-card-edge)));
37
+ }
38
+
39
+ /* Split layout: tight column-gap when media sits beside content. `row-gap`
40
+ * stays at the larger media-gap so the collapse step (split grid → single
41
+ * column) automatically picks up the generous stacked spacing instead of
42
+ * inheriting the tight beside-gap. (2-section cards have only one grid row in
43
+ * the uncollapsed state, so row-gap is dormant until collapse.) `stretch`
44
+ * lets the media zone match the content's height — the image inside uses
45
+ * `object-fit: cover` so it re-crops rather than letterboxing when content
46
+ * is taller than the media's natural aspect ratio.
47
+ *
48
+ * We set `column-gap` / `row-gap` / `align-items` directly rather than override
49
+ * `--split-gap` / `--split-valign`: the shared split layout transform emits
50
+ * those vars as inline styles even when the author doesn't set the matching
51
+ * attributes, so a CSS-variable override loses to inline-style specificity.
52
+ * Direct grid properties win via selector specificity and stay in our control. */
53
+ .rf-card[data-media-position="start"],
54
+ .rf-card[data-media-position="end"] {
55
+ column-gap: var(--rf-card-split-gap);
56
+ row-gap: var(--rf-card-media-gap);
57
+ align-items: stretch;
58
+ }
59
+
60
+ /* Stacked layouts (top / bottom): explicit gap between the media banner and
61
+ * the content zone (replaces the figure-style margin-bottom from the bleed
62
+ * model). For `bottom`, column-reverse already flips the visual order, so the
63
+ * gap still reads as the space between the two zones. */
64
+ .rf-card[data-media-position="top"] > [data-section="media"] {
65
+ margin-bottom: var(--rf-card-media-gap);
66
+ }
67
+ .rf-card[data-media-position="bottom"] > [data-section="media"] {
68
+ margin-top: var(--rf-card-media-gap);
69
+ }
70
+
71
+ /* Only a linked card (stretched `.rf-card__link` present) reacts to hover. */
72
+ .rf-card:has(.rf-card__link):hover {
18
73
  background: var(--rf-color-surface-hover);
19
74
  }
20
75
 
@@ -46,7 +46,7 @@
46
46
  font-weight: 700;
47
47
  line-height: 1.3;
48
48
  margin-bottom: 0.5rem;
49
- color: var(--rf-color-heading);
49
+ color: var(--rf-color-text);
50
50
  }
51
51
  .rf-character-section__body ul,
52
52
  .rf-character-section__body ol {
@@ -1,9 +1,103 @@
1
1
  /* Chart */
2
2
  .rf-chart {
3
+ /* ─── Theming contract (SPEC-083 / WORK-353) ───
4
+ * Every paint + geometry value the renderer uses is one of these
5
+ * `--rf-chart-*` properties; a theme overrides any of them (here, on a tint
6
+ * scope, or inline) with no renderer or selector changes. A non-CSS provider
7
+ * (canvas) reads the same props via getComputedStyle. */
8
+
9
+ /* Series palette — a dedicated categorical palette, deliberately distinct
10
+ * from the semantic status tokens (those are reserved for sentiment mode). */
11
+ --rf-chart-series-1: #6366f1;
12
+ --rf-chart-series-2: #06b6d4;
13
+ --rf-chart-series-3: #22c55e;
14
+ --rf-chart-series-4: #f59e0b;
15
+ --rf-chart-series-5: #a855f7;
16
+ --rf-chart-series-6: #ec4899;
17
+
18
+ /* Geometry (layout values the renderer reads via getComputedStyle). */
19
+ --rf-chart-bar-ratio: 0.75; /* bar width as a fraction of its slot */
20
+ --rf-chart-bar-thickness: 48px; /* max bar width cap */
21
+ --rf-chart-bar-gap: 0.15; /* slot fraction reserved as inter-group gap */
22
+ --rf-chart-bar-radius: 2px;
23
+ --rf-chart-point-radius: 4px;
24
+ --rf-chart-line-width: 2px;
25
+
26
+ /* Typography / grid (paint values applied by CSS below). */
27
+ --rf-chart-label-size: 12px;
28
+ --rf-chart-label-color: var(--rf-color-muted);
29
+ --rf-chart-grid-color: var(--rf-color-border);
30
+ --rf-chart-grid-width: 1px;
31
+
32
+ display: block;
3
33
  border-radius: var(--rf-radius-lg);
4
34
  padding: 1.5rem;
5
35
  margin: 0;
6
36
  }
37
+
38
+ /* ─── SVG paint — driven entirely by the contract (the renderer sets no inline
39
+ * fill/stroke; it only tags elements with a class + data-series + optional
40
+ * data-meta-sentiment). ─── */
41
+ .rf-chart__axis { stroke: var(--rf-chart-grid-color); stroke-width: var(--rf-chart-grid-width); }
42
+ .rf-chart__label { fill: var(--rf-chart-label-color); font-size: var(--rf-chart-label-size); }
43
+ .rf-chart__bar { rx: var(--rf-chart-bar-radius); fill: var(--rf-chart-series-1); }
44
+ .rf-chart__point { fill: var(--rf-chart-series-1); }
45
+ .rf-chart__line { fill: none; stroke: var(--rf-chart-series-1); stroke-width: var(--rf-chart-line-width); }
46
+
47
+ /* Series palette rotation (data-series 0 is the default above). */
48
+ .rf-chart__bar[data-series="1"], .rf-chart__point[data-series="1"] { fill: var(--rf-chart-series-2); }
49
+ .rf-chart__bar[data-series="2"], .rf-chart__point[data-series="2"] { fill: var(--rf-chart-series-3); }
50
+ .rf-chart__bar[data-series="3"], .rf-chart__point[data-series="3"] { fill: var(--rf-chart-series-4); }
51
+ .rf-chart__bar[data-series="4"], .rf-chart__point[data-series="4"] { fill: var(--rf-chart-series-5); }
52
+ .rf-chart__bar[data-series="5"], .rf-chart__point[data-series="5"] { fill: var(--rf-chart-series-6); }
53
+ .rf-chart__line[data-series="1"] { stroke: var(--rf-chart-series-2); }
54
+ .rf-chart__line[data-series="2"] { stroke: var(--rf-chart-series-3); }
55
+ .rf-chart__line[data-series="3"] { stroke: var(--rf-chart-series-4); }
56
+ .rf-chart__line[data-series="4"] { stroke: var(--rf-chart-series-5); }
57
+ .rf-chart__line[data-series="5"] { stroke: var(--rf-chart-series-6); }
58
+
59
+ /* Sentiment colouring — a data cell carrying `data-meta-sentiment` overrides the
60
+ * rotating palette with the semantic token (the roadmap chart renders green-done
61
+ * / red-blocked with no per-chart config). After the palette so it wins at equal
62
+ * specificity. */
63
+ .rf-chart__bar[data-meta-sentiment="positive"], .rf-chart__point[data-meta-sentiment="positive"] { fill: var(--rf-color-success); }
64
+ .rf-chart__bar[data-meta-sentiment="negative"], .rf-chart__point[data-meta-sentiment="negative"] { fill: var(--rf-color-danger); }
65
+ .rf-chart__bar[data-meta-sentiment="caution"], .rf-chart__point[data-meta-sentiment="caution"] { fill: var(--rf-color-warning); }
66
+ .rf-chart__bar[data-meta-sentiment="neutral"], .rf-chart__point[data-meta-sentiment="neutral"] { fill: var(--rf-color-muted); }
67
+ .rf-chart__line[data-meta-sentiment="positive"] { stroke: var(--rf-color-success); }
68
+ .rf-chart__line[data-meta-sentiment="negative"] { stroke: var(--rf-color-danger); }
69
+ .rf-chart__line[data-meta-sentiment="caution"] { stroke: var(--rf-color-warning); }
70
+ .rf-chart__line[data-meta-sentiment="neutral"] { stroke: var(--rf-color-muted); }
71
+
72
+ /* Data table — the no-JS / screen-reader source of truth. Visible as the
73
+ * fallback; visually-hidden (but kept for assistive tech) once rf-chart has
74
+ * rendered the svg. */
75
+ .rf-chart__data {
76
+ width: 100%;
77
+ border-collapse: collapse;
78
+ font-size: 0.875rem;
79
+ }
80
+ .rf-chart__data caption {
81
+ font-weight: 600;
82
+ margin-bottom: 0.75rem;
83
+ }
84
+ .rf-chart__data th,
85
+ .rf-chart__data td {
86
+ border: 1px solid var(--rf-color-border);
87
+ padding: 0.375rem 0.625rem;
88
+ text-align: left;
89
+ }
90
+ .rf-chart[data-rendered] .rf-chart__data {
91
+ position: absolute;
92
+ width: 1px;
93
+ height: 1px;
94
+ padding: 0;
95
+ margin: -1px;
96
+ overflow: hidden;
97
+ clip: rect(0, 0, 0, 0);
98
+ white-space: nowrap;
99
+ border: 0;
100
+ }
7
101
  .rf-chart__title {
8
102
  font-weight: 600;
9
103
  font-size: 1rem;
@@ -29,4 +123,10 @@
29
123
  width: 12px;
30
124
  height: 12px;
31
125
  border-radius: 2px;
126
+ background: var(--rf-chart-series-1);
32
127
  }
128
+ .rf-chart__legend-color[data-series="1"] { background: var(--rf-chart-series-2); }
129
+ .rf-chart__legend-color[data-series="2"] { background: var(--rf-chart-series-3); }
130
+ .rf-chart__legend-color[data-series="3"] { background: var(--rf-chart-series-4); }
131
+ .rf-chart__legend-color[data-series="4"] { background: var(--rf-chart-series-5); }
132
+ .rf-chart__legend-color[data-series="5"] { background: var(--rf-chart-series-6); }
@@ -10,12 +10,14 @@
10
10
  gap: 1.5rem;
11
11
  }
12
12
 
13
- /* Children inside design-context: remove card styling */
14
- .rf-palette--in-design-context,
15
- .rf-typography--in-design-context,
16
- .rf-spacing--in-design-context,
17
- .rf-swatch--in-design-context {
13
+ /* Children inside design-context: remove card styling so the outer
14
+ * design-context surface is the only visible frame. Structural selector
15
+ * (`> [data-rune]` inside the sections wrapper) catches every direct rune
16
+ * child, not just the four design-system runes — so dropping a future rune
17
+ * into a design-context will Just Work without a per-rune CSS update. */
18
+ .rf-design-context__sections > [data-rune] {
18
19
  background: none;
20
+ border: none;
19
21
  border-radius: 0;
20
22
  padding: 0;
21
23
  }
@@ -124,6 +124,10 @@ dialog.rf-drawer .rf-drawer__header {
124
124
  align-items: center;
125
125
  justify-content: space-between;
126
126
  gap: var(--rf-spacing-md);
127
+ /* Reset the generous `[data-section="header"]` bottom margin (3rem) — the
128
+ * dialog chrome spaces the header from the body via the body's own
129
+ * padding, so the section default would push the body off-screen. */
130
+ margin: 0;
127
131
  padding: var(--rf-spacing-md) var(--rf-spacing-lg);
128
132
  border-bottom: 1px solid var(--rf-drawer-border);
129
133
  background: var(--rf-drawer-bg);
@@ -14,7 +14,7 @@
14
14
  display: inline-block;
15
15
  padding: 0.5rem 1rem;
16
16
  background: var(--rf-color-primary);
17
- color: #fff;
17
+ color: var(--rf-color-on-primary);
18
18
  border-radius: var(--rf-radius-md);
19
19
  font-weight: 500;
20
20
  font-size: 0.875rem;
@@ -23,15 +23,13 @@
23
23
  margin: var(--rf-spacing-md) 0;
24
24
  }
25
25
 
26
- /* Peer-document mode — subtle top + bottom rule, no background, no
27
- * rounded corners. Reads as "set-aside material" without competing
28
- * visually with the host content. Authors who want a stronger
29
- * treatment can override `.rf-expand[data-outline-scope]` in their
30
- * theme. */
26
+ /* Peer-document mode — no background, no rounded corners, no rules.
27
+ * Reads as "set-aside material" through spacing alone, without competing
28
+ * visually with the host content. Authors who want a stronger treatment
29
+ * (rules, background, etc.) can override `.rf-expand[data-outline-scope]`
30
+ * in their theme. */
31
31
  .rf-expand[data-outline-scope] {
32
32
  padding: var(--rf-spacing-md) 0;
33
- border-top: 1px solid var(--rf-color-border);
34
- border-bottom: 1px solid var(--rf-color-border);
35
33
  }
36
34
 
37
35
  /* Tone down embedded headings so the host outline keeps visual primacy.