@veluai/velu 0.1.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 (90) hide show
  1. package/dist/cli.js +11 -0
  2. package/package.json +52 -0
  3. package/runtime/velu-ui/base.css +311 -0
  4. package/runtime/velu-ui/components/Accordion.jsx +64 -0
  5. package/runtime/velu-ui/components/ApiClient.jsx +121 -0
  6. package/runtime/velu-ui/components/ApiField.jsx +87 -0
  7. package/runtime/velu-ui/components/ApiPath.jsx +63 -0
  8. package/runtime/velu-ui/components/ApiSidebar.jsx +122 -0
  9. package/runtime/velu-ui/components/AskBar.jsx +71 -0
  10. package/runtime/velu-ui/components/Callout.jsx +114 -0
  11. package/runtime/velu-ui/components/Card.jsx +131 -0
  12. package/runtime/velu-ui/components/Chatbot.jsx +596 -0
  13. package/runtime/velu-ui/components/CodeBlock.jsx +375 -0
  14. package/runtime/velu-ui/components/Columns.jsx +56 -0
  15. package/runtime/velu-ui/components/Field.jsx +81 -0
  16. package/runtime/velu-ui/components/Image.jsx +163 -0
  17. package/runtime/velu-ui/components/MethodBadge.jsx +31 -0
  18. package/runtime/velu-ui/components/NavSelect.jsx +108 -0
  19. package/runtime/velu-ui/components/PageFeedback.jsx +219 -0
  20. package/runtime/velu-ui/components/PageFooter.jsx +213 -0
  21. package/runtime/velu-ui/components/PageHeader.jsx +414 -0
  22. package/runtime/velu-ui/components/PageNav.jsx +77 -0
  23. package/runtime/velu-ui/components/PoweredBy.jsx +51 -0
  24. package/runtime/velu-ui/components/Prompt.jsx +115 -0
  25. package/runtime/velu-ui/components/Search.jsx +366 -0
  26. package/runtime/velu-ui/components/Sidebar.jsx +191 -0
  27. package/runtime/velu-ui/components/Steps.jsx +65 -0
  28. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -0
  29. package/runtime/velu-ui/components/Toc.jsx +537 -0
  30. package/runtime/velu-ui/components/TocBar.jsx +195 -0
  31. package/runtime/velu-ui/components/Tree.jsx +87 -0
  32. package/runtime/velu-ui/components/TryItBar.jsx +90 -0
  33. package/runtime/velu-ui/components/accordion.css +92 -0
  34. package/runtime/velu-ui/components/api.css +479 -0
  35. package/runtime/velu-ui/components/ask-bar.css +94 -0
  36. package/runtime/velu-ui/components/card.css +105 -0
  37. package/runtime/velu-ui/components/chatbot.css +617 -0
  38. package/runtime/velu-ui/components/code-block.css +263 -0
  39. package/runtime/velu-ui/components/docs-layout.css +775 -0
  40. package/runtime/velu-ui/components/field.css +82 -0
  41. package/runtime/velu-ui/components/image.css +237 -0
  42. package/runtime/velu-ui/components/nav-select.css +157 -0
  43. package/runtime/velu-ui/components/page-feedback.css +241 -0
  44. package/runtime/velu-ui/components/page-footer.css +130 -0
  45. package/runtime/velu-ui/components/page-header.css +520 -0
  46. package/runtime/velu-ui/components/page-nav.css +50 -0
  47. package/runtime/velu-ui/components/powered-by.css +66 -0
  48. package/runtime/velu-ui/components/prompt.css +99 -0
  49. package/runtime/velu-ui/components/search.css +307 -0
  50. package/runtime/velu-ui/components/sidebar.css +144 -0
  51. package/runtime/velu-ui/components/steps.css +77 -0
  52. package/runtime/velu-ui/components/theme-toggle.css +70 -0
  53. package/runtime/velu-ui/components/toc-bar.css +234 -0
  54. package/runtime/velu-ui/components/tree.css +49 -0
  55. package/runtime/velu-ui/index.js +45 -0
  56. package/runtime/velu-ui/lib/copyText.js +64 -0
  57. package/runtime/velu-ui/lib/lang-icons.jsx +156 -0
  58. package/runtime/velu-ui/lib/prism-langs.js +957 -0
  59. package/runtime/velu-ui/lib/prism-loader.js +74 -0
  60. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -0
  61. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -0
  62. package/runtime/velu-ui/mdx-components.jsx +85 -0
  63. package/runtime/velu-ui/primitives/Cluster.jsx +49 -0
  64. package/runtime/velu-ui/primitives/Stack.jsx +63 -0
  65. package/runtime/velu-ui/primitives/Switcher.jsx +57 -0
  66. package/runtime/velu-ui/primitives/stack.css +3 -0
  67. package/runtime/velu-ui/primitives/switcher.css +25 -0
  68. package/runtime/velu-ui/styles.css +43 -0
  69. package/runtime/velu-ui/tokens.css +4 -0
  70. package/schema/velu.schema.json +167 -0
  71. package/src/navigation.js +434 -0
  72. package/src/runtime/App.jsx +1473 -0
  73. package/src/runtime/client-entry.jsx +22 -0
  74. package/src/runtime/server-entry.jsx +16 -0
  75. package/src/template.html +48 -0
  76. package/templates/starter/ai-tools/claude-code.mdx +26 -0
  77. package/templates/starter/ai-tools/cursor.mdx +17 -0
  78. package/templates/starter/api-reference/endpoint/create.mdx +24 -0
  79. package/templates/starter/api-reference/endpoint/get.mdx +27 -0
  80. package/templates/starter/api-reference/introduction.mdx +28 -0
  81. package/templates/starter/development.mdx +19 -0
  82. package/templates/starter/essentials/code.mdx +28 -0
  83. package/templates/starter/essentials/images.mdx +29 -0
  84. package/templates/starter/essentials/markdown.mdx +25 -0
  85. package/templates/starter/essentials/navigation.mdx +39 -0
  86. package/templates/starter/essentials/settings.mdx +30 -0
  87. package/templates/starter/favicon.svg +6 -0
  88. package/templates/starter/index.mdx +31 -0
  89. package/templates/starter/quickstart.mdx +31 -0
  90. package/templates/starter/velu.json +33 -0
