@miragon/slidev-toolkit 1.0.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.
@@ -0,0 +1,114 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * cover — animierte Titelseite.
4
+ * Mesh-Shader als Vollbild-Hintergrund (z0), Content darüber (z2).
5
+ * Slots: default = Titel/Subtitle (aus Markdown). Frontmatter-Props:
6
+ * eyebrow — kleine Überzeile (z. B. "Q2 Review")
7
+ * footer — Footer-Text rechts (z. B. Name · Datum)
8
+ */
9
+ defineProps<{
10
+ eyebrow?: string
11
+ footer?: string
12
+ }>()
13
+
14
+ // Brand-Asset reist mit dem Theme: statischer Import, von Vite gebundlet und
15
+ // base-aware. Das Deck erbt das Logo aus dem Theme, ohne es selbst zu duplizieren.
16
+ import logoSrc from '../assets/logo.svg'
17
+ function onLogoError(e: Event) {
18
+ (e.target as HTMLImageElement).style.display = 'none'
19
+ }
20
+ </script>
21
+
22
+ <template>
23
+ <div class="cover-layout">
24
+ <BrandMeshBackground />
25
+
26
+ <div class="cover-content">
27
+ <div v-if="eyebrow" class="cover-eyebrow">{{ eyebrow }}</div>
28
+ <div class="cover-body">
29
+ <slot />
30
+ </div>
31
+ </div>
32
+
33
+ <div class="cover-footer">
34
+ <img :src="logoSrc" alt="Miragon" class="cover-logo" @error="onLogoError" />
35
+ <span v-if="footer" class="cover-footer-text">{{ footer }}</span>
36
+ </div>
37
+ </div>
38
+ </template>
39
+
40
+ <style scoped>
41
+ .cover-layout {
42
+ position: relative;
43
+ width: 100%;
44
+ height: 100%;
45
+ overflow: hidden;
46
+ color: var(--miragon-white);
47
+ /* Brand-Gradient als unterste Ebene: greift, falls BrandMeshBackground (WebGL) gar nicht
48
+ mountet (PDF/headless ohne GPU) — so nie Weiß, immer der Miragon-Verlauf.
49
+ BrandMeshBackground liegt mit position:absolute darüber und verdeckt ihn live. */
50
+ background: linear-gradient(125deg, #335DE5 0%, #1a1a4e 35%, #0d0d2b 55%, #00C853 100%);
51
+ }
52
+
53
+ .cover-content {
54
+ position: relative;
55
+ z-index: 2;
56
+ height: 100%;
57
+ display: flex;
58
+ flex-direction: column;
59
+ justify-content: center;
60
+ padding: 0 4rem;
61
+ max-width: 56rem;
62
+ }
63
+
64
+ .cover-eyebrow {
65
+ font-size: 0.95rem;
66
+ font-weight: 600;
67
+ letter-spacing: 0.18em;
68
+ text-transform: uppercase;
69
+ color: var(--miragon-green);
70
+ margin-bottom: 1.25rem;
71
+ }
72
+
73
+ /* Titel: weiß, kursiv, sehr fett — Miragon-Charakter */
74
+ .cover-body :deep(h1) {
75
+ font-size: 3.6rem;
76
+ line-height: 1.05;
77
+ font-weight: 900;
78
+ font-style: italic;
79
+ color: var(--miragon-white);
80
+ margin: 0;
81
+ text-shadow: 0 2px 24px rgba(13, 13, 43, 0.35);
82
+ }
83
+
84
+ .cover-body :deep(p) {
85
+ font-size: 1.4rem;
86
+ font-weight: 400;
87
+ color: rgba(255, 255, 255, 0.88);
88
+ margin-top: 1.25rem;
89
+ max-width: 42rem;
90
+ }
91
+
92
+ .cover-body :deep(a) {
93
+ color: var(--miragon-green);
94
+ text-decoration: none;
95
+ }
96
+
97
+ .cover-footer {
98
+ position: absolute;
99
+ z-index: 2;
100
+ bottom: 2rem;
101
+ left: 4rem;
102
+ right: 4rem;
103
+ display: flex;
104
+ align-items: center;
105
+ justify-content: space-between;
106
+ }
107
+
108
+ .cover-logo { height: 1.75rem; opacity: 0.95; }
109
+
110
+ .cover-footer-text {
111
+ font-size: 0.85rem;
112
+ color: rgba(255, 255, 255, 0.7);
113
+ }
114
+ </style>
@@ -0,0 +1,308 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * goodbad — "Which one is right, and why?" (STATISCHE Welt, kein Mesh-Shader).
4
+ *
5
+ * Zwei neutrale weiße Panels nebeneinander, beschriftet "Model A" / "Model B".
6
+ * Beim ersten Klick erscheinen Verdict-Badges (Recommended / Avoid) UND eine
7
+ * kurze Regel-Legende — der Saal kann vorher selbst überlegen, welches Modell
8
+ * besser ist, bevor die Auflösung kommt.
9
+ *
10
+ * Bewusst WEISSE Panels (anders als `compare`, das links/rechts semantisch
11
+ * blau/grün einfärbt): das Modell-Verdict soll erst beim Reveal sichtbar
12
+ * werden, eine Farbe würde es vorab verraten.
13
+ *
14
+ * Folgt der hero/content/compare-Bildsprache: heller Grund, klarer Akzent-Balken,
15
+ * großzügige Typografie, ein einziger sparsamer Gradient-Akzent.
16
+ *
17
+ * Named Slots (in Markdown mit `::left::` / `::right::` / `::legend::`):
18
+ * left — Inhalt von Model A (Text, kleines Code-Snippet, inline-SVG)
19
+ * right — Inhalt von Model B
20
+ * legend — kurze Regel, beim Klick mit den Verdicts zusammen sichtbar
21
+ *
22
+ * Frontmatter-Props:
23
+ * title — Slide-Titel (optional)
24
+ * eyebrow — kleine Überzeile (optional)
25
+ * prompt — kursive Leitfrage als Subtitle (optional)
26
+ * leftIsGood — true → A ist "Recommended", B ist "Avoid"
27
+ * false → A ist "Avoid", B ist "Recommended" (default)
28
+ * Bewusst pro Slide variieren, sodass "Recommended" nicht
29
+ * immer auf derselben Seite landet.
30
+ * accent — "blue" | "green" | "mixed" — Balken-/Eyebrow-Akzent (default blue)
31
+ */
32
+ import { computed } from 'vue'
33
+
34
+ const props = withDefaults(
35
+ defineProps<{
36
+ eyebrow?: string
37
+ prompt?: string
38
+ leftIsGood?: boolean
39
+ accent?: 'blue' | 'green' | 'mixed'
40
+ // Slidev reicht das volle Frontmatter-Objekt als `frontmatter`-Prop durch.
41
+ frontmatter?: Record<string, unknown>
42
+ }>(),
43
+ { accent: 'blue', leftIsGood: false },
44
+ )
45
+
46
+ // `title` ist ein RESERVIERTES Slidev-Frontmatter-Feld (TOC/Nav) und wird NICHT
47
+ // als eigener Prop durchgereicht (FRONTMATTER_FIELDS). Daher aus dem
48
+ // mitgelieferten `frontmatter`-Objekt lesen, damit `title:` in slides.md wie
49
+ // dokumentiert funktioniert.
50
+ const title = computed(() => props.frontmatter?.title as string | undefined)
51
+
52
+ // Token reaktiv (siehe hero.vue) — alle Werte aus theme.css, keine Hex.
53
+ const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
54
+ const accentVar = computed(() =>
55
+ props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
56
+ )
57
+
58
+ // Verdict-Logik: leftIsGood entscheidet, welche Seite das grüne Recommended
59
+ // und welche das graue Avoid bekommt.
60
+ const leftVerdictLabel = computed(() => (props.leftIsGood ? 'Recommended' : 'Avoid'))
61
+ const rightVerdictLabel = computed(() => (props.leftIsGood ? 'Avoid' : 'Recommended'))
62
+ const leftVerdictClass = computed(() => (props.leftIsGood ? 'verdict-good' : 'verdict-bad'))
63
+ const rightVerdictClass = computed(() => (props.leftIsGood ? 'verdict-bad' : 'verdict-good'))
64
+ </script>
65
+
66
+ <template>
67
+ <div
68
+ class="goodbad-layout"
69
+ :style="{ '--gb-grad': gradientVar, '--gb-accent': accentVar }"
70
+ >
71
+ <div class="goodbad-inner">
72
+ <header v-if="title || eyebrow || prompt" class="goodbad-head">
73
+ <span class="goodbad-bar" aria-hidden="true"></span>
74
+ <div v-if="eyebrow" class="goodbad-eyebrow">{{ eyebrow }}</div>
75
+ <h2 v-if="title" class="goodbad-title">{{ title }}</h2>
76
+ <p v-if="prompt" class="goodbad-prompt">{{ prompt }}</p>
77
+ </header>
78
+
79
+ <div class="goodbad-grid">
80
+ <section class="goodbad-panel">
81
+ <div class="panel-head">
82
+ <span class="model-label">Model A</span>
83
+ <v-click at="1">
84
+ <span class="verdict" :class="leftVerdictClass">
85
+ <span class="verdict-dot" aria-hidden="true"></span>
86
+ <span class="verdict-label">{{ leftVerdictLabel }}</span>
87
+ </span>
88
+ </v-click>
89
+ </div>
90
+ <div class="panel-body"><slot name="left" /></div>
91
+ </section>
92
+
93
+ <section class="goodbad-panel">
94
+ <div class="panel-head">
95
+ <span class="model-label">Model B</span>
96
+ <v-click at="1">
97
+ <span class="verdict" :class="rightVerdictClass">
98
+ <span class="verdict-dot" aria-hidden="true"></span>
99
+ <span class="verdict-label">{{ rightVerdictLabel }}</span>
100
+ </span>
101
+ </v-click>
102
+ </div>
103
+ <div class="panel-body"><slot name="right" /></div>
104
+ </section>
105
+ </div>
106
+
107
+ <v-click at="1">
108
+ <div class="goodbad-legend"><slot name="legend" /></div>
109
+ </v-click>
110
+ </div>
111
+ </div>
112
+ </template>
113
+
114
+ <style scoped>
115
+ .goodbad-layout {
116
+ position: relative;
117
+ width: 100%;
118
+ height: 100%;
119
+ overflow: hidden;
120
+ background: var(--miragon-gray-bg);
121
+ color: var(--miragon-text-primary);
122
+ display: flex;
123
+ align-items: stretch;
124
+ }
125
+
126
+ .goodbad-inner {
127
+ position: relative;
128
+ z-index: 1;
129
+ width: 100%;
130
+ max-width: 70rem;
131
+ margin: 0 auto;
132
+ padding: 3rem 5rem;
133
+ display: flex;
134
+ flex-direction: column;
135
+ }
136
+
137
+ /* --- Kopf: Balken + Eyebrow + Titel + kursive Leitfrage ------------------ */
138
+ .goodbad-head {
139
+ flex: 0 0 auto;
140
+ margin-bottom: 1.5rem;
141
+ }
142
+ .goodbad-bar {
143
+ display: block;
144
+ width: 3.5rem;
145
+ height: 0.35rem;
146
+ border-radius: 999px;
147
+ background: var(--gb-grad);
148
+ margin-bottom: 1.1rem;
149
+ }
150
+ .goodbad-eyebrow {
151
+ font-size: 0.9rem;
152
+ font-weight: 600;
153
+ letter-spacing: 0.18em;
154
+ text-transform: uppercase;
155
+ color: var(--gb-accent);
156
+ margin-bottom: 0.6rem;
157
+ }
158
+ .goodbad-title {
159
+ font-size: clamp(1.7rem, 2.8vw, 2.2rem);
160
+ line-height: 1.15;
161
+ font-weight: 800;
162
+ letter-spacing: -0.02em;
163
+ color: var(--miragon-text-primary);
164
+ margin: 0;
165
+ }
166
+ .goodbad-prompt {
167
+ font-size: 1.05rem;
168
+ font-style: italic;
169
+ color: var(--miragon-text-muted);
170
+ margin: 0.85rem 0 0;
171
+ max-width: 46rem;
172
+ line-height: 1.5;
173
+ }
174
+
175
+ /* --- Zwei weiße Panels (neutral; Verdict erscheint erst beim Klick) ------ */
176
+ .goodbad-grid {
177
+ flex: 1 1 auto;
178
+ min-height: 0;
179
+ display: grid;
180
+ grid-template-columns: 1fr 1fr;
181
+ gap: 2rem;
182
+ }
183
+ .goodbad-panel {
184
+ background: var(--miragon-white);
185
+ border: 1px solid #E5E7EB;
186
+ border-radius: 1.1rem;
187
+ padding: 1.25rem 1.5rem 1.5rem;
188
+ box-shadow: 0 8px 20px rgba(51, 93, 229, 0.08);
189
+ display: flex;
190
+ flex-direction: column;
191
+ min-height: 0;
192
+ }
193
+
194
+ .panel-head {
195
+ display: flex;
196
+ align-items: center;
197
+ gap: 0.8rem;
198
+ min-height: 1.4rem;
199
+ margin-bottom: 1rem;
200
+ }
201
+ .model-label {
202
+ font-size: 0.72rem;
203
+ font-weight: 700;
204
+ letter-spacing: 0.14em;
205
+ text-transform: uppercase;
206
+ color: var(--miragon-text-muted);
207
+ }
208
+ .verdict {
209
+ display: inline-flex;
210
+ align-items: center;
211
+ gap: 0.45rem;
212
+ }
213
+ .verdict-dot {
214
+ width: 0.6rem;
215
+ height: 0.6rem;
216
+ border-radius: 999px;
217
+ display: inline-block;
218
+ }
219
+ .verdict-label {
220
+ font-size: 0.72rem;
221
+ font-weight: 700;
222
+ letter-spacing: 0.14em;
223
+ text-transform: uppercase;
224
+ }
225
+ .verdict-good .verdict-dot { background: var(--miragon-green-dark); }
226
+ .verdict-good .verdict-label { color: var(--miragon-green-deep); }
227
+ .verdict-bad .verdict-dot { background: var(--miragon-text-muted); }
228
+ .verdict-bad .verdict-label { color: var(--miragon-text-muted); }
229
+
230
+ /* --- Panel-Body: Markdown-Inhalt (Text, Code, kleines inline-SVG) -------- */
231
+ .panel-body {
232
+ flex: 1 1 auto;
233
+ min-height: 0;
234
+ }
235
+ .panel-body :deep(p) {
236
+ font-size: 1.05rem;
237
+ line-height: 1.5;
238
+ color: var(--miragon-text-secondary);
239
+ margin: 0 0 0.85rem;
240
+ }
241
+ .panel-body :deep(strong) {
242
+ font-weight: 700;
243
+ color: var(--miragon-text-primary);
244
+ }
245
+ .panel-body :deep(ul) {
246
+ list-style: none;
247
+ padding: 0;
248
+ margin: 0;
249
+ }
250
+ .panel-body :deep(li) {
251
+ position: relative;
252
+ font-size: 1.02rem;
253
+ line-height: 1.45;
254
+ color: var(--miragon-text-secondary);
255
+ padding-left: 1.5rem;
256
+ margin: 0 0 0.65rem;
257
+ }
258
+ .panel-body :deep(li)::before {
259
+ content: '';
260
+ position: absolute;
261
+ left: 0;
262
+ top: 0.55em;
263
+ width: 0.55rem;
264
+ height: 0.55rem;
265
+ border-radius: 0.18rem;
266
+ background: var(--gb-grad);
267
+ }
268
+ .panel-body :deep(code) {
269
+ font-family: var(--miragon-font-mono);
270
+ font-size: 0.9em;
271
+ background: var(--miragon-blue-light);
272
+ color: var(--miragon-blue-darker);
273
+ padding: 0.1em 0.4em;
274
+ border-radius: 0.35rem;
275
+ }
276
+ .panel-body :deep(pre) {
277
+ font-family: var(--miragon-font-mono);
278
+ font-size: 0.9rem;
279
+ background: var(--miragon-blue-light);
280
+ color: var(--miragon-text-primary);
281
+ padding: 0.8rem 1rem;
282
+ border-radius: 0.5rem;
283
+ margin: 0.4rem 0 0;
284
+ overflow-x: auto;
285
+ }
286
+
287
+ /* --- Legende: kurze Regel unter den Panels, beim Klick mit eingeblendet -- */
288
+ .goodbad-legend {
289
+ flex: 0 0 auto;
290
+ margin-top: 1.5rem;
291
+ padding: 0.9rem 1.2rem;
292
+ background: var(--miragon-white);
293
+ border: 1px solid #E5E7EB;
294
+ border-radius: 0.8rem;
295
+ box-shadow: 0 4px 12px rgba(51, 93, 229, 0.05);
296
+ }
297
+ .goodbad-legend :deep(p) {
298
+ font-size: 1.02rem;
299
+ line-height: 1.5;
300
+ color: var(--miragon-text-secondary);
301
+ margin: 0;
302
+ text-align: center;
303
+ }
304
+ .goodbad-legend :deep(strong) {
305
+ font-weight: 700;
306
+ color: var(--miragon-text-primary);
307
+ }
308
+ </style>
@@ -0,0 +1,168 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * hero — Hero-Statement (STATISCHE Welt, KEIN Mesh-Shader).
4
+ *
5
+ * Hebt EINE große Aussage hervor: einen Leitsatz, eine Kernthese, einen
6
+ * Schlüsselmoment. Anders als `cover` (animierte Titelseite) lebt `hero` von
7
+ * Ruhe: heller Grund, viel Weißraum, sehr große Typografie. Der einzige
8
+ * bewegte Brand-Akzent ist ein statischer Gradient-Balken (sparsam).
9
+ *
10
+ * Diese Slide definiert die visuelle Sprache der statischen Welt, an der sich
11
+ * person/content/compare später orientieren.
12
+ *
13
+ * Slots: default = Aussage (h1) + optionale Attribution/Quelle (p) aus Markdown.
14
+ * Frontmatter-Props:
15
+ * eyebrow — kleine Überzeile (optional)
16
+ * accent — "blue" | "green" | "mixed" — wählt den Gradient-Akzent (default blue)
17
+ * align — "left" | "center" — Ausrichtung (default left)
18
+ */
19
+ import { computed } from 'vue'
20
+
21
+ const props = withDefaults(
22
+ defineProps<{
23
+ eyebrow?: string
24
+ accent?: 'blue' | 'green' | 'mixed'
25
+ align?: 'left' | 'center'
26
+ }>(),
27
+ { accent: 'blue', align: 'left' },
28
+ )
29
+
30
+ // Abgeleitete Token reaktiv halten (computed), falls Slidev die Layout-Instanz
31
+ // über Slides hinweg wiederverwendet. Alle Werte zeigen auf theme.css-Variablen
32
+ // — keine hardcoded Hex.
33
+ //
34
+ // Gradient (Balken + Attributions-Strich) je nach Akzent.
35
+ const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
36
+ // Eyebrow-Solidfarbe: Grün nur beim grünen Akzent (CI: Grün sparsam).
37
+ const eyebrowVar = computed(() =>
38
+ props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
39
+ )
40
+ // Solidfarbe für das hervorgehobene Wort (**bold**). BEWUSST kein
41
+ // background-clip:text-Gradient: headless Chromium (PDF-Export via `npm run
42
+ // export`) kann die clip-Schrift unsichtbar rendern. Solide Akzentfarbe ist
43
+ // immer sichtbar und exportsicher; der Gradient lebt im Balken/Strich.
44
+ const emphasisVar = computed(() =>
45
+ props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
46
+ )
47
+ </script>
48
+
49
+ <template>
50
+ <div
51
+ class="hero-layout"
52
+ :class="[`is-${align}`]"
53
+ :style="{
54
+ '--hero-grad': gradientVar,
55
+ '--hero-eyebrow': eyebrowVar,
56
+ '--hero-emphasis': emphasisVar,
57
+ }"
58
+ >
59
+ <div class="hero-content">
60
+ <span class="hero-bar" aria-hidden="true"></span>
61
+ <div v-if="eyebrow" class="hero-eyebrow">{{ eyebrow }}</div>
62
+ <div class="hero-body">
63
+ <slot />
64
+ </div>
65
+ </div>
66
+ </div>
67
+ </template>
68
+
69
+ <style scoped>
70
+ .hero-layout {
71
+ position: relative;
72
+ width: 100%;
73
+ height: 100%;
74
+ overflow: hidden;
75
+ background: var(--miragon-gray-bg);
76
+ color: var(--miragon-text-primary);
77
+ display: flex;
78
+ align-items: center;
79
+ }
80
+
81
+ .hero-content {
82
+ position: relative;
83
+ z-index: 1;
84
+ width: 100%;
85
+ max-width: 64rem;
86
+ padding: 0 5rem;
87
+ display: flex;
88
+ flex-direction: column;
89
+ }
90
+
91
+ /* Gradient-Akzent: kurzer Balken über der Aussage (der einzige Brand-„Bewegungs"-Punkt). */
92
+ .hero-bar {
93
+ width: 4.5rem;
94
+ height: 0.4rem;
95
+ border-radius: 999px;
96
+ background: var(--hero-grad);
97
+ margin-bottom: 2rem;
98
+ }
99
+
100
+ .hero-eyebrow {
101
+ font-size: 0.95rem;
102
+ font-weight: 600;
103
+ letter-spacing: 0.18em;
104
+ text-transform: uppercase;
105
+ color: var(--hero-eyebrow);
106
+ margin-bottom: 1.5rem;
107
+ }
108
+
109
+ /* Aussage dominiert die Slide: sehr groß, dunkel, fett. */
110
+ .hero-body :deep(h1) {
111
+ font-size: clamp(3rem, 5.4vw, 4.6rem);
112
+ line-height: 1.08;
113
+ font-weight: 800;
114
+ letter-spacing: -0.02em;
115
+ color: var(--miragon-text-primary);
116
+ margin: 0;
117
+ }
118
+
119
+ /* Attribution / Quelle: dezent, mit Gradient-Strich davor. */
120
+ .hero-body :deep(p) {
121
+ position: relative;
122
+ font-size: 1.15rem;
123
+ font-weight: 500;
124
+ color: var(--miragon-text-muted);
125
+ margin: 2.25rem 0 0;
126
+ padding-left: 3rem;
127
+ }
128
+
129
+ .hero-body :deep(p)::before {
130
+ content: '';
131
+ position: absolute;
132
+ left: 0;
133
+ top: 0.7em;
134
+ width: 2rem;
135
+ height: 2px;
136
+ background: var(--hero-grad);
137
+ }
138
+
139
+ /* Optionaler Brand-Akzent auf einzelnen Wörtern: **bold** im Statement bekommt
140
+ die solide Akzentfarbe. Sparsam einsetzen (ein Schlüsselwort, nicht mehr). */
141
+ .hero-body :deep(h1 strong) {
142
+ font-weight: 800;
143
+ color: var(--hero-emphasis);
144
+ }
145
+
146
+ /* --- Ausrichtung ---------------------------------------------------------- */
147
+ .is-center {
148
+ justify-content: center;
149
+ }
150
+ .is-center .hero-content {
151
+ align-items: center;
152
+ text-align: center;
153
+ max-width: 56rem;
154
+ }
155
+ .is-center .hero-eyebrow {
156
+ margin-bottom: 1.75rem;
157
+ }
158
+ .is-center .hero-body :deep(p) {
159
+ padding-left: 0;
160
+ padding-top: 2rem;
161
+ }
162
+ .is-center .hero-body :deep(p)::before {
163
+ left: 50%;
164
+ top: 0;
165
+ transform: translateX(-50%);
166
+ width: 2.5rem;
167
+ }
168
+ </style>