@veluai/velu 0.1.13 → 0.1.15

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 (98) hide show
  1. package/README.md +80 -80
  2. package/dist/cli.js +21 -21
  3. package/package.json +3 -2
  4. package/runtime/velu-ui/components/Accordion.jsx +64 -64
  5. package/runtime/velu-ui/components/ApiClient.jsx +121 -121
  6. package/runtime/velu-ui/components/ApiField.jsx +87 -87
  7. package/runtime/velu-ui/components/ApiPath.jsx +63 -63
  8. package/runtime/velu-ui/components/ApiSidebar.jsx +122 -122
  9. package/runtime/velu-ui/components/AskBar.jsx +71 -71
  10. package/runtime/velu-ui/components/Callout.jsx +114 -114
  11. package/runtime/velu-ui/components/Card.jsx +131 -131
  12. package/runtime/velu-ui/components/Chatbot.jsx +596 -596
  13. package/runtime/velu-ui/components/CodeBlock.jsx +375 -375
  14. package/runtime/velu-ui/components/Columns.jsx +56 -56
  15. package/runtime/velu-ui/components/ErrorCard.jsx +138 -138
  16. package/runtime/velu-ui/components/Field.jsx +81 -81
  17. package/runtime/velu-ui/components/Image.jsx +163 -163
  18. package/runtime/velu-ui/components/Logo.jsx +31 -0
  19. package/runtime/velu-ui/components/MethodBadge.jsx +31 -31
  20. package/runtime/velu-ui/components/NavSelect.jsx +108 -108
  21. package/runtime/velu-ui/components/PageFeedback.jsx +219 -219
  22. package/runtime/velu-ui/components/PageFooter.jsx +145 -213
  23. package/runtime/velu-ui/components/PageHeader.jsx +422 -414
  24. package/runtime/velu-ui/components/PageNav.jsx +77 -77
  25. package/runtime/velu-ui/components/PoweredBy.jsx +51 -51
  26. package/runtime/velu-ui/components/Prompt.jsx +115 -115
  27. package/runtime/velu-ui/components/Search.jsx +411 -366
  28. package/runtime/velu-ui/components/Sidebar.jsx +191 -191
  29. package/runtime/velu-ui/components/SocialLinks.jsx +90 -0
  30. package/runtime/velu-ui/components/Steps.jsx +65 -65
  31. package/runtime/velu-ui/components/ThemeToggle.jsx +48 -48
  32. package/runtime/velu-ui/components/Toc.jsx +537 -537
  33. package/runtime/velu-ui/components/TocBar.jsx +195 -195
  34. package/runtime/velu-ui/components/Tree.jsx +87 -87
  35. package/runtime/velu-ui/components/TryItBar.jsx +90 -90
  36. package/runtime/velu-ui/components/accordion.css +92 -92
  37. package/runtime/velu-ui/components/api.css +479 -479
  38. package/runtime/velu-ui/components/ask-bar.css +94 -94
  39. package/runtime/velu-ui/components/card.css +105 -105
  40. package/runtime/velu-ui/components/chatbot.css +617 -617
  41. package/runtime/velu-ui/components/code-block.css +263 -263
  42. package/runtime/velu-ui/components/docs-layout.css +784 -775
  43. package/runtime/velu-ui/components/field.css +82 -82
  44. package/runtime/velu-ui/components/image.css +237 -237
  45. package/runtime/velu-ui/components/nav-select.css +157 -157
  46. package/runtime/velu-ui/components/page-feedback.css +241 -241
  47. package/runtime/velu-ui/components/page-footer.css +130 -130
  48. package/runtime/velu-ui/components/page-header.css +557 -520
  49. package/runtime/velu-ui/components/page-nav.css +50 -50
  50. package/runtime/velu-ui/components/powered-by.css +86 -66
  51. package/runtime/velu-ui/components/prompt.css +99 -99
  52. package/runtime/velu-ui/components/search.css +307 -307
  53. package/runtime/velu-ui/components/sidebar.css +144 -144
  54. package/runtime/velu-ui/components/steps.css +77 -77
  55. package/runtime/velu-ui/components/theme-toggle.css +101 -101
  56. package/runtime/velu-ui/components/toc-bar.css +234 -234
  57. package/runtime/velu-ui/components/tree.css +49 -49
  58. package/runtime/velu-ui/index.js +48 -46
  59. package/runtime/velu-ui/lib/component-schemas.js +100 -100
  60. package/runtime/velu-ui/lib/copyText.js +64 -64
  61. package/runtime/velu-ui/lib/lang-icons.jsx +147 -147
  62. package/runtime/velu-ui/lib/prism-langs.js +957 -957
  63. package/runtime/velu-ui/lib/prism-loader.js +74 -74
  64. package/runtime/velu-ui/lib/resolveIcon.jsx +29 -29
  65. package/runtime/velu-ui/lib/scrollIntoNearestView.js +66 -66
  66. package/runtime/velu-ui/mdx-components.jsx +105 -105
  67. package/runtime/velu-ui/primitives/Cluster.jsx +49 -49
  68. package/runtime/velu-ui/primitives/Stack.jsx +63 -63
  69. package/runtime/velu-ui/primitives/Switcher.jsx +57 -57
  70. package/runtime/velu-ui/primitives/stack.css +3 -3
  71. package/runtime/velu-ui/primitives/switcher.css +25 -25
  72. package/runtime/velu-ui/styles.css +43 -43
  73. package/runtime/velu-ui/tokens.css +4 -4
  74. package/schema/velu.schema.json +281 -167
  75. package/src/lib/extract-mdx-error.js +170 -170
  76. package/src/lib/issues.js +159 -159
  77. package/src/lib/known-components.js +34 -34
  78. package/src/navigation.js +434 -434
  79. package/src/runtime/App.jsx +1506 -1476
  80. package/src/runtime/ErrorBoundary.jsx +54 -54
  81. package/src/runtime/client-entry.jsx +22 -22
  82. package/src/runtime/server-entry.jsx +16 -16
  83. package/src/template.html +48 -48
  84. package/templates/starter/ai-tools/claude-code.mdx +26 -26
  85. package/templates/starter/ai-tools/cursor.mdx +17 -17
  86. package/templates/starter/api-reference/endpoint/create.mdx +24 -24
  87. package/templates/starter/api-reference/endpoint/get.mdx +27 -27
  88. package/templates/starter/api-reference/introduction.mdx +28 -28
  89. package/templates/starter/development.mdx +19 -19
  90. package/templates/starter/essentials/code.mdx +29 -29
  91. package/templates/starter/essentials/images.mdx +29 -29
  92. package/templates/starter/essentials/markdown.mdx +25 -25
  93. package/templates/starter/essentials/navigation.mdx +39 -39
  94. package/templates/starter/essentials/settings.mdx +30 -30
  95. package/templates/starter/favicon.svg +6 -6
  96. package/templates/starter/index.mdx +31 -31
  97. package/templates/starter/quickstart.mdx +31 -31
  98. package/templates/starter/velu.json +33 -33
