@refrakt-md/lumina 0.14.4 → 0.16.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.
- package/base.css +1 -0
- package/contracts/structures.json +105 -0
- package/index.css +9 -5
- package/package.json +4 -4
- package/styles/layouts/docs.css +17 -0
- package/styles/layouts/theme-toggle.css +67 -0
- package/styles/runes/accordion.css +56 -16
- package/styles/runes/aggregate.css +75 -0
- package/styles/runes/badge.css +20 -16
- package/styles/runes/card.css +83 -0
- package/styles/runes/collection.css +166 -0
- package/styles/runes/conversation.css +8 -3
- package/styles/runes/drawer.css +316 -0
- package/styles/runes/expand.css +74 -0
- package/styles/runes/nav.css +15 -0
- package/styles/runes/progress.css +49 -0
- package/styles/runes/relationships.css +132 -0
- package/styles/runes/snippet.css +27 -0
- package/styles/runes/xref.css +16 -0
- package/styles/runes/backlog.css +0 -108
- package/styles/runes/decision-log.css +0 -46
- package/styles/runes/plan-activity.css +0 -87
- package/styles/runes/plan-entity-tabs.css +0 -88
- package/styles/runes/plan-relationships.css +0 -40
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
/* Collection — list / grid / table / cards of registry entities (SPEC-070).
|
|
2
|
+
*
|
|
3
|
+
* The block is layout-agnostic; the `data-layout` attribute on the wrapper
|
|
4
|
+
* selects the arrangement. Built-in items carry `.rf-collection__card` /
|
|
5
|
+
* `__item`; a body template emits its own markup inside `.rf-collection__item`.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.rf-collection {
|
|
9
|
+
margin: var(--rf-spacing-md) 0;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/* Preamble — rendered above items only when the query is non-empty, so a
|
|
13
|
+
* heading/intro can live inside the rune and vanish with an empty section. */
|
|
14
|
+
.rf-collection__preamble > :first-child { margin-top: 0; }
|
|
15
|
+
|
|
16
|
+
/* Empty state — shown in place of items when the query yields nothing. */
|
|
17
|
+
.rf-collection__empty {
|
|
18
|
+
color: var(--rf-color-muted);
|
|
19
|
+
font-size: 0.9375em;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
.rf-collection__items {
|
|
23
|
+
display: flex;
|
|
24
|
+
flex-direction: column;
|
|
25
|
+
gap: var(--rf-spacing-sm);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* grid arranges items into responsive columns; list stays a stacked flow.
|
|
29
|
+
* Item chrome (a card box, bare row, …) comes from the item, not the layout. */
|
|
30
|
+
.rf-collection[data-layout='grid'] .rf-collection__items {
|
|
31
|
+
display: grid;
|
|
32
|
+
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
|
33
|
+
gap: var(--rf-spacing-md);
|
|
34
|
+
align-items: stretch;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/* Uniform heights per row: grid stretches each item to the tallest in its row;
|
|
38
|
+
* a body-template item then lets its card fill that stretched height. */
|
|
39
|
+
.rf-collection[data-layout='grid'] .rf-collection__item {
|
|
40
|
+
display: flex;
|
|
41
|
+
}
|
|
42
|
+
.rf-collection[data-layout='grid'] .rf-collection__item > * {
|
|
43
|
+
flex: 1;
|
|
44
|
+
min-width: 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/* Grouped grid: the grid belongs to each group (its items are the cells), not
|
|
48
|
+
* to __items — whose children are the group blocks (heading groups) or the
|
|
49
|
+
* accordion wrapper. Left on __items, each group becomes a single cell, i.e. a
|
|
50
|
+
* column per group. So when __items holds groups it stacks them, and each
|
|
51
|
+
* group becomes the grid instead, with the group title spanning every column. */
|
|
52
|
+
.rf-collection[data-layout='grid'] .rf-collection__items:has(.rf-collection__group),
|
|
53
|
+
.rf-collection[data-layout='grid'] .rf-collection__items:has(.rf-accordion) {
|
|
54
|
+
display: flex;
|
|
55
|
+
flex-direction: column;
|
|
56
|
+
gap: var(--rf-spacing-md);
|
|
57
|
+
}
|
|
58
|
+
.rf-collection[data-layout='grid'] .rf-collection__group {
|
|
59
|
+
display: grid;
|
|
60
|
+
grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
|
|
61
|
+
gap: var(--rf-spacing-md);
|
|
62
|
+
align-items: stretch;
|
|
63
|
+
}
|
|
64
|
+
.rf-collection[data-layout='grid'] .rf-collection__group-title {
|
|
65
|
+
grid-column: 1 / -1;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
.rf-collection__item {
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.rf-collection__card {
|
|
73
|
+
display: flex;
|
|
74
|
+
flex-direction: column;
|
|
75
|
+
gap: var(--rf-spacing-xs);
|
|
76
|
+
padding: var(--rf-spacing-md);
|
|
77
|
+
border: 1px solid var(--rf-color-border);
|
|
78
|
+
border-radius: var(--rf-radius-md);
|
|
79
|
+
background: var(--rf-color-surface);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.rf-collection__card:hover {
|
|
83
|
+
background: var(--rf-color-surface-hover);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
.rf-collection__title {
|
|
87
|
+
font-weight: 600;
|
|
88
|
+
color: var(--rf-color-text);
|
|
89
|
+
text-decoration: none;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.rf-collection__title:hover {
|
|
93
|
+
color: var(--rf-color-primary);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.rf-collection__field {
|
|
97
|
+
font-size: 0.875em;
|
|
98
|
+
color: var(--rf-color-muted);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/* grouping */
|
|
102
|
+
.rf-collection__group {
|
|
103
|
+
margin-top: var(--rf-spacing-md);
|
|
104
|
+
display: flex;
|
|
105
|
+
flex-direction: column;
|
|
106
|
+
/* Tight default for short item rows (the built-in title link, no body). */
|
|
107
|
+
gap: var(--rf-spacing-xs);
|
|
108
|
+
}
|
|
109
|
+
/* Modestly roomier when items carry a body template (block content — a card,
|
|
110
|
+
* callout, list, …), so they don't collide. Body-template items are marked
|
|
111
|
+
* `data-block` by the resolver; the built-in inline title rows are not.
|
|
112
|
+
* Mirrors the rf-relationships rule. */
|
|
113
|
+
.rf-collection__group:has(.rf-collection__item[data-block]) {
|
|
114
|
+
gap: var(--rf-spacing-sm);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* Block-content item — let the body template (card / callout / etc.) fill the
|
|
118
|
+
* container instead of shrink-fitting. No-op in grid mode (items there are
|
|
119
|
+
* already `display: flex` with `flex: 1` on the child). Trim the outer margins
|
|
120
|
+
* of the template's first/last block so the group `gap` controls inter-item
|
|
121
|
+
* spacing — works for any block content, not a wrapper element. */
|
|
122
|
+
.rf-collection__item[data-block] {
|
|
123
|
+
display: block;
|
|
124
|
+
}
|
|
125
|
+
.rf-collection__item[data-block] > :first-child {
|
|
126
|
+
margin-top: 0;
|
|
127
|
+
}
|
|
128
|
+
.rf-collection__item[data-block] > :last-child {
|
|
129
|
+
margin-bottom: 0;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.rf-collection__group-title {
|
|
133
|
+
margin: 0 0 var(--rf-spacing-sm);
|
|
134
|
+
font-size: 0.875em;
|
|
135
|
+
text-transform: uppercase;
|
|
136
|
+
letter-spacing: 0.05em;
|
|
137
|
+
color: var(--rf-color-muted);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/* table layout */
|
|
141
|
+
.rf-collection__table {
|
|
142
|
+
width: 100%;
|
|
143
|
+
border-collapse: collapse;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
.rf-collection__table th,
|
|
147
|
+
.rf-collection__table td {
|
|
148
|
+
padding: var(--rf-spacing-sm);
|
|
149
|
+
text-align: left;
|
|
150
|
+
border-bottom: 1px solid var(--rf-color-border);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.rf-collection__table th {
|
|
154
|
+
font-size: 0.875em;
|
|
155
|
+
text-transform: uppercase;
|
|
156
|
+
letter-spacing: 0.05em;
|
|
157
|
+
color: var(--rf-color-muted);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/* Heading-template cells (`layout="table"` body) wrap their content as
|
|
161
|
+
* markdown paragraphs, which pick up the global `p { margin-bottom: 1rem }`
|
|
162
|
+
* and leave dead space inside each row. Drop it inside the table — table
|
|
163
|
+
* cells don't want paragraph spacing. */
|
|
164
|
+
.rf-collection__table td p {
|
|
165
|
+
margin: 0;
|
|
166
|
+
}
|
|
@@ -47,7 +47,12 @@
|
|
|
47
47
|
}
|
|
48
48
|
.rf-conversation-message__body p { margin: 0; }
|
|
49
49
|
.rf-conversation-message__body p + p { margin-top: 0.5rem; }
|
|
50
|
-
|
|
50
|
+
/* The speaker name lives inline-bold inside the bubble (the explicit form
|
|
51
|
+
* authors it that way, and the named-speakers form injects it the same way),
|
|
52
|
+
* so the property-carrier span is data only — hide it. Same for the meta
|
|
53
|
+
* tags the engine consumes. `data-field` is the post-engine attribute set by
|
|
54
|
+
* createComponentRenderable. */
|
|
55
|
+
.rf-conversation-message > span[data-field="speaker"],
|
|
56
|
+
.rf-conversation-message > meta[data-field] { display: none; }
|
|
57
|
+
.rf-conversation-message__body > span[data-field],
|
|
51
58
|
.rf-conversation-message__body > meta { display: none; }
|
|
52
|
-
.rf-conversation-message > span[property="speaker"],
|
|
53
|
-
.rf-conversation-message > meta[property] { display: none; }
|
|
@@ -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
|
+
}
|
package/styles/runes/nav.css
CHANGED
|
@@ -67,6 +67,21 @@
|
|
|
67
67
|
color: var(--rf-color-muted);
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/* Items with an inline badge (recognised by the nav schema and tagged with
|
|
71
|
+
* data-name="badge") sit on one row so the badge rides alongside the link
|
|
72
|
+
* instead of wrapping to the next line — the link's `display: block` would
|
|
73
|
+
* otherwise eat the full row. */
|
|
74
|
+
.rf-nav-item:has(> .rf-badge[data-name="badge"]) {
|
|
75
|
+
display: flex;
|
|
76
|
+
align-items: center;
|
|
77
|
+
gap: 0.375rem;
|
|
78
|
+
padding: 0;
|
|
79
|
+
}
|
|
80
|
+
.rf-nav-item:has(> .rf-badge[data-name="badge"]) > .rf-nav-item__link {
|
|
81
|
+
flex: 0 1 auto;
|
|
82
|
+
min-width: 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
70
85
|
/* ─── Collapsible groups (vertical sidebar) ───────────────────────────── */
|
|
71
86
|
|
|
72
87
|
.rf-nav--collapsible .rf-nav-group h2,
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* progress — generic completion bar (SPEC-072 / WORK-285).
|
|
2
|
+
*
|
|
3
|
+
* Presentational: the fill width is driven by the `--rf-progress` custom
|
|
4
|
+
* property set on the root from the computed percent. Generalized from the
|
|
5
|
+
* former plan milestone progress bar.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
.rf-progress {
|
|
9
|
+
display: flex;
|
|
10
|
+
flex-wrap: wrap;
|
|
11
|
+
align-items: baseline;
|
|
12
|
+
gap: var(--rf-spacing-xs) var(--rf-spacing-sm);
|
|
13
|
+
margin: var(--rf-spacing-sm) 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.rf-progress__label {
|
|
17
|
+
font-weight: 600;
|
|
18
|
+
color: var(--rf-color-text);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.rf-progress__value {
|
|
22
|
+
font-weight: 600;
|
|
23
|
+
color: var(--rf-color-text);
|
|
24
|
+
margin-left: auto;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.rf-progress__track {
|
|
28
|
+
flex-basis: 100%;
|
|
29
|
+
height: 0.5rem;
|
|
30
|
+
border-radius: var(--rf-radius-pill, 999px);
|
|
31
|
+
background: var(--rf-color-surface-hover);
|
|
32
|
+
overflow: hidden;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
.rf-progress__fill {
|
|
36
|
+
display: block;
|
|
37
|
+
height: 100%;
|
|
38
|
+
width: var(--rf-progress, 0%);
|
|
39
|
+
border-radius: inherit;
|
|
40
|
+
background: var(--rf-color-primary);
|
|
41
|
+
transition: width 0.3s ease;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/* Sentiment variants tint the fill; the neutral default uses the primary.
|
|
45
|
+
* Token names follow the sentiment scheme in dimensions/metadata.css
|
|
46
|
+
* (positive→success, caution→warning, negative→danger). */
|
|
47
|
+
.rf-progress--positive .rf-progress__fill { background: var(--rf-color-success); }
|
|
48
|
+
.rf-progress--caution .rf-progress__fill { background: var(--rf-color-warning); }
|
|
49
|
+
.rf-progress--negative .rf-progress__fill { background: var(--rf-color-danger); }
|