@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,287 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * person — Person(en) vorstellen (STATISCHE Welt, kein Mesh-Shader).
4
+ *
5
+ * Folgt der `hero`-Bildsprache: heller Grund, viel Weißraum, große Typografie,
6
+ * sparsamer Gradient-Akzent.
7
+ *
8
+ * Zwei Modi:
9
+ * • Einzeln — Porträt + Textblock (Name, Rolle, Bio als Slot).
10
+ * • Duo — sobald `name2` gesetzt ist: zwei Speaker nebeneinander.
11
+ *
12
+ * Porträt-Rahmen ist robust: zeigt `photo`, wenn gesetzt — sonst Initialen aus
13
+ * dem Namen auf einem Gradient-Rahmen (nie ein kaputtes Bild-Icon). Der Rahmen
14
+ * ist im Hochformat gehalten, passend zu freigestellten Brand-Fotos.
15
+ *
16
+ * Frontmatter-Props (keine Multi-Line-Props → 2. Person über *2-Felder):
17
+ * name, role, photo — erste Person
18
+ * name2, role2, photo2 — zweite Person (aktiviert den Duo-Modus)
19
+ * eyebrow — kleine Überzeile (optional)
20
+ * accent — "blue" | "green" | "mixed" — Gradient-Akzent (default blue)
21
+ * side — "left" | "right" — Porträt-Seite im Einzel-Modus (default left)
22
+ * Slot: Kurzbio (einzeln) bzw. optionaler zentrierter Einleitungssatz (Duo).
23
+ */
24
+ import { computed } from 'vue'
25
+
26
+ const props = withDefaults(
27
+ defineProps<{
28
+ name?: string
29
+ role?: string
30
+ photo?: string
31
+ name2?: string
32
+ role2?: string
33
+ photo2?: string
34
+ eyebrow?: string
35
+ accent?: 'blue' | 'green' | 'mixed'
36
+ side?: 'left' | 'right'
37
+ }>(),
38
+ { accent: 'blue', side: 'left' },
39
+ )
40
+
41
+ const isDuo = computed(() => !!props.name2)
42
+
43
+ // Base-Pfad respektieren (GitHub Pages baut unter /<repo>/). Runtime-Strings
44
+ // werden von Vite NICHT umgeschrieben — daher manuell mit BASE_URL auflösen.
45
+ function withBase(path?: string) {
46
+ if (!path) return path
47
+ if (/^https?:\/\//.test(path)) return path
48
+ return import.meta.env.BASE_URL.replace(/\/$/, '') + '/' + path.replace(/^\//, '')
49
+ }
50
+ const photoSrc = computed(() => withBase(props.photo))
51
+ const photo2Src = computed(() => withBase(props.photo2))
52
+
53
+ // Token reaktiv (siehe hero.vue) — alle Werte aus theme.css, keine Hex.
54
+ const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
55
+ const roleVar = computed(() =>
56
+ props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
57
+ )
58
+
59
+ function toInitials(name?: string) {
60
+ const parts = (name ?? '').trim().split(/\s+/).filter(Boolean)
61
+ if (parts.length === 0) return '·'
62
+ const first = parts[0][0] ?? ''
63
+ const last = parts.length > 1 ? parts[parts.length - 1][0] ?? '' : ''
64
+ return (first + last).toUpperCase()
65
+ }
66
+ const initials = computed(() => toInitials(props.name))
67
+ const initials2 = computed(() => toInitials(props.name2))
68
+
69
+ function onPhotoError(e: Event) {
70
+ // Bild fehlt → Rahmen leeren, Initialen-Fallback übernimmt das Layout drumherum.
71
+ ;(e.target as HTMLImageElement).style.display = 'none'
72
+ }
73
+ </script>
74
+
75
+ <template>
76
+ <div
77
+ class="person-layout"
78
+ :class="[isDuo ? 'is-duo' : `side-${side}`]"
79
+ :style="{ '--p-grad': gradientVar, '--p-role': roleVar }"
80
+ >
81
+ <!-- ────────── Einzel-Modus ────────── -->
82
+ <div v-if="!isDuo" class="person-content">
83
+ <figure class="person-figure">
84
+ <img v-if="photo" :src="photoSrc" :alt="name" class="person-photo" @error="onPhotoError" />
85
+ <span v-else class="person-initials">{{ initials }}</span>
86
+ </figure>
87
+
88
+ <div class="person-text">
89
+ <span class="person-bar" aria-hidden="true"></span>
90
+ <div v-if="eyebrow" class="person-eyebrow">{{ eyebrow }}</div>
91
+ <h1 v-if="name" class="person-name">{{ name }}</h1>
92
+ <div v-if="role" class="person-role">{{ role }}</div>
93
+ <div class="person-bio"><slot /></div>
94
+ </div>
95
+ </div>
96
+
97
+ <!-- ────────── Duo-Modus ────────── -->
98
+ <div v-else class="person-duo">
99
+ <span class="person-bar duo-bar" aria-hidden="true"></span>
100
+ <div v-if="eyebrow" class="person-eyebrow duo-eyebrow">{{ eyebrow }}</div>
101
+ <div class="duo-lead"><slot /></div>
102
+
103
+ <div class="duo-grid">
104
+ <figure class="duo-card">
105
+ <div class="person-figure">
106
+ <img v-if="photo" :src="photoSrc" :alt="name" class="person-photo" @error="onPhotoError" />
107
+ <span v-else class="person-initials">{{ initials }}</span>
108
+ </div>
109
+ <h2 class="duo-name">{{ name }}</h2>
110
+ <div v-if="role" class="duo-role">{{ role }}</div>
111
+ </figure>
112
+
113
+ <figure class="duo-card">
114
+ <div class="person-figure">
115
+ <img v-if="photo2" :src="photo2Src" :alt="name2" class="person-photo" @error="onPhotoError" />
116
+ <span v-else class="person-initials">{{ initials2 }}</span>
117
+ </div>
118
+ <h2 class="duo-name">{{ name2 }}</h2>
119
+ <div v-if="role2" class="duo-role">{{ role2 }}</div>
120
+ </figure>
121
+ </div>
122
+ </div>
123
+ </div>
124
+ </template>
125
+
126
+ <style scoped>
127
+ .person-layout {
128
+ position: relative;
129
+ width: 100%;
130
+ height: 100%;
131
+ overflow: hidden;
132
+ background: var(--miragon-gray-bg);
133
+ color: var(--miragon-text-primary);
134
+ display: flex;
135
+ align-items: center;
136
+ justify-content: center;
137
+ }
138
+
139
+ /* --- Porträt-Rahmen (geteilt: Einzel + Duo) ------------------------------- */
140
+ .person-figure {
141
+ flex: 0 0 auto;
142
+ aspect-ratio: 4 / 5;
143
+ border-radius: 1.4rem;
144
+ overflow: hidden;
145
+ display: flex;
146
+ align-items: center;
147
+ justify-content: center;
148
+ background: var(--p-grad);
149
+ box-shadow: 0 18px 48px rgba(13, 13, 43, 0.18);
150
+ }
151
+ .person-photo {
152
+ width: 100%;
153
+ height: 100%;
154
+ object-fit: cover;
155
+ object-position: top center;
156
+ }
157
+ .person-initials {
158
+ font-weight: 800;
159
+ letter-spacing: -0.02em;
160
+ color: var(--miragon-white);
161
+ }
162
+
163
+ /* --- Akzent-Balken & Eyebrow (geteilt) ------------------------------------ */
164
+ .person-bar {
165
+ width: 3.5rem;
166
+ height: 0.35rem;
167
+ border-radius: 999px;
168
+ background: var(--p-grad);
169
+ margin-bottom: 1.5rem;
170
+ }
171
+ .person-eyebrow {
172
+ font-size: 0.9rem;
173
+ font-weight: 600;
174
+ letter-spacing: 0.18em;
175
+ text-transform: uppercase;
176
+ color: var(--p-role);
177
+ margin-bottom: 0.9rem;
178
+ }
179
+
180
+ /* ═══════════ Einzel-Modus ═══════════ */
181
+ .person-content {
182
+ display: flex;
183
+ align-items: center;
184
+ gap: 4.5rem;
185
+ width: 100%;
186
+ max-width: 64rem;
187
+ padding: 0 5rem;
188
+ }
189
+ .side-right .person-content {
190
+ flex-direction: row-reverse;
191
+ }
192
+ .person-content .person-figure {
193
+ width: clamp(12rem, 21vw, 16.5rem);
194
+ }
195
+ .person-content .person-initials {
196
+ font-size: clamp(3rem, 6vw, 4.75rem);
197
+ }
198
+
199
+ .person-text {
200
+ display: flex;
201
+ flex-direction: column;
202
+ min-width: 0;
203
+ }
204
+ .person-name {
205
+ font-size: clamp(2.4rem, 4.4vw, 3.4rem);
206
+ line-height: 1.05;
207
+ font-weight: 800;
208
+ letter-spacing: -0.02em;
209
+ color: var(--miragon-text-primary);
210
+ margin: 0;
211
+ }
212
+ .person-role {
213
+ font-size: 1.3rem;
214
+ font-weight: 600;
215
+ color: var(--p-role);
216
+ margin-top: 0.6rem;
217
+ }
218
+ .person-bio :deep(p) {
219
+ font-size: 1.15rem;
220
+ line-height: 1.6;
221
+ font-weight: 400;
222
+ color: var(--miragon-text-secondary);
223
+ margin: 1.5rem 0 0;
224
+ max-width: 34rem;
225
+ }
226
+
227
+ /* ═══════════ Duo-Modus ═══════════ */
228
+ .person-duo {
229
+ display: flex;
230
+ flex-direction: column;
231
+ align-items: center;
232
+ text-align: center;
233
+ width: 100%;
234
+ max-width: 60rem;
235
+ padding: 0 4rem;
236
+ }
237
+ .duo-bar {
238
+ margin-bottom: 1.25rem;
239
+ }
240
+ .duo-eyebrow {
241
+ margin-bottom: 0.5rem;
242
+ }
243
+ .duo-lead :deep(p) {
244
+ font-size: 1.15rem;
245
+ line-height: 1.55;
246
+ color: var(--miragon-text-secondary);
247
+ margin: 0 0 2.5rem;
248
+ max-width: 40rem;
249
+ }
250
+ .duo-lead :deep(p:only-child) {
251
+ margin-bottom: 2.5rem;
252
+ }
253
+ .duo-grid {
254
+ display: flex;
255
+ justify-content: center;
256
+ gap: 4rem;
257
+ width: 100%;
258
+ }
259
+ .duo-card {
260
+ flex: 1 1 0;
261
+ max-width: 20rem;
262
+ margin: 0;
263
+ display: flex;
264
+ flex-direction: column;
265
+ align-items: center;
266
+ }
267
+ .duo-card .person-figure {
268
+ width: clamp(8.5rem, 15vw, 12rem);
269
+ }
270
+ .duo-card .person-initials {
271
+ font-size: clamp(2.2rem, 4.5vw, 3.4rem);
272
+ }
273
+ .duo-name {
274
+ font-size: clamp(1.4rem, 2.6vw, 1.85rem);
275
+ line-height: 1.1;
276
+ font-weight: 800;
277
+ letter-spacing: -0.01em;
278
+ color: var(--miragon-text-primary);
279
+ margin: 1.4rem 0 0;
280
+ }
281
+ .duo-role {
282
+ font-size: 1.05rem;
283
+ font-weight: 600;
284
+ color: var(--p-role);
285
+ margin-top: 0.4rem;
286
+ }
287
+ </style>
@@ -0,0 +1,127 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * section — Kapiteltrenner (STATISCHE Welt, KEIN Mesh-Shader).
4
+ *
5
+ * Strukturiert das Deck: markiert den Beginn eines neuen Kapitels. Ruhig wie
6
+ * `hero`, aber mit einer großen, halbtransparenten Index-Ziffer als
7
+ * Orientierungsanker im Hintergrund. Heller Grund, viel Weißraum, ein einziger
8
+ * Gradient-Akzent.
9
+ *
10
+ * Slots: default = Kapiteltitel (h1) + optionaler Einzeiler (p) aus Markdown.
11
+ * Frontmatter-Props:
12
+ * index — Kapitelnummer als String (z. B. "01"); rendert als Ghost-Ziffer
13
+ * eyebrow — kleine Überzeile (z. B. "Chapter")
14
+ * accent — "blue" | "green" | "mixed" — Gradient-Akzent (default blue)
15
+ */
16
+ import { computed } from 'vue'
17
+
18
+ const props = withDefaults(
19
+ defineProps<{
20
+ index?: string
21
+ eyebrow?: string
22
+ accent?: 'blue' | 'green' | 'mixed'
23
+ }>(),
24
+ { accent: 'blue' },
25
+ )
26
+
27
+ // Token reaktiv (siehe hero.vue) — alle Werte aus theme.css, keine Hex.
28
+ const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
29
+ const accentVar = computed(() =>
30
+ props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
31
+ )
32
+ </script>
33
+
34
+ <template>
35
+ <div
36
+ class="section-layout"
37
+ :style="{ '--s-grad': gradientVar, '--s-accent': accentVar }"
38
+ >
39
+ <span v-if="index" class="section-ghost" aria-hidden="true">{{ index }}</span>
40
+
41
+ <div class="section-content">
42
+ <span class="section-bar" aria-hidden="true"></span>
43
+ <div v-if="eyebrow" class="section-eyebrow">{{ eyebrow }}</div>
44
+ <div class="section-body">
45
+ <slot />
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </template>
50
+
51
+ <style scoped>
52
+ .section-layout {
53
+ position: relative;
54
+ width: 100%;
55
+ height: 100%;
56
+ overflow: hidden;
57
+ background: var(--miragon-gray-bg);
58
+ color: var(--miragon-text-primary);
59
+ display: flex;
60
+ align-items: center;
61
+ }
62
+
63
+ /* Große Ghost-Ziffer als Hintergrund-Anker — rein dekorativ, sehr dezent.
64
+ BEWUSST kein background-clip:text-Gradient (headless Chromium beim PDF-Export
65
+ rendert clip-Schrift unsichtbar, vgl. hero.vue) — solide Akzentfarbe mit
66
+ geringer Deckkraft ist exportsicher. */
67
+ .section-ghost {
68
+ position: absolute;
69
+ z-index: 0;
70
+ right: 4rem;
71
+ bottom: 1.5rem;
72
+ font-size: 24rem;
73
+ line-height: 1;
74
+ font-weight: 900;
75
+ letter-spacing: -0.04em;
76
+ color: var(--s-accent);
77
+ opacity: 0.07;
78
+ user-select: none;
79
+ pointer-events: none;
80
+ }
81
+
82
+ .section-content {
83
+ position: relative;
84
+ z-index: 1;
85
+ width: 100%;
86
+ max-width: 60rem;
87
+ padding: 0 5rem;
88
+ display: flex;
89
+ flex-direction: column;
90
+ }
91
+
92
+ .section-bar {
93
+ width: 4.5rem;
94
+ height: 0.4rem;
95
+ border-radius: 999px;
96
+ background: var(--s-grad);
97
+ margin-bottom: 2rem;
98
+ }
99
+ .section-eyebrow {
100
+ font-size: 0.95rem;
101
+ font-weight: 600;
102
+ letter-spacing: 0.18em;
103
+ text-transform: uppercase;
104
+ color: var(--s-accent);
105
+ margin-bottom: 1.25rem;
106
+ }
107
+
108
+ .section-body :deep(h1) {
109
+ font-size: clamp(2.8rem, 5vw, 4.2rem);
110
+ line-height: 1.08;
111
+ font-weight: 800;
112
+ letter-spacing: -0.02em;
113
+ color: var(--miragon-text-primary);
114
+ margin: 0;
115
+ }
116
+ .section-body :deep(h1 strong) {
117
+ font-weight: 800;
118
+ color: var(--s-accent);
119
+ }
120
+ .section-body :deep(p) {
121
+ font-size: 1.2rem;
122
+ font-weight: 500;
123
+ color: var(--miragon-text-muted);
124
+ margin: 1.75rem 0 0;
125
+ max-width: 38rem;
126
+ }
127
+ </style>
@@ -0,0 +1,244 @@
1
+ <script setup lang="ts">
2
+ /**
3
+ * showcase — interactive feature explorer (STATIC world, no shader).
4
+ *
5
+ * A row of clickable cards; one is active at a time. Click any card to switch
6
+ * the active item; the detail panel below cross-fades to the new content.
7
+ * Demonstrates Vue reactivity, CSS transitions, and Vue components living
8
+ * inside a slide — beyond static Markdown.
9
+ *
10
+ * Items are passed as a YAML array via frontmatter; the layout iterates and
11
+ * the user clicks to explore. Default state: first item active.
12
+ *
13
+ * Frontmatter props:
14
+ * title — slide title (h2-level)
15
+ * eyebrow — uppercase kicker
16
+ * accent — "blue" | "green" | "mixed" (default mixed)
17
+ * items — array of `{ label, body }` objects (recommended: 3–4 cards)
18
+ */
19
+ import { computed, ref, watch } from 'vue'
20
+
21
+ interface Item {
22
+ label: string
23
+ body: string
24
+ }
25
+
26
+ const props = withDefaults(
27
+ defineProps<{
28
+ eyebrow?: string
29
+ accent?: 'blue' | 'green' | 'mixed'
30
+ items?: Item[]
31
+ frontmatter?: Record<string, unknown>
32
+ }>(),
33
+ { accent: 'mixed', items: () => [] },
34
+ )
35
+
36
+ const title = computed(() => props.frontmatter?.title as string | undefined)
37
+ const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
38
+ const accentVar = computed(() =>
39
+ props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
40
+ )
41
+
42
+ const selected = ref(0)
43
+ const activeItem = computed(() => props.items[selected.value])
44
+
45
+ // Defensive: clamp selection if the items list shrinks below the current
46
+ // index (e.g. when the slide is re-entered with a different array).
47
+ watch(
48
+ () => props.items.length,
49
+ (len) => {
50
+ if (selected.value >= len) selected.value = 0
51
+ },
52
+ )
53
+ </script>
54
+
55
+ <template>
56
+ <div class="showcase-layout" :style="{ '--sc-grad': gradientVar, '--sc-accent': accentVar }">
57
+ <div class="showcase-inner">
58
+ <header v-if="title || eyebrow" class="showcase-head">
59
+ <span class="showcase-bar" aria-hidden="true"></span>
60
+ <div v-if="eyebrow" class="showcase-eyebrow">{{ eyebrow }}</div>
61
+ <h2 v-if="title" class="showcase-title">{{ title }}</h2>
62
+ </header>
63
+
64
+ <div class="showcase-grid" :class="`cols-${items.length}`">
65
+ <button
66
+ v-for="(item, i) in items"
67
+ :key="i"
68
+ type="button"
69
+ class="showcase-card"
70
+ :class="{ 'is-active': i === selected }"
71
+ @click="selected = i"
72
+ >
73
+ <span class="card-index">{{ String(i + 1).padStart(2, '0') }}</span>
74
+ <span class="card-label">{{ item.label }}</span>
75
+ </button>
76
+ </div>
77
+
78
+ <div class="showcase-detail">
79
+ <transition name="fade-detail" mode="out-in">
80
+ <p :key="selected" class="detail-body">{{ activeItem?.body }}</p>
81
+ </transition>
82
+ </div>
83
+
84
+ <p class="showcase-hint" aria-hidden="true">Click a card to switch.</p>
85
+ </div>
86
+ </div>
87
+ </template>
88
+
89
+ <style scoped>
90
+ .showcase-layout {
91
+ position: relative;
92
+ width: 100%;
93
+ height: 100%;
94
+ overflow: hidden;
95
+ background: var(--miragon-gray-bg);
96
+ color: var(--miragon-text-primary);
97
+ display: flex;
98
+ align-items: stretch;
99
+ }
100
+
101
+ .showcase-inner {
102
+ position: relative;
103
+ z-index: 1;
104
+ width: 100%;
105
+ max-width: 72rem;
106
+ margin: 0 auto;
107
+ padding: 3rem 4rem 2.5rem;
108
+ display: flex;
109
+ flex-direction: column;
110
+ }
111
+
112
+ .showcase-head {
113
+ flex: 0 0 auto;
114
+ margin-bottom: 2rem;
115
+ }
116
+ .showcase-bar {
117
+ display: block;
118
+ width: 3.5rem;
119
+ height: 0.35rem;
120
+ border-radius: 999px;
121
+ background: var(--sc-grad);
122
+ margin-bottom: 1rem;
123
+ }
124
+ .showcase-eyebrow {
125
+ font-size: 0.9rem;
126
+ font-weight: 600;
127
+ letter-spacing: 0.18em;
128
+ text-transform: uppercase;
129
+ color: var(--sc-accent);
130
+ margin-bottom: 0.6rem;
131
+ }
132
+ .showcase-title {
133
+ font-size: clamp(1.7rem, 2.7vw, 2.2rem);
134
+ line-height: 1.15;
135
+ font-weight: 800;
136
+ letter-spacing: -0.02em;
137
+ margin: 0;
138
+ }
139
+
140
+ .showcase-grid {
141
+ flex: 0 0 auto;
142
+ display: grid;
143
+ gap: 1rem;
144
+ margin-bottom: 1.75rem;
145
+ }
146
+ .showcase-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
147
+ .showcase-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
148
+ .showcase-grid.cols-2 { grid-template-columns: repeat(2, 1fr); }
149
+
150
+ .showcase-card {
151
+ position: relative;
152
+ text-align: left;
153
+ cursor: pointer;
154
+ background: var(--miragon-white);
155
+ border: 1px solid #E5E7EB;
156
+ border-radius: 1rem;
157
+ padding: 1.1rem 1.2rem 1.2rem;
158
+ box-shadow: 0 8px 20px rgba(51, 93, 229, 0.08);
159
+ font: inherit;
160
+ color: inherit;
161
+ display: flex;
162
+ flex-direction: column;
163
+ gap: 0.55rem;
164
+ transition:
165
+ transform 320ms cubic-bezier(0.2, 0.7, 0.2, 1),
166
+ box-shadow 320ms cubic-bezier(0.2, 0.7, 0.2, 1),
167
+ border-color 320ms cubic-bezier(0.2, 0.7, 0.2, 1),
168
+ opacity 320ms cubic-bezier(0.2, 0.7, 0.2, 1);
169
+ }
170
+ .showcase-card:focus-visible {
171
+ outline: 2px solid var(--sc-accent);
172
+ outline-offset: 3px;
173
+ }
174
+ .showcase-card:hover {
175
+ transform: translateY(-2px);
176
+ }
177
+
178
+ .card-index {
179
+ font-size: 0.7rem;
180
+ font-weight: 700;
181
+ letter-spacing: 0.14em;
182
+ text-transform: uppercase;
183
+ color: var(--miragon-text-muted);
184
+ transition: color 320ms ease;
185
+ }
186
+ .card-label {
187
+ font-size: 1.05rem;
188
+ font-weight: 700;
189
+ color: var(--miragon-text-primary);
190
+ letter-spacing: -0.005em;
191
+ }
192
+
193
+ .showcase-card.is-active {
194
+ transform: translateY(-4px) scale(1.02);
195
+ border-color: var(--sc-accent);
196
+ box-shadow: 0 16px 36px rgba(51, 93, 229, 0.18);
197
+ }
198
+ .showcase-card.is-active .card-index {
199
+ color: var(--sc-accent);
200
+ }
201
+ .showcase-card:not(.is-active) {
202
+ opacity: 0.55;
203
+ }
204
+
205
+ .showcase-detail {
206
+ flex: 1 1 auto;
207
+ min-height: 5rem;
208
+ background: var(--miragon-white);
209
+ border: 1px solid #E5E7EB;
210
+ border-radius: 1rem;
211
+ padding: 1.25rem 1.5rem;
212
+ box-shadow: 0 8px 20px rgba(51, 93, 229, 0.08);
213
+ display: flex;
214
+ align-items: center;
215
+ position: relative;
216
+ }
217
+ .detail-body {
218
+ font-size: 1.1rem;
219
+ line-height: 1.55;
220
+ color: var(--miragon-text-secondary);
221
+ margin: 0;
222
+ }
223
+
224
+ /* Cross-fade for the detail panel content when the user picks another card. */
225
+ .fade-detail-enter-active,
226
+ .fade-detail-leave-active {
227
+ transition: opacity 220ms ease, transform 220ms ease;
228
+ }
229
+ .fade-detail-enter-from,
230
+ .fade-detail-leave-to {
231
+ opacity: 0;
232
+ transform: translateY(6px);
233
+ }
234
+
235
+ .showcase-hint {
236
+ flex: 0 0 auto;
237
+ margin: 0.9rem 0 0;
238
+ font-size: 0.74rem;
239
+ letter-spacing: 0.12em;
240
+ text-transform: uppercase;
241
+ color: var(--miragon-text-muted);
242
+ text-align: center;
243
+ }
244
+ </style>
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@miragon/slidev-toolkit",
3
+ "version": "1.0.0",
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
+ "keywords": [
6
+ "slidev-theme",
7
+ "slidev",
8
+ "miragon",
9
+ "toolkit"
10
+ ],
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/Miragon/slidev-deck-template.git",
15
+ "directory": "packages/toolkit"
16
+ },
17
+ "homepage": "https://github.com/Miragon/slidev-deck-template/tree/main/packages/toolkit#readme",
18
+ "bugs": "https://github.com/Miragon/slidev-deck-template/issues",
19
+ "engines": {
20
+ "slidev": ">=0.48.0"
21
+ },
22
+ "dependencies": {
23
+ "@paper-design/shaders": "0.0.77"
24
+ },
25
+ "peerDependencies": {
26
+ "vue": "^3.5"
27
+ },
28
+ "files": [
29
+ "layouts",
30
+ "components",
31
+ "styles",
32
+ "assets"
33
+ ],
34
+ "publishConfig": {
35
+ "access": "public",
36
+ "provenance": true
37
+ }
38
+ }