scavold 0.2.0-rc.7 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/COMPONENTS.md CHANGED
@@ -1,5 +1,9 @@
1
1
  # Scavold component and composable reference
2
2
 
3
+ <!-- Components and composables are listed in alphabetical order, each group on its
4
+ own; the sectioning wrappers close the components as one entry. A new one goes
5
+ where its name sorts, never simply at the end. -->
6
+
3
7
  All components are registered globally by `enhanceApp()` and are available in any
4
8
  Markdown page or Vue template without explicit imports. Composables must be imported
5
9
  explicitly.
@@ -14,154 +18,299 @@ the broader "when and why" context that hover docs cannot convey.
14
18
 
15
19
  ---
16
20
 
17
- ### `<ScavoldMenu>`
21
+ ### `<ScavoldBreadcrumb>`
18
22
 
19
- Renders a `<nav>` element containing a nested list of page links derived from the
20
- site's page hierarchy. Nothing is rendered when the resolved item set is empty.
23
+ Renders a `<nav class="breadcrumb">` element containing a flat list of links
24
+ representing the path from the root to the current page. Uses the same
25
+ `<ul>/<li>/<a>` structure as `<ScavoldMenu>` so both components can share CSS.
21
26
 
22
- The component has three independent addressing modes — path-based, absolute (from
23
- root), and relative (from current page) — that cover all common navigation patterns
24
- without requiring knowledge of the current page's depth.
27
+ Each item in the trail carries `.active` (all ancestor items) and `.current` (the
28
+ last item, if the current page is included). This makes it straightforward to style
29
+ the current crumb differently in CSS.
25
30
 
26
31
  #### Props
27
32
 
28
33
  | Prop | Type | Default | Description |
29
34
  |---|---|---|---|
30
- | `from-path` | `string` | — | Select the parent node by path. Supports `{locale}` (full BCP 47 tag, e.g. `de-CH`) and `{lang}` (primary language subtag only, e.g. `de`) placeholders, replaced with the current page's locale at runtime. Takes priority over `from-root` and `from`. Examples: `"de/footer"`, `"{lang}/footer"`, `"{locale}/footer"`. |
31
- | `from-root` | `number` | — | List items at this absolute depth from root. `1` = top-level pages, `2` = second level, etc. When set, `from` is ignored. If the current page has no ancestor at this depth, nothing is rendered. Ignored when `from-path` is set. |
32
- | `from` | `number` | `0` | List items relative to the current page. `0` = siblings, `1` = children, `-1` = aunt/uncle level (children of grandparent), etc. Ignored when `from-root` or `from-path` is set. |
33
- | `depth` | `number` | `0` | Additional levels to descend below the starting level. `0` = flat list, `1` = one level of children, `-1` = unlimited. |
34
- | `active-only` | `boolean` | `false` | When `true`, only expands children along the branch leading to the current page. Useful with `depth > 0` to show sub-items under the active section only. |
35
- | `expand` | `boolean` | `false` | When `true`, expands children of all nodes regardless of the active branch. Enables sitemap-style rendering. `active-only` takes precedence when both are set. |
36
- | `label` | `string` | — | Accessible label for the `<nav>` landmark (`aria-label`). Should be set whenever more than one `<ScavoldMenu>` appears on the same page so screen readers can distinguish them (e.g. `"Main navigation"`, `"Section navigation"`). |
35
+ | `include-current` | `boolean` | `true` | Include the current page as the last crumb |
36
+ | `include-root` | `boolean` | `false` | Include the root node (typically "Home") as the first crumb |
37
37
 
38
38
  #### Rendered markup
39
39
 
40
40
  ```html
41
- <nav>
41
+ <!-- on page de/leistungen.md with include-root omitted -->
42
+ <nav class="breadcrumb">
42
43
  <ul>
43
- <li class="active"> <!-- .active when node is current or an ancestor -->
44
- <a href="/section/">Section</a>
45
- <ul> <!-- nested only when children are included -->
46
- <li class="current">
47
- <a href="/section/page/">Page</a>
48
- </li>
49
- </ul>
44
+ <li class="active">
45
+ <a href="/de/">Deutsch</a> <!-- de/ folder node, label from de/index.md -->
46
+ </li>
47
+ <li class="active current">
48
+ <a href="/de/leistungen/">Leistungen</a>
50
49
  </li>
51
50
  </ul>
52
51
  </nav>
53
52
  ```
54
53
 
55
- The `.active` class is set on any item that is an ancestor of the current page or the
56
- current page itself. The `.current` class is set only on the item that exactly matches
57
- the current page.
54
+ #### Examples
58
55
 
59
- #### Label resolution
56
+ ```html
57
+ <!-- typical usage — section trail + current page -->
58
+ <ScavoldBreadcrumb />
60
59
 
61
- Item labels are resolved in this order:
60
+ <!-- trail without current page, e.g. when page heading serves as final crumb -->
61
+ <ScavoldBreadcrumb :include-current="false" />
62
62
 
63
- 1. `label` frontmatter — explicit navigation text, overrides everything
64
- 2. `title` frontmatter — page title, used when no `label` is set
65
- 3. First `#` heading in the page body — extracted at build time as a fallback when neither `label` nor `title` is declared in frontmatter
66
- 4. The last path segment of the page file (e.g. `about` from `de/about.md`) — last resort
63
+ <!-- full trail including home link -->
64
+ <ScavoldBreadcrumb include-root />
65
+ ```
67
66
 
68
- #### Hiding pages via front matter
67
+ ---
69
68
 
70
- A page can opt out of appearing in menus by setting `hide` in its front matter:
69
+ ### `<ScavoldContainer>`
71
70
 
72
- | Value | Effect |
73
- |---|---|
74
- | `false` / absent | Visible everywhere (default) |
75
- | `true` | Kept off every surface: menus, breadcrumbs, page lists and the feed |
76
- | `"menu"` | Kept out of menus only |
77
- | `"breadcrumb"` | Kept out of breadcrumbs only |
78
- | `"list"` | Kept out of page lists only |
79
- | `"feed"` | Left out of the RSS feed only |
80
- | `[ "menu", "feed" ]` | Kept off exactly the surfaces named |
71
+ Fallback component for Markdown container blocks whose name has no dedicated
72
+ registered component. Renders as the matching HTML sectioning element if the
73
+ container name is one (`section`, `aside`, etc.), otherwise as a `<div>`.
74
+
75
+ Any container name works without being declared first — undeclared names are caught
76
+ by this component. Because a typo would otherwise become a silent `<div>`, the build
77
+ reports each undeclared name once:
78
+
79
+ ```
80
+ [scavold] container ':::sectoin' is not declared in .cratly.config.yaml — rendering
81
+ it with ScavoldContainer. Declare it to silence this, or fix the name if it is a typo.
82
+ ```
83
+
84
+ Declaring the container in `.cratly.config.yaml` silences the report and gives it
85
+ typed properties in the cratly editor.
86
+
87
+ Not intended for direct use. Theme developers should instead create a dedicated
88
+ component for each container name they declare in `.cratly.config.yaml`.
89
+
90
+ See [`useContainer`](#usecontainer-props) for building custom container components.
81
91
 
82
- ```yaml
83
- ---
84
- hide: menu
85
92
  ---
