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