@veluai/velu 0.2.37 → 0.2.39

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 (44) hide show
  1. package/dist/cli.js +41 -41
  2. package/docs/aalam.md +44 -0
  3. package/docs/mintlify-migration.md +8 -3
  4. package/docs/thulir.md +23 -0
  5. package/docs/vepa.md +2 -1
  6. package/package.json +4 -2
  7. package/runtime/velu-ui/base.css +9 -0
  8. package/runtime/velu-ui/components/ApiReferencePage.jsx +4 -3
  9. package/runtime/velu-ui/components/ApiSamples.jsx +9 -2
  10. package/runtime/velu-ui/components/Callout.jsx +4 -1
  11. package/runtime/velu-ui/components/PageFooter.jsx +3 -1
  12. package/runtime/velu-ui/components/PageHeader.jsx +7 -1
  13. package/runtime/velu-ui/components/PageNav.jsx +98 -22
  14. package/runtime/velu-ui/components/Sidebar.jsx +21 -9
  15. package/runtime/velu-ui/components/ThemeToggle.jsx +1 -0
  16. package/runtime/velu-ui/components/TocBar.jsx +21 -2
  17. package/runtime/velu-ui/components/Update.jsx +22 -10
  18. package/runtime/velu-ui/components/VepaFooter.jsx +19 -17
  19. package/runtime/velu-ui/components/api-page.css +6 -3
  20. package/runtime/velu-ui/components/chatbot.css +5 -4
  21. package/runtime/velu-ui/components/docs-layout.css +99 -114
  22. package/runtime/velu-ui/components/page-footer.css +2 -3
  23. package/runtime/velu-ui/components/page-header.css +6 -0
  24. package/runtime/velu-ui/components/page-nav.css +192 -0
  25. package/runtime/velu-ui/components/sidebar.css +5 -13
  26. package/runtime/velu-ui/primitives/switcher.css +4 -0
  27. package/runtime/velu-ui/styles.css +2 -0
  28. package/runtime/velu-ui/themes/aalam.css +266 -0
  29. package/runtime/velu-ui/themes/thulir.css +428 -0
  30. package/runtime/velu-ui/themes/vepa.css +142 -10
  31. package/schema/velu.schema.json +23 -3
  32. package/src/navigation.js +22 -3
  33. package/src/runtime/App.jsx +182 -107
  34. package/templates/starter/ai-tools/claude-code.mdx +1 -0
  35. package/templates/starter/ai-tools/cursor.mdx +1 -0
  36. package/templates/starter/api-reference/introduction.mdx +1 -0
  37. package/templates/starter/development.mdx +1 -0
  38. package/templates/starter/essentials/code.mdx +1 -0
  39. package/templates/starter/essentials/images.mdx +1 -0
  40. package/templates/starter/essentials/markdown.mdx +1 -0
  41. package/templates/starter/essentials/navigation.mdx +1 -0
  42. package/templates/starter/essentials/settings.mdx +1 -0
  43. package/templates/starter/index.mdx +1 -0
  44. package/templates/starter/quickstart.mdx +1 -0
@@ -3,36 +3,38 @@ import Logo from './Logo.jsx';
3
3
  import SocialLinks from './SocialLinks.jsx';
4
4
  import ThemePreferenceMenu from './ThemePreferenceMenu.jsx';
5
5
 