93
+
94
+ ### `<ScavoldCover>`
95
+
96
+ Shows an image or a video across the full width of the window and at least as high as
97
+ the window, cropped to fit — a cover, or hero, at the top of a page. Written as a
98
+ `:::cover` container block; whatever the block contains is laid on top of the picture.
99
+
100
+ #### Markdown syntax
101
+
102
+ ```markdown
103
+ ::: cover src=/wind-farm.jpg focus="center top" label="Wind turbines at dusk"
104
+ # With new energy into the future
105
+ :::
106
+
107
+ ::: cover src=/turbines.mp4 poster=/turbines.jpg
108
+ :::
86
109
  ```
87
110
 
88
- #### Examples
111
+ #### Arguments
112
+
113
+ | Argument | Type | Default | Description |
114
+ |---|---|---|---|
115
+ | `src` | `media-file` | required | Image or video. A file ending in `.mp4`, `.m4v`, `.webm`, `.ogv` or `.mov` is played as a video, anything else is shown as an image. |
116
+ | `poster` | `media-file` | — | Only for a video: still image shown until it plays, and instead of playing for visitors who asked for reduced motion. |
117
+ | `focus` | `string` | `50% 50%` | Part of the picture kept visible when the window crops it, as a CSS `object-position` — `center top`, `30% 60%`. |
118
+ | `label` | `string` | — | Alternative text of an image, accessible label of a video. Left out, the picture is treated as decoration. |
119
+ | `tone` | `"light" \| "dark"` | — | Whether the content on top is meant to be light (over a dark picture) or dark (over a bright one). Only marks the cover with `scavold-cover--light` or `scavold-cover--dark`; what that looks like is the theme's. Left out, or with any other value, the cover carries neither class. |
120
+
121
+ #### Rendered markup
89
122
 
90
123
  ```html
91
- <!-- top-level pages — typical site header nav -->
92
- <ScavoldMenu :from-root="1" />
124
+ <!-- image, tone=light -->
125
+ <div class="scavold-cover scavold-cover--light" style="--scavold-cover-focus: center top">
126
+ <picture class="scavold-cover__media">
127
+ <source type="image/webp" srcset="..." sizes="(min-aspect-ratio: 2400/1350) 100vw, 177.78vh" />
128
+ <source srcset="..." sizes="..." />
129
+ <img src="..." alt="..." sizes="..." loading="eager" fetchpriority="high" decoding="async" />
130
+ </picture>
131
+ <div class="scavold-cover__overlay"><!-- block content --></div>
132
+ </div>
93
133
 
94
- <!-- second level of the active top-level section only -->
95
- <ScavoldMenu :from-root="2" active-only />
134
+ <!-- video -->
135
+ <div class="scavold-cover scavold-cover--video">
136
+ <video class="scavold-cover__media" src="..." poster="..." preload="metadata" muted loop playsinline></video>
137
+ <div class="scavold-cover__overlay"><!-- block content --></div>
138
+ <button type="button" class="scavold-cover__toggle" aria-label="Pause video">…</button>
139
+ </div>
140
+ ```
96
141
 
97
- <!-- children of the current page -->
98
- <ScavoldMenu :from="1" />
142
+ An image comes with all its variants. Its `sizes` value is derived from its aspect
143
+ ratio: a picture covering an upright phone screen is drawn much wider than the screen,
144
+ and the browser has to fetch a variant for that width, not for the width of the screen.
145
+ It is loaded at once rather than lazily, since a cover is the first thing on a page.
99
146
 