@@ -0,0 +1,99 @@
1
+ /* Prompt — surface card with title, monospace truncated body, and a
2
+ solid accent "Copy Prompt" button. All values are tokens; light /
3
+ dark auto via [data-theme] since the surface, text, and accent vars
4
+ all switch. */
5
+
6
+ .velu-prompt {
7
+ background: var(--surface-color);
8
+ border: var(--border-width) solid var(--border-color);
9
+ border-radius: var(--radius-md);
10
+ padding: var(--s2);
11
+ color: var(--text-color);
12
+ }
13
+
14
+ .velu-prompt__title {
15
+ font-size: var(--f-h4);
16
+ line-height: var(--lh-h4);
17
+ font-weight: var(--weight-medium);
18
+ margin-block-end: var(--s1);
19
+ }
20
+
21
+ /* Truncated multi-line body. white-space:pre-wrap preserves the
22
+ newlines / leading whitespace of the source prompt; -webkit-box +
23
+ -webkit-line-clamp adds the auto ellipsis on the Nth line. The
24
+ `lines` prop sets -webkit-line-clamp inline via React. */
25
+ .velu-prompt__body {
26
+ margin: 0;
27
+ font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, Consolas, monospace);
28
+ font-size: var(--f-h6);
29
+ line-height: var(--lh-h6);
30
+ white-space: pre-wrap;
31
+ overflow-wrap: anywhere;
32
+ display: -webkit-box;
33
+ -webkit-box-orient: vertical;
34
+ overflow: hidden;
35
+ }
36
+
37
+ /* Layout (flex + gap + justify-end) is provided by <Cluster>; this
38
+ rule only owns the spacing FROM the body above. */
39
+ .velu-prompt__actions {
40
+ margin-block-start: var(--s1);
41
+ }
42
+
43
+ /* Accent-filled copy button — same visual weight as the CTA on Card,
44
+ sized to read as the row's primary action. Hover dims the accent
45
+ slightly via color-mix. */
46
+ .velu-prompt__copy {
47
+ display: inline-flex;
48
+ align-items: center;
49
+ gap: var(--s-2);
50
+ padding-block: var(--s-6);
51
+ padding-inline: var(--s-3);
52
+ background: var(--accent-color);
53
+ border: 0;
54
+ border-radius: var(--radius-sm);
55
+ color: #fff;
56
+ font: inherit;
57
+ font-size: var(--f-h6);
58
+ line-height: var(--lh-h6);
59
+ font-weight: var(--weight-medium);
60
+ cursor: pointer;
61
+ }
62
+ .velu-prompt__copy:hover {
63
+ background: color-mix(in srgb, var(--accent-color) 88%, #000);
64
+ }
65
+ .velu-prompt__copy:focus-visible {
66
+ outline: var(--border-width) solid var(--text-color);
67
+ outline-offset: 2px;
68
+ }
69
+
70
+ .velu-prompt__copy-icon {
71
+ display: inline-flex;
72
+ align-items: center;
73
+ }
74
+
75
+ /* Open in Cursor — same shape as copy, inverted neutral palette so it
76
+ reads as the secondary action sitting next to the accent primary.
77
+ Inherits the cursor brand glyph color via `currentColor`. */
78
+ .velu-prompt__open-cursor {
79
+ display: inline-flex;
80
+ align-items: center;
81
+ gap: var(--s-2);
82
+ padding-block: var(--s-6);
83
+ padding-inline: var(--s-3);
84
+ background: var(--text-color);
85
+ color: var(--page-bg);
86
+ border-radius: var(--radius-sm);
87
+ font: inherit;
88
+ font-size: var(--f-h6);
89
+ line-height: var(--lh-h6);
90
+ font-weight: var(--weight-medium);
91
+ text-decoration: none;
92
+ }
93
+ .velu-prompt__open-cursor:hover {
94
+ background: color-mix(in srgb, var(--text-color) 88%, var(--accent-color));
95
+ }
96
+ .velu-prompt__open-cursor:focus-visible {
97
+ outline: var(--border-width) solid var(--accent-color);
98
+ outline-offset: 2px;
99
+ }
@@ -0,0 +1,307 @@
1
+ /* Search — command-palette search. Ported from the Claude-Design "Velu
2
+ Search" handoff and retokenized: the design's surf/txt/bord layers
3
+ map onto velu-ui tokens, so it themes for free via [data-theme].
4
+ surf-page / surf-elevated → --page-bg
5
+ surf-row-hover / kbd → --surface-color
6
+ surf-row-selected → accent tint
7
+ txt-1 → --text-color
8
+ txt-2 / txt-3 / muted → --muted-color
9
+ bord-* → --border-color
10
+ accent → --accent-color
11
+ */
12
+
13
+ /* ── Shared kbd chip (⌘K trigger hint / esc / ↵) ────────────────────── */
14
+ /* line-height: 1 so a tall glyph like ↵ doesn't stretch the chip;
15
+ justify-content centers single-character contents. */
16
+ .velu-search__kbd {
17
+ display: inline-flex;
18
+ align-items: center;
19
+ justify-content: center;
20
+ flex: none;
21
+ min-inline-size: var(--s0);
22
+ padding-block: var(--s-5);
23
+ padding-inline: var(--s-3);
24
+ background: var(--surface-color);
25
+ border: var(--border-width) solid var(--border-color);
26
+ border-radius: var(--radius-sm);
27
+ font-size: var(--f-h7);
28
+ line-height: 1;
29
+ font-weight: var(--weight-medium);
30
+ color: var(--text-color);
31
+ }
32
+
33
+ /* ── Trigger box (placed at the top of the page) ────────────────────── */
34
+ /* Content-sized; the label's flex:1 only matters if the consumer gives
35
+ the trigger an explicit width. */
36
+ .velu-search__trigger {
37
+ display: inline-flex;
38
+ align-items: center;
39
+ gap: var(--s-3);
40
+ padding-block: var(--s-3);
41
+ padding-inline: var(--s-2);
42
+ background: var(--page-bg);
43
+ border: var(--border-width) solid var(--border-color);
44
+ border-radius: var(--radius-sm);
45
+ color: var(--muted-color);
46
+ font: inherit;
47
+ font-size: var(--f-h6);
48
+ cursor: pointer;
49
+ transition: border-color 0.12s ease;
50
+ }
51
+ .velu-search__trigger:hover {
52
+ border-color: var(--accent-color);
53
+ }
54
+ .velu-search__trigger-icon {
55
+ display: inline-flex;
56
+ flex: none;
57
+ color: var(--muted-color);
58
+ }
59
+ .velu-search__trigger-label {
60
+ flex: 1;
61
+ text-align: start;
62
+ }
63
+
64
+ /* ── Mobile collapse — trigger becomes icon-only ───────────────────── */
65
+ /* At mobile widths (< 640px) the header is too crowded to show the
66
+ full search box; collapse to a magnifier icon. App.jsx passes
67
+ `inline-size: 30ch` inline on the trigger to fix its desktop width,
68
+ so we need !important here to win over the inline style. The kbd
69
+ chip is also hidden — at mobile there's no keyboard shortcut hint
70
+ to display. */
71
+ @container docs (max-width: 640px) {
72
+ .velu-search__trigger {
73
+ inline-size: auto !important;
74
+ padding-inline: var(--s-3);
75
+ }
76
+ .velu-search__trigger-label,
77
+ .velu-search__trigger .velu-search__kbd {
78
+ display: none;
79
+ }
80
+ }
81
+
82
+ /* ── Scrim + palette ────────────────────────────────────────────────── */
83
+ .velu-search__scrim {
84
+ position: fixed;
85
+ inset: 0;
86
+ z-index: 60;
87
+ display: flex;
88
+ align-items: flex-start;
89
+ justify-content: center;
90
+ /* Top offset eases down on short screens; side gutter is small so the
91
+ palette uses most of a narrow viewport (no effect on desktop —
92
+ the palette is capped at --vsearch-width there). */
93
+ padding-block-start: clamp(var(--s1), 8vh, 12vh);
94
+ padding-inline: var(--s-3);
95
+ background: color-mix(in srgb, #000 48%, transparent);
96
+ -webkit-backdrop-filter: blur(4px);
97
+ backdrop-filter: blur(4px);
98
+ animation: velu-search-scrim-in 0.18s ease-out;
99
+ }
100
+ @keyframes velu-search-scrim-in {
101
+ from { opacity: 0; }
102
+ to { opacity: 1; }
103
+ }
104
+
105
+ .velu-search__palette {
106
+ /* Overridable panel width (≈ the design's 720px). */
107
+ --vsearch-width: 45rem;
108
+ inline-size: var(--vsearch-width);
109
+ max-inline-size: 100%;
110
+ display: flex;
111
+ flex-direction: column;
112
+ overflow: hidden;
113
+ background: var(--page-bg);
114
+ color: var(--text-color);
115
+ border: var(--border-width) solid var(--border-color);
116
+ border-radius: var(--radius-md);
117
+ box-shadow:
118
+ 0 var(--s2) var(--s4) color-mix(in srgb, #000 32%, transparent),
119
+ 0 var(--s0) var(--s2) color-mix(in srgb, #000 18%, transparent);
120
+ animation: velu-search-palette-in 0.22s cubic-bezier(0.2, 0.8, 0.2, 1);
121
+ }
122
+ @keyframes velu-search-palette-in {
123
+ from {
124
+ opacity: 0;
125
+ transform: translateY(calc(var(--s-3) * -1)) scale(0.985);
126
+ }
127
+ to {
128
+ opacity: 1;
129
+ transform: none;
130
+ }
131
+ }
132
+ /* base.css's global `* { max-width: 66ch }` would cap the <input>;
133
+ reset within the palette — flex / the panel govern width here. */
134
+ .velu-search__palette * {
135
+ max-inline-size: none;
136
+ }
137
+
138
+ /* ── Input row ──────────────────────────────────────────────────────── */
139
+ .velu-search__input-wrap {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: var(--s-2);
143
+ padding: var(--s-1) var(--s0);
144
+ border-block-end: var(--border-width) solid var(--border-color);
145
+ }
146
+ .velu-search__input-icon {
147
+ display: inline-flex;
148
+ flex: none;
149
+ color: var(--muted-color);
150
+ }
151
+ .velu-search__input {
152
+ flex: 1 1 auto;
153
+ min-inline-size: 0;
154
+ padding: 0;
155
+ background: transparent;
156
+ border: 0;
157
+ outline: 0;
158
+ font: inherit;
159
+ font-size: var(--f-h4);
160
+ font-weight: var(--weight-light);
161
+ line-height: var(--lh-h4);
162
+ color: var(--text-color);
163
+ }
164
+ .velu-search__input::placeholder {
165
+ color: var(--muted-color);
166
+ }
167
+
168
+ /* ── Result list ────────────────────────────────────────────────────── */
169
+ .velu-search__list {
170
+ flex: 1 1 auto;
171
+ overflow-y: auto;
172
+ padding: var(--s-3);
173
+ max-block-size: 72vh;
174
+ }
175
+ .velu-search__group {
176
+ padding: var(--s-1) var(--s-1) var(--s-4);
177
+ font-size: var(--f-h7);
178
+ font-weight: var(--weight-medium);
179
+ letter-spacing: 0.08em;
180
+ text-transform: uppercase;
181
+ color: var(--muted-color);
182
+ }
183
+
184
+ /* Result row — grid: glyph | (breadcrumb / title / desc) | meta. */
185
+ .velu-search__row {
186
+ display: grid;
187
+ grid-template-columns: auto 1fr auto;
188
+ gap: var(--s-6) var(--s0);
189
+ align-items: center;
190
+ padding: var(--s-2);
191
+ border-radius: var(--radius-sm);
192
+ color: var(--text-color);
193
+ cursor: pointer;
194
+ }
195
+ .velu-search__row + .velu-search__row {
196
+ margin-block-start: var(--s-6);
197
+ }
198
+ .velu-search__row:hover {
199
+ background: var(--surface-color);
200
+ }
201
+ .velu-search__row--selected {
202
+ background: color-mix(in srgb, var(--accent-color) 12%, transparent);
203
+ }
204
+
205
+ .velu-search__row-glyph {
206
+ grid-column: 1;
207
+ grid-row: 1 / -1;
208
+ display: flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ inline-size: 2.5em;
212
+ block-size: 2.5em;
213
+ color: var(--text-color);
214
+ }
215
+ .velu-search__glyph-hash {
216
+ font-size: var(--f-h3);
217
+ font-weight: var(--weight-light);
218
+ line-height: 1;
219
+ color: var(--text-color);
220
+ }
221
+ .velu-search__glyph-icon {
222
+ display: inline-flex;
223
+ inline-size: var(--icon-size-lg);
224
+ block-size: var(--icon-size-lg);
225
+ color: var(--muted-color);
226
+ }
227
+ .velu-search__row--selected .velu-search__glyph-icon {
228
+ color: var(--text-color);
229
+ }
230
+
231
+ .velu-search__crumbs {
232
+ grid-column: 2;
233
+ grid-row: 1;
234
+ display: flex;
235
+ align-items: center;
236
+ flex-wrap: wrap;
237
+ gap: var(--s-4);
238
+ font-size: var(--f-h6);
239
+ line-height: var(--lh-h6);
240
+ color: var(--muted-color);
241
+ }
242
+ .velu-search__crumb-sep {
243
+ opacity: 0.6;
244
+ }
245
+ .velu-search__row-title {
246
+ grid-column: 2;
247
+ grid-row: 2;
248
+ font-size: var(--f-h5);
249
+ font-weight: var(--weight-medium);
250
+ line-height: var(--lh-h5);
251
+ color: var(--text-color);
252
+ }
253
+ .velu-search__row-desc {
254
+ grid-column: 2;
255
+ grid-row: 3;
256
+ font-size: var(--f-h6);
257
+ font-weight: var(--weight-light);
258
+ line-height: var(--lh-h6);
259
+ color: var(--muted-color);
260
+ overflow: hidden;
261
+ text-overflow: ellipsis;
262
+ white-space: nowrap;
263
+ }
264
+ .velu-search__row-meta {
265
+ grid-column: 3;
266
+ grid-row: 1 / -1;
267
+ display: flex;
268
+ align-items: center;
269
+ opacity: 0;
270
+ transition: opacity 0.15s ease;
271
+ }
272
+ .velu-search__row:hover .velu-search__row-meta,
273
+ .velu-search__row--selected .velu-search__row-meta {
274
+ opacity: 1;
275
+ }
276
+
277
+ /* ── Empty / no-results ─────────────────────────────────────────────── */
278
+ .velu-search__empty {
279
+ display: flex;
280
+ flex-direction: column;
281
+ align-items: center;
282
+ gap: var(--s-6);
283
+ padding: var(--s3) var(--s0);
284
+ text-align: center;
285
+ }
286
+ .velu-search__empty-icon {
287
+ display: inline-flex;
288
+ margin-block-end: var(--s-3);
289
+ font-size: var(--f-h2);
290
+ color: var(--muted-color);
291
+ }
292
+ .velu-search__empty-title {
293
+ font-size: var(--f-h6);
294
+ font-weight: var(--weight-medium);
295
+ color: var(--text-color);
296
+ }
297
+ .velu-search__empty-sub {
298
+ font-size: var(--f-h6);
299
+ color: var(--muted-color);
300
+ }
301
+
302
+ @media (prefers-reduced-motion: reduce) {
303
+ .velu-search__scrim,
304
+ .velu-search__palette {
305
+ animation: none;
306
+ }
307
+ }
@@ -0,0 +1,144 @@
1
+ /* Sidebar — sidebar-specific styling ONLY.
2
+
3
+ Vertical rhythm (nav, section lists, nested sublists) is composed from the
4
+ Stack primitive in Sidebar.jsx (DRY) — Stack supplies display/flex/gap
5
+ inline. This file holds only what Stack cannot express: the indent rails,
6
+ active accent rail, the horizontal item rows, summary marker, chevron
7
+ rotation, and centralized icon sizing/stroke. All values are tokens. */
8
+
9
+ .velu-sidebar {
10
+ border-left: var(--border-width) solid var(--surface-color);
11
+ margin-left: var(--s1);
12
+ }
13
+
14
+ .velu-sidebar__section {
15
+ /* Sticky section header: each heading pins to the top of the scroll
16
+ region until its own section scrolls past, then the next heading
17
+ takes over (iOS-list style). It sticks within its section wrapper
18
+ (Sidebar.jsx renders each section in its own Stack), which bounds
19
+ the push-out. Full-width opaque background so scrolling items
20
+ don't show behind it; --page-bg matches the surroundings, so the
21
+ bar is invisible until it's actually covering scrolled content. */
22
+ position: sticky;
23
+ inset-block-start: 0;
24
+ z-index: 1;
25
+ background: var(--page-bg);
26
+ display: flex;
27
+ align-items: center;
28
+ gap: var(--s-2);
29
+ padding-block: var(--s-4);
30
+ padding-inline: var(--s-3);
31
+ /* A notch smaller than the default h5, with a little tracking so the
32
+ uppercase label stays legible at the reduced size. */
33
+ font-size: var(--f-h6);
34
+ font-weight: 500;
35
+ letter-spacing: 0.02em;
36
+ text-transform: uppercase;
37
+ }
38
+ /* Fade zone just below the pinned heading: a gradient overlay
39
+ (page-bg → transparent) that sits directly under the sticky header
40
+ and scrolls with it. Items sliding up pass behind it and fade out
41
+ before they tuck under the (crisp) heading. Absolutely positioned
42
+ so it adds no resting layout space; pointer-events:none so it never
43
+ eats clicks. Hidden by default — only shown once the nav is actually
44
+ scrolled (the layout toggles it via data-fade-top on the scroll
45
+ region), so a section's first item isn't faded at rest. */
46
+ .velu-sidebar__section::after {
47
+ content: '';
48
+ position: absolute;
49
+ inset-inline: 0;
50
+ inset-block-start: 100%;
51
+ block-size: var(--s3);
52
+ background: linear-gradient(
53
+ to bottom,
54
+ var(--page-bg),
55
+ color-mix(in srgb, var(--page-bg) 55%, transparent),
56
+ transparent
57
+ );
58
+ pointer-events: none;
59
+ opacity: 0;
60
+ transition: opacity 0.15s ease;
61
+ }
62
+ /* When a heading becomes the newly-pinned one (data-stuck set by the
63
+ runtime as you scroll past sections), fade + slide it in so the
64
+ handoff from the previous heading reads as a transition rather than
65
+ a hard swap. */
66
+ .velu-sidebar__section[data-stuck='true'] {
67
+ animation: velu-sidebar-stick-in 0.2s ease;
68
+ }
69
+ @keyframes velu-sidebar-stick-in {
70
+ from {
71
+ opacity: 0.3;
72
+ transform: translateY(-0.2rem);
73
+ }
74
+ to {
75
+ opacity: 1;
76
+ transform: translateY(0);
77
+ }
78
+ }
79
+
80
+ /* Lists are Stacks (flex/gap come from Stack inline) — only reset list chrome. */
81
+ .velu-sidebar__list,
82
+ .velu-sidebar__sublist {
83
+ list-style: none;
84
+ margin: 0;
85
+ padding: 0;
86
+ }
87
+
88
+ .velu-sidebar__sublist {
89
+ margin-left: var(--s0);
90
+ border-left: var(--border-width) solid var(--border-color);
91
+ }
92
+
93
+ /* Item rows are horizontal (icon · label · chevron) — Stack is vertical-only,
94
+ so these stay CSS until/if a Cluster/Inline primitive exists. */
95
+ .velu-sidebar__item,
96
+ .velu-sidebar__subitem {
97
+ display: flex;
98
+ align-items: center;
99
+ gap: var(--s-3);
100
+ padding-inline: var(--s0);
101
+ color: var(--text-color);
102
+ text-decoration: none;
103
+ }
104
+
105
+ .velu-sidebar__subitem {
106
+ padding-block: var(--s-5);
107
+ }
108
+
109
+ .velu-sidebar__item--active {
110
+ color: var(--accent-color);
111
+ border-left: 2px solid var(--accent-color);
112
+ }
113
+
114
+ .velu-sidebar__summary {
115
+ cursor: pointer;
116
+ list-style: none;
117
+ }
118
+ .velu-sidebar__summary::-webkit-details-marker {
119
+ display: none;
120
+ }
121
+
122
+ /* Icon sizing centralized here; stroke weight is the global --icon-stroke
123
+ token (.lucide rule in base.css), not set per-component. */
124
+ .velu-sidebar__icon {
125
+ display: inline-flex;
126
+ flex: none;
127
+ inline-size: var(--f-body);
128
+ block-size: var(--f-body);
129
+ }
130
+ .velu-sidebar__icon svg {
131
+ inline-size: 100%;
132
+ block-size: 100%;
133
+ }
134
+
135
+ .velu-sidebar__chevron {
136
+ flex: none;
137
+ inline-size: var(--f-body);
138
+ block-size: var(--f-body);
139
+ transition: transform 0.15s ease;
140
+ }
141
+
142
+ details[open] > .velu-sidebar__summary .velu-sidebar__chevron {
143
+ transform: rotate(90deg);
144
+ }
@@ -0,0 +1,77 @@
1
+ /* Steps — numbered procedural list with a continuous accent rail
2
+ running through every step's left margin. All values are tokens;
3
+ light/dark via [data-theme]. */
4
+
5
+ .velu-steps {
6
+ display: block;
7
+ }
8
+
9
+ /* Each step is a 2-col / 2-row grid placed explicitly:
10
+
11
+ col 1 col 2
12
+ ┌──────────────────┬────────────────────┐
13
+ │ circle (row 1, │ title (row 1, │
14
+ │ align-self: end) │ align-self: end) │
15
+ ├──────────────────┼────────────────────┤
16
+ │ line (row 2, │ body (row 2) │
17
+ │ 100% block-size) │ │
18
+ └──────────────────┴────────────────────┘
19
+
20
+ `align-self: end` on both row-1 items puts the circle's bottom and
21
+ the title's bottom flush together — so an oversize circle no longer
22
+ hovers above the title. Row 1 height is whichever child is taller
23
+ (the circle). row-gap stays at 0 so the line touches the next step's
24
+ circle with no visible break in the rail. */
25
+ .velu-step {
26
+ display: grid;
27
+ grid-template-columns: var(--step-circle, 2em) 1fr;
28
+ column-gap: var(--s0);
29
+ row-gap: 0;
30
+ }
31
+
32
+ .velu-step__circle {
33
+ grid-row: 1;
34
+ grid-column: 1;
35
+ align-self: center;
36
+ justify-self: center;
37
+ inline-size: var(--step-circle, 2em);
38
+ block-size: var(--step-circle, 2em);
39
+ border-radius: 50%;
40
+ background: var(--accent-color);
41
+ color: var(--page-bg);
42
+ display: inline-flex;
43
+ align-items: center;
44
+ justify-content: center;
45
+ font-size: var(--f-h6);
46
+ line-height: 1;
47
+ font-weight: var(--weight-medium);
48
+ }
49
+
50
+ .velu-step__line {
51
+ grid-row: 2;
52
+ grid-column: 1;
53
+ justify-self: center;
54
+ inline-size: 2px;
55
+ block-size: 100%;
56
+ min-block-size: var(--s2);
57
+ background: var(--accent-color);
58
+ }
59
+
60
+ .velu-step__title {
61
+ grid-row: 1;
62
+ grid-column: 2;
63
+ align-self: center;
64
+ font-size: var(--f-h4);
65
+ line-height: var(--lh-h4);
66
+ font-weight: var(--weight-medium);
67
+ color: var(--text-color);
68
+ }
69
+
70
+ .velu-step__body {
71
+ grid-row: 2;
72
+ grid-column: 2;
73
+ padding-block: var(--s-1) var(--s2);
74
+ color: var(--text-color);
75
+ font-size: var(--f-body);
76
+ line-height: var(--lh-body);
77
+ }
@@ -0,0 +1,70 @@
1
+ /* ThemeToggle — circular button with a sun/moon glyph; click flips
2
+ the theme. Geometry is a 32 × 32 circle with an 18 × 18 glyph
3
+ centred inside. The pill-with-slider design was replaced earlier;
4
+ only the circle survives now.
5
+
6
+ In light mode: --page-bg circle, sun glyph.
7
+ In dark mode: --page-bg circle, moon glyph.
8
+
9
+ Visibility model: BOTH icons are always rendered, stacked at the
10
+ same `position: absolute` slot inside the button. Opacity controls
11
+ which one is visible. We use opacity (not display: none) because:
12
+
13
+ 1. Stacked icons render at the exact same point — no layout
14
+ shift when the theme flips.
15
+ 2. Opacity is not touched anywhere else for these elements, so
16
+ the cascade is trivial — no risk of a stray rule (preflight,
17
+ utility class, dev-time fallback) overriding `display: none`
18
+ and revealing the wrong icon.
19
+ 3. Identical markup on server + client → SSR-safe, no hydration
20
+ mismatch and no icon flash.
21
+
22
+ All values are tokens. */
23
+
24
+ .velu-theme-toggle {
25
+ /* `relative` so the absolutely-positioned icons inside are
26
+ anchored to this button, not the page. */
27
+ position: relative;
28
+ display: inline-flex;
29
+ align-items: center;
30
+ justify-content: center;
31
+ inline-size: 2rem;
32
+ block-size: 2rem;
33
+ border: var(--border-width) solid var(--border-color);
34
+ border-radius: 999px;
35
+ padding: 0;
36
+ background: var(--page-bg);
37
+ cursor: pointer;
38
+ transition: border-color 0.12s ease, background 0.12s ease;
39
+ }
40
+ .velu-theme-toggle:hover {
41
+ border-color: var(--accent-color);
42
+ }
43
+
44
+ /* Both icons stack here, centred. Default opacity 0 — the per-theme
45
+ rules below switch one on. */
46
+ .velu-theme-toggle__icon {
47
+ position: absolute;
48
+ inset-block-start: 50%;
49
+ inset-inline-start: 50%;
50
+ inline-size: 1.125rem;
51
+ block-size: 1.125rem;
52
+ margin-block-start: -0.5625rem; /* half of block-size */
53
+ margin-inline-start: -0.5625rem; /* half of inline-size */
54
+ color: var(--text-color);
55
+ opacity: 0;
56
+ transition: opacity 0.2s ease;
57
+ pointer-events: none;
58
+ }
59
+
60
+ /* Default (light) → sun on. */
61
+ .velu-theme-toggle__icon--sun {
62
+ opacity: 1;
63
+ }
64
+ /* Dark theme → moon on, sun off. */
65
+ [data-theme='dark'] .velu-theme-toggle__icon--sun {
66
+ opacity: 0;
67
+ }
68
+ [data-theme='dark'] .velu-theme-toggle__icon--moon {
69
+ opacity: 1;
70
+ }