@refrakt-md/lumina 0.14.3 → 0.15.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.
@@ -184,6 +184,22 @@
184
184
  "{content}"
185
185
  ]
186
186
  },
187
+ "Snippet": {
188
+ "block": "snippet",
189
+ "root": ".rf-snippet",
190
+ "dataRune": "snippet",
191
+ "childOrder": [
192
+ "{content}"
193
+ ]
194
+ },
195
+ "Expand": {
196
+ "block": "expand",
197
+ "root": ".rf-expand",
198
+ "dataRune": "expand",
199
+ "childOrder": [
200
+ "{content}"
201
+ ]
202
+ },
187
203
  "Embed": {
188
204
  "block": "embed",
189
205
  "root": ".rf-embed",
@@ -425,6 +441,32 @@
425
441
  }
426
442
  }
427
443
  },
444
+ "Drawer": {
445
+ "block": "drawer",
446
+ "root": ".rf-drawer",
447
+ "dataRune": "drawer",
448
+ "childOrder": [
449
+ "{content}"
450
+ ],
451
+ "modifiers": {
452
+ "side": {
453
+ "source": "meta",
454
+ "default": "right",
455
+ "classPattern": ".rf-drawer--{value}",
456
+ "dataAttribute": "data-side"
457
+ },
458
+ "size": {
459
+ "source": "meta",
460
+ "default": "md",
461
+ "classPattern": ".rf-drawer--{value}",
462
+ "dataAttribute": "data-size"
463
+ },
464
+ "shortcut": {
465
+ "source": "meta",
466
+ "dataAttribute": "data-shortcut"
467
+ }
468
+ }
469
+ },
428
470
  "Figure": {
429
471
  "block": "figure",
430
472
  "root": ".rf-figure",
package/index.css CHANGED
@@ -57,10 +57,13 @@
57
57
  @import './styles/runes/cta.css';
58
58
  @import './styles/runes/datatable.css';
59
59
  @import './styles/runes/details.css';
60
+ @import './styles/runes/drawer.css';
60
61
  @import './styles/runes/diagram.css';
61
62
  @import './styles/runes/diff.css';
62
63
  @import './styles/runes/codegroup.css';
64
+ @import './styles/runes/snippet.css';
63
65
  @import './styles/runes/embed.css';
66
+ @import './styles/runes/expand.css';
64
67
  @import './styles/runes/event.css';
65
68
  @import './styles/runes/faction.css';
66
69
  @import './styles/runes/feature.css';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@refrakt-md/lumina",
3
3
  "description": "Lumina theme for refrakt.md — design tokens, CSS, identity transform, and layout configs",
4
- "version": "0.14.3",
4
+ "version": "0.15.0",
5
5
  "type": "module",
6
6
  "license": "MIT",
7
7
  "repository": {
@@ -83,9 +83,9 @@
83
83
  "build": "tsc"
84
84
  },
85
85
  "dependencies": {
86
- "@refrakt-md/runes": "0.14.3",
87
- "@refrakt-md/transform": "0.14.3",
88
- "@refrakt-md/types": "0.14.3"
86
+ "@refrakt-md/runes": "0.15.0",
87
+ "@refrakt-md/transform": "0.15.0",
88
+ "@refrakt-md/types": "0.15.0"
89
89
  },
90
90
  "devDependencies": {
91
91
  "postcss": "^8.4.0"
@@ -0,0 +1,316 @@
1
+ /* Drawer — addressable modal panel (SPEC-060).
2
+ *
3
+ * Two visual modes ride on the same `.rf-drawer` element:
4
+ *
5
+ * 1. **No-JS (in-flow)** — `.rf-drawer` not inside a `<dialog>`. Renders
6
+ * as a styled callout block at its authored position, visually
7
+ * distinct so the reader understands "set-aside material". Close
8
+ * button stays `hidden` (the schema marks it that way; behaviors
9
+ * reveals it post-enhancement).
10
+ * 2. **JS (panel)** — `dialog.rf-drawer[open]`. Floats slightly off the
11
+ * viewport edge (margin on all four sides) so it reads as a "card
12
+ * that slid in" rather than a flush slab. Size modifier sets width
13
+ * (right/left) or height (top/bottom). Slide-in animation uses
14
+ * `@starting-style` to interpolate from off-screen on open.
15
+ *
16
+ * Body-scroll lock is applied as a class on `<html>` by the behavior
17
+ * (`html.rf-drawer-open` → `overflow: hidden`) so mobile Safari doesn't
18
+ * scroll the page underneath the open dialog.
19
+ */
20
+
21
+ .rf-drawer {
22
+ --rf-drawer-size-sm: 22rem;
23
+ --rf-drawer-size-md: 36rem;
24
+ --rf-drawer-size-lg: 52rem;
25
+ --rf-drawer-bg: var(--rf-color-surface);
26
+ --rf-drawer-fg: var(--rf-color-text);
27
+ --rf-drawer-border: var(--rf-color-border);
28
+ --rf-drawer-shadow: 0 18px 48px -12px rgb(0 0 0 / 0.35);
29
+ --rf-drawer-gutter: var(--rf-spacing-sm);
30
+ --rf-drawer-radius: var(--rf-radius-md);
31
+ --rf-drawer-anim-duration: 220ms;
32
+ --rf-drawer-anim-ease: cubic-bezier(0.32, 0.72, 0, 1);
33
+ }
34
+
35
+ /* ─── Body-scroll lock while any drawer is open ────────────────── */
36
+
37
+ html.rf-drawer-open,
38
+ html.rf-drawer-open body {
39
+ overflow: hidden;
40
+ }
41
+
42
+ /* ─── No-JS / in-flow mode ─────────────────────────────────────── */
43
+
44
+ section.rf-drawer {
45
+ margin: var(--rf-spacing-lg) 0;
46
+ padding: var(--rf-spacing-md) var(--rf-spacing-lg);
47
+ background: var(--rf-drawer-bg);
48
+ color: var(--rf-drawer-fg);
49
+ border: 1px solid var(--rf-drawer-border);
50
+ border-left: 4px solid var(--rf-color-primary);
51
+ border-radius: var(--rf-drawer-radius);
52
+ }
53
+
54
+ section.rf-drawer .rf-drawer__header {
55
+ display: flex;
56
+ align-items: baseline;
57
+ justify-content: space-between;
58
+ gap: var(--rf-spacing-sm);
59
+ margin-bottom: var(--rf-spacing-sm);
60
+ }
61
+
62
+ section.rf-drawer .rf-drawer__title {
63
+ margin: 0;
64
+ font-size: 1.05rem;
65
+ font-weight: 600;
66
+ color: var(--rf-color-primary);
67
+ letter-spacing: 0.01em;
68
+ }
69
+
70
+ section.rf-drawer .rf-drawer__body > :first-child { margin-top: 0; }
71
+ section.rf-drawer .rf-drawer__body > :last-child { margin-bottom: 0; }
72
+
73
+ /* ─── JS / dialog mode ─────────────────────────────────────────── */
74
+
75
+ dialog.rf-drawer {
76
+ box-sizing: border-box;
77
+ background: var(--rf-drawer-bg);
78
+ color: var(--rf-drawer-fg);
79
+ border: 1px solid var(--rf-drawer-border);
80
+ border-radius: var(--rf-drawer-radius);
81
+ box-shadow: var(--rf-drawer-shadow);
82
+ padding: 0;
83
+ margin: 0;
84
+ overflow: auto;
85
+ max-width: calc(100vw - 2 * var(--rf-drawer-gutter));
86
+ max-height: calc(100vh - 2 * var(--rf-drawer-gutter));
87
+ }
88
+
89
+ dialog.rf-drawer::backdrop {
90
+ background: rgb(0 0 0 / 0.35);
91
+ backdrop-filter: blur(2px);
92
+ }
93
+
94
+ dialog.rf-drawer .rf-drawer__header {
95
+ display: flex;
96
+ align-items: center;
97
+ justify-content: space-between;
98
+ gap: var(--rf-spacing-md);
99
+ padding: var(--rf-spacing-md) var(--rf-spacing-lg);
100
+ border-bottom: 1px solid var(--rf-drawer-border);
101
+ position: sticky;
102
+ top: 0;
103
+ background: var(--rf-drawer-bg);
104
+ z-index: 1;
105
+ }dialog.rf-drawer .rf-drawer__title {
106
+ margin: 0;
107
+ font-size: 1.05rem;
108
+ font-weight: 600;
109
+ }
110
+
111
+ dialog.rf-drawer .rf-drawer__close {
112
+ appearance: none;
113
+ background: transparent;
114
+ border: 0;
115
+ cursor: pointer;
116
+ color: var(--rf-color-muted);
117
+ padding: var(--rf-spacing-xs) var(--rf-spacing-sm);
118
+ font-size: 1.25rem;
119
+ line-height: 1;
120
+ border-radius: var(--rf-radius-sm);
121
+ flex-shrink: 0;
122
+ }
123
+ dialog.rf-drawer .rf-drawer__close:hover {
124
+ color: var(--rf-color-text);
125
+ background: var(--rf-color-surface-hover);
126
+ }
127
+
128
+ dialog.rf-drawer .rf-drawer__body {
129
+ padding: var(--rf-spacing-md) var(--rf-spacing-lg);
130
+ }
131
+
132
+ /* Collapse the vertical margins of first / last children inside the body
133
+ * so the drawer's own padding is what spaces content from the chrome —
134
+ * otherwise a `<figure>` or `<p>` with its own top/bottom margin stacks
135
+ * on top of the body's padding and the content looks adrift. A snippet
136
+ * inside a drawer compiles to `body > .rf-codeblock > .rf-snippet > <pre>`,
137
+ * so the margin-zero walk reaches two levels deep to catch the figure. */
138
+ dialog.rf-drawer .rf-drawer__body > :first-child,
139
+ dialog.rf-drawer .rf-drawer__body > :first-child > :first-child {
140
+ margin-top: 0;
141
+ }
142
+ dialog.rf-drawer .rf-drawer__body > :last-child,
143
+ dialog.rf-drawer .rf-drawer__body > :last-child > :last-child {
144
+ margin-bottom: 0;
145
+ }
146
+
147
+ /* ─── Side modifiers ───────────────────────────────────────────────
148
+ *
149
+ * Each side anchors the dialog with `inset`. The slide-in animation is
150
+ * a keyframes animation triggered by `[open]` — picked over
151
+ * `@starting-style` because keyframes have broader browser support
152
+ * (everywhere `<dialog>` works) and a clearer source-of-truth: the
153
+ * starting/ending frames are spelled out, no cascade-interaction
154
+ * footguns. The matching backdrop fade is a separate animation.
155
+ */
156
+
157
+ dialog.rf-drawer[data-side="right"] {
158
+ inset:
159
+ var(--rf-drawer-gutter)
160
+ var(--rf-drawer-gutter)
161
+ var(--rf-drawer-gutter)
162
+ auto;
163
+ height: auto;
164
+ }
165
+ dialog.rf-drawer[data-side="right"][open]:not([data-state="closing"]) {
166
+ animation: rf-drawer-slide-in-right var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease);
167
+ }
168
+ dialog.rf-drawer[data-side="right"][data-state="closing"] {
169
+ animation: rf-drawer-slide-out-right var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease) forwards;
170
+ }
171
+ dialog.rf-drawer[data-side="left"] {
172
+ inset:
173
+ var(--rf-drawer-gutter)
174
+ auto
175
+ var(--rf-drawer-gutter)
176
+ var(--rf-drawer-gutter);
177
+ height: auto;
178
+ }
179
+ dialog.rf-drawer[data-side="left"][open]:not([data-state="closing"]) {
180
+ animation: rf-drawer-slide-in-left var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease);
181
+ }
182
+ dialog.rf-drawer[data-side="left"][data-state="closing"] {
183
+ animation: rf-drawer-slide-out-left var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease) forwards;
184
+ }
185
+ dialog.rf-drawer[data-side="top"] {
186
+ inset:
187
+ var(--rf-drawer-gutter)
188
+ var(--rf-drawer-gutter)
189
+ auto
190
+ var(--rf-drawer-gutter);
191
+ width: auto;
192
+ }
193
+ dialog.rf-drawer[data-side="top"][open]:not([data-state="closing"]) {
194
+ animation: rf-drawer-slide-in-top var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease);
195
+ }
196
+ dialog.rf-drawer[data-side="top"][data-state="closing"] {
197
+ animation: rf-drawer-slide-out-top var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease) forwards;
198
+ }
199
+ dialog.rf-drawer[data-side="bottom"] {
200
+ inset:
201
+ auto
202
+ var(--rf-drawer-gutter)
203
+ var(--rf-drawer-gutter)
204
+ var(--rf-drawer-gutter);
205
+ width: auto;
206
+ }
207
+ dialog.rf-drawer[data-side="bottom"][open]:not([data-state="closing"]) {
208
+ animation: rf-drawer-slide-in-bottom var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease);
209
+ }
210
+ dialog.rf-drawer[data-side="bottom"][data-state="closing"] {
211
+ animation: rf-drawer-slide-out-bottom var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease) forwards;
212
+ }
213
+
214
+ dialog.rf-drawer[open]:not([data-state="closing"])::backdrop {
215
+ animation: rf-drawer-backdrop-in var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease);
216
+ }
217
+ dialog.rf-drawer[data-state="closing"]::backdrop {
218
+ animation: rf-drawer-backdrop-out var(--rf-drawer-anim-duration) var(--rf-drawer-anim-ease) forwards;
219
+ }
220
+
221
+ @keyframes rf-drawer-slide-in-right {
222
+ from { transform: translateX(calc(100% + var(--rf-drawer-gutter))); opacity: 0; }
223
+ to { transform: translateX(0); opacity: 1; }
224
+ }
225
+ @keyframes rf-drawer-slide-in-left {
226
+ from { transform: translateX(calc(-100% - var(--rf-drawer-gutter))); opacity: 0; }
227
+ to { transform: translateX(0); opacity: 1; }
228
+ }
229
+ @keyframes rf-drawer-slide-in-top {
230
+ from { transform: translateY(calc(-100% - var(--rf-drawer-gutter))); opacity: 0; }
231
+ to { transform: translateY(0); opacity: 1; }
232
+ }
233
+ @keyframes rf-drawer-slide-in-bottom {
234
+ from { transform: translateY(calc(100% + var(--rf-drawer-gutter))); opacity: 0; }
235
+ to { transform: translateY(0); opacity: 1; }
236
+ }
237
+ @keyframes rf-drawer-backdrop-in {
238
+ from { background: rgb(0 0 0 / 0); backdrop-filter: blur(0); }
239
+ to { background: rgb(0 0 0 / 0.35); backdrop-filter: blur(2px); }
240
+ }
241
+
242
+ @keyframes rf-drawer-slide-out-right {
243
+ from { transform: translateX(0); opacity: 1; }
244
+ to { transform: translateX(calc(100% + var(--rf-drawer-gutter))); opacity: 0; }
245
+ }
246
+ @keyframes rf-drawer-slide-out-left {
247
+ from { transform: translateX(0); opacity: 1; }
248
+ to { transform: translateX(calc(-100% - var(--rf-drawer-gutter))); opacity: 0; }
249
+ }
250
+ @keyframes rf-drawer-slide-out-top {
251
+ from { transform: translateY(0); opacity: 1; }
252
+ to { transform: translateY(calc(-100% - var(--rf-drawer-gutter))); opacity: 0; }
253
+ }
254
+ @keyframes rf-drawer-slide-out-bottom {
255
+ from { transform: translateY(0); opacity: 1; }
256
+ to { transform: translateY(calc(100% + var(--rf-drawer-gutter))); opacity: 0; }
257
+ }
258
+ @keyframes rf-drawer-backdrop-out {
259
+ from { background: rgb(0 0 0 / 0.35); backdrop-filter: blur(2px); }
260
+ to { background: rgb(0 0 0 / 0); backdrop-filter: blur(0); }
261
+ }
262
+
263
+ @media (prefers-reduced-motion: reduce) {
264
+ dialog.rf-drawer[data-side="right"][open],
265
+ dialog.rf-drawer[data-side="left"][open],
266
+ dialog.rf-drawer[data-side="top"][open],
267
+ dialog.rf-drawer[data-side="bottom"][open],
268
+ dialog.rf-drawer[data-state="closing"],
269
+ dialog.rf-drawer[open]::backdrop,
270
+ dialog.rf-drawer[data-state="closing"]::backdrop {
271
+ animation: none;
272
+ }
273
+ }
274
+
275
+ /* ─── Mobile tightening ────────────────────────────────────────────
276
+ *
277
+ * On narrow viewports the drawer fills the screen (capped by
278
+ * `max-width: 100vw - 2*gutter`), so the desktop-comfortable
279
+ * 1.5rem / 2rem padding starts crowding the content. Halve it
280
+ * roughly to 0.75rem / 1rem so the body has more room without
281
+ * losing the chrome's breathing space.
282
+ */
283
+ @media (max-width: 640px) {
284
+ dialog.rf-drawer .rf-drawer__header,
285
+ dialog.rf-drawer .rf-drawer__body {
286
+ padding: 0.75rem 1rem;
287
+ }
288
+ }
289
+
290
+ /* ─── Size modifiers (only meaningful in dialog mode) ─────────── */
291
+
292
+ dialog.rf-drawer[data-side="right"][data-size="sm"],
293
+ dialog.rf-drawer[data-side="left"][data-size="sm"] {
294
+ width: var(--rf-drawer-size-sm);
295
+ }
296
+ dialog.rf-drawer[data-side="right"][data-size="md"],
297
+ dialog.rf-drawer[data-side="left"][data-size="md"] {
298
+ width: var(--rf-drawer-size-md);
299
+ }
300
+ dialog.rf-drawer[data-side="right"][data-size="lg"],
301
+ dialog.rf-drawer[data-side="left"][data-size="lg"] {
302
+ width: var(--rf-drawer-size-lg);
303
+ }
304
+
305
+ dialog.rf-drawer[data-side="top"][data-size="sm"],
306
+ dialog.rf-drawer[data-side="bottom"][data-size="sm"] {
307
+ height: var(--rf-drawer-size-sm);
308
+ }
309
+ dialog.rf-drawer[data-side="top"][data-size="md"],
310
+ dialog.rf-drawer[data-side="bottom"][data-size="md"] {
311
+ height: var(--rf-drawer-size-md);
312
+ }
313
+ dialog.rf-drawer[data-side="top"][data-size="lg"],
314
+ dialog.rf-drawer[data-side="bottom"][data-size="lg"] {
315
+ height: var(--rf-drawer-size-lg);
316
+ }
@@ -0,0 +1,74 @@
1
+ /* Expand — inline-substitution of a registered entity (SPEC-066).
2
+ *
3
+ * The wrapper is intentionally minimal — the embedded rune (plan rune,
4
+ * character rune, etc.) renders itself standalone-style; expand just
5
+ * provides the wrapper + canonical-link affordance. Themes that want
6
+ * the embedded content visually distinguished from inline-authored
7
+ * content target `.rf-expand .rf-spec`, `.rf-expand h1`, etc.
8
+ *
9
+ * Two visual treatments:
10
+ *
11
+ * 1. **Default (`level=` unset, peer-document mode)** — the embed reads
12
+ * like a quoted document. A subtle border + indent treatment marks
13
+ * it as set-aside material. Heading scale is dialed down slightly
14
+ * so an H1 inside the embed doesn't compete with the host's H1.
15
+ * 2. **Sub-section mode (`level=` set)** — the embed merges into the
16
+ * host outline. The wrapper sheds the quoted treatment so it reads
17
+ * as authored sub-content. Authors opt into this by setting
18
+ * `level=`; CSS targets `.rf-expand:not([data-outline-scope])` to
19
+ * pick the right treatment automatically.
20
+ */
21
+
22
+ .rf-expand {
23
+ margin: var(--rf-spacing-md) 0;
24
+ }
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. */
31
+ .rf-expand[data-outline-scope] {
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
+ }
36
+
37
+ /* Tone down embedded headings so the host outline keeps visual primacy.
38
+ * The semantic levels are preserved (H1 stays H1 etc.); only the visual
39
+ * scale is adjusted in the peer-document treatment. Authors using
40
+ * `level=` for sub-section mode get the normal heading scale. */
41
+ .rf-expand[data-outline-scope] :is(h1, h2, h3, h4, h5, h6):first-child {
42
+ margin-top: 0;
43
+ }
44
+ .rf-expand[data-outline-scope] h1 { font-size: 1.5rem; }
45
+ .rf-expand[data-outline-scope] h2 { font-size: 1.25rem; }
46
+ .rf-expand[data-outline-scope] h3 { font-size: 1.1rem; }
47
+
48
+ .rf-expand > :first-child { margin-top: 0; }
49
+ .rf-expand > :last-child { margin-bottom: 0; }
50
+
51
+ .rf-expand__canonical-link {
52
+ display: inline-block;
53
+ margin-top: var(--rf-spacing-sm);
54
+ font-size: 0.875rem;
55
+ color: var(--rf-color-primary);
56
+ text-decoration: none;
57
+ }
58
+ .rf-expand__canonical-link:hover {
59
+ text-decoration: underline;
60
+ }
61
+
62
+ /* Error-state placeholder rendered when resolution fails. The build
63
+ * also surfaces the error through `ctx.error`; this is the user-facing
64
+ * affordance so the failure isn't silent. Keyed off `[data-expand-error]`
65
+ * which the resolver sets on the error wrapper. */
66
+ .rf-expand[data-expand-error] {
67
+ padding: var(--rf-spacing-sm) var(--rf-spacing-md);
68
+ border-left: 3px solid var(--rf-color-danger);
69
+ background: var(--rf-color-danger-bg);
70
+ color: var(--rf-color-danger);
71
+ font-family: var(--rf-font-mono);
72
+ font-size: 0.875rem;
73
+ border-radius: 0 var(--rf-radius-sm) var(--rf-radius-sm) 0;
74
+ }
@@ -361,8 +361,14 @@
361
361
 