100
- <!-- siblings of the current page -->
101
- <ScavoldMenu />
147
+ A video plays muted in a loop, started by script — unless the visitor's system asks
148
+ for reduced motion, in which case the still image stays and the button offers to start
149
+ it. It plays while at least half of it is on screen — half of the window, for one higher
150
+ than the window — and pauses when scrolled away. A video the visitor paused stays
151
+ paused until they start it again, and one they turned the sound up on keeps playing
152
+ when scrolled away. The button is always there: moving content that lasts more
153
+ than five seconds has to be pausable (WCAG 2.2.2). Its label follows the state and comes
154
+ from the [`@scavold.cover.*` translations](#built-in-translation-keys).
102
155
 
103
- <!-- full site hierarchy as a sitemap -->
104
- <ScavoldMenu :from-root="1" :depth="-1" />
156
+ #### Fitting it into the theme
105
157
 
106
- <!-- two levels starting from the active top-level section, active branch expanded -->
107
- <ScavoldMenu :from-root="1" :depth="1" active-only />
158
+ The component ships functional layout CSS only. It leaves the column it is written in
159
+ by `margin-inline: calc(50% - 50vw)`, which assumes a column centred in the window. Two
160
+ things are the theme's to settle:
108
161
 
109
- <!-- fixed path — same footer nav regardless of locale -->
110
- <ScavoldMenu from-path="de/footer" label="Footer-Navigation" />
162
+ ```css
163
+ /* 100vw includes a classic scrollbar, so the cover would stick out by its width */
164
+ html { overflow-x: clip; }
111
165
 
112
- <!-- locale-aware via primary language tag — works when paths use "de", "en", etc. -->
113
- <ScavoldMenu from-path="{lang}/footer" label="Footer-Navigation" />
166
+ /* leave room for a fixed header; the default is the window height, 100svh */
167
+ .scavold-cover { --scavold-cover-height: calc(100svh - var(--header-height)); }
168
+ ```
114
169
 
115
- <!-- locale-aware via full BCP 47 tag — use when paths include region codes like "de-CH" -->
116
- <ScavoldMenu from-path="{locale}/footer" label="Footer-Navigation" />
170
+ The overlay is an empty grid cell over the picture; position its content, add a scrim
171
+ for legibility and restyle `.scavold-cover__toggle` as the design asks.
172
+
173
+ The `tone` an author picked is the theme's to honour. Scavold sets no colour for it;
174
+ a theme gives each tone the colours of its design, including headings and links its
175
+ prose styles colour on their own:
176
+
177
+ ```css
178
+ .scavold-cover--light .scavold-cover__overlay { color: #fff; }
179
+ .scavold-cover--dark .scavold-cover__overlay { color: #000; }
117
180
  ```
118
181
 
119
- ---
182
+ #### A cover opening the page
120
183
 
121
- ### `<ScavoldBreadcrumb>`
184
+ Where the theme's own parts go around a cover is the theme's choice; Scavold places
185
+ none of them. It only tells, in two ways, that a page opens with a container:
122
186
 
123
- Renders a `<nav class="breadcrumb">` element containing a flat list of links
124
- representing the path from the root to the current page. Uses the same
125
- `<ul>/<li>/<a>` structure as `<ScavoldMenu>` so both components can share CSS.
187
+ - the container carries an empty `data-leading` attribute;
188
+ - the page data carry `leadingContainer`, the name of that container (`"cover"`), or
189
+ `null` when the page opens with anything else. A layout renders its own parts before
190
+ the page content, so this is known at build time, ahead of the content.
126
191
 
127
- Each item in the trail carries `.active` (all ancestor items) and `.current` (the
128
- last item, if the current page is included). This makes it straightforward to style
129
- the current crumb differently in CSS.
192
+ A theme that wants its breadcrumb below a cover opening the page, rather than above
193
+ it, combines both. The layout leaves out the breadcrumb on such a page:
194
+
195
+ ```vue
196
+ <!-- Layout.vue -->
197
+ <script setup>
198
+ import { useData } from "vitepress";
199
+ const { page } = useData();
200
+ </script>
201
+
202
+ <template>
203
+ <ScavoldBreadcrumb v-if="page.leadingContainer !== 'cover'" />
204
+ <Content />
205
+ </template>
206
+ ```
207
+
208
+ …and the cover is mapped to a component of the site's own that wraps the built-in one
209
+ and adds the breadcrumb after it when it is the leading one:
210
+
211
+ ```yaml
212
+ # .cratly.config.yaml — the editor keeps offering the cover's own properties
213
+ containers:
214
+ cover:
215
+ component: SiteCover
216
+ ```
217
+
218
+ ```vue
219
+ <!-- .vitepress/theme/components/SiteCover.vue -->
220
+ <script setup>
221
+ import ScavoldCover from "scavold/components/ScavoldCover.vue";
222
+ import ScavoldBreadcrumb from "scavold/components/ScavoldBreadcrumb.vue";
223
+
224
+ defineOptions( { inheritAttrs: false } );
225
+ </script>
226
+
227
+ <template>
228
+ <ScavoldCover v-bind="$attrs"><slot /></ScavoldCover>
229
+ <ScavoldBreadcrumb v-if="$attrs['data-leading'] != null" />
230
+ </template>
231
+ ```
232
+
233
+ ```js
234
+ // .vitepress/theme/index.js — the container renders the component by name
235
+ async function enhanceApp( context ) {
236
+ await scavoldEnhanceApp( context );
237
+ context.app.component( "SiteCover", SiteCover );
238
+ }
239
+ ```
240
+
241
+ Below a cover the breadcrumb sits inside the content column, so the theme's prose
242
+ styles reach its list; keep them out where they differ from the breadcrumb's own.
243
+
244
+ Visual and reading order stay the same this way, which a reordering stylesheet could
245
+ not promise.
246
+
247
+ ---
248
+
249
+ ### `<ScavoldImage>`
250
+
251
+ Renders a responsive `<picture>` element for local images. Not intended for direct
252
+ use in templates — it is emitted automatically by the Markdown image renderer when a
253
+ local image path is encountered. Custom components that need to display a media-file
254
+ frontmatter field can use it directly.
130
255
 
131
256
  #### Props
132
257
 
133
258
  | Prop | Type | Default | Description |
134
259
  |---|---|---|---|
135
- | `include-current` | `boolean` | `true` | Include the current page as the last crumb |
136
- | `include-root` | `boolean` | `false` | Include the root node (typically "Home") as the first crumb |
260
+ | `src` | `string` | required | URL of the largest fallback image variant |
261
+ | `srcset` | `string` | `""` | `srcset` string for the original format (JPEG/PNG) |
262
+ | `webp-srcset` | `string` | `""` | `srcset` string for the WebP variants |
263
+ | `sizes` | `string` | `"100vw"` | CSS `sizes` attribute applied to all sources |
264
+ | `alt` | `string` | `""` | Alt text for the `<img>` element |
265
+ | `loading` | `string` | `"lazy"` | `loading` attribute of the `<img>`; `"eager"` for an image at the top of a page |
266
+ | `fetchpriority` | `string` | — | `fetchpriority` attribute of the `<img>`, e.g. `"high"` for the largest image above the fold |
137
267
 
138
268
  #### Rendered markup
139
269
 
140
270
  ```html
141
- <!-- on page de/leistungen.md with include-root omitted -->
142
- <nav class="breadcrumb">
143
- <ul>
144
- <li class="active">
145
- <a href="/de/">Deutsch</a> <!-- de/ folder node, label from de/index.md -->
146
- </li>
147
- <li class="active current">
148
- <a href="/de/leistungen/">Leistungen</a>
149
- </li>
150
- </ul>
151
- </nav>
271
+ <picture>
272
+ <source type="image/webp" srcset="..." sizes="..." />
273
+ <source srcset="..." sizes="..." />
274
+ <img src="..." alt="..." sizes="..." loading="lazy" decoding="async" />
275
+ </picture>
152
276
  ```
153
277
 
154
- #### Examples
278
+ ---
155
279
 
156
- ```html
157
- <!-- typical usage — section trail + current page -->
158
- <ScavoldBreadcrumb />
280
+ ### `<ScavoldLayout>`
159
281
 
160
- <!-- trail without current page, e.g. when page heading serves as final crumb -->
161
- <ScavoldBreadcrumb :include-current="false" />
282
+ Base layout wrapper that intercepts pages with locale-conditional redirect front
283
+ matter and renders `<ScavoldLocaleRedirect>` in their place. All other pages render
284
+ the default slot.
162
285
 
163
- <!-- full trail including home link -->
164
- <ScavoldBreadcrumb include-root />
286
+ Consuming themes wrap their own layout root element in this component so they
287
+ automatically inherit redirect handling without duplicating the detection logic:
288
+
289
+ ```vue
290
+ <template>
291
+ <ScavoldLayout>
292
+ <div class="site-shell">
293
+ <!-- header, main, footer … -->
294
+ </div>
295
+ </ScavoldLayout>
296
+ </template>
297
+ ```
298
+
299
+ Themes that need finer control can skip `<ScavoldLayout>` and compose the two
300
+ building blocks themselves using `useRedirect` and `<ScavoldLocaleRedirect>`:
301
+
302
+ ```vue
303
+ <script setup>
304
+ import { useRedirect } from "./scavold/composables/useRedirect.js";
305
+ import ScavoldLocaleRedirect from "./scavold/components/ScavoldLocaleRedirect.vue";
306
+
307
+ const { isLocaleRedirect } = useRedirect();
308
+ </script>
309
+
310
+ <template>
311
+ <ScavoldLocaleRedirect v-if="isLocaleRedirect" />
312
+ <div v-else class="site-shell"><!-- … --></div>
313
+ </template>
165
314
  ```
166
315
 
167
316
  ---
@@ -263,48 +412,10 @@ pages/
263
412
 
264
413
  ---
265
414
 
266
- ### `<ScavoldLayout>`
267
-
268
- Base layout wrapper that intercepts pages with locale-conditional redirect front
269
- matter and renders `<ScavoldLocaleRedirect>` in their place. All other pages render
270
- the default slot.
415
+ ### `<ScavoldLocaleRedirect>`
271
416
 
272
- Consuming themes wrap their own layout root element in this component so they
273
- automatically inherit redirect handling without duplicating the detection logic:
274
-
275
- ```vue
276
- <template>
277
- <ScavoldLayout>
278
- <div class="site-shell">
279
- <!-- header, main, footer … -->
280
- </div>
281
- </ScavoldLayout>
282
- </template>
283
- ```
284
-
285
- Themes that need finer control can skip `<ScavoldLayout>` and compose the two
286
- building blocks themselves using `useRedirect` and `<ScavoldLocaleRedirect>`:
287
-
288
- ```vue
289
- <script setup>
290
- import { useRedirect } from "./scavold/composables/useRedirect.js";
291
- import ScavoldLocaleRedirect from "./scavold/components/ScavoldLocaleRedirect.vue";
292
-
293
- const { isLocaleRedirect } = useRedirect();
294
- </script>
295
-
296
- <template>
297
- <ScavoldLocaleRedirect v-if="isLocaleRedirect" />
298
- <div v-else class="site-shell"><!-- … --></div>
299
- </template>
300
- ```
301
-
302
- ---
303
-
304
- ### `<ScavoldLocaleRedirect>`
305
-
306
- Handles locale-conditional redirect pages declared with an object-form `redirect`
307
- in front matter:
417
+ Handles locale-conditional redirect pages declared with an object-form `redirect`
418
+ in front matter:
308
419
 
309
420
  ```yaml
310
421
  ---
@@ -328,138 +439,136 @@ The component renders no visible content of its own. It is used automatically by
328
439
 
329
440
  ---
330
441
 
331
- ### `<ScavoldMenuItems>`
442
+ ### `<ScavoldMenu>`
332
443
 
333
- Internal recursive component used by `<ScavoldMenu>` to render `<ul>/<li>` trees.
334
- Not intended for direct use. Documented here for theme developers who want to build
335
- their own menu component using `useHierarchy`.
444
+ Renders a `<nav>` element containing a nested list of page links derived from the
445
+ site's page hierarchy. Nothing is rendered when the resolved item set is empty.
336
446
 
337
- ---
447
+ The component has three independent addressing modes — path-based, absolute (from
448
+ root), and relative (from current page) — that cover all common navigation patterns
449
+ without requiring knowledge of the current page's depth.
338
450
 
339
- ### `<ScavoldVideo>`
451
+ With `menu`, it renders a **named menu** instead: the pages choosing that menu in
452
+ their [`menus`](./FRONTMATTER.md#menus) front matter, wherever they sit among the folders.
453
+ The names are declared in `.cratly.config.yaml`; where a menu goes is up to the theme.
340
454
 
341
- Renders a `<video>` element for a video embedded via the `:::video` container block.
342
- Nothing is rendered when no `src` argument is provided.
455
+ #### Props
343
456
 
344
- #### Markdown syntax
457
+ | Prop | Type | Default | Description |
458
+ |---|---|---|---|
459
+ | `menu` | `string` | — | Render the named menu declared under this name in `.cratly.config.yaml`: the pages choosing it in their `menus` front matter, in page-tree order. `from-path` and `from-root` then narrow the part of the site searched — `from-path="{lang}"` keeps a multilingual site's menu to one language — and `from` is ignored. `depth`, `active-only` and `expand` descend below each listed page. A page choosing the menu along with one of its ancestors is listed below that ancestor when `depth` allows sub-pages, and beside it when the menu is flat. |
460
+ | `from-path` | `string` | — | Select the parent node by path. Supports `{locale}` (full BCP 47 tag, e.g. `de-CH`) and `{lang}` (primary language subtag only, e.g. `de`) placeholders, replaced with the current page's locale at runtime. Takes priority over `from-root` and `from`. Examples: `"de/footer"`, `"{lang}/footer"`, `"{locale}/footer"`. |
461
+ | `from-root` | `number` | — | List items at this absolute depth from root. `1` = top-level pages, `2` = second level, etc. When set, `from` is ignored. If the current page has no ancestor at this depth, nothing is rendered. Ignored when `from-path` is set. |
462
+ | `from` | `number` | `0` | List items relative to the current page. `0` = siblings, `1` = children, `-1` = aunt/uncle level (children of grandparent), etc. Ignored when `from-root` or `from-path` is set. |
463
+ | `depth` | `number` | `0` | Additional levels to descend below the starting level. `0` = flat list, `1` = one level of children, `-1` = unlimited. |
464
+ | `active-only` | `boolean` | `false` | When `true`, only expands children along the branch leading to the current page. Useful with `depth > 0` to show sub-items under the active section only. |
465
+ | `expand` | `boolean` | `false` | When `true`, expands children of all nodes regardless of the active branch. Enables sitemap-style rendering. `active-only` takes precedence when both are set. |
466
+ | `label` | `string` | — | Accessible label for the `<nav>` landmark (`aria-label`). Should be set whenever more than one `<ScavoldMenu>` appears on the same page so screen readers can distinguish them (e.g. `"Main navigation"`, `"Section navigation"`). |
345
467
 
346
- ```markdown
347
- ::: video src=./clip.mp4 poster=./thumb.jpg
348
- Optional caption or fallback text for browsers that do not support the video element.
349
- :::
468
+ #### Rendered markup
469
+
470
+ ```html
471
+ <nav>
472
+ <ul>
473
+ <li class="active"> <!-- .active when node is current or an ancestor -->
474
+ <a href="/section/">Section</a>
475
+ <ul> <!-- nested only when children are included -->
476
+ <li class="current">
477
+ <a href="/section/page/">Page</a>
478
+ </li>
479
+ </ul>
480
+ </li>
481
+ </ul>
482
+ </nav>
350
483
  ```
351
484
 
352
- #### Arguments
485
+ The `.active` class is set on any item that is an ancestor of the current page or the
486
+ current page itself. The `.current` class is set only on the item that exactly matches
487
+ the current page.
353
488
 
354
- | Argument | Type | Default | Description |
355
- |---|---|---|---|
356
- | `src` | `string` | required | URL of the video file |
357
- | `poster` | `string` | — | URL of the poster image shown before playback |
358
- | `autoplay` | flag | — | Play automatically on load. Forces `muted` (browser requirement) |
359
- | `loop` | flag | — | Loop the video when it ends |
360
- | `muted` | flag | — | Mute the audio track |
361
- | `overlay` | flag | — | Render the video as a background and lay the block's body content on top of it (hero/background mode). See below. |
362
- | `controls` | flag | — | Show native playback controls. Always shown in the default player; opt-in in `overlay` mode, which is chrome-free by default. |
363
- | `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Browser preload hint |
364
- | `label` | `string` | — | Accessible label (`aria-label`) announced by screen readers. Use when the surrounding context does not already describe the video. |
489
+ #### Label resolution
365
490
 
366
- Boolean flags are written without a value:
491
+ Item labels are resolved in this order:
367
492
 
368
- ```markdown
369
- ::: video src=./clip.mp4 autoplay loop
370
- :::
371
- ```
493
+ 1. `label` frontmatter — explicit navigation text, overrides everything
494
+ 2. `title` frontmatter — page title, used when no `label` is set
495
+ 3. First `#` heading in the page body — extracted at build time as a fallback when neither `label` nor `title` is declared in frontmatter
496
+ 4. The last path segment of the page file (e.g. `about` from `de/about.md`) — last resort
372
497
 
373
- #### Rendered markup
498
+ #### Hiding pages via front matter
374
499
 
375
- ```html
376
- <video src="..." poster="..." preload="metadata" controls playsinline>
377
- <!-- slot content -->
378
- </video>
500
+ A page can opt out of appearing in menus by setting `hide` in its front matter:
501
+
502
+ | Value | Effect |
503
+ |---|---|
504
+ | `false` / absent | Visible everywhere (default) |
505
+ | `true` | Kept off every surface: menus, breadcrumbs, page lists and the feed |
506
+ | `"menu"` | Kept out of menus only |
507
+ | `"breadcrumb"` | Kept out of breadcrumbs only |
508
+ | `"list"` | Kept out of page lists only |
509
+ | `"feed"` | Left out of the RSS feed only |
510
+ | `[ "menu", "feed" ]` | Kept off exactly the surfaces named |
511
+
512
+ ```yaml
513
+ ---
514
+ hide: menu
515
+ ---
379
516
  ```
380
517
 
381
- The `controls` and `playsinline` attributes are always present. `autoplay`, `loop`,
382
- `muted`, and `poster` are only emitted when the corresponding argument is set.
518
+ Hiding from menus rules over any choice in `menus`.
383
519
 
384
- #### Background mode (`overlay`)
520
+ #### Choosing named menus via front matter
385
521
 
386
- With the `overlay` flag the video becomes a background and the block's body content
387
- is layered on top of it — the classic hero pattern. The video is chrome-free by
388
- default (add `controls` to bring the native controls back), and a typical hero
389
- combines `overlay` with `autoplay` + `loop` (both imply/allow `muted`):
522
+ Without `menu`, the component lists only pages that leave `menus` out or choose
523
+ `auto` among others. A page choosing named menus only appears in those:
390
524
 
391
- ```markdown
392
- ::: video src=./hero.mp4 poster=./hero.jpg overlay autoplay loop
393
- # Welcome
394
- Content rendered on top of the looping background video.
395
- :::
525
+ ```yaml
526
+ ---
527
+ menus: footer # in the footer, not in the page tree's menus
528
+ ---
396
529
  ```
397
530
 
398
- ```html
399
- <div class="scavold-video scavold-video--overlay">
400
- <video class="scavold-video__media" src="..." poster="..." autoplay loop muted
401
- preload="metadata" playsinline></video>
402
- <div class="scavold-video__overlay">
403
- <!-- slot content -->
404
- </div>
405
- </div>
406
- ```
531
+ #### Examples
407
532
 
408
- The component only ships **functional** layout CSS (the video and the content share
409
- one CSS-grid cell, so the content defines the block height and the video covers the
410
- area behind it via `object-fit: cover`). All visual styling — a darkening scrim,
411
- text colour, alignment, a `min-height` for the hero — belongs to the consuming theme,
412
- which can hook onto the `.scavold-video`, `.scavold-video__media`, and
413
- `.scavold-video__overlay` classes:
533
+ ```html
534
+ <!-- top-level pages — typical site header nav -->
535
+ <ScavoldMenu :from-root="1" />
414
536
 
415
- ```css
416
- /* theme CSS */
417
- .scavold-video--overlay { min-height: 60vh; }
418
- .scavold-video__overlay {
419
- display: grid;
420
- place-content: center;
421
- padding: 2rem;
422
- color: white;
423
- background: rgba( 0, 0, 0, 0.4 ); /* scrim for legibility */
424
- }
425
- ```
537
+ <!-- second level of the active top-level section only -->
538
+ <ScavoldMenu :from-root="2" active-only />
426
539
 
427
- Without the `overlay` flag the block behaves exactly as before: a plain inline
428
- player whose body content is only fallback text for browsers that cannot play video.
540
+ <!-- children of the current page -->
541
+ <ScavoldMenu :from="1" />
429
542
 
430
- #### Captions and accessibility
543
+ <!-- siblings of the current page -->
544
+ <ScavoldMenu />
431
545
 
432
- WCAG requires captions for prerecorded video with audio. Add a `<track>` element via
433
- the slot:
546
+ <!-- full site hierarchy as a sitemap -->
547
+ <ScavoldMenu :from-root="1" :depth="-1" />
434
548
 
435
- ```markdown
436
- ::: video src=./clip.mp4
437
- <track kind="captions" src="./clip.vtt" srclang="de" label="Deutsch" default />
438
- :::
439
- ```
549
+ <!-- two levels starting from the active top-level section, active branch expanded -->
550
+ <ScavoldMenu :from-root="1" :depth="1" active-only />
440
551
 
441
- The `label` argument provides an `aria-label` on the `<video>` element for cases where
442
- the surrounding page context does not already describe the video.
552
+ <!-- fixed path — same footer nav regardless of locale -->
553
+ <ScavoldMenu from-path="de/footer" label="Footer-Navigation" />
443
554
 
444
- #### Replacing with a custom component
555
+ <!-- locale-aware via primary language tag — works when paths use "de", "en", etc. -->
556
+ <ScavoldMenu from-path="{lang}/footer" label="Footer-Navigation" />
445
557
 
446
- Register a component under the name `ScavoldVideo` before calling `scavoldEnhanceApp`,
447
- or map the `video` container name to your component in `augmentConfig`:
558
+ <!-- locale-aware via full BCP 47 tag — use when paths include region codes like "de-CH" -->
559
+ <ScavoldMenu from-path="{locale}/footer" label="Footer-Navigation" />
448
560
 
449
- ```js
450
- export default defineConfig( await augmentConfig( config, {
451
- containers: { video: "MyVideoPlayer" },
452
- } ) );
561
+ <!-- named menu: the pages choosing "footer", wherever they sit, within the current language -->
562
+ <ScavoldMenu menu="footer" from-path="{lang}" label="Footer-Navigation" />
453
563
  ```
454
564
 
455
- Custom components can reuse the resolution logic via `useVideo` and `videoProps`:
565
+ ---
456
566
 
457
- ```js
458
- import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
567
+ ### `<ScavoldMenuItems>`
459
568
 
460
- const props = defineProps( { ...videoProps } );
461
- const { src, poster, autoplay, loop, muted, preload, overlay, controls } = useVideo( props );
462
- ```
569
+ Internal recursive component used by `<ScavoldMenu>` to render `<ul>/<li>` trees.
570
+ Not intended for direct use. Documented here for theme developers who want to build
571
+ their own menu component using `useHierarchy`.
463
572
 
464
573
  ---
465
574
 
@@ -598,90 +707,277 @@ them discoverable.
598
707
 
599
708
  ---
600
709
 
601
- ### `<ScavoldImage>`
710
+ ### `<ScavoldVideo>`
602
711
 
603
- Renders a responsive `<picture>` element for local images. Not intended for direct
604
- use in templates — it is emitted automatically by the Markdown image renderer when a
605
- local image path is encountered. Custom components that need to display a media-file
606
- frontmatter field can use it directly.
712
+ Renders a video player in the text column for a video embedded via the `:::video`
713
+ container block — a clip visitors watch and listen to, pause and wind back. Nothing is
714
+ rendered when no `src` argument is provided. For a video across the whole window,
715
+ with content laid on top, use [`<ScavoldCover>`](#scavoldcover).
607
716
 
608
- #### Props
717
+ #### Markdown syntax
609
718
 
610
- | Prop | Type | Default | Description |
719
+ ```markdown
720
+ ::: video src=./clip.mp4 poster=./thumb.jpg
721
+ Optional caption or fallback text for browsers that do not support the video element.
722
+ :::
723
+ ```
724
+
725
+ #### Arguments
726
+
727
+ | Argument | Type | Default | Description |
611
728
  |---|---|---|---|
612
- | `src` | `string` | required | URL of the largest fallback image variant |
613
- | `srcset` | `string` | `""` | `srcset` string for the original format (JPEG/PNG) |
614
- | `webp-srcset` | `string` | `""` | `srcset` string for the WebP variants |
615
- | `sizes` | `string` | `"100vw"` | CSS `sizes` attribute applied to all sources |
616
- | `alt` | `string` | `""` | Alt text for the `<img>` element |
729
+ | `src` | `string` | required | URL of the video file |
730
+ | `poster` | `string` | — | URL of the poster image shown before playback |
731
+ | `autoplay` | flag | — | Play automatically while on screen, unless the visitor asks for reduced motion. Forces `muted` (browser requirement) |
732
+ | `loop` | flag | — | Loop the video when it ends |
733
+ | `muted` | flag | — | Mute the audio track |
734
+ | `preload` | `"none" \| "metadata" \| "auto"` | `"metadata"` | Browser preload hint |
735
+ | `label` | `string` | — | Accessible label (`aria-label`) announced by screen readers. Use when the surrounding context does not already describe the video. |
736
+
737
+ Boolean flags are written without a value:
738
+
739
+ ```markdown
740
+ ::: video src=./clip.mp4 autoplay loop
741
+ :::
742
+ ```
617
743
 
618
744
  #### Rendered markup
619
745
 
620
746
  ```html
621
- <picture>
622
- <source type="image/webp" srcset="..." sizes="..." />
623
- <source srcset="..." sizes="..." />
624
- <img src="..." alt="..." sizes="..." loading="lazy" decoding="async" />
625
- </picture>
747
+ <video class="scavold-video" src="..." poster="..." preload="metadata" controls playsinline>
748
+ <!-- slot content -->
749
+ </video>
750
+ ```
751
+
752
+ The `controls` and `playsinline` attributes are always present: every video can be
753
+ paused, which WCAG 2.2.2 asks of anything moving for longer than five seconds. `loop`,
754
+ `muted` and `poster` are only emitted when the corresponding argument is set.
755
+
756
+ `autoplay` is not emitted: the attribute starts a video before hydration, wherever it
757
+ is on the page, and before anyone could ask whether the visitor wants reduced motion.
758
+ The video is started by script instead. A visitor whose system asks for reduced motion
759
+ finds it paused, with the controls to start it. For everyone else it plays while at
760
+ least half of it is on screen — half of the window, for one higher than the window —
761
+ and pauses when scrolled away. A video the visitor paused stays paused until they
762
+ start it again, and one they turned the sound up on keeps playing when scrolled away.
763
+
764
+ #### Captions and accessibility
765
+
766
+ WCAG requires captions for prerecorded video with audio. Add a `<track>` element via
767
+ the slot:
768
+
769
+ ```markdown
770
+ ::: video src=./clip.mp4
771
+ <track kind="captions" src="./clip.vtt" srclang="de" label="Deutsch" default />
772
+ :::
773
+ ```
774
+
775
+ The `label` argument provides an `aria-label` on the `<video>` element for cases where
776
+ the surrounding page context does not already describe the video.
777
+
778
+ #### Replacing with a custom component
779
+
780
+ Register a component under the name `ScavoldVideo` before calling `scavoldEnhanceApp`,
781
+ or map the `video` container name to your component in `augmentConfig`:
782
+
783
+ ```js
784
+ export default defineConfig( await augmentConfig( config, {
785
+ containers: { video: "MyVideoPlayer" },
786
+ } ) );
787
+ ```
788
+
789
+ Custom components can reuse the resolution logic via `useVideo` and `videoProps`:
790
+
791
+ ```js
792
+ import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
793
+
794
+ const props = defineProps( { ...videoProps } );
795
+ const { src, poster, autoplay, loop, muted, preload, ariaLabel } = useVideo( props );
796
+ ```
797
+
798
+ ---
799
+
800
+ ### Sectioning wrappers
801
+
802
+ `<ScavoldArticle>`, `<ScavoldAside>`, `<ScavoldFooter>`, `<ScavoldHeader>`, `<ScavoldMain>`,
803
+ `<ScavoldNav>`, `<ScavoldSection>`
804
+
805
+ Thin wrapper components for the seven HTML sectioning elements. Each renders its
806
+ corresponding element, forwarding `class` and `data-*` attributes from the container
807
+ arguments.
808
+
809
+ These are the default targets for their respective container names. A theme developer
810
+ can replace any of them by registering a component with the same name before calling
811
+ `scavoldEnhanceApp`, or by passing an explicit override to `augmentConfig`:
812
+
813
+ ```js
814
+ // .vitepress/config.js
815
+ export default defineConfig( await augmentConfig( config, {
816
+ containers: {
817
+ section: "MySiteSection",
818
+ }
819
+ } ) );
820
+ ```
821
+
822
+ ```js
823
+ // .vitepress/theme/index.js
824
+ async function enhanceApp( context ) {
825
+ app.component( "ScavoldSection", MySiteSection ); // registered before scavold
826
+ await scavoldEnhanceApp( context );
827
+ }
828
+ ```
829
+
830
+ ---
831
+
832
+ ## Composables
833
+
834
+ ---
835
+
836
+ ### `formatTimestamp( timestamp, options )`
837
+
838
+ ```js
839
+ import { formatTimestamp } from "scavold/lib/dateFormat.js";
840
+ ```
841
+
842
+ Renders a timestamp the way page lists do, for a theme's own components — a byline
843
+ under a post's title, for instance. Free of Vue and VitePress, so build-time code can
844
+ use it too.
845
+
846
+ | Option | Type | Default | Description |
847
+ |---|---|---|---|
848
+ | `locale` | `string` | the runtime's | Locale to phrase the result in |
849
+ | `dateStyle` | `"short" \| "medium" \| "long" \| "full" \| "iso"` | `"long"` | Style of the date; `iso` writes `2020-10-20` in every language |
850
+ | `timeStyle` | same values | — | Style of the time of day; omitted, only the date is rendered |
851
+ | `timeZone` | `string` | `"UTC"` | IANA name of the zone the timestamp is read in |
852
+
853
+ The result depends on the arguments alone, never on the machine it runs on, so
854
+ pre-rendered markup and the browser agree — a timestamp that changes on hydration
855
+ would tear the markup apart. A timestamp that cannot be read renders as an empty
856
+ string rather than as `Invalid Date`.
857
+
858
+ ---
859
+
860
+ ### `useAutoplay( videoRef, enabled )`
861
+
862
+ ```js
863
+ import { useAutoplay } from "scavold/composables/useAutoplay.js";
864
+ ```
865
+
866
+ The autoplay behaviour of `<ScavoldVideo>` and `<ScavoldCover>`, for a site's own
867
+ video component: plays the video muted while at least half of it is on screen, pauses
868
+ it when scrolled away, leaves it alone once the visitor paused it or turned the sound
869
+ up, and does nothing for visitors who ask for reduced motion.
870
+
871
+ ```js
872
+ const video = ref( null ); // template ref of the <video>
873
+ useAutoplay( video, () => autoplay.value );
874
+ ```
875
+
876
+ Leave the `autoplay` attribute off the element; it would start the video regardless.
877
+
878
+ ---
879
+
880
+ ### `useContainer( props )`
881
+
882
+ ```js
883
+ import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
884
+ ```
885
+
886
+ Shared composable for custom container components. Parses the props emitted by the
887
+ Markdown container renderer and exposes derived values ready for template binding.
888
+
889
+ #### Usage
890
+
891
+ ```vue
892
+ <script setup>
893
+ import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
894
+
895
+ const props = defineProps( {
896
+ ...containerProps,
897
+ background: String, // custom KV argument: background=/media/hero.jpg
898
+ } );
899
+
900
+ const { containerName, classes, dataAttrs } = useContainer( props );
901
+ </script>
902
+
903
+ <template>
904
+ <section
905
+ :class="[containerName, classes]"
906
+ :style="background ? `--bg: url(${background})` : ''"
907
+ v-bind="dataAttrs"
908
+ >
909
+ <slot />
910
+ </section>
911
+ </template>
626
912
  ```
627
913
 
628
- ---
914
+ #### `containerProps`
629
915
 
630
- ### `<ScavoldContainer>`
916
+ The shared props definition every container component must declare. Spread it into
917
+ `defineProps` to avoid duplicating the base prop declarations.
631
918
 
632
- Fallback component for Markdown container blocks whose name has no dedicated
633
- registered component. Renders as the matching HTML sectioning element if the
634
- container name is one (`section`, `aside`, etc.), otherwise as a `<div>`.
919
+ | Prop | Type | Description |
920
+ |---|---|---|
921
+ | `class` | `string` | Space-separated boolean flags from the container opening line |
922
+ | `data-container` | `string` | The container name as written in Markdown |
635
923
 
636
- Any container name works without being declared first — undeclared names are caught
637
- by this component. Because a typo would otherwise become a silent `<div>`, the build
638
- reports each undeclared name once:
924
+ All `key=value` pairs from the opening line are passed as additional props with a
925
+ `data-` prefix (e.g. `background=/img.jpg` → prop `data-background`). Declare them
926
+ explicitly in `defineProps` to use them.
639
927
 
640
- ```
641
- [scavold] container ':::sectoin' is not declared in .cratly.config.yaml — rendering
642
- it with ScavoldContainer. Declare it to silence this, or fix the name if it is a typo.
643
- ```
928
+ Flags arrive **both ways**: as part of `class` and as an empty `data-<flag>` prop, so
929
+ a boolean container parameter can be declared as a prop (this is how `useVideo` reads
930
+ `autoplay` and `loop`) while purely presentational flags keep working as
931
+ class names. A flag a component does not declare falls through into the markup as an
932
+ empty attribute — `::: section highlight` renders `<section class="highlight"
933
+ data-highlight>`.
644
934
 
645
- Declaring the container in `.cratly.config.yaml` silences the report and gives it
646
- typed properties in the cratly editor.
935
+ A container that opens the page — its fence is the first thing in the page body —
936
+ additionally receives an empty `data-leading` attribute. It is information only and
937
+ changes nothing by itself; a component or a stylesheet can tell from it that this
938
+ block stands at the very top. The same fact reaches the layout ahead of the content as
939
+ `page.leadingContainer` (see [A cover opening the page](#a-cover-opening-the-page)).
647
940
 
648
- Not intended for direct use. Theme developers should instead create a dedicated
649
- component for each container name they declare in `.cratly.config.yaml`.
941
+ #### Returns
650
942
 
651
- See `useContainer` below for building custom container components.
943
+ | Name | Type | Description |
944
+ |---|---|---|
945
+ | `containerName` | `ComputedRef<string>` | The container name (value of `data-container`) |
946
+ | `rootTag` | `ComputedRef<string>` | `containerName` if it is a sectioning element, otherwise `"div"` |
947
+ | `classes` | `ComputedRef<string>` | The `class` prop value |
948
+ | `dataAttrs` | `ComputedRef<object>` | All `data-*` props as a plain object, suitable for `v-bind` |
652
949
 
653
- ---
950
+ #### Markdown syntax reminder
654
951
 
655
- ### `<ScavoldSection>`, `<ScavoldAside>`, `<ScavoldArticle>`, `<ScavoldHeader>`, `<ScavoldFooter>`, `<ScavoldNav>`, `<ScavoldMain>`
952
+ ```markdown
953
+ ::: hero dark centered background=/media/hero.jpg
954
+ Content here.
955
+ :::
956
+ ```
656
957
 
657
- Thin wrapper components for the seven HTML sectioning elements. Each renders its
658
- corresponding element, forwarding `class` and `data-*` attributes from the container
659
- arguments.
958
+ This emits `class="dark centered"`, `data-container="hero"`, and
959
+ `data-background="/media/hero.jpg"` onto the component.
660
960
 
661
- These are the default targets for their respective container names. A theme developer
662
- can replace any of them by registering a component with the same name before calling
663
- `scavoldEnhanceApp`, or by passing an explicit override to `augmentConfig`:
961
+ ---
962
+
963
+ ### `useCover( props )`
664
964
 
665
965
  ```js
666
- // .vitepress/config.js
667
- export default defineConfig( await augmentConfig( config, {
668
- containers: {
669
- section: "MySiteSection",
670
- }
671
- } ) );
966
+ import { useCover, coverProps } from "scavold/composables/useCover.js";
672
967
  ```
673
968
 
969
+ Resolves the arguments of a `:::cover` block for a site's own cover component, so it
970
+ renders the same picture the way its design asks:
971
+
674
972
  ```js
675
- // .vitepress/theme/index.js
676
- async function enhanceApp( context ) {
677
- app.component( "ScavoldSection", MySiteSection ); // registered before scavold
678
- await scavoldEnhanceApp( context );
679
- }
973
+ const props = defineProps( { ...coverProps } );
974
+ const { src, isVideo, srcset, webpSrcset, sizes, poster, focus, label, tone } = useCover( props );
680
975
  ```
681
976
 
682
- ---
683
-
684
- ## Composables
977
+ `srcset`, `webpSrcset` and `sizes` are filled for an image only; the build provides
978
+ the variants and the dimensions `sizes` is derived from. `tone` is `"light"`, `"dark"`
979
+ or empty, one of `COVER_TONES`. `isVideoUrl( url )` and `coverSizes( width, height )`
980
+ are exported as well.
685
981
 
686
982
  ---
687
983
 
@@ -767,46 +1063,6 @@ const items = computed( () =>
767
1063
 
768
1064
  ---
769
1065
 
770
- ### `useVideo( props )`
771
-
772
- ```js
773
- import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
774
- ```
775
-
776
- Composable for custom video components. Resolves the `:::video` container props into
777
- clean, typed values ready for template binding. `autoplay` automatically forces `muted`
778
- to satisfy browser autoplay policies.
779
-
780
- #### `videoProps`
781
-
782
- Spread into `defineProps` to declare all video-related props at once.
783
-
784
- | Prop | Argument | Description |
785
- |---|---|---|
786
- | `dataSrc` | `src=…` | Video file URL |
787
- | `dataPoster` | `poster=…` | Poster image URL |
788
- | `dataAutoplay` | `autoplay` | Present when the `autoplay` flag is set |
789
- | `dataLoop` | `loop` | Present when the `loop` flag is set |
790
- | `dataMuted` | `muted` | Present when the `muted` flag is set |
791
- | `dataOverlay` | `overlay` | Present when the `overlay` flag is set (background mode) |
792
- | `dataControls` | `controls` | Present when the `controls` flag is set |
793
- | `dataPreload` | `preload=…` | One of `"none"`, `"metadata"`, `"auto"` |
794
-
795
- #### Returns
796
-
797
- | Name | Type | Description |
798
- |---|---|---|
799
- | `src` | `ComputedRef<string>` | Video URL |
800
- | `poster` | `ComputedRef<string>` | Poster URL, empty string if absent |
801
- | `autoplay` | `ComputedRef<boolean>` | `true` when `autoplay` flag is set |
802
- | `loop` | `ComputedRef<boolean>` | `true` when `loop` flag is set |
803
- | `muted` | `ComputedRef<boolean>` | `true` when `muted` flag is set or `autoplay` is true |
804
- | `overlay` | `ComputedRef<boolean>` | `true` when the `overlay` flag is set (render as background) |
805
- | `controls` | `ComputedRef<boolean>` | `true` in the default player; in overlay mode only when the `controls` flag is set |
806
- | `preload` | `ComputedRef<string>` | Resolved preload value, defaults to `"metadata"` |
807
-
808
- ---
809
-
810
1066
  ### `usePageList( props )`
811
1067
 
812
1068
  ```js
@@ -878,107 +1134,6 @@ argument arrives as `data…`, in the same way as `videoProps`: `dataFrom` for `
878
1134
 
879
1135
  ---
880
1136
 
881
- ### `formatTimestamp( timestamp, options )`
882
-
883
- ```js
884
- import { formatTimestamp } from "scavold/lib/dateFormat.js";
885
- ```
886
-
887
- Renders a timestamp the way page lists do, for a theme's own components — a byline
888
- under a post's title, for instance. Free of Vue and VitePress, so build-time code can
889
- use it too.
890
-
891
- | Option | Type | Default | Description |
892
- |---|---|---|---|
893
- | `locale` | `string` | the runtime's | Locale to phrase the result in |
894
- | `dateStyle` | `"short" \| "medium" \| "long" \| "full" \| "iso"` | `"long"` | Style of the date; `iso` writes `2020-10-20` in every language |
895
- | `timeStyle` | same values | — | Style of the time of day; omitted, only the date is rendered |
896
- | `timeZone` | `string` | `"UTC"` | IANA name of the zone the timestamp is read in |
897
-
898
- The result depends on the arguments alone, never on the machine it runs on, so
899
- pre-rendered markup and the browser agree — a timestamp that changes on hydration
900
- would tear the markup apart. A timestamp that cannot be read renders as an empty
901
- string rather than as `Invalid Date`.
902
-
903
- ---
904
-
905
- ### `useContainer( props )`
906
-
907
- ```js
908
- import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
909
- ```
910
-
911
- Shared composable for custom container components. Parses the props emitted by the
912
- Markdown container renderer and exposes derived values ready for template binding.
913
-
914
- #### Usage
915
-
916
- ```vue
917
- <script setup>
918
- import { useContainer, containerProps } from "./scavold/composables/useContainer.js";
919
-
920
- const props = defineProps( {
921
- ...containerProps,
922
- background: String, // custom KV argument: background=/media/hero.jpg
923
- } );
924
-
925
- const { containerName, classes, dataAttrs } = useContainer( props );
926
- </script>
927
-
928
- <template>
929
- <section
930
- :class="[containerName, classes]"
931
- :style="background ? `--bg: url(${background})` : ''"
932
- v-bind="dataAttrs"
933
- >
934
- <slot />
935
- </section>
936
- </template>
937
- ```
938
-
939
- #### `containerProps`
940
-
941
- The shared props definition every container component must declare. Spread it into
942
- `defineProps` to avoid duplicating the base prop declarations.
943
-
944
- | Prop | Type | Description |
945
- |---|---|---|
946
- | `class` | `string` | Space-separated boolean flags from the container opening line |
947
- | `data-container` | `string` | The container name as written in Markdown |
948
-
949
- All `key=value` pairs from the opening line are passed as additional props with a
950
- `data-` prefix (e.g. `background=/img.jpg` → prop `data-background`). Declare them
951
- explicitly in `defineProps` to use them.
952
-
953
- Flags arrive **both ways**: as part of `class` and as an empty `data-<flag>` prop, so
954
- a boolean container parameter can be declared as a prop (this is how `useVideo` reads
955
- `overlay`, `autoplay` and `loop`) while purely presentational flags keep working as
956
- class names. A flag a component does not declare falls through into the markup as an
957
- empty attribute — `::: section highlight` renders `<section class="highlight"
958
- data-highlight>`.
959
-
960
- #### Returns
961
-
962
- | Name | Type | Description |
963
- |---|---|---|
964
- | `containerName` | `ComputedRef<string>` | The container name (value of `data-container`) |
965
- | `rootTag` | `ComputedRef<string>` | `containerName` if it is a sectioning element, otherwise `"div"` |
966
- | `classes` | `ComputedRef<string>` | The `class` prop value |
967
- | `dataAttrs` | `ComputedRef<object>` | All `data-*` props as a plain object, suitable for `v-bind` |
968
-
969
- #### Markdown syntax reminder
970
-
971
- ```markdown
972
- ::: hero dark centered background=/media/hero.jpg
973
- Content here.
974
- :::
975
- ```
976
-
977
- This emits `class="dark centered"`, `data-container="hero"`, and
978
- `data-background="/media/hero.jpg"` onto the component.
979
-
980
- ---
981
-
982
1137
  ### `useRedirect()`
983
1138
 
984
1139
  ```js
@@ -1040,6 +1195,8 @@ const label = t("nav.breadcrumb"); // ComputedRef<string>, reactive to locale ch
1040
1195
  | Key | Default (en) | Description |
1041
1196
  |---|---|---|
1042
1197
  | `@scavold.nav.breadcrumb` | `"Breadcrumb"` | `aria-label` on the breadcrumb `<nav>` |
1198
+ | `@scavold.cover.play` | `"Play video"` | Label of a cover video's button while the video is paused |
1199
+ | `@scavold.cover.pause` | `"Pause video"` | Label of a cover video's button while the video plays |
1043
1200
 
1044
1201
  #### Providing translations in your theme
1045
1202
 
@@ -1079,6 +1236,44 @@ the Scavold default.
1079
1236
 
1080
1237
  ---
1081
1238
 
1239
+ ### `useVideo( props )`
1240
+
1241
+ ```js
1242
+ import { useVideo, videoProps } from "./scavold/composables/useVideo.js";
1243
+ ```
1244
+
1245
+ Composable for custom video components. Resolves the `:::video` container props into
1246
+ clean, typed values ready for template binding. `autoplay` automatically forces `muted`
1247
+ to satisfy browser autoplay policies.
1248
+
1249
+ #### `videoProps`
1250
+
1251
+ Spread into `defineProps` to declare all video-related props at once.
1252
+
1253
+ | Prop | Argument | Description |
1254
+ |---|---|---|
1255
+ | `dataSrc` | `src=…` | Video file URL |
1256
+ | `dataPoster` | `poster=…` | Poster image URL |
1257
+ | `dataAutoplay` | `autoplay` | Present when the `autoplay` flag is set |
1258
+ | `dataLoop` | `loop` | Present when the `loop` flag is set |
1259
+ | `dataMuted` | `muted` | Present when the `muted` flag is set |
1260
+ | `dataPreload` | `preload=…` | One of `"none"`, `"metadata"`, `"auto"` |
1261
+ | `dataLabel` | `label=…` | Accessible label |
1262
+
1263
+ #### Returns
1264
+
1265
+ | Name | Type | Description |
1266
+ |---|---|---|
1267
+ | `src` | `ComputedRef<string>` | Video URL |
1268
+ | `poster` | `ComputedRef<string>` | Poster URL, empty string if absent |
1269
+ | `autoplay` | `ComputedRef<boolean>` | `true` when `autoplay` flag is set |
1270
+ | `loop` | `ComputedRef<boolean>` | `true` when `loop` flag is set |
1271
+ | `muted` | `ComputedRef<boolean>` | `true` when `muted` flag is set or `autoplay` is true |
1272
+ | `preload` | `ComputedRef<string>` | Resolved preload value, defaults to `"metadata"` |
1273
+ | `ariaLabel` | `ComputedRef<string \| undefined>` | Accessible label, `undefined` when the author set none |
1274
+
1275
+ ---
1276
+
1082
1277
  ## Design patterns
1083
1278
 
1084
1279
  ---
@@ -1107,6 +1302,12 @@ their context and pass it through to `<ScavoldImage>`:
1107
1302
 
1108
1303
  Content authors pick the component (`HeroImage`, `ThumbImage`, etc.) that matches
1109
1304
  their intent. The `sizes` value is baked into the component and invisible to them.
1305
+ The built-in [`<ScavoldCover>`](#scavoldcover) works this way.
1306
+
1307
+ A container argument declared as `media-file` that names an image reaches the
1308
+ component with everything it needs for this: besides its URL, as `data-<arg>`, the
1309
+ build adds `data-<arg>-srcset`, `data-<arg>-webp-srcset`, `data-<arg>-width` and
1310
+ `data-<arg>-height` — the variants and the upright dimensions of the source.
1110
1311
 
1111
1312
  **Why not measure at runtime?** A `ResizeObserver`-based approach that reads the
1112
1313
  container's pixel size and sets `src` dynamically is accurate but has serious
@@ -1119,4 +1320,3 @@ The global `image_sizes` in `.cratly.config.yaml` is a fallback for images that
1119
1320
  no layout-aware component wrapping them. Set it to the most common case in the
1120
1321
  theme (typically the prose column width) and override via dedicated components for
1121
1322
  everything else.
1122
-