@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.
@@ -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
- * Props: eyebrow (kicker, default "Agenda"), title (h2), accent (blue|green|mixed).
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].routes.push(route)
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(activeChapter.value?.routes.length ?? 0, MAX_MINIS)
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 activeChapter?.routes"
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.7.2",
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",