Files
familienarchiv/docs/presentation/index.html
2026-05-05 12:39:20 +02:00

539 lines
27 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>KI als Team, nicht als Werkzeug</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/theme/black.css" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/highlight/monokai.css" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500;700&display=swap" />
<style>
/* ── Tokens (same as persona cards) ─────────────────── */
:root {
--bg: #0D1117;
--surface: #161B22;
--surface-2: #1C2128;
--border: #21262D;
--text: #C9D1D9;
--text-muted: #6E7681;
--text-bright:#F0F6FC;
--accent: #22D3EE;
--red: #F85149;
/* reveal overrides */
--r-background-color: var(--bg);
--r-main-color: var(--text);
--r-heading-color: var(--text-bright);
--r-link-color: var(--accent);
--r-link-color-hover: #67E8F9;
--r-main-font: 'Inter', system-ui, sans-serif;
--r-heading-font: 'Inter', system-ui, sans-serif;
--r-code-font: 'JetBrains Mono', monospace;
--r-main-font-size: 36px;
}
/* ── Dot-grid background (persona card pattern) ──────── */
.reveal::before {
content: '';
position: fixed;
inset: 0;
background-image: radial-gradient(circle, rgba(255,255,255,.045) 1px, transparent 1px);
background-size: 26px 26px;
pointer-events: none;
z-index: 0;
}
.reveal .slides { z-index: 1; }
/* ── Headings ─────────────────────────────────────────── */
.reveal h1, .reveal h2, .reveal h3 {
font-family: 'Inter', sans-serif;
font-weight: 800;
letter-spacing: -.5px;
text-transform: none;
color: var(--text-bright);
}
.reveal h2 {
font-size: 1.8em;
padding-bottom: .35em;
border-bottom: 1px solid var(--border);
margin-bottom: .6em;
}
.reveal h2::before {
content: '// ';
font-family: 'JetBrains Mono', monospace;
font-size: .7em;
color: var(--accent);
font-weight: 500;
}
.reveal h3 {
font-size: 1.15em;
color: var(--accent);
margin-bottom: .5em;
}
/* ── Body text ───────────────────────────────────────── */
.reveal p, .reveal li { color: var(--text); font-size: .85em; line-height: 1.6; }
.reveal strong { color: var(--text-bright); }
.reveal em { color: var(--text-muted); font-style: italic; }
.reveal small { color: var(--text-muted); font-size: .65em; }
/* ── Links ───────────────────────────────────────────── */
.reveal a {
color: var(--accent);
font-family: 'JetBrains Mono', monospace;
font-size: .75em;
text-decoration: none;
border-bottom: 1px solid color-mix(in srgb, var(--accent) 40%, transparent);
}
.reveal a:hover { color: #67E8F9; border-bottom-color: #67E8F9; }
/* ── Tables ──────────────────────────────────────────── */
.reveal table {
width: 100%;
border-collapse: collapse;
font-size: .72em;
}
.reveal table thead tr {
border-bottom: 1px solid var(--accent);
}
.reveal table th {
font-family: 'JetBrains Mono', monospace;
font-size: .85em;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 1.2px;
color: var(--text-muted);
padding: 6px 12px;
text-align: left;
}
.reveal table td {
padding: 7px 12px;
border-bottom: 1px solid var(--border);
color: var(--text);
vertical-align: top;
}
.reveal table tbody tr:last-child td { border-bottom: none; }
.reveal table tbody tr:hover td { background: rgba(255,255,255,.03); }
/* ── Code blocks ─────────────────────────────────────── */
.reveal pre {
background: var(--surface);
border: 1px solid var(--border);
border-left: 3px solid var(--accent);
border-radius: 6px;
font-size: .58em;
box-shadow: none;
}
.reveal code:not(pre code) {
font-family: 'JetBrains Mono', monospace;
font-size: .85em;
background: var(--surface-2);
border: 1px solid var(--border);
border-radius: 3px;
padding: 1px 6px;
color: var(--accent);
}
/* ── Blockquotes ─────────────────────────────────────── */
.reveal blockquote {
background: color-mix(in srgb, var(--accent) 5%, transparent);
border-left: 3px solid var(--accent);
border-radius: 0 6px 6px 0;
padding: 12px 20px;
font-style: italic;
font-size: .8em;
color: var(--text-muted);
box-shadow: none;
width: 100%;
}
.reveal blockquote p { color: var(--text-muted); font-size: 1em; }
/* ── Topbar label (reusable) ─────────────────────────── */
.slide-label {
font-family: 'JetBrains Mono', monospace;
font-size: .45em;
color: var(--text-muted);
letter-spacing: 1px;
display: block;
margin-bottom: .5em;
}
.slide-label .accent { color: var(--accent); }
/* ── Slide number ────────────────────────────────────── */
.reveal .slide-number {
font-family: 'JetBrains Mono', monospace;
font-size: 11px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--text-muted);
padding: 2px 7px;
bottom: 12px;
right: 14px;
}
/* ── Progress bar ────────────────────────────────────── */
.reveal .progress { height: 2px; }
.reveal .progress span { background: var(--accent); }
/* ── highlight-row fragment: visible from start, styled on advance ── */
.reveal .fragment.highlight-row { opacity: 1; visibility: inherit; }
.reveal .fragment.highlight-row.visible {
font-weight: 700;
color: #22D3EE !important;
background: rgba(34,211,238,.08) !important;
}
</style>
</head>
<body>
<div class="reveal">
<div class="slides">
<!-- ══════════════════════════════════════════════
1 · TITEL
══════════════════════════════════════════════ -->
<section>
<h2>KI als Team, nicht als Werkzeug</h2>
<p><small>Marcel Raddatz · 2026</small></p>
<aside class="notes">
Einstieg: „Die meisten nutzen KI als sehr schnelle Schreibkraft. Ich nutze sie als Team von Spezialisten."
Kein Agenda-Slide — das Workflow-Diagramm kommt nach der Team-Vorstellung.
</aside>
</section>
<!-- ══════════════════════════════════════════════
2 · DAS PROJEKT
══════════════════════════════════════════════ -->
<section>
<section>
<h2>Das Projekt: Familienarchiv</h2>
<p>Handgeschriebene Kurrent- und Sütterlin-Briefe, 18991950</p>
<ul>
<li>Crowd-Transkription durch 60+-Jährige am Laptop</li>
<li>Jüngere Familienmitglieder lesen's am Handy</li>
<li>Solo-Entwickler — kein Team zum Review oder Pair-Programming</li>
</ul>
<aside class="notes">PM-Takeaway: echte Domäne, echte Nutzer, echte Constraints.</aside>
</section>
<section>
<h3>Stack &amp; Umfang</h3>
<table>
<tr><td><strong>Backend</strong></td><td>Spring Boot 4 · Java 21 · JPA · Flyway · Spring Security</td></tr>
<tr><td><strong>Frontend</strong></td><td>SvelteKit 2 / Svelte 5 · TypeScript · Tailwind CSS 4</td></tr>
<tr><td><strong>DB / Storage</strong></td><td>PostgreSQL 16 · MinIO (S3-kompatibel)</td></tr>
<tr><td><strong>OCR</strong></td><td>Python · FastAPI · lernende ML-Modelle für Kurrent &amp; Sütterlin</td></tr>
<tr><td><strong>i18n</strong></td><td>Paraglide.js — de / en / es</td></tr>
<tr><td><strong>Umfang</strong></td><td>40+ UI/UX-Specs · 360+ Gitea-Issues · 7 Personas</td></tr>
</table>
<aside class="notes">Dev-Takeaway: kein Toy-Projekt — voller Stack, Backend und Frontend.</aside>
</section>
</section>
<!-- ══════════════════════════════════════════════
3 · DAS PROBLEM MIT "EINFACH FRAGEN"
══════════════════════════════════════════════ -->
<section>
<h2>Das Problem mit „Einfach die KI fragen"</h2>
<table>
<thead><tr><th>Was man tut</th><th>Was schiefläuft</th></tr></thead>
<tbody>
<tr class="fragment"><td>Vage Idee → Code anfordern</td><td>Output passt nicht zu dem, was man wollte</td></tr>
<tr class="fragment"><td>Review in derselben Session</td><td>Befangenheit: genehmigt sich selbst</td></tr>
<tr class="fragment"><td>Die KI einfach fragen ob's gut ist</td><td>Kein echter Widerspruch</td></tr>
<tr class="fragment"><td>Auf Gesprächskontext vertrauen</td><td>Nächste Session startet blank</td></tr>
</tbody>
</table>
<p class="fragment"><em>Das ist ein Workflow-Problem, kein Prompt-Problem.</em></p>
<aside class="notes">
Dieser Slide schafft das Warum für alles was folgt.
Das Publikum erkennt sich in mindestens einer Zeile wieder.
</aside>
</section>
<!-- ══════════════════════════════════════════════
4 · STUFEN DER KI-ENTWICKLUNG
══════════════════════════════════════════════ -->
<section>
<h2>Wo stehst du?</h2>
<table style="font-size:.6em;border-collapse:collapse;width:100%">
<tbody>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">1 — Autocomplete</td><td style="padding:4px 10px">Tab-Completion, eine Zeile vorschlagen</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">2 — Chat / Pair</td><td style="padding:4px 10px">Fragen stellen, Code kopieren, selbst einfügen</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">3 — Agentic Edit</td><td style="padding:4px 10px">KI editiert Dateien direkt — Cursor, Claude Code</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">4 — Spec → Code</td><td style="padding:4px 10px">Mensch schreibt Spec, KI implementiert</td></tr>
<tr class="fragment highlight-row" style="color:#6E7681"><td style="padding:6px 10px;white-space:nowrap">5 — Idea → Spec → Code</td><td style="padding:6px 10px">KI hilft beim Spec-Schreiben — Mensch behält jedes Gate</td></tr>
<tr style="color:#6E7681"><td style="padding:4px 10px;white-space:nowrap">6 — Fully Autonomous</td><td style="padding:4px 10px">KI plant, spezifiziert, implementiert, testet — kein Mensch</td></tr>
</tbody>
</table>
<aside class="notes">
Kurz im Publikum fragen: wer nutzt Autocomplete? Wer Chat? Wer Agentic?
Pointe: Stufe 6 klingt verlockend — aber ohne Gates verliert man Kontrolle und Kontext.
Stufe 5 ist der Sweet Spot: KI als Denkpartner, Mensch als Entscheider.
</aside>
</section>
<!-- ══════════════════════════════════════════════
5 · WORKFLOW ÜBERBLICK
══════════════════════════════════════════════ -->
<section>
<h2>Der Workflow im Überblick</h2>
<pre style="font-size:.42em;overflow:visible"><code class="text" data-trim>
Vage Idee
UI-Exploration ──▶ Spec (Elicit) ──▶ Gitea-Issue
Persona-Review ──▶ discuss(Persona) ─┐
(6 Spezialisten) │
▲ │
└──────────────────────────────┘
Feature-Branch
TDD: Red → Green → Refactor → Commit
Pull Request ──▶ Persona-PR-Review ──▶ Merge
</code></pre>
<aside class="notes">
Roter Faden: wir verfolgen Issue #358 (Stammbaum) durch jeden Schritt.
Jeder folgende Abschnitt zoomt in einen Schritt hinein.
</aside>
</section>
<!-- ══════════════════════════════════════════════
6 · DAS TEAM
══════════════════════════════════════════════ -->
<section>
<h2>Das Team</h2>
<table>
<thead><tr><th>Name</th><th>Rolle</th><th>Kernprinzip</th></tr></thead>
<tbody>
<tr><td>Elicit</td><td>Requirements Engineer</td><td>Schreibt nie Code</td></tr>
<tr><td>Markus Keller</td><td>Architekt</td><td>Boring technology wins</td></tr>
<tr><td>Felix Brandt</td><td>Senior Developer</td><td>Test first. Immer.</td></tr>
<tr><td>Leonie Voss</td><td>UX Design Lead</td><td>Hardest constraint first</td></tr>
<tr><td>Sara Holt</td><td>QA Engineer</td><td>A passing test that was never failing is a lie</td></tr>
<tr><td>Nora „NullX" Steiner</td><td>Security Engineer</td><td>Jeder neue Endpoint braucht <code>@RequirePermission</code></td></tr>
<tr><td>Tobias „tobi" Wendt</td><td>DevOps</td><td>Komplexität ist eine Liability</td></tr>
</tbody>
</table>
<aside class="notes">
Markdown-Dateien in .claude/personas/ — jede mit echtem Namen und Biografie.
Überleitung: jetzt zoomen wir in jede Persona hinein.
</aside>
</section>
<section>
<span class="slide-label"><span class="accent">.claude/personas/</span>developer.md</span>
<h2>Aufbau einer Persona</h2>
<div style="display:grid;grid-template-columns:auto 1fr;gap:1.6em;margin-top:.5em;align-items:start">
<div style="font-family:'JetBrains Mono',monospace;font-size:.48em;line-height:1.85;border-right:1px solid var(--border);padding-right:1.5em;white-space:nowrap">
<div style="color:var(--text-muted)">## Your Identity</div>
<div style="color:var(--text-muted)">## Readable &amp; Clean Code</div>
<div style="color:var(--text-muted)">## Reliable Code</div>
<div style="color:var(--text-muted)">## Modern Code</div>
<div style="color:var(--accent);font-weight:700">## Secure Code ◀</div>
<div style="color:var(--text-muted)">## Testable Code</div>
<div style="color:var(--text-muted)">## How You Work</div>
<div style="color:var(--text-muted)">## Relationships</div>
<div style="color:var(--text-muted)">## Your Tone</div>
</div>
<div style="background:var(--surface);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:0 6px 6px 0;padding:12px 18px;display:flex;flex-direction:column;gap:.55em">
<div style="font-family:'JetBrains Mono',monospace;font-size:.48em;font-weight:700;color:var(--accent)">## Secure Code</div>
<div>
<div style="font-family:'JetBrains Mono',monospace;font-size:.37em;color:var(--text-muted);letter-spacing:1px;text-transform:uppercase;margin-bottom:.3em">### General — projektunabhängig</div>
<p style="font-size:.58em;line-height:1.6;color:var(--text-muted);font-style:italic">
Secure code treats all external input as hostile. Authentication and authorization
are enforced via framework annotations, not scattered if-statements.
<strong style="color:var(--text-bright);font-style:normal">Error messages reveal nothing about the implementation.</strong>
</p>
</div>
<div>
<div style="font-family:'JetBrains Mono',monospace;font-size:.37em;color:var(--text-muted);letter-spacing:1px;text-transform:uppercase;margin-bottom:.35em">### per Stack — projektspezifisch (Frontend · Java · Python)</div>
<div style="display:grid;grid-template-columns:1fr 1fr;gap:.5em;font-size:.58em">
<div style="background:rgba(52,211,153,.07);border:1px solid rgba(52,211,153,.25);border-radius:5px;padding:7px 12px">
<div style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--accent);font-weight:700;margin-bottom:.25em">✓ DO</div>
<div style="color:var(--text);line-height:1.45"><strong>Java:</strong> <code>@RequirePermission</code> on every write endpoint</div>
<div style="color:var(--text);line-height:1.45;margin-top:.4em"><strong>Python:</strong> SSRF host whitelist before any outbound request</div>
</div>
<div style="background:rgba(248,81,73,.07);border:1px solid rgba(248,81,73,.25);border-radius:5px;padding:7px 12px">
<div style="font-family:'JetBrains Mono',monospace;font-size:.8em;color:var(--red);font-weight:700;margin-bottom:.25em">✗ DON'T</div>
<div style="color:var(--text);line-height:1.45"><strong>Java:</strong> string concat in JPQL queries</div>
<div style="color:var(--text);line-height:1.45;margin-top:.4em"><strong>Svelte:</strong> <code>fetch('/api/…')</code> inside <code>onMount</code></div>
</div>
</div>
</div>
</div>
</div>
<aside class="notes">
Alle Personas teilen dieselbe Abschnittsstruktur — aber jede schreibt sie aus ihrer eigenen Perspektive.
Dieser Abschnitt ist Felix' Kernphilosophie: TDD ist kein Tool, es ist die einzige Arbeitsweise.
"Never write implementation code before a failing test exists" — das ist sein Hard Limit.
</aside>
</section>
<!-- ══════════════════════════════════════════════
7 · IDEE & UI-EXPLORATION
══════════════════════════════════════════════ -->
<section>
<h2>Idee &amp; UI-Exploration</h2>
<ol>
<li class="fragment">Vage Anforderung: <em>„Wir brauchen einen Stammbaum"</em></li>
<li class="fragment">UI-Persona bitten, <strong>4 Specs mit unterschiedlichem Fokus</strong> zu generieren</li>
<li class="fragment">Einen Spec auswählen — Elemente aus anderen übernehmen</li>
<li class="fragment">Finales vollständiges Spec schreiben lassen</li>
<li class="fragment">Committen und am Issue verlinken</li>
</ol>
<p class="fragment"><a href="../specs/stammbaum-tree-spec.html" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ stammbaum-tree-spec.html</a></p>
<aside class="notes">
stammbaum-tree-spec.html im Browser zeigen.
Der Trick mit den 4 Varianten: man merkt erst beim Vergleichen was man eigentlich will.
Scope-Entscheidungen auf Pixel-Ebene sind billiger als im Issue.
</aside>
</section>
<!-- ══════════════════════════════════════════════
7 · SPEC MIT ELICIT → GITEA-ISSUE
══════════════════════════════════════════════ -->
<section>
<section>
<h2>Spec mit Elicit → Gitea-Issue</h2>
<p>Warum Gitea, nicht eine Datei?</p>
<ul>
<li class="fragment">LLM-Gespräche sind <strong>flüchtig</strong> — Issues sind <strong>dauerhaft</strong></li>
<li class="fragment">Issues sind verlinkbar: jeder Commit referenziert sie</li>
<li class="fragment">Review-Kommentare, Spec und Entscheidungen leben im selben Thread</li>
<li class="fragment">Kein Risiko einer veralteten Spec-Datei im Repo</li>
</ul>
<aside class="notes">
Das ist die architektonische Kernidee des ganzen Talks.
</aside>
</section>
<section>
<h3>Aufbau eines Spec-Issues</h3>
<ul>
<li class="fragment"><strong>Problem statement</strong> — was fehlt und warum es wichtig ist</li>
<li class="fragment"><strong>User Journey</strong> — Schritt für Schritt in Prosa</li>
<li class="fragment"><strong>E2E-Szenarien</strong> (Given/When/Then) → werden zu Red-Tests</li>
<li class="fragment"><strong>Requirements</strong> — funktional + NFR</li>
<li class="fragment"><strong>Out of scope</strong> — explizite Ausschlüsse</li>
</ul>
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/issues/358" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ Issue #358 — Stammbaum</a></p>
<aside class="notes">Echtes Issue #358 zeigen — annotiert.
PM: das ist der Vertrag vor Baubeginn.
Dev: das Szenario IST der erste fehlschlagende Test.</aside>
</section>
</section>
<!-- ══════════════════════════════════════════════
8 · PERSONA ISSUE REVIEW
══════════════════════════════════════════════ -->
<section>
<h2>Persona Issue Review</h2>
<p>Vor dem ersten Code: die Spec reviewen lassen</p>
<ul>
<li class="fragment">Jede Persona liest das Issue für sich — kein Austausch untereinander</li>
<li class="fragment">Kommentare werden direkt als Gitea-Kommentare gepostet</li>
<li class="fragment">Nora: Auth spezifiziert? · Sara: testbar? · Leonie: Mobile? · Markus: Domain-Grenzen?</li>
</ul>
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/issues/358#issuecomment-5176" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ Issue #358 — Kommentar</a></p>
<aside class="notes">review-issue-Skill schickt einen Agenten pro Persona.
„Hier zu finden kostet null. Nach dem PR-Öffnen kostet es einen Tag."
Den Kommentar zeigen der den größten Impact hatte.</aside>
</section>
<!-- ══════════════════════════════════════════════
9 · IMPLEMENTIERUNG: TDD
══════════════════════════════════════════════ -->
<section>
<section>
<h2>Implementierung: Red/Green/Refactor</h2>
<ul>
<li class="fragment">Die Szenarien aus dem Issue werden direkt zu Unit-Tests</li>
<li class="fragment">Failing Unit-Test → minimaler Code → grün → Refactor → Commit</li>
<li class="fragment">Immer Feature-Branch · ein logischer Change pro Commit</li>
</ul>
<aside class="notes">deliver-issue-Skill koordiniert den gesamten Zyklus.
PM: die Akzeptanzkriterien aus dem Issue sind die Definition of Done.</aside>
</section>
<section>
<h3>Red/Green im git log</h3>
<pre><code class="java" data-trim>
// Erst der Test — schlägt fehl (RED)
@Test
void getFamilyNetwork_excludesEdgesWithNonFamilyEndpoints() {
var result = service.getFamilyNetwork(personId);
assertThat(result.edges())
.noneMatch(e -> isNonFamilyPerson(e.targetId()));
}
</code></pre>
<pre><code class="text" data-trim>
32622b9b test(stammbaum): getFamilyNetwork excludes edges ... ← RED
656c93ca fix(stammbaum): exclude non-family edges in network ← GREEN
</code></pre>
<aside class="notes">
Das Log ist der Beweis, dass der Red-Schritt stattgefunden hat.
Test und Implementierung im selben atomaren Commit.
</aside>
</section>
</section>
<!-- ══════════════════════════════════════════════
10 · PR REVIEW LOOP
══════════════════════════════════════════════ -->
<section>
<h2>PR Review Loop</h2>
<p><code>review-pr</code>-Skill liest den Diff — jede Persona postet Kommentare zur PR</p>
<ul>
<li class="fragment">Felix: Projektstil, Dead Code?</li>
<li class="fragment">Nora: <code>@RequirePermission</code> auf jedem neuen Endpoint?</li>
<li class="fragment">Sara: fehlende Randfallstests?</li>
<li class="fragment">Leonie: stimmt der Output mit der Spec überein?</li>
</ul>
<p class="fragment">Kommentare adressieren → pushen → Review wiederholen → Merge</p>
<p class="fragment"><a href="http://heim-nas:3005/marcel/familienarchiv/pulls/360" target="_blank" style="font-family:monospace;font-size:.7em;color:#A6DAD8">→ PR #360 — feat(stammbaum)</a></p>
<aside class="notes">
Einen PR-Kommentar zeigen der den Merge blockiert hat.
Merge erst wenn alle Stimmen zufrieden sind.
</aside>
</section>
<!-- ══════════════════════════════════════════════
11 · FAZIT
══════════════════════════════════════════════ -->
<section>
<h2>Fazit</h2>
<ol>
<li><strong>Separation of Concerns gilt auch für KI-Rollen</strong> — eine Persona die alles macht, reviewed sich selbst</li>
<li><strong>Das LLM vergisst. Das Issue nicht.</strong> — Spec, Entscheidungen und Review-Kommentare überleben jeden Context Reset</li>
</ol>
<aside class="notes">
Ehrliches Caveat: Aufwand am Anfang. Der Ertrag ist kumulativ.
</aside>
</section>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/dist/reveal.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/highlight/highlight.js"></script>
<script src="https://cdn.jsdelivr.net/npm/reveal.js@5/plugin/notes/notes.js"></script>
<script>
Reveal.initialize({
hash: true,
slideNumber: 'c/t',
transition: 'slide',
plugins: [RevealHighlight, RevealNotes],
});
</script>
</body>
</html>