6
- export default function VepaFooter({ brand, columns = [], socials = [], linkComponent: Link = 'a' }) {
6
+ export default function VepaFooter({ preset = 'vepa', brand, columns = [], socials = [], linkComponent: Link = 'a' }) {
7
7
  const single = columns.length === 1;
8
8
  const completeHeadings = columns.every((c) => c.title);
9
- return <footer id="footer" className="vepa-footer" data-layout={columns.length <= 3 ? 'compact' : 'expanded'} data-single={single} data-headings={completeHeadings}>
10
- <div className="vepa-footer__inner">
11
- <div className="vepa-footer__top">
12
- <div className="vepa-footer__brand-column">
13
- <Link href={brand?.href || '/'} className="vepa-footer__brand">
9
+ const root = ['thulir', 'aalam'].includes(preset) ? 'thulir-footer' : 'vepa-footer';
10
+ return <footer id="footer" className={root} data-layout={columns.length <= 3 ? 'compact' : 'expanded'} data-single={single} data-headings={completeHeadings}>
11
+ <div className={`${root}__inner`}>
12
+ <div className={`${root}__top`}>
13
+ <div className={`${root}__brand-column`}>
14
+ <Link href={brand?.href || '/'} className={`${root}__brand`}>
14
15
  {brand?.logo ? <Logo logo={brand.logo} alt={brand.label} /> : <span>{brand?.label || 'Velu'}</span>}
15
16
  </Link>
16
- <SocialLinks socials={socials} className="vepa-footer__brand-socials" />
17
+ {['thulir', 'aalam'].includes(preset) && <SocialLinks socials={socials} className="thulir-footer__brand-mobile-socials" />}
18
+ <SocialLinks socials={socials} className={`${root}__brand-socials`} />
17
19
  </div>
18
- <div className="vepa-footer__columns" style={{ '--footer-columns': columns.length }}>
19
- {columns.map((column, i) => <div className="vepa-footer__column" key={i}>
20
- <div className="vepa-footer__links">
21
- {column.title && !single && <p className="vepa-footer__heading">{column.title}</p>}
20
+ <div className={`${root}__columns`} style={{ '--footer-columns': columns.length }}>
21
+ {columns.map((column, i) => <div className={`${root}__column`} key={i}>
22
+ <div className={`${root}__links`}>
23
+ {column.title && !single && <p className={`${root}__heading`}>{column.title}</p>}
22
24
  {(column.items || []).map((item, j) => {
23
25
  const external = /^https?:\/\//.test(item.href || '');
24
26
  const Tag = external ? 'a' : Link;
25
- return <Tag className="vepa-footer__link" key={j} href={item.href || '#'} {...(external ? { target: '_blank', rel: 'noreferrer' } : {})}>{item.label}</Tag>;
27
+ return <Tag className={`${root}__link`} key={j} href={item.href || '#'} {...(external ? { target: '_blank', rel: 'noreferrer' } : {})}>{item.label}</Tag>;
26
28
  })}
27
29
  </div>
28
30
  </div>)}
29
31
  </div>
30
- <SocialLinks socials={socials} className="vepa-footer__end-socials" />
32
+ <SocialLinks socials={socials} className={`${root}__end-socials`} />
31
33
  </div>
32
- {!completeHeadings && <SocialLinks socials={socials} className="vepa-footer__mobile-socials" />}
33
- <div className="vepa-footer__divider" />
34
- <div className="vepa-footer__bottom">
35
- <a href="https://veludocs.com" target="_blank" rel="noreferrer" className="vepa-footer__powered">Powered by <strong>Velu</strong></a>
34
+ {!completeHeadings && <SocialLinks socials={socials} className={`${root}__mobile-socials`} />}
35
+ <div className={`${root}__divider`} />
36
+ <div className={`${root}__bottom`}>
37
+ <a href="https://veludocs.com" target="_blank" rel="noreferrer" className={`${root}__powered`}>Powered by <strong>Velu</strong></a>
36
38
  <ThemePreferenceMenu inline />
37
39
  </div>
38
40
  </div>
@@ -71,12 +71,15 @@
71
71
  font-size: var(--f-h6);
72
72
  }
73
73
 
74
- /* Body + Header tabs — fixed height, vertically scrollable, so a long
75
- response never makes the panel grow unbounded. */
74
+ /* Body + Header tabs — fixed height, scrollable on both axes. Vertical
75
+ for long responses; horizontal so nowrap header keys stay reachable
76
+ when wider than the panel (document overflow-x is clipped to keep
77
+ sticky chrome from panning). */
76
78
  .velu-api-resp-headers__scroll,
77
79
  .velu-api-resp-body__scroll {
78
80
  max-block-size: 15rem;
79
- overflow-y: auto;
81
+ min-inline-size: 0;
82
+ overflow: auto;
80
83
  }
81
84
  /* The CodeBlock owns its own border/radius; let it fill the scroll box. */
82
85
  .velu-api-resp-body__scroll > .velu-code-block {
@@ -12,7 +12,8 @@
12
12
 
13
13
  /* ── Panel + slide ──────────────────────────────────────────────────── */
14
14
  /* Fixed panel width via `--vchat-width` (overridable, like --card-min);
15
- capped at 100vw so it can never exceed the viewport. */
15
+ capped at 100% (not 100vw) so a vertical scrollbar can't push the
16
+ panel a few pixels past the layout width and create page h-scroll. */
16
17
  .velu-chatbot {
17
18
  /* Comfortable fixed panel width. The panel slides OVER the right
18
19
  edge (it's a floating overlay, z-50) so the article doesn't
@@ -24,7 +25,7 @@
24
25
  inset-inline-end: 0;
25
26
  z-index: 50;
26
27
  inline-size: var(--vchat-width);
27
- max-inline-size: 100vw;
28
+ max-inline-size: 100%;
28
29
  display: flex;
29
30
  flex-direction: column;
30
31
  background: var(--page-bg);
@@ -97,8 +98,8 @@
97
98
  inset-inline: 0;
98
99
  inset-block-end: 0;
99
100
  inset-block-start: auto;
100
- inline-size: 100vw;
101
- max-inline-size: 100vw;
101
+ inline-size: 100%;
102
+ max-inline-size: 100%;
102
103
  block-size: min(85vh, 40rem);
103
104
  transform: translateY(100%);
104
105
  border-inline-start: 0;
@@ -37,6 +37,11 @@
37
37
  display: flex;
38
38
  flex-direction: column;
39
39
  min-height: 100vh;
40
+ /* Never let a wide child widen the page: that would horizontally
41
+ scroll the sticky header with the article. Local scroll lives on
42
+ `__main` (and on code/table wrappers) instead. */
43
+ max-inline-size: 100%;
44
+ overflow-x: clip;
40
45
  background: var(--page-bg);
41
46
  }
42
47
 
@@ -90,13 +95,11 @@
90
95
  display: flex;
91
96
  flex-direction: column;
92
97
  overflow: hidden;
93
- /* Bottom boundary: end the column a fixed gap above the viewport
94
- bottom (mirrors the pinned top), so the nav scrolls within a
95
- contained panel rather than running to the screen edge. The value
96
- is driven by --velu-aside-bottom (set inline in App.jsx) so the
97
- edge lifts to stay above the footer as it scrolls in; falls back
98
- to a static gap. The mobile drawer resets this to 0. */
99
- inset-block-end: var(--velu-aside-bottom, var(--s4));
98
+ /* Full viewport height the footer sits under content + TOC only
99
+ (see `[data-velu-footer]`), so the left rail never lifts. No bottom
100
+ padding: the nav scroller (and its scrollbar) must reach the edge. */
101
+ inset-block-end: 0;
102
+ padding-block-end: 0;
100
103
  }
101
104
  /* Stack wrapper fills the aside so the inner nav region can flex-grow
102
105
  and own the scroll. */
@@ -127,77 +130,15 @@
127
130
  .velu-docs-nav-scroll {
128
131
  flex: 1 1 auto;
129
132
  min-block-size: 0;
130
- overflow-y: auto;
131
- }
132
-
133
- /* Up / down scroll arrows circular buttons centered on the top and
134
- bottom edges of the scroll region. Hidden by default; revealed (via
135
- the sibling combinator off the scroll div's data-fade-* attrs) only
136
- when there's content beyond that edge. Clicking smooth-scrolls the
137
- nav fully to that end (handlers in App.jsx). */
138
- .velu-docs-nav-arrow {
139
- position: absolute;
140
- inset-inline-start: 50%;
141
- display: inline-flex;
142
- align-items: center;
143
- justify-content: center;
144
- inline-size: 2rem;
145
- block-size: 2rem;
146
- padding: 0;
147
- border: var(--border-width) solid var(--border-color);
148
- border-radius: 999px;
149
- /* Frosted glass — same recipe as the scrolled header / toc-bar: 75%
150
- --page-bg + a saturate/blur backdrop, so the nav content shows through
151
- faintly behind the button. */
152
- background: color-mix(in srgb, var(--page-bg) 75%, transparent);
153
- backdrop-filter: saturate(140%) blur(12px);
154
- -webkit-backdrop-filter: saturate(140%) blur(12px);
155
- color: var(--muted-color);
156
- cursor: pointer;
157
- z-index: 3;
158
- opacity: 0;
159
- pointer-events: none;
160
- transform: translateX(-50%) scale(0.9);
161
- transition: opacity 0.15s ease, transform 0.15s ease, color 0.12s ease,
162
- border-color 0.12s ease;
163
- box-shadow: 0 var(--s-4) var(--s-1) color-mix(in srgb, #000 10%, transparent);
164
- }
165
- .velu-docs-nav-arrow:hover {
166
- color: var(--accent-color);
167
- border-color: var(--accent-color);
168
- }
169
- .velu-docs-nav-arrow svg {
170
- inline-size: 1.1em;
171
- block-size: 1.1em;
172
- }
173
- .velu-docs-nav-arrow--up {
174
- /* Float ABOVE the scroll region — up in the context-divider gap — instead
175
- of over the first section heading. The lift is the arrow's own height
176
- (2rem, matching block-size) plus a small gap, so its bottom clears the
177
- scroll content and it never overlaps nav text. */
178
- inset-block-start: calc(-2rem - var(--s-4));
179
- }
180
- .velu-docs-nav-arrow--down {
181
- inset-block-end: var(--s-3);
182
- }
183
- /* Reveal whenever there's content beyond that edge — always on (not just on
184
- hover), so the scroll affordance is visible as long as there's more to
185
- scroll. The data-fade-* attrs (set by the runtime from scroll position)
186
- already gate each arrow to the direction that actually has overflow. */
187
- .velu-docs-nav-scroll[data-fade-top='true'] ~ .velu-docs-nav-arrow--up,
188
- .velu-docs-nav-scroll[data-fade-bottom='true'] ~ .velu-docs-nav-arrow--down {
189
- opacity: 1;
190
- pointer-events: auto;
191
- transform: translateX(-50%) scale(1);
133
+ /* Always reserve the scrollbar gutter (see always-on track styles
134
+ further below) instead of only appearing when content overflows. */
135
+ overflow-y: scroll;
136
+ /* Clear sticky section headings when scrolling the active item into view. */
137
+ scroll-padding-block-start: 2.75rem;
192
138
  }
193
139
 
194
- /* Edge fade: the scroll regions (left nav + right TOC) fade to
195
- transparent at whichever edge has more content beyond it. The
196
- --vf-top / --vf-bottom lengths default to 0 (no fade) and are
197
- switched on by the data-fade-* attrs that App.jsx sets from scroll
198
- position — so the top only fades once you've scrolled down, and the
199
- bottom fade vanishes when you reach the end. */
200
- .velu-docs-nav-scroll,
140
+ /* Edge fade: right TOC only. The left nav keeps crisp edges (no mask
141
+ blur) so the always-on scrollbar and sticky headings read cleanly. */
201
142
  .velu-docs-layout__aside--right {
202
143
  --vf-top: 0px;
203
144
  --vf-bottom: 0px;
@@ -216,22 +157,9 @@
216
157
  transparent
217
158
  );
218
159
  }
219
- /* Top fade only on the right TOC — the left nav uses sticky section
220
- headers as its top treatment, so a top fade would just blur the
221
- pinned heading. */
222
160
  .velu-docs-layout__aside--right[data-fade-top='true'] {
223
161
  --vf-top: var(--s2);
224
162
  }
225
- /* The left nav's under-heading fade (the ::after below a sticky section
226
- heading) is shown only while the nav is scrolled up AND only under the
227
- currently-PINNED section ([data-stuck], set by the runtime). Other sections'
228
- headings scroll normally, so their first item must not be dimmed — only the
229
- item sliding under the top-pinned heading fades. */
230
- .velu-docs-nav-scroll[data-fade-top='true']
231
- .velu-sidebar__section[data-stuck='true']::after {
232
- opacity: 1;
233
- }
234
- .velu-docs-nav-scroll[data-fade-bottom='true'],
235
163
  .velu-docs-layout__aside--right[data-fade-bottom='true'] {
236
164
  --vf-bottom: var(--s2);
237
165
  }
@@ -242,33 +170,61 @@
242
170
  padding-inline: var(--s1);
243
171
  }
244
172
 
245
- /* Scrollbars hide at rest and reveal only while the cursor is over the
246
- column. Applies to the right TOC aside (scrolls directly) and the
247
- left nav's inner scroll region (.velu-docs-nav-scroll). The gutter
248
- stays reserved (scrollbar-width: thin from .velu-hide-scrollbar) —
249
- only the thumb's color toggles — so revealing it never shifts
250
- content. Overrides .velu-hide-scrollbar's always-on thumb. */
251
- .velu-docs-layout__aside--right,
252
- .velu-docs-nav-scroll {
173
+ /* Right TOC: hide the thumb at rest, reveal on hover. Gutter stays
174
+ reserved so revealing never shifts content. */
175
+ .velu-docs-layout__aside--right {
253
176
  scrollbar-color: transparent transparent;
254
177
  }
255
- .velu-docs-layout__aside--right:hover,
256
- .velu-docs-layout__aside--left:hover .velu-docs-nav-scroll {
178
+ .velu-docs-layout__aside--right:hover {
257
179
  scrollbar-color: var(--border-color) transparent;
258
180
  }
259
- .velu-docs-layout__aside--right::-webkit-scrollbar-thumb,
260
- .velu-docs-nav-scroll::-webkit-scrollbar-thumb {
181
+ .velu-docs-layout__aside--right::-webkit-scrollbar-thumb {
261
182
  background: transparent;
262
183
  }
263
- .velu-docs-layout__aside--right:hover::-webkit-scrollbar-thumb,
264
- .velu-docs-layout__aside--left:hover .velu-docs-nav-scroll::-webkit-scrollbar-thumb {
184
+ .velu-docs-layout__aside--right:hover::-webkit-scrollbar-thumb {
265
185
  background: var(--border-color);
266
186
  }
267
- .velu-docs-layout__aside--right:hover::-webkit-scrollbar-thumb:hover,
268
- .velu-docs-layout__aside--left:hover .velu-docs-nav-scroll::-webkit-scrollbar-thumb:hover {
187
+ .velu-docs-layout__aside--right:hover::-webkit-scrollbar-thumb:hover {
269
188
  background: var(--muted-color);
270
189
  }
271
190
 
191
+ /* Left nav: scrollbar always on (gutter + track + thumb), flush to the
192
+ top and bottom of the scroll column — no inset rail or rounded ends. */
193
+ .velu-docs-nav-scroll {
194
+ scrollbar-width: thin;
195
+ /* Firefox: thumb then track. */
196
+ scrollbar-color: var(--muted-color)
197
+ color-mix(in srgb, var(--muted-color) 28%, var(--page-bg));
198
+ /* Chromium overlay scrollbars often ignore ::-webkit-scrollbar-track,
199
+ so paint a permanent rail on the inline-end edge as well. */
200
+ background-image: linear-gradient(
201
+ to left,
202
+ color-mix(in srgb, var(--muted-color) 28%, var(--page-bg)) 10px,
203
+ transparent 10px
204
+ );
205
+ }
206
+ .velu-docs-nav-scroll::-webkit-scrollbar {
207
+ inline-size: 10px;
208
+ }
209
+ .velu-docs-nav-scroll::-webkit-scrollbar-track {
210
+ background: color-mix(in srgb, var(--muted-color) 28%, var(--page-bg));
211
+ border-radius: 0;
212
+ }
213
+ .velu-docs-nav-scroll::-webkit-scrollbar-thumb {
214
+ background: var(--muted-color);
215
+ border-radius: 0;
216
+ }
217
+ .velu-docs-nav-scroll::-webkit-scrollbar-thumb:hover,
218
+ .velu-docs-nav-scroll::-webkit-scrollbar-thumb:active {
219
+ background: var(--text-color);
220
+ }
221
+
222
+ /* Last-item breathing room lives on the nav content, not the scroller,
223
+ so the scrollbar track still runs edge-to-edge. */
224
+ .velu-docs-nav-scroll > :last-child {
225
+ padding-block-end: var(--s1);
226
+ }
227
+
272
228
  /* ── Center column ─────────────────────────────────────────────────── */
273
229
  /* Reserves the aside widths via margin so the article never slides
274
230
  under them. The end margin collapses to 0 while the chatbot is
@@ -293,6 +249,11 @@
293
249
  defined here (not inline) so the narrow + sidebar-closed @container
294
250
  rule below can override just the inline-start side. */
295
251
  .velu-docs-layout__main {
252
+ min-inline-size: 0;
253
+ /* Clip — not auto. `overflow-x: auto` makes <main> a scrollport, so
254
+ trackpad/wheel horizontal pans slide the article even when nothing
255
+ should scroll. Wide code/tables keep their own overflow-x: auto. */
256
+ overflow-x: clip;
296
257
  padding-block-start: var(--s3);
297
258
  padding-inline: var(--s4);
298
259
  }
@@ -307,6 +268,17 @@
307
268
  margin-inline: auto;
308
269
  }
309
270
 
271
+ /* Site footer sits beside the left rail and shares the centre column's
272
+ horizontal margins (sidebar + right TOC reserve). That way the
273
+ footer's margin:auto content shares the same optical center as the
274
+ article — a pseudo-center that accounts for the left column instead
275
+ of the full viewport. */
276
+ .velu-docs-layout > [data-velu-footer] {
277
+ margin-inline-start: var(--velu-sidebar-width);
278
+ margin-inline-end: var(--velu-aside-right-width);
279
+ transition: margin-inline-start 0.2s ease, margin-inline-end 0.2s ease;
280
+ }
281
+
310
282
  /* Inner sidebar content (the <Sidebar> nav root, direct child of the
311
283
  left aside) cross-fades during the collapse animation. Hidden when
312
284
  the layout's `data-sidebar-open='false'` flag is set — see the
@@ -538,6 +510,9 @@
538
510
  .velu-docs-layout__center {
539
511
  margin-inline-end: 0;
540
512
  }
513
+ .velu-docs-layout > [data-velu-footer] {
514
+ margin-inline-end: 0;
515
+ }
541
516
  .velu-docs-layout__sidebar-toggle {
542
517
  display: inline-flex;
543
518
  }
@@ -549,15 +524,10 @@
549
524
  .velu-docs-layout__aside--left {
550
525
  border-inline-end: var(--border-width) solid var(--surface-color);
551
526
  padding-inline-start: var(--s2);
552
- /* The divider IS this aside's right edge, so it can only reach as far
553
- down as the box does. The base rule lifts the box above the footer
554
- (inset-block-end) to keep nav content clear of it — but that also
555
- cuts the divider short. Here (where the divider shows) span the box
556
- to the bottom instead and push the *content* up with matching
557
- padding, so the line runs to the footer while the nav still clears
558
- it. The footer's own z-index covers the line where they overlap. */
559
- inset-block-end: 0;
560
- padding-block-end: var(--velu-aside-bottom, var(--s4));
527
+ }
528
+ /* Footer tracks the sidebar / rail the same way the centre column does. */
529
+ .velu-docs-layout[data-sidebar-open='false'] > [data-velu-footer] {
530
+ margin-inline-start: var(--velu-rail-width);
561
531
  }
562
532
  /* Sidebar closed → shrink the aside's `inline-size` from 240px to
563
533
  the rail width. The aside stays in the DOM and keeps its own
@@ -630,6 +600,11 @@
630
600
  .velu-docs-layout[data-sidebar-open='false'] .velu-docs-layout__center {
631
601
  margin-inline-start: 0;
632
602
  }
603
+ .velu-docs-layout > [data-velu-footer],
604
+ .velu-docs-layout[data-sidebar-open='false'] > [data-velu-footer] {
605
+ margin-inline-start: 0;
606
+ margin-inline-end: 0;
607
+ }
633
608
  .velu-docs-layout__aside--left {
634
609
  /* Anchor to the right edge instead of the left — override the
635
610
  base `inset-inline-start: 0` from .velu-docs-layout__aside--left
@@ -827,6 +802,9 @@
827
802
  .velu-docs-layout[data-page-mode='frame'] .velu-docs-layout__center {
828
803
  margin-inline-end: 0;
829
804
  }
805
+ .velu-docs-layout[data-page-mode='frame'] > [data-velu-footer] {
806
+ margin-inline-end: 0;
807
+ }
830
808
 
831
809
  /* Custom / center / assistant: no sidebar, no TOC. */
832
810
  .velu-docs-layout[data-page-mode='custom'] .velu-docs-layout__aside,
@@ -839,6 +817,13 @@
839
817
  .velu-docs-layout[data-page-mode='assistant'] .velu-docs-layout__center {
840
818
  margin-inline: 0;
841
819
  }
820
+ .velu-docs-layout[data-page-mode='custom'] > [data-velu-footer],
821
+ .velu-docs-layout[data-page-mode='center'] > [data-velu-footer],
822
+ .velu-docs-layout[data-page-mode='assistant'] > [data-velu-footer],
823
+ .velu-docs-layout[data-not-found='true'] > [data-velu-footer] {
824
+ margin-inline-start: 0;
825
+ margin-inline-end: 0;
826
+ }
842
827
  .velu-docs-layout[data-page-mode='custom'] .velu-docs-layout__sidebar-toggle,
843
828
  .velu-docs-layout[data-page-mode='center'] .velu-docs-layout__sidebar-toggle,
844
829
  .velu-docs-layout[data-page-mode='assistant'] .velu-docs-layout__sidebar-toggle,
@@ -10,9 +10,8 @@
10
10
  resolves to #f2f3f4 in light / #323334 in dark). */
11
11
  border-block-start: var(--border-width) solid var(--surface-color);
12
12
  color: var(--text-color);
13
- /* Opaque so the footer visually eclipses the fixed sidebar / TOC
14
- when the page scrolls into it (DocsPage gives the footer a higher
15
- z-index than the asides). */
13
+ /* Opaque so the footer sits cleanly under content + TOC while the
14
+ fixed left rail runs full height beside it. */
16
15
  background: var(--page-bg);
17
16
  }
18
17
 
@@ -10,6 +10,10 @@
10
10
  position: sticky;
11
11
  inset-block-start: 0;
12
12
  z-index: 30;
13
+ /* Stay within the viewport even if a child wants to be wider —
14
+ page-level h-scroll would slide this sticky bar sideways. */
15
+ max-inline-size: 100%;
16
+ overflow-x: clip;
13
17
  /* No bottom padding — the tabs row sits flush at the header's
14
18
  bottom edge so the active tab's 2px underline meets the header's
15
19
  bottom border (the selector "touches" the header line). */
@@ -73,6 +77,7 @@
73
77
  }
74
78
  .velu-header__col--brand {
75
79
  justify-self: start;
80
+ min-inline-size: 0;
76
81
  }
77
82
  .velu-header__col--center {
78
83
  justify-self: center;
@@ -80,6 +85,7 @@
80
85
  }
81
86
  .velu-header__col--actions {
82
87
  justify-self: end;
88
+ min-inline-size: 0;
83
89
  }
84
90
 
85
91
  /* Brand */
@@ -48,3 +48,195 @@
48
48
  .velu-pagenav__card:hover .velu-pagenav__dir {
49
49
  color: var(--accent-color);
50
50
  }
51
+
52
+ /* Aalam / Maple pagination tray — featured destination card + optional
53
+ compact Previous. Scoped via .velu-pagenav--aalam so default/Thulir
54
+ cards stay untouched. */
55
+ .velu-pagenav--aalam {
56
+ display: flex;
57
+ align-items: stretch;
58
+ width: 100%;
59
+ margin-block: 2.5rem;
60
+ padding: 4px;
61
+ border-radius: 16px;
62
+ /* Maple: bg-gray-50/80 */
63
+ background: rgb(249 250 251 / 0.8);
64
+ font-size: 14px;
65
+ line-height: 20px;
66
+ text-decoration: none;
67
+ }
68
+
69
+ [data-theme='dark'] .velu-pagenav--aalam {
70
+ /* Maple: dark:bg-white/[0.03] */
71
+ background: rgb(255 255 255 / 0.03);
72
+ }
73
+
74
+ .velu-pagenav--aalam a {
75
+ color: inherit;
76
+ text-decoration: none;
77
+ }
78
+
79
+ .velu-pagenav__compact {
80
+ display: flex;
81
+ flex: none;
82
+ align-items: center;
83
+ justify-content: space-between;
84
+ gap: 6px;
85
+ padding: 0 24px 0 12px;
86
+ color: var(--muted-color);
87
+ font-weight: 500;
88
+ letter-spacing: -0.01em;
89
+ }
90
+
91
+ .velu-pagenav__compact svg {
92
+ flex: none;
93
+ /* Maple: text-gray-300 / dark:text-gray-700 */
94
+ color: #d1d5db;
95
+ transition: color 0.12s ease;
96
+ }
97
+
98
+ [data-theme='dark'] .velu-pagenav__compact svg {
99
+ color: #374151;
100
+ }
101
+
102
+ .velu-pagenav__compact:hover span {
103
+ color: var(--thulir-heading, var(--text-color));
104
+ }
105
+
106
+ .velu-pagenav__compact:hover svg {
107
+ /* Maple: group-hover:text-gray-600 / dark:group-hover:text-gray-400 */
108
+ color: #4b5563;
109
+ }
110
+
111
+ [data-theme='dark'] .velu-pagenav__compact:hover svg {
112
+ color: #9ca3af;
113
+ }
114
+
115
+ .velu-pagenav__featured {
116
+ display: block;
117
+ flex: 1 1 auto;
118
+ min-inline-size: 0;
119
+ }
120
+
121
+ .velu-pagenav__featured-inner {
122
+ display: flex;
123
+ align-items: center;
124
+ height: 64px;
125
+ border-radius: 12px;
126
+ background: var(--page-bg);
127
+ transition: box-shadow 0.12s ease;
128
+ }
129
+
130
+ .velu-pagenav__featured:hover .velu-pagenav__featured-inner {
131
+ /* Maple: hover:ring-1 hover:ring-gray-200 / dark:hover:ring-gray-800 */
132
+ box-shadow: 0 0 0 1px rgb(229 231 235);
133
+ }
134
+
135
+ [data-theme='dark'] .velu-pagenav__featured:hover .velu-pagenav__featured-inner {
136
+ box-shadow: 0 0 0 1px rgb(31 41 55);
137
+ }
138
+
139
+ .velu-pagenav__featured--next .velu-pagenav__featured-inner {
140
+ justify-content: flex-end;
141
+ }
142
+
143
+ .velu-pagenav__featured--prev .velu-pagenav__featured-inner {
144
+ justify-content: flex-start;
145
+ }
146
+
147
+ .velu-pagenav__meta {
148
+ display: flex;
149
+ flex-direction: column;
150
+ justify-content: center;
151
+ gap: 0;
152
+ min-inline-size: 0;
153
+ padding-inline: 20px;
154
+ }
155
+
156
+ .velu-pagenav__featured--next .velu-pagenav__meta {
157
+ align-items: flex-end;
158
+ text-align: end;
159
+ }
160
+
161
+ .velu-pagenav__featured--prev .velu-pagenav__meta {
162
+ align-items: flex-start;
163
+ text-align: start;
164
+ }
165
+
166
+ .velu-pagenav--aalam .velu-pagenav__title {
167
+ font-size: 14px;
168
+ line-height: 20px;
169
+ font-weight: 600;
170
+ /* Maple: text-gray-800 / dark:text-gray-200 */
171
+ color: var(--thulir-heading, var(--text-color));
172
+ }
173
+
174
+ .velu-pagenav__desc {
175
+ display: none;
176
+ width: 100%;
177
+ max-inline-size: 18rem;
178
+ overflow: hidden;
179
+ text-overflow: ellipsis;
180
+ white-space: nowrap;
181
+ color: var(--muted-color);
182
+ font-size: 14px;
183
+ line-height: 20px;
184
+ font-weight: 400;
185
+ }
186
+
187
+ @media (min-width: 1024px) {
188
+ .velu-pagenav__desc {
189
+ display: block;
190
+ }
191
+ }
192
+
193
+ .velu-pagenav__rule {
194
+ flex: none;
195
+ width: 1px;
196
+ height: 32px;
197
+ background: color-mix(in srgb, var(--muted-color) 14%, transparent);
198
+ }
199
+
200
+ [data-theme='dark'] .velu-pagenav__rule {
201
+ background: rgb(255 255 255 / 0.05);
202
+ }
203
+
204
+ .velu-pagenav--aalam .velu-pagenav__dir {
205
+ display: flex;
206
+ flex: none;
207
+ align-items: center;
208
+ gap: 6px;
209
+ color: var(--muted-color);
210
+ font-weight: 500;
211
+ letter-spacing: -0.01em;
212
+ }
213
+
214
+ .velu-pagenav__featured--next .velu-pagenav__dir {
215
+ padding: 0 12px 0 20px;
216
+ }
217
+
218
+ .velu-pagenav__featured--prev .velu-pagenav__dir {
219
+ padding: 0 20px 0 12px;
220
+ }
221
+
222
+ .velu-pagenav--aalam .velu-pagenav__dir svg {
223
+ flex: none;
224
+ color: #d1d5db;
225
+ transition: color 0.12s ease;
226
+ }
227
+
228
+ [data-theme='dark'] .velu-pagenav--aalam .velu-pagenav__dir svg {
229
+ color: #374151;
230
+ }
231
+
232
+ .velu-pagenav__featured:hover .velu-pagenav__dir span {
233
+ color: var(--thulir-heading, var(--text-color));
234
+ }
235
+
236
+ .velu-pagenav__featured:hover .velu-pagenav__dir svg {
237
+ color: #4b5563;
238
+ }
239
+
240
+ [data-theme='dark'] .velu-pagenav__featured:hover .velu-pagenav__dir svg {
241
+ color: #9ca3af;
242
+ }