@miragon/slidev-toolkit 1.7.2 → 1.9.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/Agenda.vue +34 -7
- package/layouts/subsection.vue +129 -0
- package/package.json +1 -1
package/components/Agenda.vue
CHANGED
|
@@ -8,7 +8,15 @@
|
|
|
8
8
|
* Past six the rail WRAPS into balanced rows and drops the previews, becoming a
|
|
9
9
|
* static, top-aligned overview.
|
|
10
10
|
*
|
|
11
|
-
*
|
|
11
|
+
* `layout: subsection` slides do NOT open a chapter (they divide a chapter
|
|
12
|
+
* internally); they are collected onto the enclosing chapter's `subsections`.
|
|
13
|
+
* With `preview="subsections"` the previews show just those sub-chapter dividers
|
|
14
|
+
* instead of every slide — a sparse overview for chapters with many slides.
|
|
15
|
+
* Chapters without any subsection fall back to their full slide list.
|
|
16
|
+
*
|
|
17
|
+
* Props: eyebrow (kicker, default "Agenda"), title (h2), accent (blue|green|mixed),
|
|
18
|
+
* preview ("slides" | "subsections", default "slides"), gap (CSS length for the
|
|
19
|
+
* space between the head and the stepper below, default "1.4rem").
|
|
12
20
|
*/
|
|
13
21
|
import { computed, ref, watch } from 'vue'
|
|
14
22
|
import { useElementSize } from '@vueuse/core'
|
|
@@ -27,8 +35,10 @@ const props = withDefaults(
|
|
|
27
35
|
eyebrow?: string
|
|
28
36
|
title?: string
|
|
29
37
|
accent?: 'blue' | 'green' | 'mixed'
|
|
38
|
+
preview?: 'slides' | 'subsections'
|
|
39
|
+
gap?: string
|
|
30
40
|
}>(),
|
|
31
|
-
{ eyebrow: 'Agenda', accent: 'mixed' },
|
|
41
|
+
{ eyebrow: 'Agenda', accent: 'mixed', preview: 'slides', gap: '1.4rem' },
|
|
32
42
|
)
|
|
33
43
|
|
|
34
44
|
const { slides, go, currentPage } = useNav()
|
|
@@ -48,6 +58,7 @@ interface Chapter {
|
|
|
48
58
|
eyebrow: string
|
|
49
59
|
title: string
|
|
50
60
|
routes: any[]
|
|
61
|
+
subsections: any[]
|
|
51
62
|
}
|
|
52
63
|
|
|
53
64
|
const chapters = computed<Chapter[]>(() => {
|
|
@@ -61,9 +72,13 @@ const chapters = computed<Chapter[]>(() => {
|
|
|
61
72
|
eyebrow: clean(fm.eyebrow) || `Chapter ${n}`,
|
|
62
73
|
title: clean(route.meta?.slide?.title) || clean(fm.eyebrow) || `Chapter ${n}`,
|
|
63
74
|
routes: [route],
|
|
75
|
+
subsections: [],
|
|
64
76
|
})
|
|
65
77
|
} else if (out.length) {
|
|
66
|
-
out[out.length - 1]
|
|
78
|
+
const chapter = out[out.length - 1]
|
|
79
|
+
chapter.routes.push(route)
|
|
80
|
+
// Sub-chapter dividers structure a chapter without opening a new one.
|
|
81
|
+
if (fm.layout === 'subsection') chapter.subsections.push(route)
|
|
67
82
|
}
|
|
68
83
|
}
|
|
69
84
|
return out
|
|
@@ -86,6 +101,17 @@ watch(
|
|
|
86
101
|
|
|
87
102
|
const activeChapter = computed(() => chapters.value[selected.value])
|
|
88
103
|
|
|
104
|
+
// The slides shown as previews for the selected chapter. In "subsections" mode
|
|
105
|
+
// that is just the chapter's sub-chapter dividers (a sparse overview); chapters
|
|
106
|
+
// without any subsection fall back to their full slide list so nothing vanishes.
|
|
107
|
+
const previewRoutes = computed(() => {
|
|
108
|
+
const ch = activeChapter.value
|
|
109
|
+
if (!ch) return []
|
|
110
|
+
return props.preview === 'subsections' && ch.subsections.length
|
|
111
|
+
? ch.subsections
|
|
112
|
+
: ch.routes
|
|
113
|
+
})
|
|
114
|
+
|
|
89
115
|
// Mini size is computed as if a chapter held at most this many slides, so the
|
|
90
116
|
// frame never shrinks below the clean size; extra slides scroll below the fold.
|
|
91
117
|
const MAX_MINIS = 21
|
|
@@ -146,7 +172,7 @@ const ASPECT = 16 / 9
|
|
|
146
172
|
const MAX_W = 320
|
|
147
173
|
|
|
148
174
|
const miniWidth = computed(() => {
|
|
149
|
-
const n = Math.min(
|
|
175
|
+
const n = Math.min(previewRoutes.value.length, MAX_MINIS)
|
|
150
176
|
const W = stageW.value
|
|
151
177
|
const H = stageH.value
|
|
152
178
|
if (!n || W < 1 || H < 1) return 200
|
|
@@ -173,7 +199,7 @@ function openSlide(no: number, ev: MouseEvent) {
|
|
|
173
199
|
</script>
|
|
174
200
|
|
|
175
201
|
<template>
|
|
176
|
-
<div class="agenda-layout" :style="{ '--ag-grad': gradientVar, '--ag-accent': accentVar }">
|
|
202
|
+
<div class="agenda-layout" :style="{ '--ag-grad': gradientVar, '--ag-accent': accentVar, '--ag-head-gap': gap }">
|
|
177
203
|
<div class="agenda-inner">
|
|
178
204
|
<header class="agenda-head">
|
|
179
205
|
<span class="agenda-bar" aria-hidden="true"></span>
|
|
@@ -230,7 +256,7 @@ function openSlide(no: number, ev: MouseEvent) {
|
|
|
230
256
|
<transition name="fade-preview" mode="out-in">
|
|
231
257
|
<div :key="selected" class="preview-row">
|
|
232
258
|
<button
|
|
233
|
-
v-for="route in
|
|
259
|
+
v-for="route in previewRoutes"
|
|
234
260
|
:key="route.no"
|
|
235
261
|
type="button"
|
|
236
262
|
class="mini"
|
|
@@ -256,6 +282,7 @@ function openSlide(no: number, ev: MouseEvent) {
|
|
|
256
282
|
/* Fallback for the linter; the inline :style binding overrides at runtime. */
|
|
257
283
|
--ag-grad: var(--miragon-gradient-mixed);
|
|
258
284
|
--ag-accent: var(--miragon-blue);
|
|
285
|
+
--ag-head-gap: 1.4rem;
|
|
259
286
|
position: absolute;
|
|
260
287
|
inset: 0;
|
|
261
288
|
width: 100%;
|
|
@@ -279,7 +306,7 @@ function openSlide(no: number, ev: MouseEvent) {
|
|
|
279
306
|
|
|
280
307
|
.agenda-head {
|
|
281
308
|
flex: 0 0 auto;
|
|
282
|
-
margin-bottom: 1.4rem;
|
|
309
|
+
margin-bottom: var(--ag-head-gap, 1.4rem);
|
|
283
310
|
}
|
|
284
311
|
.agenda-bar {
|
|
285
312
|
display: block;
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* subsection — Unterkapitel-Trenner (STATISCHE Welt, KEIN Mesh-Shader).
|
|
4
|
+
*
|
|
5
|
+
* Der kleine Bruder von `section`: gliedert ein Kapitel INTERN, ohne im Agenda-
|
|
6
|
+
* Rail ein eigenes Kapitel zu eröffnen (die Agenda zählt nur `layout: section`).
|
|
7
|
+
* Optisch untergeordnet — kleinerer Titel, dezentere Ghost-Ziffer, eine
|
|
8
|
+
* optionale Kapitel-Rückreferenz als Überzeile — damit klar bleibt: dies ist
|
|
9
|
+
* eine Zwischenüberschrift, kein neues Kapitel.
|
|
10
|
+
*
|
|
11
|
+
* Die Agenda kann diese Trenner mit `<Agenda preview="subsections">` statt der
|
|
12
|
+
* Einzelfolien als Kapitel-Vorschau rendern.
|
|
13
|
+
*
|
|
14
|
+
* Slots: default = Unterkapiteltitel (h1) + optionaler Einzeiler (p) aus Markdown.
|
|
15
|
+
* Frontmatter-Props:
|
|
16
|
+
* index — Nummer als String (z. B. "2.1"); rendert als Ghost-Ziffer
|
|
17
|
+
* eyebrow — kleine Überzeile (z. B. das übergeordnete Kapitel)
|
|
18
|
+
* accent — "blue" | "green" | "mixed" — Gradient-Akzent (default blue)
|
|
19
|
+
*/
|
|
20
|
+
import { computed } from 'vue'
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(
|
|
23
|
+
defineProps<{
|
|
24
|
+
index?: string
|
|
25
|
+
eyebrow?: string
|
|
26
|
+
accent?: 'blue' | 'green' | 'mixed'
|
|
27
|
+
}>(),
|
|
28
|
+
{ accent: 'blue' },
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
// Token reaktiv (siehe section.vue) — alle Werte aus theme.css, keine Hex.
|
|
32
|
+
const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
|
|
33
|
+
const accentVar = computed(() =>
|
|
34
|
+
props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
|
|
35
|
+
)
|
|
36
|
+
</script>
|
|
37
|
+
|
|
38
|
+
<template>
|
|
39
|
+
<div
|
|
40
|
+
class="subsection-layout"
|
|
41
|
+
:style="{ '--s-grad': gradientVar, '--s-accent': accentVar }"
|
|
42
|
+
>
|
|
43
|
+
<span v-if="index" class="subsection-ghost" aria-hidden="true">{{ index }}</span>
|
|
44
|
+
|
|
45
|
+
<div class="subsection-content">
|
|
46
|
+
<span class="subsection-bar" aria-hidden="true"></span>
|
|
47
|
+
<div v-if="eyebrow" class="subsection-eyebrow">{{ eyebrow }}</div>
|
|
48
|
+
<div class="subsection-body">
|
|
49
|
+
<slot />
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
</div>
|
|
53
|
+
</template>
|
|
54
|
+
|
|
55
|
+
<style scoped>
|
|
56
|
+
.subsection-layout {
|
|
57
|
+
position: relative;
|
|
58
|
+
width: 100%;
|
|
59
|
+
height: 100%;
|
|
60
|
+
overflow: hidden;
|
|
61
|
+
background: var(--miragon-gray-bg);
|
|
62
|
+
color: var(--miragon-text-primary);
|
|
63
|
+
display: flex;
|
|
64
|
+
align-items: center;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* Ghost-Ziffer wie section, aber kleiner und dezenter — Unterkapitel ordnet sich
|
|
68
|
+
dem Kapitel unter. Solide Akzentfarbe (exportsicher, vgl. section.vue). */
|
|
69
|
+
.subsection-ghost {
|
|
70
|
+
position: absolute;
|
|
71
|
+
z-index: 0;
|
|
72
|
+
right: 4.5rem;
|
|
73
|
+
bottom: 2rem;
|
|
74
|
+
font-size: 20rem;
|
|
75
|
+
line-height: 1;
|
|
76
|
+
font-weight: 900;
|
|
77
|
+
letter-spacing: -0.04em;
|
|
78
|
+
color: var(--s-accent);
|
|
79
|
+
opacity: 0.06;
|
|
80
|
+
user-select: none;
|
|
81
|
+
pointer-events: none;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.subsection-content {
|
|
85
|
+
position: relative;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
width: 100%;
|
|
88
|
+
max-width: 60rem;
|
|
89
|
+
padding: 0 5rem;
|
|
90
|
+
display: flex;
|
|
91
|
+
flex-direction: column;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
.subsection-bar {
|
|
95
|
+
width: 3rem;
|
|
96
|
+
height: 0.3rem;
|
|
97
|
+
border-radius: 999px;
|
|
98
|
+
background: var(--s-grad);
|
|
99
|
+
margin-bottom: 1.5rem;
|
|
100
|
+
}
|
|
101
|
+
.subsection-eyebrow {
|
|
102
|
+
font-size: 0.85rem;
|
|
103
|
+
font-weight: 600;
|
|
104
|
+
letter-spacing: 0.18em;
|
|
105
|
+
text-transform: uppercase;
|
|
106
|
+
color: var(--s-accent);
|
|
107
|
+
margin-bottom: 1rem;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.subsection-body :deep(h1) {
|
|
111
|
+
font-size: clamp(2rem, 3.6vw, 3rem);
|
|
112
|
+
line-height: 1.1;
|
|
113
|
+
font-weight: 800;
|
|
114
|
+
letter-spacing: -0.02em;
|
|
115
|
+
color: var(--miragon-text-primary);
|
|
116
|
+
margin: 0;
|
|
117
|
+
}
|
|
118
|
+
.subsection-body :deep(h1 strong) {
|
|
119
|
+
font-weight: 800;
|
|
120
|
+
color: var(--s-accent);
|
|
121
|
+
}
|
|
122
|
+
.subsection-body :deep(p) {
|
|
123
|
+
font-size: 1.1rem;
|
|
124
|
+
font-weight: 500;
|
|
125
|
+
color: var(--miragon-text-muted);
|
|
126
|
+
margin: 1.5rem 0 0;
|
|
127
|
+
max-width: 38rem;
|
|
128
|
+
}
|
|
129
|
+
</style>
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@miragon/slidev-toolkit",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "Miragon Slidev toolkit: the brand theme, the layout archetypes, the reusable components (Card, CardGrid, StepList, Figure, SplitView) and the BrandMeshBackground animation. Single source of design truth, consumed by the reference deck in this repo.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"slidev-theme",
|