@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.
- package/LICENSE +21 -0
- package/assets/fonts/Geist-Variable.woff2 +0 -0
- package/assets/fonts/GeistMono-Variable.woff2 +0 -0
- package/assets/fonts/LICENSE.txt +92 -0
- package/assets/komet.svg +1 -0
- package/assets/logo.svg +1 -0
- package/components/Agenda.vue +517 -0
- package/components/BrandMeshBackground.vue +159 -0
- package/components/Card.vue +72 -0
- package/components/CardGrid.vue +46 -0
- package/components/Figure.vue +101 -0
- package/components/SplitView.vue +71 -0
- package/components/Step.vue +19 -0
- package/components/StepList.vue +28 -0
- package/layouts/bpmn.vue +162 -0
- package/layouts/closing.vue +127 -0
- package/layouts/compare.vue +214 -0
- package/layouts/content-image.vue +182 -0
- package/layouts/content.vue +186 -0
- package/layouts/cover.vue +114 -0
- package/layouts/goodbad.vue +308 -0
- package/layouts/hero.vue +168 -0
- package/layouts/person.vue +287 -0
- package/layouts/section.vue +127 -0
- package/layouts/showcase.vue +244 -0
- package/package.json +38 -0
- package/styles/fonts.css +23 -0
- package/styles/index.css +25 -0
- package/styles/theme.css +80 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* closing — animierte Abschlussseite (Gegenstück zu `cover`).
|
|
4
|
+
*
|
|
5
|
+
* Schließt das Deck mit demselben Brand-Moment, mit dem `cover` es öffnet:
|
|
6
|
+
* Mesh-Shader als Vollbild-Hintergrund (z0), Content darüber (z2). Trägt die
|
|
7
|
+
* Schlussaussage („Thank you", Call-to-Action) und Kontaktangaben.
|
|
8
|
+
*
|
|
9
|
+
* Slots: default = Schlussaussage (h1) + optionaler Satz (p) aus Markdown.
|
|
10
|
+
* Frontmatter-Props:
|
|
11
|
+
* eyebrow — kleine Überzeile (z. B. "Let's talk")
|
|
12
|
+
* contact — Kontaktzeile rechts im Footer (z. B. "name@miragon.io")
|
|
13
|
+
* footer — Footer-Text links neben dem Logo (optional)
|
|
14
|
+
*/
|
|
15
|
+
defineProps<{
|
|
16
|
+
eyebrow?: string
|
|
17
|
+
contact?: string
|
|
18
|
+
footer?: string
|
|
19
|
+
}>()
|
|
20
|
+
|
|
21
|
+
// Brand-Asset reist mit dem Theme: statischer Import, von Vite gebundlet und
|
|
22
|
+
// base-aware. Das Deck erbt das Logo aus dem Theme, ohne es selbst zu duplizieren.
|
|
23
|
+
import logoSrc from '../assets/logo.svg'
|
|
24
|
+
function onLogoError(e: Event) {
|
|
25
|
+
;(e.target as HTMLImageElement).style.display = 'none'
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div class="closing-layout">
|
|
31
|
+
<BrandMeshBackground />
|
|
32
|
+
|
|
33
|
+
<div class="closing-content">
|
|
34
|
+
<div v-if="eyebrow" class="closing-eyebrow">{{ eyebrow }}</div>
|
|
35
|
+
<div class="closing-body">
|
|
36
|
+
<slot />
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
<div class="closing-footer">
|
|
41
|
+
<img :src="logoSrc" alt="Miragon" class="closing-logo" @error="onLogoError" />
|
|
42
|
+
<span v-if="contact" class="closing-contact">{{ contact }}</span>
|
|
43
|
+
<span v-else-if="footer" class="closing-footer-text">{{ footer }}</span>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
</template>
|
|
47
|
+
|
|
48
|
+
<style scoped>
|
|
49
|
+
.closing-layout {
|
|
50
|
+
position: relative;
|
|
51
|
+
width: 100%;
|
|
52
|
+
height: 100%;
|
|
53
|
+
overflow: hidden;
|
|
54
|
+
color: var(--miragon-white);
|
|
55
|
+
/* Brand-Gradient als unterste Ebene: greift, falls BrandMeshBackground (WebGL) gar nicht
|
|
56
|
+
mountet (PDF/headless ohne GPU) — so nie Weiß, immer der Miragon-Verlauf.
|
|
57
|
+
BrandMeshBackground liegt mit position:absolute darüber und verdeckt ihn live. */
|
|
58
|
+
background: linear-gradient(125deg, #335DE5 0%, #1a1a4e 35%, #0d0d2b 55%, #00C853 100%);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.closing-content {
|
|
62
|
+
position: relative;
|
|
63
|
+
z-index: 2;
|
|
64
|
+
height: 100%;
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
justify-content: center;
|
|
68
|
+
padding: 0 4rem;
|
|
69
|
+
max-width: 56rem;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.closing-eyebrow {
|
|
73
|
+
font-size: 0.95rem;
|
|
74
|
+
font-weight: 600;
|
|
75
|
+
letter-spacing: 0.18em;
|
|
76
|
+
text-transform: uppercase;
|
|
77
|
+
color: var(--miragon-green);
|
|
78
|
+
margin-bottom: 1.25rem;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* Schlussaussage: weiß, kursiv, sehr fett — spiegelt cover. */
|
|
82
|
+
.closing-body :deep(h1) {
|
|
83
|
+
font-size: 3.6rem;
|
|
84
|
+
line-height: 1.05;
|
|
85
|
+
font-weight: 900;
|
|
86
|
+
font-style: italic;
|
|
87
|
+
color: var(--miragon-white);
|
|
88
|
+
margin: 0;
|
|
89
|
+
text-shadow: 0 2px 24px rgba(13, 13, 43, 0.35);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
.closing-body :deep(p) {
|
|
93
|
+
font-size: 1.4rem;
|
|
94
|
+
font-weight: 400;
|
|
95
|
+
color: rgba(255, 255, 255, 0.88);
|
|
96
|
+
margin-top: 1.25rem;
|
|
97
|
+
max-width: 42rem;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
.closing-body :deep(a) {
|
|
101
|
+
color: var(--miragon-green);
|
|
102
|
+
text-decoration: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.closing-footer {
|
|
106
|
+
position: absolute;
|
|
107
|
+
z-index: 2;
|
|
108
|
+
bottom: 2rem;
|
|
109
|
+
left: 4rem;
|
|
110
|
+
right: 4rem;
|
|
111
|
+
display: flex;
|
|
112
|
+
align-items: center;
|
|
113
|
+
justify-content: space-between;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.closing-logo { height: 1.75rem; opacity: 0.95; }
|
|
117
|
+
|
|
118
|
+
.closing-contact {
|
|
119
|
+
font-size: 0.95rem;
|
|
120
|
+
font-weight: 600;
|
|
121
|
+
color: var(--miragon-green);
|
|
122
|
+
}
|
|
123
|
+
.closing-footer-text {
|
|
124
|
+
font-size: 0.85rem;
|
|
125
|
+
color: rgba(255, 255, 255, 0.7);
|
|
126
|
+
}
|
|
127
|
+
</style>
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* compare — Gegenüberstellung zweier Seiten (STATISCHE Welt, kein Mesh-Shader).
|
|
4
|
+
*
|
|
5
|
+
* Zwei gleichwertige Panels nebeneinander: vorher/nachher, manuell/automatisiert,
|
|
6
|
+
* Option A/B. Die Panels sind WEISS (white-card rule, siehe slides Skill) —
|
|
7
|
+
* die semantische Unterscheidung (links = Problem, rechts = Lösung) trägt die
|
|
8
|
+
* Panel-Titel-Farbe (blue-deep vs. green-deep) sowie der Bullet-Marker (blauer
|
|
9
|
+
* vs. grüner Gradient). Folgt der hero/person-Bildsprache: heller Grund, klarer
|
|
10
|
+
* Akzent-Balken, große Typografie.
|
|
11
|
+
*
|
|
12
|
+
* Named Slots (in Markdown mit `::left::` / `::right::` befüllen):
|
|
13
|
+
* left — Inhalt der linken Spalte (Bullets/Text)
|
|
14
|
+
* right — Inhalt der rechten Spalte
|
|
15
|
+
* default — optionaler Einleitungssatz unter dem Titel
|
|
16
|
+
*
|
|
17
|
+
* Frontmatter-Props:
|
|
18
|
+
* title — Slide-Titel (optional)
|
|
19
|
+
* eyebrow — kleine Überzeile (optional)
|
|
20
|
+
* leftTitle — Kopf des linken Panels (default "Before")
|
|
21
|
+
* rightTitle — Kopf des rechten Panels (default "After")
|
|
22
|
+
* accent — "blue" | "green" | "mixed" — Balken-/Eyebrow-Akzent (default blue)
|
|
23
|
+
*/
|
|
24
|
+
import { computed } from 'vue'
|
|
25
|
+
|
|
26
|
+
const props = withDefaults(
|
|
27
|
+
defineProps<{
|
|
28
|
+
eyebrow?: string
|
|
29
|
+
leftTitle?: string
|
|
30
|
+
rightTitle?: string
|
|
31
|
+
accent?: 'blue' | 'green' | 'mixed'
|
|
32
|
+
// Slidev reicht das volle Frontmatter-Objekt als `frontmatter`-Prop durch.
|
|
33
|
+
frontmatter?: Record<string, unknown>
|
|
34
|
+
}>(),
|
|
35
|
+
{ accent: 'blue', leftTitle: 'Before', rightTitle: 'After' },
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
// `title` ist ein RESERVIERTES Slidev-Frontmatter-Feld (TOC/Nav) und wird NICHT
|
|
39
|
+
// als eigener Prop durchgereicht (FRONTMATTER_FIELDS). Daher aus dem
|
|
40
|
+
// mitgelieferten `frontmatter`-Objekt lesen, damit `title:` in slides.md wie
|
|
41
|
+
// dokumentiert funktioniert.
|
|
42
|
+
const title = computed(() => props.frontmatter?.title as string | undefined)
|
|
43
|
+
|
|
44
|
+
// Token reaktiv (siehe hero.vue) — alle Werte aus theme.css, keine Hex.
|
|
45
|
+
const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
|
|
46
|
+
const accentVar = computed(() =>
|
|
47
|
+
props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
// Panel-Töne folgen dem accent: "mixed" trägt die Problem/Lösung-Semantik
|
|
51
|
+
// (links blau, rechts grün); "blue"/"green" macht beide Seiten einfarbig.
|
|
52
|
+
// Default bleibt mixed-artig (blau/grün), damit bestehende Decks unverändert sind.
|
|
53
|
+
const twoTone = computed(() => props.accent !== 'blue' && props.accent !== 'green')
|
|
54
|
+
const mono = computed(() => (props.accent === 'green' ? 'green-deep' : 'blue-deep'))
|
|
55
|
+
const leftTone = computed(() => (twoTone.value ? 'var(--miragon-blue-deep)' : `var(--miragon-${mono.value})`))
|
|
56
|
+
const rightTone = computed(() => (twoTone.value ? 'var(--miragon-green-deep)' : `var(--miragon-${mono.value})`))
|
|
57
|
+
const leftMark = computed(() => (twoTone.value || props.accent === 'blue' ? 'var(--miragon-gradient-blue)' : 'var(--miragon-gradient-green)'))
|
|
58
|
+
const rightMark = computed(() => (twoTone.value || props.accent === 'green' ? 'var(--miragon-gradient-green)' : 'var(--miragon-gradient-blue)'))
|
|
59
|
+
</script>
|
|
60
|
+
|
|
61
|
+
<template>
|
|
62
|
+
<div
|
|
63
|
+
class="compare-layout"
|
|
64
|
+
:style="{ '--cmp-grad': gradientVar, '--cmp-accent': accentVar, '--cmp-left': leftTone, '--cmp-right': rightTone, '--cmp-left-mark': leftMark, '--cmp-right-mark': rightMark }"
|
|
65
|
+
>
|
|
66
|
+
<div class="compare-inner">
|
|
67
|
+
<header v-if="title || eyebrow" class="compare-head">
|
|
68
|
+
<span class="compare-bar" aria-hidden="true"></span>
|
|
69
|
+
<div v-if="eyebrow" class="compare-eyebrow">{{ eyebrow }}</div>
|
|
70
|
+
<h2 v-if="title" class="compare-title">{{ title }}</h2>
|
|
71
|
+
<div class="compare-lead"><slot /></div>
|
|
72
|
+
</header>
|
|
73
|
+
|
|
74
|
+
<div class="compare-grid">
|
|
75
|
+
<section class="compare-panel panel-left">
|
|
76
|
+
<h3 class="panel-title">{{ leftTitle }}</h3>
|
|
77
|
+
<div class="panel-body"><slot name="left" /></div>
|
|
78
|
+
</section>
|
|
79
|
+
|
|
80
|
+
<section class="compare-panel panel-right">
|
|
81
|
+
<h3 class="panel-title">{{ rightTitle }}</h3>
|
|
82
|
+
<div class="panel-body"><slot name="right" /></div>
|
|
83
|
+
</section>
|
|
84
|
+
</div>
|
|
85
|
+
</div>
|
|
86
|
+
</div>
|
|
87
|
+
</template>
|
|
88
|
+
|
|
89
|
+
<style scoped>
|
|
90
|
+
.compare-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
|
+
.compare-inner {
|
|
102
|
+
position: relative;
|
|
103
|
+
z-index: 1;
|
|
104
|
+
width: 100%;
|
|
105
|
+
max-width: 70rem;
|
|
106
|
+
margin: 0 auto;
|
|
107
|
+
padding: 3.5rem 5rem;
|
|
108
|
+
display: flex;
|
|
109
|
+
flex-direction: column;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/* --- Kopf ----------------------------------------------------------------- */
|
|
113
|
+
.compare-head {
|
|
114
|
+
flex: 0 0 auto;
|
|
115
|
+
margin-bottom: 2rem;
|
|
116
|
+
}
|
|
117
|
+
.compare-bar {
|
|
118
|
+
display: block;
|
|
119
|
+
width: 3.5rem;
|
|
120
|
+
height: 0.35rem;
|
|
121
|
+
border-radius: 999px;
|
|
122
|
+
background: var(--cmp-grad);
|
|
123
|
+
margin-bottom: 1.25rem;
|
|
124
|
+
}
|
|
125
|
+
.compare-eyebrow {
|
|
126
|
+
font-size: 0.9rem;
|
|
127
|
+
font-weight: 600;
|
|
128
|
+
letter-spacing: 0.18em;
|
|
129
|
+
text-transform: uppercase;
|
|
130
|
+
color: var(--cmp-accent);
|
|
131
|
+
margin-bottom: 0.75rem;
|
|
132
|
+
}
|
|
133
|
+
.compare-title {
|
|
134
|
+
font-size: clamp(1.9rem, 3.2vw, 2.7rem);
|
|
135
|
+
line-height: 1.1;
|
|
136
|
+
font-weight: 800;
|
|
137
|
+
letter-spacing: -0.02em;
|
|
138
|
+
margin: 0;
|
|
139
|
+
}
|
|
140
|
+
.compare-lead :deep(p) {
|
|
141
|
+
font-size: 1.15rem;
|
|
142
|
+
line-height: 1.55;
|
|
143
|
+
color: var(--miragon-text-secondary);
|
|
144
|
+
margin: 1rem 0 0;
|
|
145
|
+
max-width: 46rem;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/* --- Zwei-Panel-Raster ---------------------------------------------------- */
|
|
149
|
+
.compare-grid {
|
|
150
|
+
flex: 1 1 auto;
|
|
151
|
+
min-height: 0;
|
|
152
|
+
display: grid;
|
|
153
|
+
grid-template-columns: 1fr 1fr;
|
|
154
|
+
gap: 2rem;
|
|
155
|
+
}
|
|
156
|
+
/* Weisse Panels (white-card rule §6) — die Semantik trägt der Titel + Marker. */
|
|
157
|
+
.compare-panel {
|
|
158
|
+
background: var(--miragon-white);
|
|
159
|
+
border: 1px solid #E5E7EB;
|
|
160
|
+
border-radius: 1.1rem;
|
|
161
|
+
padding: 2rem 2rem 1.5rem;
|
|
162
|
+
box-shadow: 0 8px 20px rgba(51, 93, 229, 0.08);
|
|
163
|
+
}
|
|
164
|
+
.panel-title {
|
|
165
|
+
font-size: 1.35rem;
|
|
166
|
+
font-weight: 800;
|
|
167
|
+
letter-spacing: -0.01em;
|
|
168
|
+
margin: 0 0 1.25rem;
|
|
169
|
+
}
|
|
170
|
+
/* Titel-Farbe trägt die semantische Unterscheidung links=Problem / rechts=Lösung
|
|
171
|
+
(bei accent "mixed"); bei accent "blue"/"green" sind beide Seiten einfarbig. */
|
|
172
|
+
.panel-left .panel-title { color: var(--cmp-left); }
|
|
173
|
+
.panel-right .panel-title { color: var(--cmp-right); }
|
|
174
|
+
|
|
175
|
+
/* Panel-Body: Bullets mit Akzent-Marker passend zur Panel-Seite. */
|
|
176
|
+
.panel-body :deep(ul) {
|
|
177
|
+
list-style: none;
|
|
178
|
+
padding: 0;
|
|
179
|
+
margin: 0;
|
|
180
|
+
}
|
|
181
|
+
.panel-body :deep(li) {
|
|
182
|
+
position: relative;
|
|
183
|
+
font-size: 1.1rem;
|
|
184
|
+
line-height: 1.45;
|
|
185
|
+
color: var(--miragon-text-secondary);
|
|
186
|
+
padding-left: 1.7rem;
|
|
187
|
+
margin: 0 0 0.95rem;
|
|
188
|
+
}
|
|
189
|
+
.panel-body :deep(li)::before {
|
|
190
|
+
content: '';
|
|
191
|
+
position: absolute;
|
|
192
|
+
left: 0;
|
|
193
|
+
top: 0.55em;
|
|
194
|
+
width: 0.6rem;
|
|
195
|
+
height: 0.6rem;
|
|
196
|
+
border-radius: 0.18rem;
|
|
197
|
+
}
|
|
198
|
+
.panel-left .panel-body :deep(li)::before {
|
|
199
|
+
background: var(--cmp-left-mark);
|
|
200
|
+
}
|
|
201
|
+
.panel-right .panel-body :deep(li)::before {
|
|
202
|
+
background: var(--cmp-right-mark);
|
|
203
|
+
}
|
|
204
|
+
.panel-body :deep(p) {
|
|
205
|
+
font-size: 1.1rem;
|
|
206
|
+
line-height: 1.5;
|
|
207
|
+
color: var(--miragon-text-secondary);
|
|
208
|
+
margin: 0 0 0.9rem;
|
|
209
|
+
}
|
|
210
|
+
.panel-body :deep(strong) {
|
|
211
|
+
font-weight: 700;
|
|
212
|
+
color: var(--miragon-text-primary);
|
|
213
|
+
}
|
|
214
|
+
</style>
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* content-image — content slide with a side image (STATIC world, no shader).
|
|
4
|
+
*
|
|
5
|
+
* Two-column layout: an image on one side, the content (heading + body) on
|
|
6
|
+
* the other. The same pattern as a typical "We are X, but who are you?"
|
|
7
|
+
* slide: a visual anchor + a short narrative or list. Use whenever you want
|
|
8
|
+
* a visual to accompany prose.
|
|
9
|
+
*
|
|
10
|
+
* Frontmatter props:
|
|
11
|
+
* title — slide title (h2-level)
|
|
12
|
+
* eyebrow — uppercase kicker
|
|
13
|
+
* accent — "blue" | "green" | "mixed" (default blue)
|
|
14
|
+
* image — served URL path to the image (e.g. /resources/02-content/team.jpg)
|
|
15
|
+
* imageAlt — alt text for accessibility (default "")
|
|
16
|
+
* side — "left" | "right" (default "left") — which side the image is on
|
|
17
|
+
* Slot:
|
|
18
|
+
* default — free Markdown body (lead, bullets, numbered steps, …)
|
|
19
|
+
*/
|
|
20
|
+
import { computed } from 'vue'
|
|
21
|
+
|
|
22
|
+
const props = withDefaults(
|
|
23
|
+
defineProps<{
|
|
24
|
+
eyebrow?: string
|
|
25
|
+
accent?: 'blue' | 'green' | 'mixed'
|
|
26
|
+
image?: string
|
|
27
|
+
imageAlt?: string
|
|
28
|
+
side?: 'left' | 'right'
|
|
29
|
+
frontmatter?: Record<string, unknown>
|
|
30
|
+
}>(),
|
|
31
|
+
{ accent: 'blue', side: 'left', imageAlt: '' },
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
const title = computed(() => props.frontmatter?.title as string | undefined)
|
|
35
|
+
const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
|
|
36
|
+
const accentVar = computed(() =>
|
|
37
|
+
props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
// Base-Pfad respektieren (GitHub Pages baut unter /<repo>/). Runtime-Strings
|
|
41
|
+
// werden von Vite NICHT umgeschrieben — daher manuell mit BASE_URL auflösen.
|
|
42
|
+
// Gleiche Logik wie in person.vue.
|
|
43
|
+
function withBase(path?: string) {
|
|
44
|
+
if (!path) return path
|
|
45
|
+
if (/^https?:\/\//.test(path)) return path
|
|
46
|
+
return import.meta.env.BASE_URL.replace(/\/$/, '') + '/' + path.replace(/^\//, '')
|
|
47
|
+
}
|
|
48
|
+
const imageSrc = computed(() => withBase(props.image))
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div
|
|
53
|
+
class="ci-layout"
|
|
54
|
+
:class="[`image-${side}`]"
|
|
55
|
+
:style="{ '--ci-grad': gradientVar, '--ci-accent': accentVar }"
|
|
56
|
+
>
|
|
57
|
+
<div class="ci-inner">
|
|
58
|
+
<div class="ci-image">
|
|
59
|
+
<img v-if="image" :src="imageSrc" :alt="imageAlt" />
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
<div class="ci-content">
|
|
63
|
+
<span class="ci-bar" aria-hidden="true"></span>
|
|
64
|
+
<div v-if="eyebrow" class="ci-eyebrow">{{ eyebrow }}</div>
|
|
65
|
+
<h2 v-if="title" class="ci-title">{{ title }}</h2>
|
|
66
|
+
<div class="ci-body"><slot /></div>
|
|
67
|
+
</div>
|
|
68
|
+
</div>
|
|
69
|
+
</div>
|
|
70
|
+
</template>
|
|
71
|
+
|
|
72
|
+
<style scoped>
|
|
73
|
+
.ci-layout {
|
|
74
|
+
position: relative;
|
|
75
|
+
width: 100%;
|
|
76
|
+
height: 100%;
|
|
77
|
+
overflow: hidden;
|
|
78
|
+
background: var(--miragon-gray-bg);
|
|
79
|
+
color: var(--miragon-text-primary);
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: stretch;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
.ci-inner {
|
|
85
|
+
position: relative;
|
|
86
|
+
z-index: 1;
|
|
87
|
+
width: 100%;
|
|
88
|
+
max-width: 74rem;
|
|
89
|
+
margin: 0 auto;
|
|
90
|
+
padding: 3rem 4rem;
|
|
91
|
+
display: grid;
|
|
92
|
+
grid-template-columns: 1fr 1.1fr;
|
|
93
|
+
gap: 3rem;
|
|
94
|
+
align-items: center;
|
|
95
|
+
}
|
|
96
|
+
.image-right .ci-inner {
|
|
97
|
+
grid-template-columns: 1.1fr 1fr;
|
|
98
|
+
}
|
|
99
|
+
.image-right .ci-image {
|
|
100
|
+
order: 2;
|
|
101
|
+
}
|
|
102
|
+
.image-right .ci-content {
|
|
103
|
+
order: 1;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
.ci-image {
|
|
107
|
+
display: flex;
|
|
108
|
+
justify-content: center;
|
|
109
|
+
align-items: center;
|
|
110
|
+
}
|
|
111
|
+
.ci-image img {
|
|
112
|
+
width: 100%;
|
|
113
|
+
max-height: 26rem;
|
|
114
|
+
object-fit: cover;
|
|
115
|
+
border-radius: 1.1rem;
|
|
116
|
+
box-shadow: 0 12px 30px rgba(51, 93, 229, 0.18);
|
|
117
|
+
background: var(--miragon-white);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.ci-content {
|
|
121
|
+
display: flex;
|
|
122
|
+
flex-direction: column;
|
|
123
|
+
}
|
|
124
|
+
.ci-bar {
|
|
125
|
+
display: block;
|
|
126
|
+
width: 3.5rem;
|
|
127
|
+
height: 0.35rem;
|
|
128
|
+
border-radius: 999px;
|
|
129
|
+
background: var(--ci-grad);
|
|
130
|
+
margin-bottom: 1rem;
|
|
131
|
+
}
|
|
132
|
+
.ci-eyebrow {
|
|
133
|
+
font-size: 0.9rem;
|
|
134
|
+
font-weight: 600;
|
|
135
|
+
letter-spacing: 0.18em;
|
|
136
|
+
text-transform: uppercase;
|
|
137
|
+
color: var(--ci-accent);
|
|
138
|
+
margin-bottom: 0.6rem;
|
|
139
|
+
}
|
|
140
|
+
.ci-title {
|
|
141
|
+
font-size: clamp(1.75rem, 2.6vw, 2rem);
|
|
142
|
+
line-height: 1.2;
|
|
143
|
+
font-weight: 800;
|
|
144
|
+
letter-spacing: -0.02em;
|
|
145
|
+
margin: 0 0 1.25rem;
|
|
146
|
+
}
|
|
147
|
+
.ci-body :deep(p) {
|
|
148
|
+
font-size: 1.05rem;
|
|
149
|
+
line-height: 1.55;
|
|
150
|
+
color: var(--miragon-text-secondary);
|
|
151
|
+
margin: 0 0 0.85rem;
|
|
152
|
+
}
|
|
153
|
+
.ci-body :deep(strong) {
|
|
154
|
+
font-weight: 700;
|
|
155
|
+
color: var(--miragon-text-primary);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* Bullets with accent marker (same pattern as the `content` layout). */
|
|
159
|
+
.ci-body :deep(ul) {
|
|
160
|
+
list-style: none;
|
|
161
|
+
padding: 0;
|
|
162
|
+
margin: 0;
|
|
163
|
+
}
|
|
164
|
+
.ci-body :deep(li) {
|
|
165
|
+
position: relative;
|
|
166
|
+
font-size: 1.05rem;
|
|
167
|
+
line-height: 1.5;
|
|
168
|
+
color: var(--miragon-text-secondary);
|
|
169
|
+
padding-left: 1.6rem;
|
|
170
|
+
margin: 0 0 0.6rem;
|
|
171
|
+
}
|
|
172
|
+
.ci-body :deep(li)::before {
|
|
173
|
+
content: '';
|
|
174
|
+
position: absolute;
|
|
175
|
+
left: 0;
|
|
176
|
+
top: 0.55em;
|
|
177
|
+
width: 0.55rem;
|
|
178
|
+
height: 0.55rem;
|
|
179
|
+
border-radius: 0.18rem;
|
|
180
|
+
background: var(--ci-grad);
|
|
181
|
+
}
|
|
182
|
+
</style>
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* content — Workhorse-Inhaltsslide (STATISCHE Welt, KEIN Mesh-Shader).
|
|
4
|
+
*
|
|
5
|
+
* Der Alltags-Archetyp: ein Titel oben, darunter freier Body (Bullets,
|
|
6
|
+
* <v-clicks>, kleine Karten). Folgt der hero/person-Bildsprache: heller Grund,
|
|
7
|
+
* großzügige Typografie, ein einziger sparsamer Gradient-Akzent (Balken +
|
|
8
|
+
* Bullet-Marker). Strukturelle Visualisierung statt dekoriertem Text —
|
|
9
|
+
* Aufzählungen bekommen einen klaren Akzent-Marker (kein Emoji).
|
|
10
|
+
*
|
|
11
|
+
* Frontmatter-Props:
|
|
12
|
+
* title — Slide-Titel (h2-Ebene; optional, sonst nur Body)
|
|
13
|
+
* eyebrow — kleine Überzeile (optional)
|
|
14
|
+
* accent — "blue" | "green" | "mixed" — Gradient-Akzent (default blue)
|
|
15
|
+
* Slot: default = Body aus Markdown (max. 5 Bullets · max. 1 Verschachtelung).
|
|
16
|
+
*/
|
|
17
|
+
import { computed } from 'vue'
|
|
18
|
+
|
|
19
|
+
const props = withDefaults(
|
|
20
|
+
defineProps<{
|
|
21
|
+
eyebrow?: string
|
|
22
|
+
accent?: 'blue' | 'green' | 'mixed'
|
|
23
|
+
// Slidev reicht das volle Frontmatter-Objekt als `frontmatter`-Prop durch.
|
|
24
|
+
frontmatter?: Record<string, unknown>
|
|
25
|
+
}>(),
|
|
26
|
+
{ accent: 'blue' },
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
// `title` ist ein RESERVIERTES Slidev-Frontmatter-Feld (TOC/Nav) und wird NICHT
|
|
30
|
+
// als eigener Prop durchgereicht (FRONTMATTER_FIELDS). Daher aus dem
|
|
31
|
+
// mitgelieferten `frontmatter`-Objekt lesen, damit `title:` in slides.md wie
|
|
32
|
+
// dokumentiert funktioniert.
|
|
33
|
+
const title = computed(() => props.frontmatter?.title as string | undefined)
|
|
34
|
+
|
|
35
|
+
// Token reaktiv (siehe hero.vue) — alle Werte aus theme.css, keine Hex.
|
|
36
|
+
const gradientVar = computed(() => `var(--miragon-gradient-${props.accent})`)
|
|
37
|
+
const accentVar = computed(() =>
|
|
38
|
+
props.accent === 'green' ? 'var(--miragon-green-deep)' : 'var(--miragon-blue)',
|
|
39
|
+
)
|
|
40
|
+
</script>
|
|
41
|
+
|
|
42
|
+
<template>
|
|
43
|
+
<div
|
|
44
|
+
class="content-layout"
|
|
45
|
+
:style="{ '--c-grad': gradientVar, '--c-accent': accentVar }"
|
|
46
|
+
>
|
|
47
|
+
<div class="content-inner">
|
|
48
|
+
<header v-if="title || eyebrow" class="content-head">
|
|
49
|
+
<span class="content-bar" aria-hidden="true"></span>
|
|
50
|
+
<div v-if="eyebrow" class="content-eyebrow">{{ eyebrow }}</div>
|
|
51
|
+
<h2 v-if="title" class="content-title">{{ title }}</h2>
|
|
52
|
+
</header>
|
|
53
|
+
|
|
54
|
+
<div class="content-body">
|
|
55
|
+
<slot />
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<style scoped>
|
|
62
|
+
.content-layout {
|
|
63
|
+
position: relative;
|
|
64
|
+
width: 100%;
|
|
65
|
+
height: 100%;
|
|
66
|
+
overflow: hidden;
|
|
67
|
+
background: var(--miragon-gray-bg);
|
|
68
|
+
color: var(--miragon-text-primary);
|
|
69
|
+
display: flex;
|
|
70
|
+
align-items: stretch;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
.content-inner {
|
|
74
|
+
position: relative;
|
|
75
|
+
z-index: 1;
|
|
76
|
+
width: 100%;
|
|
77
|
+
max-width: 68rem;
|
|
78
|
+
margin: 0 auto;
|
|
79
|
+
padding: 1.75rem 5rem 2rem;
|
|
80
|
+
display: flex;
|
|
81
|
+
flex-direction: column;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/* --- Kopf: Balken + Eyebrow + Titel (wie hero, kompakter) ----------------- */
|
|
85
|
+
.content-head {
|
|
86
|
+
flex: 0 0 auto;
|
|
87
|
+
margin-bottom: 1.5rem;
|
|
88
|
+
}
|
|
89
|
+
.content-bar {
|
|
90
|
+
display: block;
|
|
91
|
+
width: 3.5rem;
|
|
92
|
+
height: 0.35rem;
|
|
93
|
+
border-radius: 999px;
|
|
94
|
+
background: var(--c-grad);
|
|
95
|
+
margin-bottom: 0.75rem;
|
|
96
|
+
}
|
|
97
|
+
.content-eyebrow {
|
|
98
|
+
font-size: 0.9rem;
|
|
99
|
+
font-weight: 600;
|
|
100
|
+
letter-spacing: 0.18em;
|
|
101
|
+
text-transform: uppercase;
|
|
102
|
+
color: var(--c-accent);
|
|
103
|
+
margin-bottom: 0.75rem;
|
|
104
|
+
}
|
|
105
|
+
.content-title {
|
|
106
|
+
font-size: clamp(2rem, 3.4vw, 2.9rem);
|
|
107
|
+
line-height: 1.1;
|
|
108
|
+
font-weight: 800;
|
|
109
|
+
letter-spacing: -0.02em;
|
|
110
|
+
color: var(--miragon-text-primary);
|
|
111
|
+
margin: 0;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/* --- Body: freier Markdown-Inhalt ----------------------------------------- */
|
|
115
|
+
.content-body {
|
|
116
|
+
flex: 1 1 auto;
|
|
117
|
+
min-height: 0;
|
|
118
|
+
}
|
|
119
|
+
.content-body :deep(p) {
|
|
120
|
+
font-size: 1.2rem;
|
|
121
|
+
line-height: 1.6;
|
|
122
|
+
color: var(--miragon-text-secondary);
|
|
123
|
+
margin: 0 0 1rem;
|
|
124
|
+
max-width: 48rem;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/* Aufzählungen: klarer Akzent-Marker (CSS-Quadrat, kein Emoji). */
|
|
128
|
+
.content-body :deep(ul) {
|
|
129
|
+
list-style: none;
|
|
130
|
+
padding: 0;
|
|
131
|
+
margin: 0;
|
|
132
|
+
}
|
|
133
|
+
.content-body :deep(li) {
|
|
134
|
+
position: relative;
|
|
135
|
+
font-size: 1.25rem;
|
|
136
|
+
line-height: 1.5;
|
|
137
|
+
color: var(--miragon-text-secondary);
|
|
138
|
+
padding-left: 2rem;
|
|
139
|
+
margin: 0 0 1.1rem;
|
|
140
|
+
max-width: 50rem;
|
|
141
|
+
}
|
|
142
|
+
.content-body :deep(li)::before {
|
|
143
|
+
content: '';
|
|
144
|
+
position: absolute;
|
|
145
|
+
left: 0;
|
|
146
|
+
top: 0.5em;
|
|
147
|
+
width: 0.7rem;
|
|
148
|
+
height: 0.7rem;
|
|
149
|
+
border-radius: 0.2rem;
|
|
150
|
+
background: var(--c-grad);
|
|
151
|
+
}
|
|
152
|
+
/* Eine Verschachtelungsebene: dezenter, kleinerer Marker. */
|
|
153
|
+
.content-body :deep(li ul) {
|
|
154
|
+
margin-top: 0.8rem;
|
|
155
|
+
}
|
|
156
|
+
.content-body :deep(li li) {
|
|
157
|
+
font-size: 1.05rem;
|
|
158
|
+
color: var(--miragon-text-muted);
|
|
159
|
+
margin-bottom: 0.6rem;
|
|
160
|
+
}
|
|
161
|
+
.content-body :deep(li li)::before {
|
|
162
|
+
top: 0.55em;
|
|
163
|
+
width: 0.45rem;
|
|
164
|
+
height: 0.45rem;
|
|
165
|
+
border-radius: 999px;
|
|
166
|
+
background: var(--c-accent);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
.content-body :deep(strong) {
|
|
170
|
+
font-weight: 700;
|
|
171
|
+
color: var(--miragon-text-primary);
|
|
172
|
+
}
|
|
173
|
+
.content-body :deep(a) {
|
|
174
|
+
color: var(--c-accent);
|
|
175
|
+
text-decoration: none;
|
|
176
|
+
border-bottom: 1px solid currentColor;
|
|
177
|
+
}
|
|
178
|
+
.content-body :deep(code) {
|
|
179
|
+
font-family: var(--miragon-font-mono);
|
|
180
|
+
font-size: 0.9em;
|
|
181
|
+
background: var(--miragon-blue-light);
|
|
182
|
+
color: var(--miragon-blue-darker);
|
|
183
|
+
padding: 0.1em 0.4em;
|
|
184
|
+
border-radius: 0.35rem;
|
|
185
|
+
}
|
|
186
|
+
</style>
|