362
362
  @media (max-width: 600px) {
363
363
  .rf-nav--columns {
364
- grid-template-columns: 1fr;
365
- gap: 1.25rem;
364
+ /* Keep two columns on mobile. `minmax(0, 1fr)` lets columns shrink
365
+ * below their min-content width so long link labels don't push the
366
+ * grid wider than the viewport and trigger horizontal scroll. */
367
+ grid-template-columns: repeat(2, minmax(0, 1fr));
368
+ gap: 1.25rem 1.5rem;
369
+ }
370
+ .rf-nav--columns .rf-nav-item__link {
371
+ overflow-wrap: anywhere;
366
372
  }
367
373
  }
368
374
 
@@ -600,7 +606,10 @@
600
606
  .rf-nav--columns:has(> div[data-name="column"]) {
601
607
  grid-auto-flow: row;
602
608
  grid-auto-columns: unset;
603
- grid-template-columns: 1fr;
609
+ /* Keep two columns on mobile. `minmax(0, 1fr)` lets columns shrink
610
+ * below their min-content width so long link labels don't push the
611
+ * grid wider than the viewport and trigger horizontal scroll. */
612
+ grid-template-columns: repeat(2, minmax(0, 1fr));
604
613
  }
605
614
  }
606
615
 
@@ -0,0 +1,27 @@
1
+ /* Snippet — file embedded as a code block.
2
+ *
3
+ * The standalone form is a `<figure class="rf-snippet">` wrapping the
4
+ * existing `<pre class="rf-code-block">`. Nested forms (snippet inside
5
+ * codegroup or diff) skip the figure wrapper entirely — the container's
6
+ * chrome owns the layout there.
7
+ *
8
+ * The figure exists primarily so themes can style snippet-derived code
9
+ * blocks distinctly from regular fences, and so tooling can find
10
+ * `data-source-path` for "edit this file" affordances. Authors wanting a
11
+ * labelled caption should wrap the snippet in `{% codegroup title="..." %}`.
12
+ *
13
+ * SPEC-062.
14
+ */
15
+
16
+ .rf-snippet {
17
+ /* Override the UA `<figure>` defaults — browsers ship with
18
+ * margin-inline: 40px and margin-block: 1em which makes the snippet
19
+ * figure look indented inside any container. The longhand resets
20
+ * cover both physical and logical margin properties because the
21
+ * physical longhands set by `margin:` don't always cascade-override
22
+ * the UA's logical longhands. */
23
+ margin: var(--rf-spacing-md) 0;
24
+ margin-inline-start: 0;
25
+ margin-inline-end: 0;
26
+ padding: 0;
27
+ }
@@ -4,6 +4,22 @@
4
4
  /* Inherits normal link styling from the theme */
5
5
  }
6
6
 
7
+ /* External — pattern-resolved refs and any other type that doesn't have a
8
+ * dedicated treatment. A subtle outbound indicator distinguishes these
9
+ * from local refs without being noisy. */
10
+ .rf-xref--external {
11
+ /* Theme-inherited link colour; outbound indicator via a small inline
12
+ * arrow that adapts to RTL via the logical-property side. */
13
+ }
14
+ .rf-xref--external::after {
15
+ content: '\2197'; /* ↗ */
16
+ display: inline-block;
17
+ margin-inline-start: 0.15em;
18
+ font-size: 0.85em;
19
+ vertical-align: 0.05em;
20
+ opacity: 0.6;
21
+ }
22
+
7
23
  /* Unresolved reference */
8
24
  .rf-xref--unresolved {
9
25
  text-decoration: underline dashed;