@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,159 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* BrandMeshBackground — animierter WebGL2 Mesh-Gradient-Hintergrund (Miragon CI).
|
|
4
|
+
*
|
|
5
|
+
* HEILIG: Farbstops und Parameter stammen 1:1 aus dem bestehenden
|
|
6
|
+
* @miragon/slidev-theme. Nicht ohne Rücksprache ändern.
|
|
7
|
+
*
|
|
8
|
+
* Verwendung: liegt als unterste Ebene (z-index 0) in den animierten
|
|
9
|
+
* Layouts (cover, closing). pointer-events:none, damit Klicks durchgehen.
|
|
10
|
+
* Fallback-Navy #0d0d2b für den ersten Frame / kein WebGL.
|
|
11
|
+
*/
|
|
12
|
+
import { onMounted, onBeforeUnmount, ref } from 'vue'
|
|
13
|
+
import {
|
|
14
|
+
ShaderMount,
|
|
15
|
+
meshGradientFragmentShader,
|
|
16
|
+
getShaderColorFromString,
|
|
17
|
+
defaultObjectSizing,
|
|
18
|
+
ShaderFitOptions,
|
|
19
|
+
} from '@paper-design/shaders'
|
|
20
|
+
|
|
21
|
+
// --- Heilige Markenparameter (DO NOT CHANGE) -------------------------------
|
|
22
|
+
const COLORS = ['#335DE5', '#1a1a4e', '#00E676', '#335DE5', '#0d0d2b', '#00C853']
|
|
23
|
+
const DISTORTION = 1 // Verformung der Farbfelder
|
|
24
|
+
const SWIRL = 0.5 // Verwirbelung der Übergänge
|
|
25
|
+
const SPEED = 0.6 // Animationsgeschwindigkeit
|
|
26
|
+
// ---------------------------------------------------------------------------
|
|
27
|
+
|
|
28
|
+
const host = ref<HTMLDivElement | null>(null)
|
|
29
|
+
let mount: ShaderMount | null = null
|
|
30
|
+
|
|
31
|
+
onMounted(() => {
|
|
32
|
+
if (!host.value) return
|
|
33
|
+
try {
|
|
34
|
+
// Sizing/Fit-Uniforms explizit übergeben (sonst nutzt der Shader Default-
|
|
35
|
+
// Werte, die je nach Slide-Format zu inkonsistentem Crop/Scale führen).
|
|
36
|
+
const uniforms = {
|
|
37
|
+
u_colors: COLORS.map((c) => getShaderColorFromString(c)),
|
|
38
|
+
u_colorsCount: COLORS.length,
|
|
39
|
+
u_distortion: DISTORTION,
|
|
40
|
+
u_swirl: SWIRL,
|
|
41
|
+
u_grainMixer: 0,
|
|
42
|
+
u_grainOverlay: 0,
|
|
43
|
+
u_fit: ShaderFitOptions[defaultObjectSizing.fit],
|
|
44
|
+
u_rotation: defaultObjectSizing.rotation,
|
|
45
|
+
u_scale: defaultObjectSizing.scale,
|
|
46
|
+
u_offsetX: defaultObjectSizing.offsetX,
|
|
47
|
+
u_offsetY: defaultObjectSizing.offsetY,
|
|
48
|
+
u_originX: defaultObjectSizing.originX,
|
|
49
|
+
u_originY: defaultObjectSizing.originY,
|
|
50
|
+
u_worldWidth: defaultObjectSizing.worldWidth,
|
|
51
|
+
u_worldHeight: defaultObjectSizing.worldHeight,
|
|
52
|
+
}
|
|
53
|
+
// Performance (rührt die heiligen COLORS/DISTORTION/SWIRL/SPEED NICHT an):
|
|
54
|
+
// die Library-Defaults erzwingen minPixelRatio=2 und cappen bei 1920*1080*4
|
|
55
|
+
// (~8.3M) Pixeln, d. h. auf einem Retina-Display läuft dieser teure Mesh-
|
|
56
|
+
// Gradient-Fragment-Shader über ~8.3M Pixel pro Frame und ruckelt. Ein weicher
|
|
57
|
+
// Verlauf braucht keinen 4K-Backing-Store: Supersampling weg (minPixelRatio 1)
|
|
58
|
+
// und den Render auf ~0.9M Pixel cappen. CSS skaliert das Canvas hoch, der Blur
|
|
59
|
+
// kaschiert es.
|
|
60
|
+
const MIN_PIXEL_RATIO = 1
|
|
61
|
+
const MAX_PIXEL_COUNT = 640 * 360
|
|
62
|
+
// Reduced-Motion respektieren: auf dem ersten Frame einfrieren (speed 0)
|
|
63
|
+
// statt zu animieren — kostet ebenfalls nichts.
|
|
64
|
+
const reduceMotion = window.matchMedia?.('(prefers-reduced-motion: reduce)').matches ?? false
|
|
65
|
+
mount = new ShaderMount(
|
|
66
|
+
host.value,
|
|
67
|
+
meshGradientFragmentShader,
|
|
68
|
+
uniforms,
|
|
69
|
+
undefined,
|
|
70
|
+
reduceMotion ? 0 : SPEED,
|
|
71
|
+
0,
|
|
72
|
+
MIN_PIXEL_RATIO,
|
|
73
|
+
MAX_PIXEL_COUNT,
|
|
74
|
+
)
|
|
75
|
+
// WebGL läuft: den blurred, dauerhaft animierten CSS-Fallback-Layer abschalten.
|
|
76
|
+
// Er liegt unsichtbar hinter dem nun opaken Canvas, kostet aber pro Frame
|
|
77
|
+
// Compositor-Zeit (ein blur(48px)-Layer in doppelter Viewport-Größe), was die
|
|
78
|
+
// FPS senkt und das Canvas während Slide-Transitions desynchronisiert.
|
|
79
|
+
host.value.classList.add('webgl-active')
|
|
80
|
+
} catch (e) {
|
|
81
|
+
// WebGL nicht verfügbar (z. B. headless) → Fallback-Navy bleibt sichtbar.
|
|
82
|
+
console.warn('[BrandMeshBackground] WebGL unavailable, falling back to solid navy.', e)
|
|
83
|
+
}
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
onBeforeUnmount(() => {
|
|
87
|
+
mount?.dispose()
|
|
88
|
+
mount = null
|
|
89
|
+
})
|
|
90
|
+
</script>
|
|
91
|
+
|
|
92
|
+
<template>
|
|
93
|
+
<div ref="host" class="mesh-bg" aria-hidden="true"></div>
|
|
94
|
+
</template>
|
|
95
|
+
|
|
96
|
+
<style scoped>
|
|
97
|
+
.mesh-bg {
|
|
98
|
+
position: absolute;
|
|
99
|
+
inset: 0;
|
|
100
|
+
width: 100%;
|
|
101
|
+
height: 100%;
|
|
102
|
+
background: #0d0d2b; /* Navy-Basis für allerersten Frame */
|
|
103
|
+
pointer-events: none;
|
|
104
|
+
z-index: 0;
|
|
105
|
+
overflow: hidden; /* hält den überdimensionierten Fallback-Layer im Rahmen */
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/*
|
|
109
|
+
* CSS-Fallback (additiv, ändert NICHT die heiligen Shader-Werte):
|
|
110
|
+
* Animierter Mesh-Verlauf in den Markenfarben. Liegt HINTER dem Canvas.
|
|
111
|
+
* - WebGL ok → ShaderMount-Canvas ist opak und überdeckt diesen Layer.
|
|
112
|
+
* - WebGL fehlt (Browser ohne GPU, `npm run export`/headless) → der Canvas
|
|
113
|
+
* bleibt transparent, dieser Layer scheint durch und animiert weiter.
|
|
114
|
+
* Farben 1:1 aus der Mesh-Palette (Blau/Navy/Grün), kein totes Navy mehr.
|
|
115
|
+
*
|
|
116
|
+
* z-index: -2 → liegt HINTER dem Library-Canvas (der bei WebGL-Erfolg per
|
|
117
|
+
* `@layer paper-shaders` z-index:-1 bekommt). So überdeckt der echte Shader
|
|
118
|
+
* diesen Fallback; bei WebGL-Ausfall (transparenter/leerer Canvas) scheint er
|
|
119
|
+
* über der Navy-Basis durch.
|
|
120
|
+
*/
|
|
121
|
+
.mesh-bg::before {
|
|
122
|
+
content: '';
|
|
123
|
+
position: absolute;
|
|
124
|
+
z-index: -2;
|
|
125
|
+
inset: -50%;
|
|
126
|
+
background:
|
|
127
|
+
radial-gradient(38% 38% at 24% 30%, #335DE5 0%, transparent 60%),
|
|
128
|
+
radial-gradient(34% 34% at 76% 22%, #00E676 0%, transparent 60%),
|
|
129
|
+
radial-gradient(44% 44% at 70% 76%, #00C853 0%, transparent 60%),
|
|
130
|
+
radial-gradient(40% 40% at 28% 80%, #1a1a4e 0%, transparent 62%),
|
|
131
|
+
radial-gradient(55% 55% at 50% 50%, #335DE5 0%, transparent 72%);
|
|
132
|
+
filter: blur(48px);
|
|
133
|
+
will-change: transform;
|
|
134
|
+
animation: mesh-fallback-float 22s ease-in-out infinite alternate;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@keyframes mesh-fallback-float {
|
|
138
|
+
0% { transform: translate(-4%, -3%) scale(1.06) rotate(0deg); }
|
|
139
|
+
50% { transform: translate(3%, 2%) scale(1.16) rotate(7deg); }
|
|
140
|
+
100% { transform: translate(2%, -4%) scale(1.10) rotate(-6deg); }
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
@media (prefers-reduced-motion: reduce) {
|
|
144
|
+
.mesh-bg::before { animation: none; }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/* WebGL erfolgreich: das opake Shader-Canvas überdeckt den Fallback, also den
|
|
148
|
+
animierten Blur-Layer ganz weglassen (kein verschwendetes Compositing). */
|
|
149
|
+
.mesh-bg.webgl-active::before {
|
|
150
|
+
display: none;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/* Canvas-Positionierung der Library NICHT überschreiben (sie setzt position
|
|
154
|
+
absolute + z-index:-1 via @layer). Nur die Größe absichern. */
|
|
155
|
+
.mesh-bg canvas {
|
|
156
|
+
width: 100% !important;
|
|
157
|
+
height: 100% !important;
|
|
158
|
+
}
|
|
159
|
+
</style>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Card — kanonische weiße Brand-Card (Design-System §6).
|
|
4
|
+
* Hintergrund IMMER weiß, Akzent NUR auf dem Titel. `accent` wählt einen Stop
|
|
5
|
+
* der blue → teal → green-Progression (§6.3) — dies ist der einzige
|
|
6
|
+
* sanktionierte Ort für rohe Hex-Werte, hier zentral statt im Deck verstreut.
|
|
7
|
+
*
|
|
8
|
+
* Mehrkartige Grids: Titel von links nach rechts entlang der Progression.
|
|
9
|
+
* 2 Karten: blue, green
|
|
10
|
+
* 3 Karten: blue, teal, green
|
|
11
|
+
* 4 Karten: blue, blue-mid, green-deep, green
|
|
12
|
+
* <Card title="Deployment" accent="blue">Body …</Card>
|
|
13
|
+
*/
|
|
14
|
+
const props = withDefaults(
|
|
15
|
+
defineProps<{
|
|
16
|
+
title?: string
|
|
17
|
+
accent?: 'blue' | 'blue-mid' | 'teal' | 'green-deep' | 'green-mid' | 'green'
|
|
18
|
+
padding?: 'compact' | 'standard' | 'generous'
|
|
19
|
+
}>(),
|
|
20
|
+
{ accent: 'blue', padding: 'standard' },
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
// Die §6.3-Progressions-Stops. Bewusst lokal: der einzige sanktionierte Hex-Ort.
|
|
24
|
+
const ACCENTS: Record<string, string> = {
|
|
25
|
+
blue: '#335DE5',
|
|
26
|
+
'blue-mid': '#2B5ACE',
|
|
27
|
+
teal: '#1E7A8A',
|
|
28
|
+
'green-deep': '#0E8E6E',
|
|
29
|
+
'green-mid': '#00974F',
|
|
30
|
+
green: '#00C263',
|
|
31
|
+
}
|
|
32
|
+
</script>
|
|
33
|
+
|
|
34
|
+
<template>
|
|
35
|
+
<div class="mg-card" :class="`mg-card--${props.padding}`">
|
|
36
|
+
<h3 v-if="title" class="mg-card__title" :style="{ color: ACCENTS[props.accent] }">{{ title }}</h3>
|
|
37
|
+
<div class="mg-card__body"><slot /></div>
|
|
38
|
+
</div>
|
|
39
|
+
</template>
|
|
40
|
+
|
|
41
|
+
<style scoped>
|
|
42
|
+
.mg-card {
|
|
43
|
+
border-radius: 0.75rem;
|
|
44
|
+
border: 1px solid #e5e7eb;
|
|
45
|
+
background: var(--miragon-white);
|
|
46
|
+
box-shadow: 0 8px 20px rgba(51, 93, 229, 0.08);
|
|
47
|
+
}
|
|
48
|
+
/* Nur Karten mit Zeichnung (compact) werden zur Flex-Spalte: im Grid stretchen
|
|
49
|
+
die Karten auf gleiche Höhe, eine abschließende Zeichnung (margin-top:auto)
|
|
50
|
+
dockt am Kartenboden an und liegt über alle Karten hinweg auf gleicher Höhe.
|
|
51
|
+
Standard-Karten bleiben normaler Fluss, sonst würde inline <strong> im Body
|
|
52
|
+
zu eigenen Flex-Items (eigene Zeilen) umbrechen. */
|
|
53
|
+
.mg-card--compact { padding: 1rem; display: flex; flex-direction: column; }
|
|
54
|
+
.mg-card--standard { padding: 1.25rem; }
|
|
55
|
+
.mg-card--generous { padding: 1.5rem; }
|
|
56
|
+
.mg-card__title {
|
|
57
|
+
margin: 0;
|
|
58
|
+
font-weight: 700;
|
|
59
|
+
font-size: 1.05rem;
|
|
60
|
+
line-height: 1.2;
|
|
61
|
+
}
|
|
62
|
+
.mg-card__body {
|
|
63
|
+
margin-top: 0.5rem;
|
|
64
|
+
font-size: 0.875rem;
|
|
65
|
+
line-height: 1.55;
|
|
66
|
+
color: #4b5563;
|
|
67
|
+
}
|
|
68
|
+
.mg-card--compact .mg-card__body { flex: 1 1 auto; display: flex; flex-direction: column; }
|
|
69
|
+
/* A trailing diagram (<Figure src>) docks at the card bottom, so the image sits at
|
|
70
|
+
the same position in every card of a stretched grid regardless of text length. */
|
|
71
|
+
.mg-card--compact .mg-card__body :deep(.mg-figure) { margin-top: auto; }
|
|
72
|
+
</style>
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* CardGrid — gleichmaessiges Spalten-Raster fuer Karten/Figuren (Design-System §6).
|
|
4
|
+
* Ersetzt das rohe `<div class="grid grid-cols-N gap-…">` im Markdown, damit Folien
|
|
5
|
+
* keine Utility-Klassen tragen: nur semantische Tags. Karten im Grid strecken auf
|
|
6
|
+
* gleiche Hoehe (align: stretch). `cols` setzt die Spaltenzahl, `gap` den Abstand.
|
|
7
|
+
*
|
|
8
|
+
* <CardGrid cols="3">
|
|
9
|
+
* <Card title="…" accent="blue">…</Card>
|
|
10
|
+
* <Card title="…" accent="teal">…</Card>
|
|
11
|
+
* <Card title="…" accent="green">…</Card>
|
|
12
|
+
* </CardGrid>
|
|
13
|
+
*/
|
|
14
|
+
const props = withDefaults(
|
|
15
|
+
defineProps<{
|
|
16
|
+
cols?: number | string
|
|
17
|
+
gap?: 'compact' | 'standard' | 'generous'
|
|
18
|
+
}>(),
|
|
19
|
+
{ cols: 3, gap: 'standard' },
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
const GAPS: Record<string, string> = {
|
|
23
|
+
compact: '1rem',
|
|
24
|
+
standard: '1.5rem',
|
|
25
|
+
generous: '2rem',
|
|
26
|
+
}
|
|
27
|
+
</script>
|
|
28
|
+
|
|
29
|
+
<template>
|
|
30
|
+
<div
|
|
31
|
+
class="mg-card-grid"
|
|
32
|
+
:style="{ '--mg-cols': String(props.cols), '--mg-gap': GAPS[props.gap] }"
|
|
33
|
+
>
|
|
34
|
+
<slot />
|
|
35
|
+
</div>
|
|
36
|
+
</template>
|
|
37
|
+
|
|
38
|
+
<style scoped>
|
|
39
|
+
.mg-card-grid {
|
|
40
|
+
display: grid;
|
|
41
|
+
grid-template-columns: repeat(var(--mg-cols), minmax(0, 1fr));
|
|
42
|
+
gap: var(--mg-gap);
|
|
43
|
+
align-items: stretch;
|
|
44
|
+
margin-top: 0.5rem;
|
|
45
|
+
}
|
|
46
|
+
</style>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
import { computed } from 'vue'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Figure — beschriftete Diagramm-Spalte: Titel oben, Grafik (Slot) mittig,
|
|
6
|
+
* Caption unten. Ersetzt die handgestylten text-center-<div>/<p>-Paare
|
|
7
|
+
* (z. B. der VM/Container/Pod-Vergleich).
|
|
8
|
+
* <Figure title="Container" caption="Container teilen sich den **Host-Kernel**.">
|
|
9
|
+
* <svg …></svg>
|
|
10
|
+
* </Figure>
|
|
11
|
+
* Die Caption versteht Inline-Markdown: **fett**, *kursiv*, `code`.
|
|
12
|
+
*
|
|
13
|
+
* Statt eines Slot-Visuals kann auch ein Public-Asset per `src` eingebunden
|
|
14
|
+
* werden (z. B. eine .excalidraw.png). Der Pfad wird base-aware über
|
|
15
|
+
* BASE_URL aufgelöst — KEIN Markdown-Bild verwenden: Slidev kompiliert ein
|
|
16
|
+
* `` in einen Vite-Import, der bei Public-Assets den Build bricht.
|
|
17
|
+
* <Figure src="images/foo.excalidraw.png" alt="…" caption="…"></Figure>
|
|
18
|
+
*/
|
|
19
|
+
const props = withDefaults(
|
|
20
|
+
defineProps<{
|
|
21
|
+
title?: string
|
|
22
|
+
caption?: string
|
|
23
|
+
src?: string
|
|
24
|
+
alt?: string
|
|
25
|
+
maxHeight?: string
|
|
26
|
+
}>(),
|
|
27
|
+
{ maxHeight: '340px' },
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
// Assets werden unter BASE_URL serviert (z. B. "/" lokal, "/<repo>/" auf GitHub
|
|
31
|
+
// Pages) — etwa Kapitel-Ressourcen unter /resources/<chapter>/<file>. Slash normalisieren.
|
|
32
|
+
const resolvedSrc = computed(() => {
|
|
33
|
+
if (!props.src) return ''
|
|
34
|
+
const base = import.meta.env.BASE_URL.replace(/\/$/, '')
|
|
35
|
+
return `${base}/${props.src.replace(/^\//, '')}`
|
|
36
|
+
})
|
|
37
|
+
|
|
38
|
+
// HTML-sicher: erst escapen, dann nur die erlaubten Inline-Marker zu Tags machen.
|
|
39
|
+
const captionHtml = computed(() => {
|
|
40
|
+
if (!props.caption) return ''
|
|
41
|
+
const escaped = props.caption
|
|
42
|
+
.replace(/&/g, '&')
|
|
43
|
+
.replace(/</g, '<')
|
|
44
|
+
.replace(/>/g, '>')
|
|
45
|
+
return escaped
|
|
46
|
+
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
|
47
|
+
.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>')
|
|
48
|
+
.replace(/(^|[^*])\*([^*]+)\*/g, '$1<em>$2</em>')
|
|
49
|
+
})
|
|
50
|
+
</script>
|
|
51
|
+
|
|
52
|
+
<template>
|
|
53
|
+
<div class="mg-figure">
|
|
54
|
+
<div v-if="title" class="mg-figure__title">{{ title }}</div>
|
|
55
|
+
<div class="mg-figure__media">
|
|
56
|
+
<img v-if="resolvedSrc" :src="resolvedSrc" :alt="alt" :style="{ maxHeight }" />
|
|
57
|
+
<slot v-else />
|
|
58
|
+
</div>
|
|
59
|
+
<div v-if="caption" class="mg-figure__caption" v-html="captionHtml"></div>
|
|
60
|
+
</div>
|
|
61
|
+
</template>
|
|
62
|
+
|
|
63
|
+
<style scoped>
|
|
64
|
+
.mg-figure {
|
|
65
|
+
display: flex;
|
|
66
|
+
flex-direction: column;
|
|
67
|
+
}
|
|
68
|
+
.mg-figure__title {
|
|
69
|
+
text-align: center;
|
|
70
|
+
font-weight: 700;
|
|
71
|
+
font-size: 1rem;
|
|
72
|
+
color: var(--miragon-text-primary);
|
|
73
|
+
margin-bottom: 0.5rem;
|
|
74
|
+
}
|
|
75
|
+
.mg-figure__media {
|
|
76
|
+
display: flex;
|
|
77
|
+
justify-content: center;
|
|
78
|
+
}
|
|
79
|
+
.mg-figure__media img {
|
|
80
|
+
max-width: 100%;
|
|
81
|
+
height: auto;
|
|
82
|
+
}
|
|
83
|
+
.mg-figure__caption {
|
|
84
|
+
text-align: center;
|
|
85
|
+
color: var(--miragon-text-secondary);
|
|
86
|
+
line-height: 1.4;
|
|
87
|
+
font-size: 0.8rem;
|
|
88
|
+
margin-top: 1.5rem;
|
|
89
|
+
}
|
|
90
|
+
.mg-figure__caption :deep(strong) {
|
|
91
|
+
font-weight: 700;
|
|
92
|
+
color: var(--miragon-text-primary);
|
|
93
|
+
}
|
|
94
|
+
.mg-figure__caption :deep(em) {
|
|
95
|
+
font-style: italic;
|
|
96
|
+
}
|
|
97
|
+
.mg-figure__caption :deep(code) {
|
|
98
|
+
font-family: var(--miragon-font-mono, ui-monospace, monospace);
|
|
99
|
+
font-size: 0.92em;
|
|
100
|
+
}
|
|
101
|
+
</style>
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* SplitView — zweispaltiges „Visual + Erklärung"-Raster: links eine Grafik
|
|
4
|
+
* (Diagramm-Komponente oder Bild über den #visual-Slot), rechts der erklärende
|
|
5
|
+
* Text im Default-Slot (Bullets oder <StepList>). Ersetzt die handgestylten
|
|
6
|
+
* <div class="grid grid-cols-2 …">-Wrapper in slides.md und vereinheitlicht
|
|
7
|
+
* Spaltenverhältnis, Abstand und vertikale Ausrichtung.
|
|
8
|
+
*
|
|
9
|
+
* Beide Spalten sind standardmäßig vertikal zentriert (align="center"), damit
|
|
10
|
+
* die Stichpunkte mittig zur Grafik sitzen.
|
|
11
|
+
*
|
|
12
|
+
* Props:
|
|
13
|
+
* ratio — Spaltenverhältnis links/rechts als "a/b" (Default "1/1";
|
|
14
|
+
* z. B. "1.5/1" für eine breitere Grafik)
|
|
15
|
+
* align — vertikale Ausrichtung der Spalten: "center" | "start" | "end" |
|
|
16
|
+
* "stretch" (Default "center")
|
|
17
|
+
* gap — Spaltenabstand als CSS-Länge (Default "2.5rem")
|
|
18
|
+
*
|
|
19
|
+
* Verwendung:
|
|
20
|
+
* <SplitView ratio="1.5/1">
|
|
21
|
+
* <template #visual>
|
|
22
|
+
* <RuntimeView></RuntimeView>
|
|
23
|
+
* </template>
|
|
24
|
+
*
|
|
25
|
+
* - Punkt eins
|
|
26
|
+
* - Punkt zwei
|
|
27
|
+
* </SplitView>
|
|
28
|
+
*
|
|
29
|
+
* Styling lebt ausschließlich hier; die Bullet-Marker kommen weiterhin vom
|
|
30
|
+
* content-Layout, nie aus diesem Component.
|
|
31
|
+
*/
|
|
32
|
+
import { computed } from 'vue'
|
|
33
|
+
|
|
34
|
+
const props = withDefaults(
|
|
35
|
+
defineProps<{
|
|
36
|
+
ratio?: string
|
|
37
|
+
align?: 'center' | 'start' | 'end' | 'stretch'
|
|
38
|
+
gap?: string
|
|
39
|
+
}>(),
|
|
40
|
+
{ ratio: '1/1', align: 'center', gap: '2.5rem' },
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
const columns = computed(() => {
|
|
44
|
+
const parts = props.ratio.split('/').map((n) => n.trim() || '1')
|
|
45
|
+
const left = parts[0] || '1'
|
|
46
|
+
const right = parts[1] || '1'
|
|
47
|
+
return `${left}fr ${right}fr`
|
|
48
|
+
})
|
|
49
|
+
</script>
|
|
50
|
+
|
|
51
|
+
<template>
|
|
52
|
+
<div
|
|
53
|
+
class="mg-split"
|
|
54
|
+
:style="{ gridTemplateColumns: columns, alignItems: align, gap }"
|
|
55
|
+
>
|
|
56
|
+
<div class="mg-split__visual"><slot name="visual" /></div>
|
|
57
|
+
<div class="mg-split__body"><slot /></div>
|
|
58
|
+
</div>
|
|
59
|
+
</template>
|
|
60
|
+
|
|
61
|
+
<style scoped>
|
|
62
|
+
.mg-split {
|
|
63
|
+
display: grid;
|
|
64
|
+
margin-top: 0.5rem;
|
|
65
|
+
}
|
|
66
|
+
/* min-width:0 verhindert, dass breite Grafiken die Spalte über fr sprengen. */
|
|
67
|
+
.mg-split__visual,
|
|
68
|
+
.mg-split__body {
|
|
69
|
+
min-width: 0;
|
|
70
|
+
}
|
|
71
|
+
</style>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* Step — eine Zeile in einer <StepList>: fett gesetztes Label + Fließtext.
|
|
4
|
+
* Das Label nutzt die primäre Textfarbe, der Body erbt die Farbe der StepList.
|
|
5
|
+
* <Step label="Client">ruft … auf</Step>
|
|
6
|
+
*/
|
|
7
|
+
defineProps<{ label?: string }>()
|
|
8
|
+
</script>
|
|
9
|
+
|
|
10
|
+
<template>
|
|
11
|
+
<div class="step"><strong v-if="label" class="step__label">{{ label }}:</strong> <slot /></div>
|
|
12
|
+
</template>
|
|
13
|
+
|
|
14
|
+
<style scoped>
|
|
15
|
+
.step__label {
|
|
16
|
+
font-weight: 700;
|
|
17
|
+
color: var(--miragon-text-primary);
|
|
18
|
+
}
|
|
19
|
+
</style>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* StepList — kompakte, beschriftete Schrittliste für die Seitenspalte neben
|
|
4
|
+
* einem Diagramm (Laufzeit-/Deploymentsicht). Ersetzt handgestylte
|
|
5
|
+
* <div style="font-size:…"><div style="margin-bottom:…"><strong style="…">-Blöcke.
|
|
6
|
+
*
|
|
7
|
+
* Styling lebt ausschließlich hier (Tokens aus theme.css), nie in slides.md.
|
|
8
|
+
* Verwendung:
|
|
9
|
+
* <StepList>
|
|
10
|
+
* <Step label="Client">ruft … auf</Step>
|
|
11
|
+
* </StepList>
|
|
12
|
+
*/
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<template>
|
|
16
|
+
<div class="step-list"><slot /></div>
|
|
17
|
+
</template>
|
|
18
|
+
|
|
19
|
+
<style scoped>
|
|
20
|
+
.step-list {
|
|
21
|
+
display: flex;
|
|
22
|
+
flex-direction: column;
|
|
23
|
+
gap: 0.65rem;
|
|
24
|
+
font-size: 0.8rem;
|
|
25
|
+
line-height: 1.4;
|
|
26
|
+
color: var(--miragon-text-secondary);
|
|
27
|
+
}
|
|
28
|
+
</style>
|
package/layouts/bpmn.vue
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
<script setup lang="ts">
|
|
2
|
+
/**
|
|
3
|
+
* bpmn — Slide centered on a BPMN diagram (STATIC world, no Mesh shader).
|
|
4
|
+
*
|
|
5
|
+
* Renders a .bpmn file with the token simulation from `slidev-addon-bpmn`,
|
|
6
|
+
* with an optional title/eyebrow header above and an optional caption below.
|
|
7
|
+
* The diagram is the focal point.
|
|
8
|
+
*
|
|
9
|
+
* Requires: `slidev-addon-bpmn` must be listed in the slides.md frontmatter
|
|
10
|
+
* `addons:` block. The component used is `<BpmnTokenSimulation>`, registered
|
|
11
|
+
* automatically by the addon.
|
|
12
|
+
*
|
|
13
|
+
* Frontmatter props:
|
|
14
|
+
* title — slide title (h2-level)
|
|
15
|
+
* eyebrow — uppercase kicker
|
|
16
|
+
* accent — "blue" | "green" | "mixed" (default blue)
|
|
17
|
+
* diagram — served URL path to the .bpmn file, resolved base-aware
|
|
18
|
+
* (e.g. "/resources/05-diagrams/recruitment.bpmn")
|
|
19
|
+
* height — CSS height for the BPMN canvas (default "380px")
|
|
20
|
+
* Slot:
|
|
21
|
+
* default — optional caption / explanatory line below the diagram
|
|
22
|
+
*/
|
|
23
|
+
import { computed } from 'vue'
|
|
24
|
+
|
|
25
|
+
const props = withDefaults(
|
|
26
|
+
defineProps<{
|
|
27
|
+
eyebrow?: string
|
|
28
|
+
accent?: 'blue' | 'green' | 'mixed'
|
|
29
|
+
diagram?: string
|
|
30
|
+
height?: string
|
|
31
|
+
frontmatter?: Record<string, unknown>
|
|
32
|
+
}>(),
|
|
33
|
+
{ accent: 'blue', height: '380px' },
|
|
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
|
+
// Base-Pfad respektieren (GitHub Pages baut unter /<repo>/). Runtime-Strings
|
|
43
|
+
// werden von Vite NICHT umgeschrieben — daher manuell mit BASE_URL auflösen.
|
|
44
|
+
// Gleiche Logik wie in person.vue / content-image.vue.
|
|
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 diagramSrc = computed(() => withBase(props.diagram))
|
|
51
|
+
</script>
|
|
52
|
+
|
|
53
|
+
<template>
|
|
54
|
+
<div class="bpmn-layout" :style="{ '--bp-grad': gradientVar, '--bp-accent': accentVar }">
|
|
55
|
+
<div class="bpmn-inner">
|
|
56
|
+
<header v-if="title || eyebrow" class="bpmn-head">
|
|
57
|
+
<span class="bpmn-bar" aria-hidden="true"></span>
|
|
58
|
+
<div v-if="eyebrow" class="bpmn-eyebrow">{{ eyebrow }}</div>
|
|
59
|
+
<h2 v-if="title" class="bpmn-title">{{ title }}</h2>
|
|
60
|
+
</header>
|
|
61
|
+
|
|
62
|
+
<div class="bpmn-canvas">
|
|
63
|
+
<BpmnTokenSimulation
|
|
64
|
+
v-if="diagram"
|
|
65
|
+
:bpmnFilePath="diagramSrc"
|
|
66
|
+
width="100%"
|
|
67
|
+
:height="height"
|
|
68
|
+
/>
|
|
69
|
+
</div>
|
|
70
|
+
|
|
71
|
+
<div v-if="$slots.default" class="bpmn-caption">
|
|
72
|
+
<slot />
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
</div>
|
|
76
|
+
</template>
|
|
77
|
+
|
|
78
|
+
<style scoped>
|
|
79
|
+
.bpmn-layout {
|
|
80
|
+
position: relative;
|
|
81
|
+
width: 100%;
|
|
82
|
+
height: 100%;
|
|
83
|
+
overflow: hidden;
|
|
84
|
+
background: var(--miragon-gray-bg);
|
|
85
|
+
color: var(--miragon-text-primary);
|
|
86
|
+
display: flex;
|
|
87
|
+
align-items: stretch;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
.bpmn-inner {
|
|
91
|
+
position: relative;
|
|
92
|
+
z-index: 1;
|
|
93
|
+
width: 100%;
|
|
94
|
+
max-width: 78rem;
|
|
95
|
+
margin: 0 auto;
|
|
96
|
+
padding: 2.5rem 4rem;
|
|
97
|
+
display: flex;
|
|
98
|
+
flex-direction: column;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.bpmn-head {
|
|
102
|
+
flex: 0 0 auto;
|
|
103
|
+
margin-bottom: 1.25rem;
|
|
104
|
+
}
|
|
105
|
+
.bpmn-bar {
|
|
106
|
+
display: block;
|
|
107
|
+
width: 3.5rem;
|
|
108
|
+
height: 0.35rem;
|
|
109
|
+
border-radius: 999px;
|
|
110
|
+
background: var(--bp-grad);
|
|
111
|
+
margin-bottom: 0.9rem;
|
|
112
|
+
}
|
|
113
|
+
.bpmn-eyebrow {
|
|
114
|
+
font-size: 0.9rem;
|
|
115
|
+
font-weight: 600;
|
|
116
|
+
letter-spacing: 0.18em;
|
|
117
|
+
text-transform: uppercase;
|
|
118
|
+
color: var(--bp-accent);
|
|
119
|
+
margin-bottom: 0.55rem;
|
|
120
|
+
}
|
|
121
|
+
.bpmn-title {
|
|
122
|
+
font-size: clamp(1.7rem, 2.7vw, 2.2rem);
|
|
123
|
+
line-height: 1.15;
|
|
124
|
+
font-weight: 800;
|
|
125
|
+
letter-spacing: -0.02em;
|
|
126
|
+
margin: 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.bpmn-canvas {
|
|
130
|
+
flex: 1 1 auto;
|
|
131
|
+
min-height: 0;
|
|
132
|
+
background: var(--miragon-white);
|
|
133
|
+
border: 1px solid #E5E7EB;
|
|
134
|
+
border-radius: 1.1rem;
|
|
135
|
+
padding: 0.75rem;
|
|
136
|
+
box-shadow: 0 8px 20px rgba(51, 93, 229, 0.08);
|
|
137
|
+
overflow: hidden;
|
|
138
|
+
display: flex;
|
|
139
|
+
align-items: center;
|
|
140
|
+
justify-content: center;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
.bpmn-caption {
|
|
144
|
+
flex: 0 0 auto;
|
|
145
|
+
margin-top: 1rem;
|
|
146
|
+
font-size: 0.95rem;
|
|
147
|
+
color: var(--miragon-text-muted);
|
|
148
|
+
text-align: center;
|
|
149
|
+
}
|
|
150
|
+
.bpmn-caption :deep(p) {
|
|
151
|
+
margin: 0;
|
|
152
|
+
line-height: 1.5;
|
|
153
|
+
}
|
|
154
|
+
.bpmn-caption :deep(code) {
|
|
155
|
+
font-family: var(--miragon-font-mono);
|
|
156
|
+
font-size: 0.9em;
|
|
157
|
+
background: var(--miragon-blue-light);
|
|
158
|
+
color: var(--miragon-blue-darker);
|
|
159
|
+
padding: 0.1em 0.4em;
|
|
160
|
+
border-radius: 0.35rem;
|
|
161
|
+
}
|
|
162
|
+
</style>
|