Visueller Erklärer
Das Bild, das der Benutzer sieht, ist der letzte Frame. Der Play-Button spielt die Bewegung einmal ab. Daher muss das Standbild für sich allein erklären; die Bewegung zeigt nur die Reihenfolge der Dinge.
Dies ist der Hausstil: dunkle Tinte, ein grüner Akzent und ruhiges Timing, auf Weiß mit Haarfeinlinien anstelle von Schatten.
Lesbarkeit zuerst
Das Bild wird 1:1 neben Terminaltext von etwa 15px gezeichnet. Alles, was kleiner ist, liest sich schlechter als der Chat darum herum, sodass der Benutzer es nicht lesen kann.
- Kein Text unter 16px (Code 15px). Wenn es nicht passt, kürzen Sie den Inhalt oder teilen Sie ihn auf zwei Grafiken auf; verkleinern Sie niemals die Schrift.
- Schriftart: Atkinson Hyperlegible (für Lesbarkeit entwickelt), mit den unten genannten Fallbacks. Textstärke 400, Titel 700.
- Weniger Dinge pro Bild: etwa 5 Knoten pro Zeile, höchstens zwei Zeilen.
Farben
:root{
--bg:#fff; /* immer weiß */
--ink:#1d2520; /* primärer Text, Hauptlinien */
--muted:#5f6561; /* sekundärer Text, Beschriftungen auf Linien */
--faint:#9aa19c; /* inaktive Knoten, hintere/optionale Pfade */
--line:#e4e4e0; /* Kartenränder, Trennlinien, Raster */
--tint:#f4f2ef; /* seltene Füllung: Code-Chips, hervorgehobene Zeile */
--accent:#3b7a57; /* die EINE Farbe: der Pfad, der zählt */
--bad:#9a4b3c; /* nur für ein „Nein“ / „blockiert“-Zeichen, niemals dekorativ */
}
body{font:400 18px/1.4 "Atkinson Hyperlegible",Inter,system-ui,sans-serif;color:var(--ink)}
code{font:15px "Intel One Mono",ui-monospace,monospace}
- Schriftskala: Titel 26px/700, Knotenlabel 18px, Untertitel und Linienlabel 16px. Titel
letter-spacing:-.01em. Zahlen erhaltenfont-variant-numeric:tabular-nums. - Linien: Ränder 1px. Verbinder 1.5px,
stroke-linecap:round. Nichts dicker als 2px. - Radien: Karten 10px, Chips 8px, Token-Pille 999px. Keine anderen Werte.
- Abstände: 8px-Raster. Innenabstand des Body 24px. Innenabstand der Karte 14px 18px. Abstand zwischen Knoten 96-120px, damit ein Linienlabel Platz hat.
- Verwendung des Akzents: ein Pfad, ein Rand oder ein Chip. Wenn zwei Dinge grün sind, sollte eines davon nicht sein.
Leinwand
- Breite = Inhalt + 48px Innenabstand. Typisch 600-1100. Niemals volle Breite „um zu füllen“. Inhalt beginnt oben links.
- Zeilen: Inhalt Höhe + 48px, geteilt durch 19, aufgerundet. Ein einzeiliger Ablauf ist 9-11 Zeilen; zwei Ebenen 16-20. Maximal 30.
- Breit und kurz ist besser als hoch. Bevorzugen Sie einen von links nach rechts verlaufenden Ablauf; führen Sie eine zweite Zeile ein, bevor Sie hoch gehen.
- Der Basisstil ist weiß, 16px padding,
box-sizing:border-box,overflow:hidden. Setzen Siebody{padding:24px}und den Hintergrund selbst.
Layout
- Leserichtung: Titel oben links, dann von links nach rechts, dann von oben nach unten. Die Animation folgt derselben Reihenfolge.
- Der Titel ist ein einfacher Satz, der die Kernaussage formuliert („Ein Cache-Treffer überspringt die Datenbank“), kein Thema („Caching“).
- Karten nur für Dinge, die existieren (ein Dienst, eine Tabelle, ein Konto, ein Schritt). Labels, Werte und Notizen sind reiner Text.
- Verbinder: SVG-Overlay (
position:absolute;inset:0;overflow:visible) im selben positionierten Elternelement wie die Knoten. Messen Sie Endpunkte mitgetBoundingClientRect(); kodieren Sie niemals Koordinaten fest. Lassen Sie einen Abstand von 8px zwischen Linie und Karte. - Pfeilspitzen: ein offener Chevron-Pfad
l5 4-5 4in der Farbe der Linie. Keine gefüllten Dreiecke, keine<marker>-Voreinstellungen. - Zuerst gerade Linien. Kurven nur, um etwas herumzuführen: eine kubische, horizontale Tangenten (
C midX y1, midX y2, x2 y2). - Ein Pfad, der nicht stattfindet:
--faint-Farbe, gestrichelt2 5. Ein blockierter Pfad: dasselbe, plus ein kleines--bad-x in seiner Mitte. - Linienlabel zentriert über der Linie in
--muted16px, 2-3 Wörter. - Bewegte Daten (eine Anfrage, ein Betrag, eine Nachricht) sind eine kleine dunkle Pille:
--ink-Hintergrund, weißer 16px-Text, Radius 999px. Sie endet an dem Ort, an dem sie angekommen ist, sodass das Standbild sie zeigt. - Diagramme: eine Achse, 2px-Linie oder dünne Balken, Gitterlinien in
--line, die relevante Datenreihe in--accentund der Rest in--faint, direkte Beschriftungen anstelle einer Legende, Werte in Tintenfarbe (niemals in der Farbe der Datenreihe). Wenn eine Zahl der Punkt ist, zeigen Sie die Zahl, kein Diagramm.
Bewegung
- Jede Szene endet mit einem aussagekräftigen letzten Frame. Keine Ausgänge, keine Schleifen, keine Ausblendungen. Die gesamte Animation endet mindestens 200 ms vor
duration_ms. - Typische Gesamtdauer 2-4 s. Bleiben Sie unter 6 s, es sei denn, es handelt sich um eine mehrschrittige Geschichte.
duration_ms= letztes Ende + 300 ms. - Reihenfolge: Titel, dann Knoten in Lesereihenfolge, dann Verbinder zeichnen, dann Beschriftungen, dann bewegtes Token, dann Endzustandsänderung. Ein Ding beginnt, während sich das vorherige beruhigt (Überlappung ~0.1-0.3 s); niemals alle auf einmal, niemals streng nacheinander.
- Werte:
| Was | Von | Dauer | Easing (GSAP / cubic-bezier) |
|---|---|---|---|
| Titel | opacity 0, y 8 | 0.6s | power3.out / .16,1,.3,1 |
| Karte | opacity 0, y 12, scale .97 | 0.8s, Staffelung 0.12s | expo.out / .16,1,.3,1 |
| Verbinder zeichnen | dashoffset L+4 | 0.7s, Staffelung 0.1-0.3s | power2.inOut / .65,0,.35,1 |
| Pfeilspitze | opacity 0 | 0.2s, wenn Linie endet | linear |
| Linienlabel | opacity 0, y 4 | 0.5s | power3.out |
| Token-Weg | entlang der Linie | 1.0s | power2.inOut |
| Wertwechsel | alt: y -8, blur 3px, out 0.35s; neu: y 8, blur 3px, in 0.5s | power2.in / power3.out |
- Animieren Sie nur
transform,opacity,filter: blurund SVGstroke-dashoffset. - Niemals von
scale(0); beginnen Sie bei .97. Kein Bounce, kein Überschwingen, kein elastisches Verhalten. Die einzige Ausnahme: ein kleinerback.out(1.6)-Pop für ein „Nein“-Zeichen. - Animieren Sie niemals Text, den der Leser lesen muss, während er sich bewegt; lassen Sie ihn zuerst landen, dann bewegen Sie das nächste Ding.
- Physische Metapher, wenn es passt: Wenn das Ding ein Brief, eine Rechnung oder eine Karte ist, bewegen Sie es wie das reale Objekt.
Bibliotheken
Wählen Sie die Bibliothek basierend auf dem, was das Bild darstellt, bevor Sie HTML schreiben. Von Hand das nachzubauen, was eine Bibliothek bereits kann (ein Kraftlayout, eine mathematische Formel, Code-Hervorhebung), sieht schlechter aus und geht häufiger kaputt.
| Das Bild ist | Verwenden Sie | Warum |
|---|---|---|
| Karten und Pfeile, ein Ablauf, eine Abfolge von Schritten | HTML-Karten + SVG-Overlay + GSAP-Timeline | Volle Kontrolle über den Stil; GSAP ordnet die Schritte |
| Ein kurzer Auftritt, eine Liste erscheint | Motion animate + stagger, oder CSS @keyframes | Weniger Code als eine Timeline |
| Ein Graph mit vielen Knoten, deren Positionen Sie nicht von Hand auswählen können | elkjs (geschichtetes Layout) oder d3-force, dann in unserem Stil zeichnen | Layout-Mathematik ist schwer von Hand richtig hinzubekommen |
| Ein echtes Diagramm (Achsen, viele Punkte, eine Skala) | d3 (Skalen, Achsen, Formen) von Hand gestylt | Chart.js nur für einen schnellen Balken/Linie; seine Voreinstellungen umgestalten |
| Mathematik, Formeln | KaTeX | Echtes mathematisches Setzen |
| Code | highlight.js mit dem github-Theme, Schriftart 15px | Lesbare Farben auf Weiß |
| Viele Partikel, eine Simulation, Physik | <canvas> mit rAF, gesteuert von performance.now() | Das DOM ist zu langsam für hunderte Elemente |
| Etwas 3D | three.js (ES-Modul + importmap) | Nur wenn Tiefe der Punkt ist |
| Eine „Skizzen“-Idee | rough.js | Absichtlich handgezeichnete Linien |
Niemals Mermaid für Erklärer: Sein Standard-Look kämpft gegen diesen Stil. Verwenden Sie stattdessen elkjs für das Layout.
Festgepinnt, alle geprüft auf cdn.jsdelivr.net (Präfix https://cdn.jsdelivr.net/npm/):
- GSAP
[email protected]/dist/gsap.min.js(globalgsap). Nichtpaused:true; lassen Sie es beim Laden abspielen. - Motion
[email protected]/dist/motion.js(globalMotion;const {animate, stagger} = Motion). - d3
[email protected]/dist/d3.min.js(globald3). - elkjs
[email protected]/lib/elk.bundled.js(globalELK;new ELK().layout(graph)gibt ein Promise zurück). - Chart.js
[email protected]/dist/chart.umd.min.js(globalChart). Setzen Sieanimation:falseoder seine eigene Dauer unterduration_ms. - KaTeX
[email protected]/dist/katex.min.js+[email protected]/dist/katex.min.css. - highlight.js
@highlightjs/[email protected]/highlight.min.js+.../styles/github.min.css. - rough.js
[email protected]/bundled/rough.js(globalrough). - three.js
[email protected]/build/three.module.min.jsüber<script type="importmap">.
Asynchrones Layout (elkjs, Schriftarten, KaTeX): Bauen Sie die Szene, dann starten Sie die Timeline. Der letzte Frame wird bei duration_ms aufgenommen, also muss das Ganze trotzdem rechtzeitig fertig sein.
Tücken:
- Motion: Der End-Keyframe muss dieselben Transform-Funktionen verwenden wie der Start.
['translateY(12px) scale(.97)','none']rendertscale(0), also verschwindet das Element. Schreiben Sie'translateY(0px) scale(1)'. - Linien zeichnen: Niemals
pathLength="1"; GSAPs Offset-Tween ignoriert es und die Linie erscheint komplett. Messen SieL=p.getTotalLength(), setzen SiestrokeDasharray:${L} ${L+4}``,strokeDashoffset:L+4(die +4 versteckt den runden Endpunkt), tween Sie den Offset auf 0. Ein gestrichelter Pfad kann so nicht gezeichnet werden; blenden Sie ihn ein. - Fügen Sie gemessene SVG und Labels vor dem Start der Animation ein, und messen Sie, bevor eine Transformation angewendet wird.
- Skripte am Ende von
<body>; laden Sie das CDN-Tag vor dem Inline-Skript.
Niemals
- Verläufe, Leuchten, Neon, Glas sieht man durchschimmern, schwere oder farbige Schatten.
- Lila oder mehr als ein Akzent-Farbton. Farbige Hintergründe auf großen Flächen.
- Emoji- oder Icon-Font-Dekoration. Ein kleines Inline-SVG mit 1.5px Linie, wenn ein Icon wirklich nötig ist.
- Alles in Boxen. Zentrierte Hero-Karte. Eine Karte in einer Karte.
- Eine Leinwand, die auf volle Breite gestreckt ist, mit dem Inhalt, der in der Mitte schwebt.
- Pfeile, die sich kreuzen, Linien, die Karten berühren, Labels, die Linien überlappen.
- Bildunterschriften, die den Titel wiederholen. Mehr als ~12 Wörter Text innerhalb des Bildes.
- Text unter 16px oder eine andere Schriftart als die oben genannte in einem Erklärer.
Nach dem Rendern: Lesen Sie das PNG und prüfen Sie: Lücken, Überlappungen, abgeschnittener Text, leere rechte Seite.
Grundgerüst
<style>/* Farben von oben */ h2{font-size:26px;font-weight:700;letter-spacing:-.01em;margin:0 0 32px}
.row{position:relative;display:flex;gap:112px;width:max-content}
.node{padding:14px 18px;border:1px solid var(--line);border-radius:10px}
.node small{display:block;color:var(--muted);font-size:16px}
.hot{border-color:var(--accent)} svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}
path{fill:none;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}</style>
<h2>Ein Cache-Treffer überspringt die Datenbank</h2>
<div class="row" id="row">
<div class="node" id="a">Anfrage<small>GET /user/42</small></div>
<div class="node hot" id="b">Cache<small>Redis, 5 Min. TTL</small></div>
<svg id="s"></svg>
</div>
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<script>
const R=row.getBoundingClientRect();
function link(p,q,color){ const a=p.getBoundingClientRect(),b=q.getBoundingClientRect();
const y=a.top+a.height/2-R.top, x1=a.right-R.left+8, x2=b.left-R.left-8;
s.insertAdjacentHTML('beforeend',`<path class="l" stroke="${color}" d="M${x1} ${y}H${x2}"/><path class="h" opacity="0" stroke="${color}" d="M${x2-5} ${y-4}l5 4-5 4"/>`); }
link(a,b,'#3b7a57');
s.querySelectorAll('.l').forEach(p=>{const L=p.getTotalLength();gsap.set(p,{strokeDasharray:`${L} ${L+4}`,strokeDashoffset:L+4})});
gsap.timeline()
.from('h2',{opacity:0,y:8,duration:.6,ease:'power3.out'})
.from('.node',{opacity:0,y:12,scale:.97,duration:.8,ease:'expo.out',stagger:.12},.2)
.to('.l',{strokeDashoffset:0,duration:.7,ease:'power2.inOut'},1)
.to('.h',{opacity:1,duration:.2},1.6);
</script>
Aufruf mit width ≈ 560, rows: 10, duration_ms: 2200.