@@ -1,520 +1,557 @@
1
- /* PageHeader — top-of-page header. All values are tokens; light/dark
2
- via [data-theme]. Vertical rhythm is the Stack primitive; columns
3
- are CSS grid (so the centre slot stays centred regardless of how
4
- wide the brand or actions get). */
5
-
6
- .velu-header {
7
- /* Sticky to the viewport top so the header stays visible as the
8
- page scrolls. z-index is above sidebars/article content but
9
- below modals (chatbot z:50, search palette z:60). */
10
- position: sticky;
11
- inset-block-start: 0;
12
- z-index: 30;
13
- /* No bottom padding — the tabs row sits flush at the header's
14
- bottom edge so the active tab's 2px underline meets the header's
15
- bottom border (the selector "touches" the header line). */
16
- padding-block-start: var(--s-1);
17
- padding-block-end: 0;
18
- /* Symmetric inline gutter so the brand (left) and the actions
19
- (right) are pulled in to the same level and align with the page
20
- content below. */
21
- padding-inline: var(--s5);
22
- /* --surface-color resolves to #f2f3f4 in light mode (and themes
23
- to #323334 in dark) — a subtle hairline separator. */
24
- border-block-end: var(--border-width) solid var(--surface-color);
25
- background: var(--page-bg);
26
- color: var(--text-color);
27
- /* Background + shadow animate so the rest-state↔scrolled-state flip
28
- is smooth (not a hard cut). backdrop-filter can't transition
29
- cleanly in every engine, so we don't try. */
30
- transition: background 0.18s ease, box-shadow 0.18s ease;
31
- /* Lift shadow recipe, theme-aware. On the near-black dark surface a
32
- low-alpha black shadow is invisible, so dark mode uses a much
33
- higher alpha + larger blur to read as a lift. */
34
- --velu-header-shadow: 0 var(--s-4) var(--s0)
35
- color-mix(in srgb, #000 8%, transparent);
36
- }
37
- [data-theme='dark'] .velu-header {
38
- --velu-header-shadow: 0 var(--s-3) var(--s1)
39
- color-mix(in srgb, #000 55%, transparent);
40
- }
41
-
42
- /* Once the user has scrolled even a single pixel, the header becomes
43
- a translucent frosted-glass overlay so the article content shows
44
- through faintly underneath. color-mix keeps the recipe token-driven:
45
- 75% --page-bg + transparent → 75% opaque in either theme. */
46
- .velu-header--scrolled {
47
- background: color-mix(in srgb, var(--page-bg) 75%, transparent);
48
- backdrop-filter: saturate(140%) blur(12px);
49
- -webkit-backdrop-filter: saturate(140%) blur(12px);
50
- /* Lift shadow (theme-aware recipe defined on .velu-header) so the
51
- header reads as raised above the article scrolling beneath it. */
52
- box-shadow: var(--velu-header-shadow);
53
- }
54
-
55
-
56
- /* ── Top row ────────────────────────────────────────────────────────── */
57
- /* 3-column grid: brand at the inline-start, center slot in the middle,
58
- actions at the inline-end. The center column flexes (1fr) so the
59
- brand and actions stay snug to their edges while the search sits
60
- between them; justify-self centers the center node within its 1fr
61
- cell so it doesn't stretch. */
62
- .velu-header__top {
63
- display: grid;
64
- grid-template-columns: auto 1fr auto;
65
- align-items: center;
66
- gap: var(--s1);
67
- }
68
- .velu-header__col--brand {
69
- justify-self: start;
70
- }
71
- .velu-header__col--center {
72
- justify-self: center;
73
- min-inline-size: 0;
74
- }
75
- .velu-header__col--actions {
76
- justify-self: end;
77
- }
78
-
79
- /* Brand */
80
- .velu-header__brand-link {
81
- display: inline-flex;
82
- align-items: center;
83
- text-decoration: none;
84
- color: inherit;
85
- }
86
- .velu-header__mark {
87
- flex: none;
88
- /* 32 × 24 — exact ratio of the brand mark's viewBox. */
89
- inline-size: 2em;
90
- block-size: 1.5em;
91
- color: var(--accent-color);
92
- }
93
- .velu-header__wordmark {
94
- font-family: var(--font-brand);
95
- font-weight: var(--weight-light);
96
- /* 32px wordmark. */
97
- font-size: 2rem;
98
- line-height: 1;
99
- letter-spacing: -0.01em;
100
- color: var(--text-color);
101
- }
102
-
103
- /* ── Action buttons ─────────────────────────────────────────────────── */
104
- .velu-header__action {
105
- display: inline-flex;
106
- align-items: center;
107
- gap: var(--s-3);
108
- padding: var(--s-3);
109
- border-radius: var(--radius-sm);
110
- font: inherit;
111
- font-size: var(--f-h6);
112
- font-weight: var(--weight-medium);
113
- /* No explicit line-height inherits the page's --lh-body so the
114
- button computes to the same height as the Search trigger (which
115
- also inherits). The button's text contributes the same vertical
116
- space as the search label. */
117
- text-decoration: none;
118
- cursor: pointer;
119
- transition: background 0.12s ease, border-color 0.12s ease,
120
- color 0.12s ease;
121
- }
122
- .velu-header__action--outlined {
123
- background: var(--page-bg);
124
- border: var(--border-width) solid var(--border-color);
125
- color: var(--text-color);
126
- }
127
- .velu-header__action--outlined:hover {
128
- border-color: var(--accent-color);
129
- color: var(--accent-color);
130
- }
131
- .velu-header__action--primary {
132
- background: var(--accent-color);
133
- border: var(--border-width) solid var(--accent-color);
134
- color: #fff;
135
- }
136
- .velu-header__action--primary:hover {
137
- background: color-mix(in srgb, var(--accent-color) 88%, #000);
138
- }
139
- .velu-header__action-icon {
140
- display: inline-flex;
141
- flex: none;
142
- /* Match the Search trigger's icon at rest — muted. Flips to the
143
- accent color on hover of an outlined action (the text + border
144
- also go accent there, so the icon shifts with them). */
145
- color: var(--muted-color);
146
- transition: color 0.12s ease;
147
- }
148
- .velu-header__action--outlined:hover .velu-header__action-icon {
149
- color: var(--accent-color);
150
- }
151
-
152
- /* Inline list of action buttons. Same cluster spacing as before; gets
153
- `display: none` at narrow widths so the kebab takes over. */
154
- .velu-header__actions-list {
155
- display: inline-flex;
156
- align-items: center;
157
- gap: var(--s-2);
158
- }
159
-
160
- /* ── Kebab overflow menu (narrow widths only) ──────────────────────── */
161
- /* Hidden at wide widthsthe inline action list is shown instead. */
162
- .velu-header__kebab {
163
- position: relative;
164
- display: none;
165
- }
166
- .velu-header__kebab-toggle {
167
- display: inline-flex;
168
- align-items: center;
169
- justify-content: center;
170
- inline-size: 2rem;
171
- block-size: 2rem;
172
- padding: 0;
173
- background: transparent;
174
- border: 0;
175
- border-radius: var(--radius-sm);
176
- color: var(--muted-color);
177
- cursor: pointer;
178
- transition: color 0.12s ease, background 0.12s ease;
179
- }
180
- .velu-header__kebab-toggle:hover {
181
- color: var(--text-color);
182
- background: var(--surface-color);
183
- }
184
-
185
- /* Dropdown — anchored to the kebab button's bottom-right. Same fabric
186
- as the page (no shadow flourish — matches the TocBar dropdown). */
187
- .velu-header__kebab-menu {
188
- position: absolute;
189
- inset-block-start: calc(100% + var(--s-3));
190
- inset-inline-end: 0;
191
- margin: 0;
192
- padding-block: var(--s-2);
193
- padding-inline: 0;
194
- list-style: none;
195
- min-inline-size: 10rem;
196
- background: var(--page-bg);
197
- border: var(--border-width) solid var(--border-color);
198
- border-radius: var(--radius-sm);
199
- opacity: 0;
200
- visibility: hidden;
201
- transform: translateY(-0.25rem);
202
- pointer-events: none;
203
- transition:
204
- opacity 0.15s ease,
205
- transform 0.15s ease,
206
- visibility 0s linear 0.15s;
207
- z-index: 31;
208
- }
209
- .velu-header__kebab[data-open='true'] .velu-header__kebab-menu {
210
- opacity: 1;
211
- visibility: visible;
212
- transform: translateY(0);
213
- pointer-events: auto;
214
- transition:
215
- opacity 0.15s ease,
216
- transform 0.15s ease,
217
- visibility 0s;
218
- }
219
- .velu-header__kebab-item {
220
- display: flex;
221
- align-items: center;
222
- gap: var(--s-3);
223
- inline-size: 100%;
224
- padding-block: var(--s-2);
225
- padding-inline: var(--s0);
226
- background: transparent;
227
- border: 0;
228
- font: inherit;
229
- font-size: var(--f-h6);
230
- color: var(--text-color);
231
- text-align: start;
232
- text-decoration: none;
233
- cursor: pointer;
234
- transition: background 0.12s ease, color 0.12s ease;
235
- }
236
- .velu-header__kebab-item:hover {
237
- background: var(--surface-color);
238
- color: var(--accent-color);
239
- }
240
- .velu-header__kebab-icon {
241
- display: inline-flex;
242
- flex: none;
243
- color: var(--muted-color);
244
- }
245
- .velu-header__kebab-item:hover .velu-header__kebab-icon {
246
- color: var(--accent-color);
247
- }
248
-
249
- /* ── Breadcrumb (mobile-only replacement for tabs) ─────────────────── */
250
- /* Single-line trail with character-unit max width — long content
251
- truncates with `text-overflow: ellipsis` rather than reflowing the
252
- row or pushing the trailing burger. `display: inline-block` (not
253
- flex) so the inline children can be ellipsed by text-overflow,
254
- which only applies to inline-level content. Hidden by default;
255
- the @container block at the bottom flips it on at mobile widths. */
256
- .velu-header__crumbs {
257
- --velu-crumb-max: 24ch;
258
- display: none;
259
- align-items: center;
260
- flex-wrap: nowrap;
261
- max-inline-size: var(--velu-crumb-max);
262
- min-inline-size: 0;
263
- overflow: hidden;
264
- /* Native-button reset — the strip is now a <button> so we need to
265
- null out the browser's default chrome (background, border,
266
- focus padding) and inherit the font from the header. */
267
- padding: 0;
268
- background: transparent;
269
- border: 0;
270
- font: inherit;
271
- color: var(--text-color);
272
- font-size: var(--f-h6);
273
- cursor: pointer;
274
- }
275
- /* Each segment is a flex child that can shrink independently — the
276
- final ("current page") segment is the one that ellipses if the
277
- trail outgrows the max-width. */
278
- .velu-header__crumb {
279
- flex: 0 1 auto;
280
- min-inline-size: 0;
281
- overflow: hidden;
282
- white-space: nowrap;
283
- text-overflow: ellipsis;
284
- }
285
- .velu-header__crumb-link {
286
- flex: 0 1 auto;
287
- min-inline-size: 0;
288
- overflow: hidden;
289
- white-space: nowrap;
290
- text-overflow: ellipsis;
291
- color: inherit;
292
- text-decoration: none;
293
- }
294
- .velu-header__crumb-link:hover {
295
- color: var(--accent-color);
296
- }
297
- /* Chevron separator — never shrinks (flex: none). Sized in `em` so
298
- it scales with the breadcrumb's font-size (h6 currently); 1em
299
- matches the text glyph height, keeping the trail visually
300
- balanced if --f-h6 ever changes. Gap to neighbouring segments
301
- comes from `margin-inline: var(--s-3)` (~8px). */
302
- .velu-header__crumb-sep {
303
- flex: none;
304
- inline-size: 1em;
305
- block-size: 1em;
306
- stroke-width: 1;
307
- margin-inline: var(--s-3);
308
- }
309
-
310
- /* ── Mobile burger nav button ──────────────────────────────────────── */
311
- /* Hidden by default; the @container query at the bottom flips it to
312
- `inline-flex` at mobile widths. Defined BEFORE the @container so
313
- that block's `display: inline-flex` overrides this `display: none`
314
- via source-order cascade (same specificity). Sized 1.5rem (24px)
315
- to match the top-row icon glyphs — no inner padding, the button
316
- IS the icon. */
317
- /* Trailing slot at the right end of the tabs row (e.g. language
318
- switcher). Pushed right by auto margin; hidden on mobile (above). */
319
- .velu-header__tabs-trailing {
320
- margin-inline-start: auto;
321
- }
322
-
323
- .velu-header__menu {
324
- display: none;
325
- align-items: center;
326
- justify-content: center;
327
- inline-size: 1.5rem;
328
- block-size: 1.5rem;
329
- padding: 0;
330
- background: transparent;
331
- border: 0;
332
- border-radius: var(--radius-sm);
333
- color: var(--muted-color);
334
- cursor: pointer;
335
- transition: color 0.12s ease, background 0.12s ease;
336
- }
337
- .velu-header__menu:hover {
338
- color: var(--text-color);
339
- background: var(--surface-color);
340
- }
341
-
342
- /* ── Narrow-width swap ─────────────────────────────────────────────── */
343
- /* Same threshold as docs-layout / toc-bar — at < 1024px the inline
344
- action buttons collapse into the kebab menu. The header lives inside
345
- `.velu-docs-layout` (which is the `docs` container), so this query
346
- reacts to the layout's width, not the viewport. */
347
- @container docs (max-width: 1024px) {
348
- .velu-header__actions-list {
349
- display: none;
350
- }
351
- .velu-header__kebab {
352
- display: inline-block;
353
- }
354
- /* The big desktop inline inset is desktop-only; dial it back at
355
- tablet (mobile reduces it further below). */
356
- .velu-header {
357
- padding-inline: var(--s2);
358
- }
359
- }
360
-
361
- /* ── Mobile collapse (< 640px) ─────────────────────────────────────── */
362
- /* At mobile widths the header has to fit brand + search-icon + ask-ai
363
- + kebab on one row at 375px. Hide the labels on action buttons (the
364
- icon span keeps its class, so the unnamed text span is the second
365
- child) and tighten the column gap. The trailing slot (theme toggle)
366
- is hidden — it can be re-surfaced inside the kebab dropdown later. */
367
- @container docs (max-width: 640px) {
368
- /* Language switcher (tabs-row trailing slot) folds into the drawer
369
- on mobile. */
370
- .velu-header__tabs-trailing {
371
- display: none;
372
- }
373
- /* Drop the visible "Ask AI" / "Book Demo" label; icon survives. */
374
- .velu-header__action > span:not(.velu-header__action-icon) {
375
- display: none;
376
- }
377
- /* Compact padding when only the icon remains. */
378
- .velu-header__action {
379
- padding: var(--s-3);
380
- }
381
- /* Theme toggle hidden at mobile too crowded; users can rely on
382
- OS-level theme until we surface it inside the kebab. */
383
- .velu-header__col--actions .velu-theme-toggle {
384
- display: none;
385
- }
386
- /* 16px gap between the top-right icons (Search ↔ AskAI ↔ Kebab) —
387
- applied at three levels because the icons live in two separate
388
- Cluster components whose `space` prop sets an inline `gap`, so the
389
- CSS needs `!important` to win over the inline style. --s0 is 1rem
390
- which evaluates to 16px at the default root font-size. */
391
- .velu-header__top {
392
- gap: var(--s0) !important;
393
- }
394
- .velu-header__col--center,
395
- .velu-header__col--actions {
396
- gap: var(--s0) !important;
397
- }
398
- /* Reduce the header's outer horizontal padding so brand + actions
399
- get more breathing room at 375px. */
400
- .velu-header {
401
- padding-inline: var(--s-1);
402
- }
403
- /* Shrink the wordmark slightly 2rem competes with the icon row
404
- for horizontal space; 1.5rem still reads as the brand. */
405
- .velu-header__wordmark {
406
- font-size: 1.5rem;
407
- }
408
- /* Flatten the 3-col grid into a flex row so the center cluster
409
- (Search + Ask AI) sits flush with the actions cluster (Kebab) on
410
- the right side. `margin-inline-start: auto` on center pushes both
411
- clusters to the trailing edge, brand stays at the leading edge. */
412
- .velu-header__top {
413
- display: flex;
414
- align-items: center;
415
- }
416
- .velu-header__col--center {
417
- justify-self: auto;
418
- margin-inline-start: auto;
419
- }
420
- .velu-header__col--actions {
421
- justify-self: auto;
422
- }
423
- /* Borderless / transparent icon buttons in the right cluster at
424
- mobile they read as a row of icon chips alongside the kebab, not
425
- pill-shaped buttons. */
426
- .velu-header__col--center .velu-search__trigger,
427
- .velu-header__col--center .velu-header__action--outlined,
428
- .velu-header__col--center .velu-header__action--primary {
429
- background: transparent;
430
- border: 0;
431
- }
432
- /* Uniform 1.5rem glyphs across all icon buttons in the right cluster
433
- (Search, Ask AI, kebab, burger) so they read as a single coherent
434
- row. Targets the SVG directly — lucide icons render as <svg>.
435
- `stroke-width: 1` thins the lucide default (2) so the icons read
436
- as light line glyphs rather than chunky outlines. */
437
- .velu-header__col--center .velu-search__trigger-icon svg,
438
- .velu-header__col--center .velu-header__action-icon svg,
439
- .velu-header__kebab-toggle svg,
440
- .velu-header__menu svg {
441
- inline-size: 1.5rem;
442
- block-size: 1.5rem;
443
- stroke-width: 1;
444
- }
445
- /* Use the body text color (not muted) for the icon buttons at
446
- mobile without the surrounding label/border to give them
447
- weight, the muted-grey tint reads as "disabled". */
448
- .velu-header__col--center .velu-search__trigger,
449
- .velu-header__col--center .velu-search__trigger-icon,
450
- .velu-header__col--center .velu-header__action,
451
- .velu-header__col--center .velu-header__action-icon,
452
- .velu-header__kebab-toggle,
453
- .velu-header__menu {
454
- color: var(--text-color);
455
- }
456
- /* Tabs row swap: hide the individual tabs (they collapse into the
457
- mobile menu later), reveal the breadcrumb in their place. The
458
- `.velu-header__tabs > .velu-header__tab` selector raises the
459
- specificity above the base `.velu-header__tab { display: inline-flex }`
460
- rule that appears later in this file, so the cascade resolves in
461
- this block's favor without re-ordering the whole file. */
462
- .velu-header__tabs > .velu-header__tab {
463
- display: none;
464
- }
465
- .velu-header__crumbs {
466
- display: flex;
467
- }
468
- /* Burger sits at the trailing edge of the tabs row — pushes itself
469
- to the right via auto margin so the tabs cluster left as usual.
470
- Hidden at wider widths via the default `display: none` below. */
471
- .velu-header__menu {
472
- display: inline-flex;
473
- margin-inline-start: auto;
474
- }
475
- .velu-header__menu svg {
476
- inline-size: 1.5rem;
477
- block-size: 1.5rem;
478
- }
479
- }
480
-
481
-
482
- /* ── Tabs row ───────────────────────────────────────────────────────── */
483
- /* Tab is a horizontal link row with an optional icon, a label, and an
484
- optional external-link chip. Active tab has accent color + an
485
- underline rail. */
486
- .velu-header__tab {
487
- display: inline-flex;
488
- align-items: center;
489
- gap: var(--s-3);
490
- padding-block: var(--s-3);
491
- padding-inline: var(--s-3);
492
- border-block-end: 2px solid transparent;
493
- color: var(--text-color);
494
- font-size: var(--f-h6);
495
- font-weight: var(--weight-medium);
496
- text-decoration: none;
497
- transition: color 0.12s ease, border-color 0.12s ease;
498
- }
499
- .velu-header__tab:hover {
500
- color: var(--accent-color);
501
- }
502
- .velu-header__tab--active {
503
- color: var(--accent-color);
504
- border-block-end-color: var(--accent-color);
505
- }
506
- .velu-header__tab-icon,
507
- .velu-header__tab-external {
508
- display: inline-flex;
509
- flex: none;
510
- color: var(--muted-color);
511
- }
512
- /* On hover / active, the icon follows the tab's accent color. */
513
- .velu-header__tab:hover .velu-header__tab-icon,
514
- .velu-header__tab--active .velu-header__tab-icon {
515
- color: inherit;
516
- }
517
- .velu-header__tab-external {
518
- font-size: var(--f-h7);
519
- opacity: 0.7;
520
- }
1
+ /* PageHeader — top-of-page header. All values are tokens; light/dark
2
+ via [data-theme]. Vertical rhythm is the Stack primitive; columns
3
+ are CSS grid (so the centre slot stays centred regardless of how
4
+ wide the brand or actions get). */
5
+
6
+ .velu-header {
7
+ /* Sticky to the viewport top so the header stays visible as the
8
+ page scrolls. z-index is above sidebars/article content but
9
+ below modals (chatbot z:50, search palette z:60). */
10
+ position: sticky;
11
+ inset-block-start: 0;
12
+ z-index: 30;
13
+ /* No bottom padding — the tabs row sits flush at the header's
14
+ bottom edge so the active tab's 2px underline meets the header's
15
+ bottom border (the selector "touches" the header line). */
16
+ padding-block-start: var(--s-1);
17
+ padding-block-end: 0;
18
+ /* Symmetric inline gutter so the brand (left) and the actions
19
+ (right) are pulled in to the same level and align with the page
20
+ content below. */
21
+ padding-inline: var(--s5);
22
+ /* --surface-color resolves to #f2f3f4 in light mode (and themes
23
+ to #323334 in dark) — a subtle hairline separator. */
24
+ border-block-end: var(--border-width) solid var(--surface-color);
25
+ background: var(--page-bg);
26
+ color: var(--text-color);
27
+ /* Background + shadow animate so the rest-state↔scrolled-state flip
28
+ is smooth (not a hard cut). backdrop-filter can't transition
29
+ cleanly in every engine, so we don't try. */
30
+ transition: background 0.18s ease, box-shadow 0.18s ease;
31
+ /* Lift shadow recipe, theme-aware. On the near-black dark surface a
32
+ low-alpha black shadow is invisible, so dark mode uses a much
33
+ higher alpha + larger blur to read as a lift. */
34
+ --velu-header-shadow: 0 var(--s-4) var(--s0)
35
+ color-mix(in srgb, #000 8%, transparent);
36
+ }
37
+ [data-theme='dark'] .velu-header {
38
+ --velu-header-shadow: 0 var(--s-3) var(--s1)
39
+ color-mix(in srgb, #000 55%, transparent);
40
+ }
41
+
42
+ /* Once the user has scrolled even a single pixel, the header becomes
43
+ a translucent frosted-glass overlay so the article content shows
44
+ through faintly underneath. color-mix keeps the recipe token-driven:
45
+ 75% --page-bg + transparent → 75% opaque in either theme. */
46
+ .velu-header--scrolled {
47
+ background: color-mix(in srgb, var(--page-bg) 75%, transparent);
48
+ backdrop-filter: saturate(140%) blur(12px);
49
+ -webkit-backdrop-filter: saturate(140%) blur(12px);
50
+ /* Lift shadow (theme-aware recipe defined on .velu-header) so the
51
+ header reads as raised above the article scrolling beneath it. */
52
+ box-shadow: var(--velu-header-shadow);
53
+ }
54
+
55
+
56
+ /* ── Top row ────────────────────────────────────────────────────────── */
57
+ /* 3-column grid: brand at the inline-start, center slot in the middle,
58
+ actions at the inline-end. The center column flexes (1fr) so the
59
+ brand and actions stay snug to their edges while the search sits
60
+ between them; justify-self centers the center node within its 1fr
61
+ cell so it doesn't stretch. */
62
+ .velu-header__top {
63
+ display: grid;
64
+ grid-template-columns: auto 1fr auto;
65
+ align-items: center;
66
+ gap: var(--s1);
67
+ }
68
+ .velu-header__col--brand {
69
+ justify-self: start;
70
+ }
71
+ .velu-header__col--center {
72
+ justify-self: center;
73
+ min-inline-size: 0;
74
+ }
75
+ .velu-header__col--actions {
76
+ justify-self: end;
77
+ }
78
+
79
+ /* Brand */
80
+ .velu-header__brand-link {
81
+ display: inline-flex;
82
+ align-items: center;
83
+ text-decoration: none;
84
+ color: inherit;
85
+ }
86
+ .velu-header__mark {
87
+ flex: none;
88
+ /* 32 × 24 — exact ratio of the brand mark's viewBox. */
89
+ inline-size: 2em;
90
+ block-size: 1.5em;
91
+ color: var(--accent-color);
92
+ }
93
+ .velu-header__wordmark {
94
+ font-family: var(--font-brand);
95
+ font-weight: var(--weight-light);
96
+ /* 32px wordmark. */
97
+ font-size: 2rem;
98
+ line-height: 1;
99
+ letter-spacing: -0.01em;
100
+ color: var(--text-color);
101
+ }
102
+
103
+ /* Configured site logo (replaces the mark + wordmark). Height-constrained to
104
+ the brand row; width scales by aspect ratio up to a sane cap. */
105
+ .velu-logo {
106
+ display: block;
107
+ block-size: 2rem;
108
+ inline-size: auto;
109
+ max-inline-size: 12rem;
110
+ object-fit: contain;
111
+ }
112
+ /* Per-theme swap: light image by default, dark image under [data-theme=dark].
113
+ `display` (not opacity) so the inactive one leaves the a11y tree too. The
114
+ `mono` single-image case has neither modifier and always shows. */
115
+ .velu-logo--dark {
116
+ display: none;
117
+ }
118
+ [data-theme='dark'] .velu-logo--light {
119
+ display: none;
120
+ }
121
+ [data-theme='dark'] .velu-logo--dark {
122
+ display: block;
123
+ }
124
+
125
+ /* ── Action buttons ─────────────────────────────────────────────────── */
126
+ .velu-header__action {
127
+ display: inline-flex;
128
+ align-items: center;
129
+ gap: var(--s-3);
130
+ padding: var(--s-3);
131
+ border-radius: var(--radius-sm);
132
+ font: inherit;
133
+ font-size: var(--f-h6);
134
+ font-weight: var(--weight-medium);
135
+ /* No explicit line-height — inherits the page's --lh-body so the
136
+ button computes to the same height as the Search trigger (which
137
+ also inherits). The button's text contributes the same vertical
138
+ space as the search label. */
139
+ text-decoration: none;
140
+ cursor: pointer;
141
+ transition: background 0.12s ease, border-color 0.12s ease,
142
+ color 0.12s ease;
143
+ }
144
+ .velu-header__action--outlined {
145
+ background: var(--page-bg);
146
+ border: var(--border-width) solid var(--border-color);
147
+ color: var(--text-color);
148
+ }
149
+ .velu-header__action--outlined:hover {
150
+ border-color: var(--accent-color);
151
+ color: var(--accent-color);
152
+ }
153
+ .velu-header__action--primary {
154
+ background: var(--accent-color);
155
+ border: var(--border-width) solid var(--accent-color);
156
+ color: #fff;
157
+ }
158
+ .velu-header__action--primary:hover {
159
+ background: color-mix(in srgb, var(--accent-color) 88%, #000);
160
+ }
161
+ /* Plain text link (navbar.links)no fill or border, just text that goes
162
+ accent on hover. Transparent border keeps it the same height as the
163
+ buttons beside it; tighter inline padding reads as a link, not a button. */
164
+ .velu-header__action--link {
165
+ background: transparent;
166
+ border: var(--border-width) solid transparent;
167
+ color: var(--text-color);
168
+ padding-inline: var(--s-5);
169
+ }
170
+ .velu-header__action--link:hover {
171
+ color: var(--accent-color);
172
+ }
173
+ .velu-header__action--link:hover .velu-header__action-icon {
174
+ color: var(--accent-color);
175
+ }
176
+ .velu-header__action-icon {
177
+ display: inline-flex;
178
+ flex: none;
179
+ /* Match the Search trigger's icon at rest — muted. Flips to the
180
+ accent color on hover of an outlined action (the text + border
181
+ also go accent there, so the icon shifts with them). */
182
+ color: var(--muted-color);
183
+ transition: color 0.12s ease;
184
+ }
185
+ .velu-header__action--outlined:hover .velu-header__action-icon {
186
+ color: var(--accent-color);
187
+ }
188
+
189
+ /* Inline list of action buttons. Same cluster spacing as before; gets
190
+ `display: none` at narrow widths so the kebab takes over. */
191
+ .velu-header__actions-list {
192
+ display: inline-flex;
193
+ align-items: center;
194
+ gap: var(--s-2);
195
+ }
196
+
197
+ /* ── Kebab overflow menu (narrow widths only) ──────────────────────── */
198
+ /* Hidden at wide widths — the inline action list is shown instead. */
199
+ .velu-header__kebab {
200
+ position: relative;
201
+ display: none;
202
+ }
203
+ .velu-header__kebab-toggle {
204
+ display: inline-flex;
205
+ align-items: center;
206
+ justify-content: center;
207
+ inline-size: 2rem;
208
+ block-size: 2rem;
209
+ padding: 0;
210
+ background: transparent;
211
+ border: 0;
212
+ border-radius: var(--radius-sm);
213
+ color: var(--muted-color);
214
+ cursor: pointer;
215
+ transition: color 0.12s ease, background 0.12s ease;
216
+ }
217
+ .velu-header__kebab-toggle:hover {
218
+ color: var(--text-color);
219
+ background: var(--surface-color);
220
+ }
221
+
222
+ /* Dropdown — anchored to the kebab button's bottom-right. Same fabric
223
+ as the page (no shadow flourish — matches the TocBar dropdown). */
224
+ .velu-header__kebab-menu {
225
+ position: absolute;
226
+ inset-block-start: calc(100% + var(--s-3));
227
+ inset-inline-end: 0;
228
+ margin: 0;
229
+ padding-block: var(--s-2);
230
+ padding-inline: 0;
231
+ list-style: none;
232
+ min-inline-size: 10rem;
233
+ background: var(--page-bg);
234
+ border: var(--border-width) solid var(--border-color);
235
+ border-radius: var(--radius-sm);
236
+ opacity: 0;
237
+ visibility: hidden;
238
+ transform: translateY(-0.25rem);
239
+ pointer-events: none;
240
+ transition:
241
+ opacity 0.15s ease,
242
+ transform 0.15s ease,
243
+ visibility 0s linear 0.15s;
244
+ z-index: 31;
245
+ }
246
+ .velu-header__kebab[data-open='true'] .velu-header__kebab-menu {
247
+ opacity: 1;
248
+ visibility: visible;
249
+ transform: translateY(0);
250
+ pointer-events: auto;
251
+ transition:
252
+ opacity 0.15s ease,
253
+ transform 0.15s ease,
254
+ visibility 0s;
255
+ }
256
+ .velu-header__kebab-item {
257
+ display: flex;
258
+ align-items: center;
259
+ gap: var(--s-3);
260
+ inline-size: 100%;
261
+ padding-block: var(--s-2);
262
+ padding-inline: var(--s0);
263
+ background: transparent;
264
+ border: 0;
265
+ font: inherit;
266
+ font-size: var(--f-h6);
267
+ color: var(--text-color);
268
+ text-align: start;
269
+ text-decoration: none;
270
+ cursor: pointer;
271
+ transition: background 0.12s ease, color 0.12s ease;
272
+ }
273
+ .velu-header__kebab-item:hover {
274
+ background: var(--surface-color);
275
+ color: var(--accent-color);
276
+ }
277
+ .velu-header__kebab-icon {
278
+ display: inline-flex;
279
+ flex: none;
280
+ color: var(--muted-color);
281
+ }
282
+ .velu-header__kebab-item:hover .velu-header__kebab-icon {
283
+ color: var(--accent-color);
284
+ }
285
+
286
+ /* ── Breadcrumb (mobile-only replacement for tabs) ─────────────────── */
287
+ /* Single-line trail with character-unit max width — long content
288
+ truncates with `text-overflow: ellipsis` rather than reflowing the
289
+ row or pushing the trailing burger. `display: inline-block` (not
290
+ flex) so the inline children can be ellipsed by text-overflow,
291
+ which only applies to inline-level content. Hidden by default;
292
+ the @container block at the bottom flips it on at mobile widths. */
293
+ .velu-header__crumbs {
294
+ --velu-crumb-max: 24ch;
295
+ display: none;
296
+ align-items: center;
297
+ flex-wrap: nowrap;
298
+ max-inline-size: var(--velu-crumb-max);
299
+ min-inline-size: 0;
300
+ overflow: hidden;
301
+ /* Native-button reset the strip is now a <button> so we need to
302
+ null out the browser's default chrome (background, border,
303
+ focus padding) and inherit the font from the header. */
304
+ padding: 0;
305
+ background: transparent;
306
+ border: 0;
307
+ font: inherit;
308
+ color: var(--text-color);
309
+ font-size: var(--f-h6);
310
+ cursor: pointer;
311
+ }
312
+ /* Each segment is a flex child that can shrink independently the
313
+ final ("current page") segment is the one that ellipses if the
314
+ trail outgrows the max-width. */
315
+ .velu-header__crumb {
316
+ flex: 0 1 auto;
317
+ min-inline-size: 0;
318
+ overflow: hidden;
319
+ white-space: nowrap;
320
+ text-overflow: ellipsis;
321
+ }
322
+ .velu-header__crumb-link {
323
+ flex: 0 1 auto;
324
+ min-inline-size: 0;
325
+ overflow: hidden;
326
+ white-space: nowrap;
327
+ text-overflow: ellipsis;
328
+ color: inherit;
329
+ text-decoration: none;
330
+ }
331
+ .velu-header__crumb-link:hover {
332
+ color: var(--accent-color);
333
+ }
334
+ /* Chevron separator — never shrinks (flex: none). Sized in `em` so
335
+ it scales with the breadcrumb's font-size (h6 currently); 1em
336
+ matches the text glyph height, keeping the trail visually
337
+ balanced if --f-h6 ever changes. Gap to neighbouring segments
338
+ comes from `margin-inline: var(--s-3)` (~8px). */
339
+ .velu-header__crumb-sep {
340
+ flex: none;
341
+ inline-size: 1em;
342
+ block-size: 1em;
343
+ stroke-width: 1;
344
+ margin-inline: var(--s-3);
345
+ }
346
+
347
+ /* ── Mobile burger nav button ──────────────────────────────────────── */
348
+ /* Hidden by default; the @container query at the bottom flips it to
349
+ `inline-flex` at mobile widths. Defined BEFORE the @container so
350
+ that block's `display: inline-flex` overrides this `display: none`
351
+ via source-order cascade (same specificity). Sized 1.5rem (24px)
352
+ to match the top-row icon glyphs — no inner padding, the button
353
+ IS the icon. */
354
+ /* Trailing slot at the right end of the tabs row (e.g. language
355
+ switcher). Pushed right by auto margin; hidden on mobile (above). */
356
+ .velu-header__tabs-trailing {
357
+ margin-inline-start: auto;
358
+ }
359
+
360
+ .velu-header__menu {
361
+ display: none;
362
+ align-items: center;
363
+ justify-content: center;
364
+ inline-size: 1.5rem;
365
+ block-size: 1.5rem;
366
+ padding: 0;
367
+ background: transparent;
368
+ border: 0;
369
+ border-radius: var(--radius-sm);
370
+ color: var(--muted-color);
371
+ cursor: pointer;
372
+ transition: color 0.12s ease, background 0.12s ease;
373
+ }
374
+ .velu-header__menu:hover {
375
+ color: var(--text-color);
376
+ background: var(--surface-color);
377
+ }
378
+
379
+ /* ── Narrow-width swap ─────────────────────────────────────────────── */
380
+ /* Same threshold as docs-layout / toc-bar — at < 1024px the inline
381
+ action buttons collapse into the kebab menu. The header lives inside
382
+ `.velu-docs-layout` (which is the `docs` container), so this query
383
+ reacts to the layout's width, not the viewport. */
384
+ @container docs (max-width: 1024px) {
385
+ .velu-header__actions-list {
386
+ display: none;
387
+ }
388
+ .velu-header__kebab {
389
+ display: inline-block;
390
+ }
391
+ /* The big desktop inline inset is desktop-only; dial it back at
392
+ tablet (mobile reduces it further below). */
393
+ .velu-header {
394
+ padding-inline: var(--s2);
395
+ }
396
+ }
397
+
398
+ /* ── Mobile collapse (< 640px) ─────────────────────────────────────── */
399
+ /* At mobile widths the header has to fit brand + search-icon + ask-ai
400
+ + kebab on one row at 375px. Hide the labels on action buttons (the
401
+ icon span keeps its class, so the unnamed text span is the second
402
+ child) and tighten the column gap. The trailing slot (theme toggle)
403
+ is hidden it can be re-surfaced inside the kebab dropdown later. */
404
+ @container docs (max-width: 640px) {
405
+ /* Language switcher (tabs-row trailing slot) folds into the drawer
406
+ on mobile. */
407
+ .velu-header__tabs-trailing {
408
+ display: none;
409
+ }
410
+ /* Drop the visible "Ask AI" / "Book Demo" label; icon survives. */
411
+ .velu-header__action > span:not(.velu-header__action-icon) {
412
+ display: none;
413
+ }
414
+ /* Compact padding when only the icon remains. */
415
+ .velu-header__action {
416
+ padding: var(--s-3);
417
+ }
418
+ /* Theme toggle hidden at mobile — too crowded; users can rely on
419
+ OS-level theme until we surface it inside the kebab. */
420
+ .velu-header__col--actions .velu-theme-toggle {
421
+ display: none;
422
+ }
423
+ /* 16px gap between the top-right icons (Search AskAI ↔ Kebab)
424
+ applied at three levels because the icons live in two separate
425
+ Cluster components whose `space` prop sets an inline `gap`, so the
426
+ CSS needs `!important` to win over the inline style. --s0 is 1rem
427
+ which evaluates to 16px at the default root font-size. */
428
+ .velu-header__top {
429
+ gap: var(--s0) !important;
430
+ }
431
+ .velu-header__col--center,
432
+ .velu-header__col--actions {
433
+ gap: var(--s0) !important;
434
+ }
435
+ /* Reduce the header's outer horizontal padding so brand + actions
436
+ get more breathing room at 375px. */
437
+ .velu-header {
438
+ padding-inline: var(--s-1);
439
+ }
440
+ /* Shrink the wordmark slightly — 2rem competes with the icon row
441
+ for horizontal space; 1.5rem still reads as the brand. */
442
+ .velu-header__wordmark {
443
+ font-size: 1.5rem;
444
+ }
445
+ /* Flatten the 3-col grid into a flex row so the center cluster
446
+ (Search + Ask AI) sits flush with the actions cluster (Kebab) on
447
+ the right side. `margin-inline-start: auto` on center pushes both
448
+ clusters to the trailing edge, brand stays at the leading edge. */
449
+ .velu-header__top {
450
+ display: flex;
451
+ align-items: center;
452
+ }
453
+ .velu-header__col--center {
454
+ justify-self: auto;
455
+ margin-inline-start: auto;
456
+ }
457
+ .velu-header__col--actions {
458
+ justify-self: auto;
459
+ }
460
+ /* Borderless / transparent icon buttons in the right cluster — at
461
+ mobile they read as a row of icon chips alongside the kebab, not
462
+ pill-shaped buttons. */
463
+ .velu-header__col--center .velu-search__trigger,
464
+ .velu-header__col--center .velu-header__action--outlined,
465
+ .velu-header__col--center .velu-header__action--primary {
466
+ background: transparent;
467
+ border: 0;
468
+ }
469
+ /* Uniform 1.5rem glyphs across all icon buttons in the right cluster
470
+ (Search, Ask AI, kebab, burger) so they read as a single coherent
471
+ row. Targets the SVG directly — lucide icons render as <svg>.
472
+ `stroke-width: 1` thins the lucide default (2) so the icons read
473
+ as light line glyphs rather than chunky outlines. */
474
+ .velu-header__col--center .velu-search__trigger-icon svg,
475
+ .velu-header__col--center .velu-header__action-icon svg,
476
+ .velu-header__kebab-toggle svg,
477
+ .velu-header__menu svg {
478
+ inline-size: 1.5rem;
479
+ block-size: 1.5rem;
480
+ stroke-width: 1;
481
+ }
482
+ /* Use the body text color (not muted) for the icon buttons at
483
+ mobile without the surrounding label/border to give them
484
+ weight, the muted-grey tint reads as "disabled". */
485
+ .velu-header__col--center .velu-search__trigger,
486
+ .velu-header__col--center .velu-search__trigger-icon,
487
+ .velu-header__col--center .velu-header__action,
488
+ .velu-header__col--center .velu-header__action-icon,
489
+ .velu-header__kebab-toggle,
490
+ .velu-header__menu {
491
+ color: var(--text-color);
492
+ }
493
+ /* Tabs row swap: hide the individual tabs (they collapse into the
494
+ mobile menu later), reveal the breadcrumb in their place. The
495
+ `.velu-header__tabs > .velu-header__tab` selector raises the
496
+ specificity above the base `.velu-header__tab { display: inline-flex }`
497
+ rule that appears later in this file, so the cascade resolves in
498
+ this block's favor without re-ordering the whole file. */
499
+ .velu-header__tabs > .velu-header__tab {
500
+ display: none;
501
+ }
502
+ .velu-header__crumbs {
503
+ display: flex;
504
+ }
505
+ /* Burger sits at the trailing edge of the tabs row — pushes itself
506
+ to the right via auto margin so the tabs cluster left as usual.
507
+ Hidden at wider widths via the default `display: none` below. */
508
+ .velu-header__menu {
509
+ display: inline-flex;
510
+ margin-inline-start: auto;
511
+ }
512
+ .velu-header__menu svg {
513
+ inline-size: 1.5rem;
514
+ block-size: 1.5rem;
515
+ }
516
+ }
517
+
518
+
519
+ /* ── Tabs row ───────────────────────────────────────────────────────── */
520
+ /* Tab is a horizontal link row with an optional icon, a label, and an
521
+ optional external-link chip. Active tab has accent color + an
522
+ underline rail. */
523
+ .velu-header__tab {
524
+ display: inline-flex;
525
+ align-items: center;
526
+ gap: var(--s-3);
527
+ padding-block: var(--s-3);
528
+ padding-inline: var(--s-3);
529
+ border-block-end: 2px solid transparent;
530
+ color: var(--text-color);
531
+ font-size: var(--f-h6);
532
+ font-weight: var(--weight-medium);
533
+ text-decoration: none;
534
+ transition: color 0.12s ease, border-color 0.12s ease;
535
+ }
536
+ .velu-header__tab:hover {
537
+ color: var(--accent-color);
538
+ }
539
+ .velu-header__tab--active {
540
+ color: var(--accent-color);
541
+ border-block-end-color: var(--accent-color);
542
+ }
543
+ .velu-header__tab-icon,
544
+ .velu-header__tab-external {
545
+ display: inline-flex;
546
+ flex: none;
547
+ color: var(--muted-color);
548
+ }
549
+ /* On hover / active, the icon follows the tab's accent color. */
550
+ .velu-header__tab:hover .velu-header__tab-icon,
551
+ .velu-header__tab--active .velu-header__tab-icon {
552
+ color: inherit;
553
+ }
554
+ .velu-header__tab-external {
555
+ font-size: var(--f-h7);
556
+ opacity: 0.7;
557
+ }