- Format-Schalter 4:5 / 1:1 in Post 9, Stand auf 1:1 (Meta nimmt ein Carousel mit Video als Anzeige nur quadratisch), Video in 1:1 - animation.mjs baut das Format der Seite, Datei mit -1x1 - Variante 2/3: Kundengeschichte nach dem weclapp-Karussell, hell und dunkel - Variante 4/5: Zahlen-Karussell nach HERO Insights mit KfW- und Bitkom-Zahlen, Noah als Teaser, hell und dunkel - post9-varianten.html zeigt alle Varianten untereinander - Menüs und Übersicht um die Varianten ergänzt, Regel zu 1:1 in rules.md Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1018 lines
46 KiB
Markdown
1018 lines
46 KiB
Markdown
# Post-Generator — Regeln & Konventionen
|
||
|
||
Arbeitsgrundlage für alles, was in diesem Repo an Social-Grafiken entsteht.
|
||
**Stand:** 20.10.2026
|
||
|
||
Der Generator läuft lokal: `./serve.sh` → <http://127.0.0.1:4321/post1.html>
|
||
(Post 1, Carousel).
|
||
|
||
---
|
||
|
||
## 0. PFLICHT: Oberflächen aus den Produkt-Komponenten, nie eigene Grafik
|
||
|
||
> **Diese Regel gilt für jeden Post und hat Vorrang vor allem anderen in dieser Datei.**
|
||
>
|
||
> Jede Zeichnung, die die Software zeigt, wird aus den echten Komponenten von
|
||
> `hivegrid-cloud` nachgebaut: mit deren Maßen, Farben, Schrift (Instrument Sans),
|
||
> Icons (Feather/Lucide) und Beschriftungen aus `translations.json`. Jede Zeichnung
|
||
> bildet einen Screen nach, den es im Produkt wirklich gibt.
|
||
>
|
||
> **Verboten:** ausgedachte Oberflächen, eigene Marken-Pillen, Häkchen-Chips,
|
||
> Reiter, Kacheln oder Effekte, die es im Produkt nicht gibt. Kurz: keine
|
||
> „vibecoded“ Grafiken.
|
||
>
|
||
> - Nachschlagewerk mit allen Tokens, Komponenten, Bausteinen und dem Vorgehen:
|
||
> **`komponenten.md`**. Vor jedem neuen Post lesen.
|
||
> - Referenz-Umsetzung: `post8.html` (seit 20.10.2026). Die Bausteine `pText`,
|
||
> `pBadge`, `pKnopf`, `pFeld`, `pTabs`, `pTabelle`, `pRahmen` und so weiter von
|
||
> dort übernehmen.
|
||
> - Code immer von `origin/main` lesen (`git show origin/main:<pfad>`), nicht aus
|
||
> der veralteten Arbeitskopie.
|
||
> - Was nicht auf die Slide passt, weglassen und im Kommentar vermerken. Nicht
|
||
> umbauen, nicht erfinden. Erfunden werden darf nur Demo-Inhalt.
|
||
>
|
||
> **Warum:** Post 8 sah mit eigenem Design „total vibecoded“ aus. Nachgebaut aus
|
||
> den Komponenten „sieht jetzt genau so aus wie die Software“ (Dejan, 20.10.2026).
|
||
|
||
---
|
||
|
||
## 1. Wo die Wahrheit steht
|
||
|
||
> **Neue Basis ab 12.10.2026: `hivegrid-cloud`.** Das ist die aktuelle Version
|
||
> der Software, mit den neuen Komponenten. Neue Posts bauen auf ihrem Look auf,
|
||
> nicht mehr auf der Website. Bei Zweifel dort nachschauen:
|
||
>
|
||
> | Was | Quelle in `hivegrid-cloud/` |
|
||
> |---|---|
|
||
> | Farben, Schriftgrößen, Radien, Schatten | `apps/web/app/ui/theme.css` |
|
||
> | Komponenten (109, aus Subframe) | `apps/web/app/ui/components/` |
|
||
> | Subframe-Projekt | `f023064a5ca0`, siehe `.subframe/sync.json` |
|
||
> | Schrift-Dateien | `apps/web/public/fonts/instrument-sans-*.woff2` |
|
||
> | Module und Screens | `apps/web/app/modules/` |
|
||
> | Logo | `apps/web/app/ui/components/Logo/` |
|
||
>
|
||
> Die wichtigsten Werte aus `theme.css`:
|
||
>
|
||
> | Was | Wert |
|
||
> |---|---|
|
||
> | Schrift | Instrument Sans: in allen Oberflächen-Zeichnungen (§0), die Slide-Überschriften bleiben Manrope |
|
||
> | Text, Knöpfe | `#24242B` (grey-900), Knöpfe sind fast schwarz |
|
||
> | Text zweitrangig | `#505057`, drittrangig `#6D6D71` |
|
||
> | Markengelb | `#F1B729` (brand-600), unverändert |
|
||
> | Zweitfarbe | Sky `#0791F0` (brand-secondary-500) |
|
||
> | Flächen | `#FAFAFA` und `#F5F4F4` |
|
||
> | Rahmen | `#E5E5E5` (neutral-200) |
|
||
> | Radien | 4, 8, 12 px |
|
||
> | Größen | Hero 72/72, Seite 48/56, Untertitel 32/40, alle 600 |
|
||
> | Symbole | Feather über `@subframe/core`: in Oberflächen als Lucide-Formen (§0), Phosphor nur noch in älteren Posts |
|
||
>
|
||
> Entschieden am 20.10.2026: Oberflächen nehmen Rahmen `#E5E5E5` und
|
||
> Feather/Lucide aus dem Produkt (§0). Offen bleiben die Schatten
|
||
> (`--shadow-md`, `--shadow-lg`) gegen §5 „Keine Schatten“: In den Zeichnungen
|
||
> lassen wir sie weg, siehe `komponenten.md`.
|
||
|
||
Alle Marken-Werte stammen aus der Website, nicht aus diesem Repo. Bei Zweifel
|
||
**dort nachschauen, nicht hier raten**:
|
||
|
||
| Was | Quelle |
|
||
|---|---|
|
||
| Farben, Radien, Abstände | `HiveGrid-Website/app/globals.css` (`:root`) |
|
||
| Chamfer-Form | `HiveGrid-Website/lib/chamfer.ts` |
|
||
| Schrift | `HiveGrid-Website/app/layout.tsx` |
|
||
| Badge-Styling | `HiveGrid-Website/components/sections/ScreenshotAccordion.tsx` |
|
||
| Feature-Texte | `HiveGrid-Website/app/playground/PlaygroundClient.tsx` → `solutionModules` |
|
||
| Wabenfeld: Geometrie, Zellnummern, Belegung | `HiveGrid-Website/components/landing/HoneycombSection.tsx` |
|
||
|
||
Die Werte sind hier gespiegelt, weil der Export keine Tailwind-/oklch-Farben
|
||
verträgt (siehe §7). Ändert sich die Website, müssen sie **hier nachgezogen
|
||
werden** — das passiert nicht automatisch.
|
||
|
||
---
|
||
|
||
## 1a. Themes
|
||
|
||
**Projekt und Theme sind zwei verschiedene Dinge.** Das Projekt sagt, *welcher*
|
||
Beitrag; das Theme sagt, *wie er aussieht*. Beide Themes teilen Aufbau, Texte
|
||
und Kompositionen der Visualisierungen.
|
||
|
||
> **Der Grund ist über alle Beiträge hinweg derselbe: `--surface` `#FFFFFF`.**
|
||
> Kein Theme darf ihn ändern. Im Profilraster stehen die Beiträge nebeneinander,
|
||
> und schon ein leicht getönter Grund fällt dort als Fehler auf. Ein Theme, das
|
||
> mehr Kontrast für seine Karten braucht, holt ihn sich über die Kante
|
||
> (`--border-neutral` `#D5DBE1`), nicht über den Grund.
|
||
|
||
| Theme | Erkennungszeichen | Datei |
|
||
|---|---|---|
|
||
| `notebook` | Rasterhintergrund, Zeichenblock-Optik, Marker in der Überschrift, handgezogene Formen | `post2.html` |
|
||
| `modern` | Ruhiger Grund ohne Textur, sehr große Überschrift mit einem farbigen Wort, schwebende Karten mit Haarlinie, präzise Formen | `post2-alt.html`, `post3.html` |
|
||
|
||
Jede Datei trägt oben eine Konstante `THEME`. Sie steuert nichts, sie benennt
|
||
nur — die Unterschiede stecken in den CSS-Werten und in den Zeichen-Primitiven.
|
||
|
||
**Was beide Themes trennt, ist genau festgelegt:**
|
||
|
||
| | notebook | modern |
|
||
|---|---|---|
|
||
| Grund | Raster, 78 px | weiß, keine Textur |
|
||
| Über alle Slides | — | ein durchgehendes Band |
|
||
| Akzent in der Überschrift | Marker-Fläche | farbiges Wort |
|
||
| Formen | `strich` schwingt, Ecken schießen über | exakt, keine Abweichung |
|
||
| Flächen | Schraffur | Füllung |
|
||
| Zweitfarbe | keine | Teal `#0D6E6E` |
|
||
| Zeichen | selbst gezeichnet | Feather aus `@subframe/core` |
|
||
|
||
**Karten stehen weiß auf weiß und tragen über Kante und Schatten.** Kante
|
||
`#D5DBE1`, nicht `#E5E5E5` — die hellere Linie verschwindet auf Weiß. Den
|
||
Grund zu tönen wäre der einfachere Weg und ist verboten, siehe oben. Der
|
||
Schatten ist die Ausnahme von §5, die dieses Theme sich nimmt; dort steht,
|
||
warum und mit welchen Werten.
|
||
|
||
**Das Band ist ein einziges Bild über den ganzen Streifen.** Jede Slide zeigt
|
||
ihren Ausschnitt, verschoben über `--slide-i`. An der Naht läuft es deshalb
|
||
weiter, statt neu anzusetzen — das ist der Übergang, den ein Carousel sonst
|
||
nicht hat, weil jede Slide für sich steht. Drei Punkte, die daran hängen:
|
||
|
||
- **Die Wellenlänge darf kein Vielfaches von 1080 sein.** Sonst liegt an jeder
|
||
Naht derselbe Punkt der Welle, meist ein Scheitel, und ein waagerechtes Band
|
||
an der Kante sagt dem Auge nichts.
|
||
- **Flach und deckend, wie die Formen bei Slack.** Eine kräftige Fläche,
|
||
dahinter eine blasse zweite als Tiefe. Kein Verlauf, keine Wölbung, keine
|
||
Glanzkante. Ein Zwischenstand hatte hier einen metallisch schattierten
|
||
Zylinder aus vierzig Streifen — fotorealistisch und damit ein Fremdkörper
|
||
auf einem flachen, ruhigen Blatt. Er steht im Commit *„Theme modern:
|
||
Metallband, Icon-Satz und echte G-Marke"*, falls ihn jemand wiederhaben will.
|
||
- **Zwei Töne, umschaltbar** unter *Band* im Bedienfeld (`BAND_TOENE`):
|
||
|
||
| Ton | Fläche | Blasse zweite |
|
||
|---|---|---|
|
||
| `orange` | `--accent-yellow` `#F1B729` | `#FCE7B4` |
|
||
| `teal` | `--dark-bg` `#30313D` | `#D3D5DE` |
|
||
|
||
> **„HiveGrid Teal" ist `--dark-bg` `#30313D`, nicht `--accent-teal`.** Im
|
||
> Haus heißt der dunkle Ton des Story-Abschnitts auf der Website so. Er ist
|
||
> ein blaustichiges Anthrazit; `--accent-teal` `#0D6E6E` ist ein anderes
|
||
> Grün und trägt im Theme `modern` die Zeichenkacheln. Beim Reden über
|
||
> Farben lohnt die Nachfrage, welches gemeint ist.
|
||
- **Der Umschalter greift an `.bg`, nicht über `applyAll`.** Das Band setzt
|
||
`buildSlide`, nicht der Neuaufbau der Seiten; der Handler tauscht deshalb
|
||
`.bg .layer-band` direkt aus.
|
||
- **Die Bahn steht als Stützpunkte da, nicht als Formel** (`BAND_BAHN`, einer
|
||
je halbe Slide). Eine Sinuskurve war kürzer, ließ sich aber nicht steuern:
|
||
auf Slide 5 lief sie quer durch die Kacheln, und dort verschwinden die
|
||
orangenen Zeichen. Jeder Wert ist am gerenderten Bild geprüft, nicht
|
||
gerechnet. Wer `BAND_VON` oder `BAND_BIS` ändert, muss die Liste mitziehen:
|
||
die Punkte verteilen sich gleichmäßig über die neue Strecke, und alle
|
||
Werte verschieben sich.
|
||
- **Wo das Band läuft, steht in `BAND_VON` / `BAND_BIS`** — Anzahl Slides ohne
|
||
Band am Anfang und am Ende. Bei Post 2 sind das 2 und 1, das Band trägt also
|
||
Slide 3 bis 5. Slide 1 und die letzte sollen ruhig sein, Slide 2 trägt ihre
|
||
eigene orangene Leitung und würde sich damit beißen.
|
||
- **Als gefüllte Fläche gezeichnet, nicht als dicke Linie.** Eine Linie bekommt
|
||
am Ende eine Kappe rechtwinklig zur Laufrichtung; bei schräger Bahn steht die
|
||
seitlich über die Naht hinaus, und auf der Nachbarslide bleibt ein Splitter
|
||
stehen. Eine geschlossene Kontur endet exakt senkrecht — dann genügen zwei
|
||
Pixel Abstand zur Naht.
|
||
|
||
**Was über dem Band liegt, ist eine Karte mit Schatten.** Nicht randlos: der
|
||
Versuch mit reinen Konturen sah gestochen aus. Kleine Karten über einem Band
|
||
sind die Vorlage; eine randlose Karte über die ganze Fläche war der Fehler
|
||
davor, weil das Band dahinter komplett verschwand.
|
||
|
||
> **Eine Ausnahme bleibt: Slide 3.** Deren Karte *ist* die Visualisierung, ein
|
||
> Oberflächen-Panel mit Fortschrittsbalken und Statuszeilen. Die kann nicht
|
||
> randlos sein, also zeigt sich das Band dort nur in den Seitenrändern.
|
||
|
||
**Die Zeichen kommen aus dem Icon-Satz der Software, nicht aus der Hand.**
|
||
`@subframe/core`, dieselben Feather-Icons, mit denen die Oberfläche gebaut ist
|
||
(im Pitch-Paket unter `node_modules/@subframe/core/…/icons/final/`). Sie liegen
|
||
als `FEATHER`-Tabelle in der Datei, gezogen mit dem Skript aus dem
|
||
Gesprächsverlauf. Zwei Fallen beim Ziehen: die Props stehen teils als Zahl ohne
|
||
Anführungszeichen (`x: 3`), und bei Icons mit nur einem Kind muss der
|
||
`<svg>`-Rahmen weg, sonst rendert nichts.
|
||
|
||
Ausnahme ist Slide 6 in `post2-alt.html`: deren drei Zeichen sind Nachbauten
|
||
aus `WaitlistNext.tsx` und bleiben es.
|
||
|
||
**Teal ist die Zweitfarbe des Themes `modern`.** In `notebook` kommt sie nicht
|
||
vor. Damit hat der bisher ungenutzte Marken-Ton eine Aufgabe: er trägt
|
||
Zeichen und Kacheln, während Orange den einen Akzent setzt.
|
||
|
||
**Eine Änderung an einer Visualisierung betrifft beide Dateien.** Das ist der
|
||
Preis dafür, dass die Themes echte Alternativen sind und nicht Varianten
|
||
derselben Datei.
|
||
|
||
---
|
||
|
||
## 2. Farben
|
||
|
||
### Marke
|
||
|
||
| Token | Wert | Verwendung |
|
||
|---|---|---|
|
||
| `--accent-yellow` | `#F1B729` | Primärer Akzent, Marker, G-Marke |
|
||
| `--accent-yellow-light` | `#FFE082` | Badge-Hintergrund |
|
||
| `--accent-yellow-dark` | `#3E2C0A` | Badge-Text |
|
||
| `--accent-teal` | `#0D6E6E` | Sekundär; im Theme `modern` die Zeichenkacheln |
|
||
| `--dark-bg` | `#30313D` | Dunkle Variante + Formen-Ebene |
|
||
|
||
### Flächen & Text
|
||
|
||
| Token | Wert |
|
||
|---|---|
|
||
| `--surface` | `#FFFFFF` |
|
||
| `--bg-primary` | `#FAFAFA` |
|
||
| `--text-primary` | `#1A1A1A` |
|
||
| `--text-secondary` | `#555555` |
|
||
| `--text-on-dark` | `#C8D0DC` |
|
||
|
||
### Pro Variante
|
||
|
||
**Hell** (Grund `#FFFFFF`)
|
||
|
||
```
|
||
--head-color #1A1A1A --frame-bg #E8EBF0
|
||
--body-color #555555 --shape-fill #30313D (deckend!)
|
||
--accent-on #C8901A --shape-line rgba(48,49,61,.13)
|
||
--marker #F1B729 --shape-accent #F1B729
|
||
--pat-color #30313D --gmark-color rgba(48,49,61,.16)
|
||
```
|
||
|
||
**Dunkel** (Grund `#30313D`)
|
||
|
||
```
|
||
--head-color #FFFFFF --frame-bg rgba(255,255,255,.11)
|
||
--body-color #B9BDC9 --shape-fill #414252
|
||
--accent-on #F1B729 --shape-line rgba(255,255,255,.10)
|
||
--marker #F1B729 --shape-accent #F1B729
|
||
--pat-color #FFFFFF --gmark-color rgba(255,255,255,.13)
|
||
```
|
||
|
||
> **`--accent-on` ist nicht `#F1B729`, sondern `#C8901A` auf Hell.** Reines
|
||
> Markengelb trägt auf Weiß bei kleinem Text nicht. Der abgedunkelte Ton ist
|
||
> Absicht, kein Fehler.
|
||
|
||
> **Der Block oben rechts ist auf Hell deckendes `#30313D`.** Das ist derselbe
|
||
> Wert wie die dunkle Story-Sektion der Website. Er darf **nicht** über die
|
||
> Intensitätsstufe aufgehellt werden, sonst ist es nicht mehr die Markenfarbe
|
||
> (siehe §5).
|
||
|
||
---
|
||
|
||
## 3. Geometrie
|
||
|
||
### Chamfer
|
||
|
||
Die Marken-Form: abgeschnittene Ecken, Echo der Honigwabe. Auf der Website
|
||
16 px, hier hochskaliert, weil die Fläche viermal so groß ist.
|
||
|
||
```css
|
||
clip-path: polygon(
|
||
Xpx 0, calc(100% - Xpx) 0, 100% Xpx, 100% calc(100% - Xpx),
|
||
calc(100% - Xpx) 100%, Xpx 100%, 0 calc(100% - Xpx), 0 Xpx
|
||
);
|
||
```
|
||
|
||
| Element | X |
|
||
|---|---|
|
||
| Badge | 11 px |
|
||
| Mockup-Rahmen außen | 22 px |
|
||
| Mockup-Rahmen innen | 14 px |
|
||
|
||
**Drei Fallstricke** (stehen auch in `chamfer.ts`):
|
||
|
||
- `clip-path` beschneidet **auch den Schatten** → wäre über `filter: drop-shadow()` zu lösen, nie über `box-shadow`. Praktisch irrelevant, seit die Posts schattenfrei sind (siehe §5)
|
||
- `clip-path` beschneidet **auch den Rahmen** → Rand über eine Hintergrundfläche lösen, nicht über `border`
|
||
- Kinder, die aus der Box ragen (Popups, Tooltips), werden abgeschnitten → solche Elemente nicht chamfern
|
||
|
||
### Hexagon
|
||
|
||
Flat-Top, also flache Ober- und Unterkante mit Spitzen links und rechts:
|
||
|
||
```css
|
||
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
|
||
```
|
||
|
||
**Seitenverhältnis 384,5 : 332,9 = 1,155 (= 2/√3).** Aus der Logomarke
|
||
abgeleitet, nicht geschätzt. Wer davon abweicht, staucht das Sechseck.
|
||
|
||
### G-Marke
|
||
|
||
Ein einziges Polygon, aus `logo.svg` extrahiert (dort die einzige Fläche mit
|
||
`class="st3"`), auf 0,0 normalisiert:
|
||
|
||
```
|
||
384.5,166.5 336.4,83.2 192.2,166.5 288.3,166.5 240.3,249.7 144.2,249.7
|
||
96.1,166.5 144.2,83.2 336.4,83.2 298,16.8 288.3,0 96.1,0 0,166.5
|
||
96.1,332.9 288.3,332.9
|
||
```
|
||
|
||
Immer **inline** als SVG einsetzen, nie als `<img>` — nur so lässt sie sich per
|
||
`currentColor` pro Variante umfärben, und nur so überlebt sie den Export
|
||
zuverlässig (§7).
|
||
|
||
### Wabenraster
|
||
|
||
Nahtlose Kachel aus derselben G-Form. Bei Flat-Top-Hexagonen springen die
|
||
Spalten um ¾ der Breite, benachbarte Spalten sitzen eine halbe Reihe versetzt.
|
||
Eine Kachel = zwei Spalten breit, eine Reihe hoch. **Die über den Kachelrand
|
||
hinausragenden Kopien sind Pflicht** — ohne sie sieht man die Nähte.
|
||
|
||
### Wabenfeld
|
||
|
||
Nicht zu verwechseln mit dem Wabenraster: das ist die Textur im Hintergrund,
|
||
das Wabenfeld ist die **Grafik von der Startseite** — die Marke als Nabe, die
|
||
fremden Programme als Waben darum, verbunden über Stränge. Sie steht in
|
||
`post3.html` als `visWabenfeld()` und ist aus `HoneycombSection.tsx`
|
||
übernommen, nicht nachempfunden:
|
||
|
||
| | Wert | Warum genau der |
|
||
|---|---|---|
|
||
| Umkreisradius | 62 | alles andere leitet sich davon ab |
|
||
| Flächenfaktor | 0,84 | lässt die Fuge zwischen zwei Waben offen |
|
||
| Achsrechnung | `x = 1,5·R·q`, `y = √3·R·(r + q/2)` | flat-top |
|
||
| Zellnummern | 1 Nabe, 2–7 innerer Ring, 8–19 Deko-Ring, dann das Feld | die Belegung hängt daran |
|
||
| Marken-Größe | sichtbar 56,5 px | Claude und OpenAI sind die Eichmarken |
|
||
|
||
**Die Zellnummern sind die Schnittstelle.** Welche Marke wo sitzt, steht auf
|
||
der Website in `HONEYCOMB_DEFAULT_LOGO_PLACEMENTS` und hier in `WABE_MARKEN`,
|
||
beide über dieselben Nummern. Wer den Ringgang in `wabeRing()` umstellt,
|
||
verschiebt damit stillschweigend jede Marke.
|
||
|
||
**Eine Marke wird über zwei Zahlen eingepasst**, nicht über eine: die
|
||
Grundfläche ihres Renderpfads (Feld 59,52 px, Deko 50,44 px) mal ihre eigene
|
||
Skalierung. Das Verhältnis 1,18 zwischen beiden Pfaden stammt aus der Website
|
||
und ist der Grund, warum dieselbe sichtbare Größe zwei verschiedene Faktoren
|
||
braucht. Für eine neue Marke gilt: `gr = Grundfläche / Anteil, den die Datei in
|
||
ihrer eigenen Fläche ausfüllt` — eine Datei mit Luft ringsum braucht mehr.
|
||
|
||
**Die vier Waben „In Arbeit" gehören nicht in einen Beitrag.** Auf der
|
||
Startseite sind sie ein Versprechen, im Feed wären sie ein Eingeständnis. Es
|
||
sind die Zellen 29, 41, 46 und 59, und sie bleiben gewöhnliche leere Waben.
|
||
|
||
---
|
||
|
||
## 4. Typografie
|
||
|
||
### Die Schrift
|
||
|
||
**Manrope** — dieselbe wie auf der Website. Bewusst nicht Inter: Der Kommentar in
|
||
`app/layout.tsx` nennt Inter „the default AI/website-builder font"; Manrope ist
|
||
eine Spur wärmer und geometrischer.
|
||
|
||
```css
|
||
font-family: 'Manrope', system-ui, sans-serif;
|
||
```
|
||
|
||
| | |
|
||
|---|---|
|
||
| Schnitte | 400, 500, 600, 700, 800 |
|
||
| Ablage | `assets/fonts/manrope.css` + 6 `.woff2` |
|
||
| Subsets | latin, latin-ext, cyrillic, cyrillic-ext, greek, vietnamese |
|
||
| Herkunft | Google Fonts, einmalig heruntergeladen |
|
||
|
||
**Warum lokal und nicht per CDN:** Der Export darf zur Laufzeit an keiner
|
||
fremden Origin hängen. Ein Font von `fonts.gstatic.com` wäre cross-origin, würde
|
||
den Export vom Netz abhängig machen und im Zweifel den Canvas verunreinigen. Die
|
||
URLs in `manrope.css` sind deshalb auf relative Pfade umgeschrieben.
|
||
|
||
> **Vor jedem Export auf `document.fonts.ready` warten.** Sonst rendert der erste
|
||
> Durchgang still in einer System-Fallback-Schrift, und man sieht es erst in der
|
||
> fertigen Datei. Der Download-Button bleibt bis dahin deaktiviert.
|
||
|
||
### Größen
|
||
|
||
| Element | Größe | Weight | Sonstiges |
|
||
|---|---|---|---|
|
||
| Headline | 74 px | 800 | `line-height: 1.06`, `letter-spacing: -0.03em` |
|
||
| Titel-Tagline | 74 px | 800 | identisch zur Headline, bewusst |
|
||
| Fließtext | 30 px | 500 | `line-height: 1.48`, `max-width: 830px` |
|
||
| Badge | 26 px | 600 | `letter-spacing: 0.1em` |
|
||
| Claim (Titel) | 31 px | 500 | `line-height: 1.44` |
|
||
|
||
### Akzent-Wörter
|
||
|
||
Zwei Behandlungen, **nie beide im selben Textblock**:
|
||
|
||
- **Marker** — handgezeichneter SVG-Strich als `background-image`, wellige
|
||
Kanten, angeschrägtes linkes Ende. Für Headlines und Payoffs.
|
||
Ein CSS-Verlauf ist hier **verboten**: er kann geometrisch nur ein perfektes
|
||
Rechteck erzeugen und sieht gedruckt statt gemalt aus.
|
||
- **Farbe** (`--accent-on`) — für kleinere Zeilen.
|
||
|
||
Pro Slide höchstens **ein** markiertes Wort. Zwei Marker in einem Block waren
|
||
schon zweimal zu viel.
|
||
|
||
### Sprache
|
||
|
||
- Alles Sichtbare auf **Deutsch**, Code-Kommentare auf **Englisch**
|
||
- **Keine Em-Dashes (—)** in sichtbaren Texten. Komma, Punkt oder `·`
|
||
- Feature-Texte wortgleich von der Website. Headlines dürfen gekürzt und
|
||
umgestellt werden, aber **keine neuen Wörter erfinden**
|
||
- **Umlaute sind Umlaute.** Kein `Sanitaer`, kein `Aufmass`. Das gilt auch
|
||
dann, wenn die Quelle es so schreibt: eine ASCII-Ersatzschreibung im
|
||
Klickdummy ist ein Tippfehler und kein Produktbegriff, den man zitiert
|
||
|
||
#### Der Export prüft das
|
||
|
||
Aufgefallen ist es an „Sanitaer“ und „Klima / Lueftung“ auf einer fertigen
|
||
Slide — beim Bauen hatte es niemand gesehen, weil beides wörtlich aus der
|
||
Quelle kam. Seitdem prüft es die Maschine, an zwei Stellen:
|
||
|
||
| Weg | Wo |
|
||
|---|---|
|
||
| Bildexport | `renderSlide()` bricht ab, bevor ein PNG entsteht |
|
||
| Bewegtbild | `animation.mjs` prüft an 24 Stellen über den Ablauf |
|
||
|
||
Geprüft wird beides: fehlende Umlaute **und** Em-Dashes. Die Em-Dash-Regel
|
||
stand von Anfang an hier und wurde in Post 8 trotzdem gebrochen — eine Regel,
|
||
die nur im Dokument steht, hält nur so lange, wie jemand daran denkt.
|
||
|
||
**Bei den Umlauten genügt ein reines Muster nicht**, aber fast. Ein
|
||
ersatzweise geschriebenes `ae`/`oe`/`ue` steht nie hinter einem Vokal: „für“,
|
||
„über“, „grün“, „Sanitär“, „Lüftung“ folgen alle auf einen Konsonanten oder
|
||
stehen am Wortanfang. Hinter einem Vokal sind es dagegen immer zwei eigene
|
||
Laute — bauen, Bauer, neue, Dauer, Steuer, Mauer, genauer. Das Muster prüft
|
||
deshalb `(^|[^aeiou])(ae|oe|ue)`, und die Ausnahmeliste `UMLAUT_ERLAUBT` ist
|
||
auf die echten Sonderfälle geschrumpft: „Michael“ (ae hinter h), „aktuell“
|
||
(ue hinter t). Vorher standen dort sechs Wörter nach demselben Muster, und
|
||
die Liste wäre mit jedem Beitrag weitergewachsen.
|
||
|
||
Bei `ss` ist es umgekehrt: fast immer richtig (dass, Schluss, Netzanschluss),
|
||
nur wenige Wörter brauchen ein scharfes s. Die stehen einzeln in
|
||
`BRAUCHT_SCHARFES_S`.
|
||
|
||
**Mailadressen und Domains sind ausgenommen.** In `buero@muster-shk.de` kann
|
||
gar kein ü stehen; die umlautfreie Schreibung ist dort keine Schludrigkeit,
|
||
sondern Pflicht.
|
||
|
||
**Ein Zeilenumbruch ist eine Wortgrenze.** Die Prüfung liest nicht
|
||
`textContent`, sondern die einzelnen Textknoten mit Leerzeichen dazwischen.
|
||
Sonst klebt eine Überschrift wie „Formulare selbst bauen.`<br>`Per
|
||
Drag-and-drop.“ zu `bauen.Per` zusammen — ein Wort, das es nicht gibt, das
|
||
aber `ue` enthält und die Prüfung auslöst.
|
||
|
||
Schlägt die Prüfung bei einem Wort an, das so richtig ist, gehört es in die
|
||
Ausnahmeliste — nicht die Prüfung abgeschaltet.
|
||
|
||
> **Beim Bewegtbild reicht ein Blick auf das erste Bild nicht.** Der erste
|
||
> Anlauf prüfte nur den Anfangszustand und fand nichts: die fehlerhaften
|
||
> Wörter erscheinen dort erst ab `t = 0,4`. Der Text einer Animation ändert
|
||
> sich mit der Zeit, die Prüfung muss es auch.
|
||
|
||
---
|
||
|
||
## 5. Aufbau der Slides
|
||
|
||
### Formate
|
||
|
||
**Die Anzahl der Slides ist frei.** Sie kommt aus dem Deck im Projektstand,
|
||
nicht aus dem Code. Das durchgehende Blatt (§5, „Durchgehend") ist deshalb
|
||
`--sheet-w` breit statt fester 5400 px; die vier Sechsecke auf den Nähten
|
||
sitzen weiterhin auf 1080 / 2160 / 3240 / 4320 und decken damit die ersten
|
||
fünf Slides ab. Wer länger baut, bekommt dahinter nur noch das Band.
|
||
|
||
**1080 × 1350 px (4:5)** — das hochkantigste offiziell unterstützte
|
||
Feed-Format. Belegt: Instagram unterstützt 1.91:1 bis 4:5, Breiten 320–1080
|
||
behalten die Originalauflösung, alles außerhalb wird beschnitten.
|
||
|
||
- **Alle Slides eines Carousels müssen dasselbe Seitenverhältnis haben** — die
|
||
erste Slide setzt es, der Rest wird hart darauf beschnitten
|
||
- Safe Zone: nichts Lesbares näher als ~92 px an den Rand
|
||
- Der **Profil-Grid** zeigt seit 2025 Hochformat und beschneidet auf 3:4 —
|
||
im Grid fehlen links und rechts je ~33 px. Für die drei angepinnten Cover
|
||
relevant, im Feed nicht
|
||
|
||
**1080 × 1080 px (1:1) für Anzeigen mit Video im Carousel.** Ein normaler
|
||
Beitrag darf ein Video im 4:5-Carousel haben. Als Anzeige nicht: Meta schreibt
|
||
im Ads Guide für den Instagram-Feed „Derzeit wird bei Carousels, die Videos
|
||
enthalten, nur das Format 1:1 unterstützt.“ Reine Bild-Carousels dürfen 4:5
|
||
bleiben. In Reels laufen nur Bild-Carousels (9:16). Abgerufen 02.11.2026.
|
||
Post 9 hat dafür den Schalter „Format“ (4:5 · Beitrag, 1:1 · Anzeige), die
|
||
Karten tragen ihre 1:1-Maße unter `quadrat`, und
|
||
`node animation.mjs post9.html animWebinar 1x1` baut das Video in 1:1
|
||
(Datei mit `-1x1` im Namen).
|
||
|
||
### Layout-Template: Tag, Überschrift, Beschreibung, Bild
|
||
|
||
Jede Feature-Slide besteht aus denselben vier Bausteinen in derselben Reihenfolge.
|
||
Das ist keine Geschmacksfrage — Wiedererkennung über fünf Slides entsteht genau
|
||
dadurch, dass sich das Gerüst nicht bewegt.
|
||
|
||
```
|
||
92 ┌──────────────────────────────────────┐
|
||
│ ▌ TAG │ ← ausgerückt (siehe unten)
|
||
│ │
|
||
│ Überschrift, zwei Zeilen │ ← dominiert die Slide
|
||
│ mit einem markierten Wort │
|
||
│ │
|
||
│ Beschreibung, zwei bis drei │ ← kürzer als man denkt
|
||
│ Zeilen Fließtext │
|
||
│ │
|
||
│ ┌────────────────────────────────── │
|
||
│ │ Bild │ ← ein dominanter Blickfang
|
||
│ │ │
|
||
└──┴───────────────────────────────────┘
|
||
```
|
||
|
||
**Größenverhältnisse.** Die Hierarchie muss ohne Nachdenken lesbar sein — was
|
||
zuerst, was zweitens, was drittens. Bei uns: Überschrift 74 px, Fließtext 30 px,
|
||
Tag 26 px. Das Verhältnis Überschrift zu Fließtext liegt bei rund **2,5 : 1**;
|
||
gängige Empfehlungen für Carousels nennen Überschrift 48–72 pt gegen Fließtext
|
||
24–36 pt, also dieselbe Größenordnung. Weniger Abstand als Faktor 2 und die
|
||
Hierarchie kippt.
|
||
|
||
**Ein Blickfang pro Slide.** Genau ein Element, auf das das Auge sofort springt —
|
||
bei uns die Überschrift, danach das Bild. Zwei gleich starke Elemente lassen den
|
||
Betrachter raten und wegwischen.
|
||
|
||
**Weißraum ist kein verschenkter Platz**, sondern das, was die Blöcke trennt.
|
||
Der Abstand zwischen Tag und Überschrift ist deshalb größer als der zwischen
|
||
Überschrift und Beschreibung: getrennte Gedanken brauchen mehr Luft als
|
||
zusammengehörige.
|
||
|
||
#### Das Tag wird ausgerückt
|
||
|
||
Der Tag hat 20 px Innenabstand. Steht sein **Kasten** bündig zur Inhaltskante,
|
||
beginnt sein **Text** 20 px weiter rechts als die Überschrift — und genau das
|
||
sieht man:
|
||
|
||
```
|
||
vorher nachher
|
||
92 │▌ WORKFLOW-ENGINE 72 │▌ WORKFLOW-ENGINE
|
||
92 │Wenn-dann 92 │Wenn-dann
|
||
└ Kasten bündig, └ Text bündig,
|
||
Text 20 px versetzt Kasten ragt aus
|
||
```
|
||
|
||
Gelöst über `margin-left: -20px`, also genau den Innenabstand. Das ist dasselbe
|
||
Prinzip wie hängende Interpunktion im Satz: Ausgerichtet wird nach der
|
||
**wahrgenommenen** Kante, nicht nach der geometrischen. Der Kasten steht dann bei
|
||
x 72 — immer noch weit innerhalb der Safe Zone.
|
||
|
||
> Wer die Innenabstände des Tags ändert, muss das negative Margin mitziehen.
|
||
> Die beiden Werte gehören zusammen.
|
||
|
||
### Ebenen (von hinten nach vorn)
|
||
|
||
| z-index | Ebene |
|
||
|---|---|
|
||
| 0 | Hintergrund: Formen, Wabenraster, Punkte, Raster, G-Marke |
|
||
| 1 | Mockup |
|
||
| 2 | Inhalt: Badge, Headline, Fließtext |
|
||
| 3 | Swipe-Hinweis |
|
||
|
||
### Keine Schatten
|
||
|
||
**Nichts wirft einen Schatten** — weder Bilder noch Mockups, Logos, Zettel oder
|
||
Text. Kein `box-shadow`, kein `filter: drop-shadow()`, kein `text-shadow`.
|
||
|
||
Tiefe entsteht stattdessen über:
|
||
|
||
- **Überlappung** — ein zweites Bild vor dem ersten liest sich auch ohne Schlagschatten als davor
|
||
- **Rahmenfarbe** — der chamferte Rahmen (`--frame-bg`) setzt das Bild von der Fläche ab
|
||
- **Versatz und Drehung** — beim Stapel und beim Notizzettel
|
||
|
||
**Ausnahme, und nur diese:** Die Blätter im Stapel-Rahmen bekommen eine
|
||
Haarlinie (`1px rgba(48,49,61,.18)`). Ohne sie läge auf der hellen Variante
|
||
Weiß auf Weiß und der Stapel wäre nicht mehr zu erkennen. Eine Linie ist kein
|
||
Schatten.
|
||
|
||
Warum: Weichgezeichnete Schatten sind das erste, was einen Post nach Vorlage
|
||
aussehen lässt. Flächig gesetzt wirkt es ruhiger und passt zur Website, die
|
||
denselben Weg geht. Nebenbei ist es beim Export billiger — jeder unscharfe
|
||
Filter muss beim 3-fachen Rendern dreifach berechnet werden.
|
||
|
||
> **Für das Theme `modern` ist diese Regel aufgehoben.** Dort schweben Karten
|
||
> über einem durchlaufenden Band, und ohne Schatten stehen sie gestochen auf
|
||
> dem Grund statt darüber. Der Schatten ist ein SVG-`feDropShadow`
|
||
> (`dy 7`, `stdDeviation 10`, `#101828` bei 14 %), kein `box-shadow`: die
|
||
> Karten sind gezeichnet und nicht im DOM, und ein CSS-Schatten würde vom
|
||
> `clip-path` ohnehin mit beschnitten. Der Kostenpunkt aus dem Absatz oben
|
||
> bleibt — **nach jeder Änderung daran einmal 3-fach exportieren und
|
||
> hinsehen.** Im Theme `notebook` gilt die Regel unverändert.
|
||
>
|
||
> **`.vis svg` braucht dafür `overflow: visible`.** Ein SVG beschneidet an
|
||
> seiner viewBox, und die Karten füllen ihre Zeichnung randlos aus. Ohne die
|
||
> Regel wird der Schatten genau an der Kartenkante gekappt: außen ein grauer
|
||
> Streifen mit hartem Schnitt, in den Chamfer-Kerben graue Keile. Am Layout
|
||
> ändert sich nichts, gezeichnet wird weiterhin nur, was in der viewBox steht.
|
||
|
||
### Was global gilt und was pro Slide
|
||
|
||
| Einstellung | Geltung | Warum |
|
||
|---|---|---|
|
||
| Variante (Hell/Dunkel) | **global** | gemischt fällt das Carousel auseinander |
|
||
| Mockup-Perspektive | **global** | dito |
|
||
| Renderqualität | **global** | Exportstufe, kein Gestaltungsmittel (§7) |
|
||
| Layout | pro Slide | jede Slide hat einen eigenen Katalog |
|
||
| Muster | pro Slide | |
|
||
| Intensität | pro Slide | |
|
||
|
||
### Durchgehendes Carousel (Panorama)
|
||
|
||
Der Post ist dann kein Stapel von fünf Bildern mehr, sondern **ein einziges
|
||
Blatt von 5400 × 1350 px**, das in fünf 1080er Fenster geschnitten wird. Wer
|
||
swipet, sieht Elemente über die Kante hinauslaufen und auf der nächsten Slide
|
||
weitergehen.
|
||
|
||
```
|
||
Slide 1 │ Slide 2 │ Slide 3 │ Slide 4 │ Slide 5
|
||
0 ─────── 1080 ────── 2160 ────── 3240 ────── 4320 ────── 5400
|
||
↑ ↑ ↑ ↑
|
||
die vier Nähte
|
||
```
|
||
|
||
**Technisch:** Eine Ebene von 5400 px Breite liegt in jeder Slide, verschoben um
|
||
`index × -1080 px`. Jede Slide zeigt dadurch ihr Fenster desselben Blatts. Kein
|
||
Zuschneiden von Hand, kein Splitter-Tool — die Slides *sind* die Schnitte.
|
||
|
||
**Regeln, die dabei zählen:**
|
||
|
||
1. **Slide 1 muss allein funktionieren.** Sie ist das Cover im Profil-Grid. Sie
|
||
darf nicht wie „das linke Fünftel von etwas" wirken
|
||
2. **Die Naht ist nie unsichtbar.** Instagram zeigt beim Swipen ein paar Pixel
|
||
Abstand zwischen den Slides. Das lässt sich nicht abstellen und ist auch nicht
|
||
schlimm — der Effekt trägt trotzdem
|
||
3. **Nichts Wichtiges auf die Naht legen.** Keine Schrift, kein Gesicht, kein
|
||
Logo. Der Schnitt gehört in ruhige Zonen
|
||
4. **Aber etwas Auffälliges über die Naht laufen lassen.** Eine halbierte Form
|
||
zieht das Auge weiter und lenkt vom Spalt ab. Genau dafür sitzen unsere
|
||
Sechsecke **mittig auf 1080 / 2160 / 3240 / 4320** — je zur Hälfte links und
|
||
rechts der Naht
|
||
5. **Nur Standbilder.** Ein Video mitten im Carousel bricht den Fluss
|
||
|
||
**Was bei uns auf dem Blatt liegt:** vier Sechsecke auf den vier Nähten
|
||
(abwechselnd massiv und Kontur, unterschiedliche Höhen), die G-Marke auf der Naht
|
||
2|3, und ein durchgehendes Band über die volle Breite als Klammer.
|
||
|
||
Das ist ein **globaler** Schalter, kein Muster pro Slide — er wirkt naturgemäß
|
||
auf alle fünf. Die Muster pro Slide bleiben zusätzlich verfügbar; für den reinen
|
||
Panorama-Look überall auf „Aus" stellen.
|
||
|
||
> Gängige Empfehlung sind 2–4 Slides fürs Panorama. Wir haben fünf. Das
|
||
> funktioniert, verlangt aber eine ruhigere Gestaltung — bei fünf Fenstern wird
|
||
> jede zusätzliche Form schnell unruhig.
|
||
|
||
### Intensität
|
||
|
||
Die Stufe „Dezent" (Faktor 0,55) wirkt **nur auf Nebenelemente** — Hexagon-Kontur,
|
||
Waben, Punkt- und Linienraster, G-Marke. Der große Block oben rechts bleibt
|
||
immer deckend, sonst wäre er nicht mehr exakt `#30313D`.
|
||
|
||
---
|
||
|
||
## 6. Mockups
|
||
|
||
Kein fotorealistischer Laptop-Rahmen für die Feature-Slides: Die Website rahmt
|
||
jeden Screenshot mit dem Chamfer (`.screenshot-frame` in `globals.css`). Das ist
|
||
die markeneigene Mockup-Form und verbindet die Posts mit der Seite.
|
||
|
||
Der Laptop auf der Titel-Slide ist die Ausnahme und bewusst ein Blickfang.
|
||
|
||
### Geometrie pro Slide
|
||
|
||
Breite, Seitenverhältnis und Position sind CSS-Variablen mit Defaults:
|
||
|
||
```
|
||
--mock-w 1090px (ragt ~100 px über die rechte Kante)
|
||
--mock-ar 1.6 (16:10)
|
||
--mock-left 92px
|
||
```
|
||
|
||
**Regel: Das Format des Rahmens folgt dem Inhalt des Screenshots, nicht
|
||
umgekehrt.** Der Form Builder ist eine schmale, zentrierte Spalte mit breiten
|
||
leeren Rändern — in einem 16:10-Rahmen zeigt er vor allem Leere. Er bekommt
|
||
deshalb 540 × 720 (3:4), mittig gesetzt, und einen Zoom von 3,8 direkt auf die
|
||
Spalte.
|
||
|
||
### Screenshots zuschneiden
|
||
|
||
Quellen sind ~2000 px breit und im Vollbild unlesbar. Jeder Screen wird auf
|
||
seinen aussagekräftigen Bereich gezoomt. Die Werte sind gerechnet, nicht geraten:
|
||
|
||
```
|
||
Rahmenbreite = 1 Einheit, Bild bei scale 1 = 1 Einheit breit
|
||
Bildhöhe = 1/BildAR Rahmenhöhe = 1/RahmenAR
|
||
|
||
Punkt fx horizontal mittig: tx = 0.5/s − fx
|
||
Punkt fy vertikal mittig: ty = (RahmenH/2)/(s · BildH) − fy
|
||
```
|
||
|
||
Mindest-Zoom zum Füllen: `RahmenH / BildH`. Darunter entstehen Lücken.
|
||
|
||
**Ausnahme Titel-Slide:** Der Laptop zeigt den Screen **1:1**, ohne Zoom. Dort
|
||
soll ein Bildschirm zeigen, was ein Bildschirm zeigt. Die Displayfläche misst
|
||
739 × 489 (AR 1,511), `workflow.png` hat 1,504 — passt praktisch deckungsgleich.
|
||
|
||
### Neue Geräte-Mockups einbauen
|
||
|
||
1. Braucht **echte Transparenz**. Ein Screenshot eines transparenten PNGs ist
|
||
keine Transparenz — das Karomuster steckt dann als Pixel drin und lässt sich
|
||
nicht sauber entfernen (einmal versucht, Ergebnis unbrauchbar)
|
||
2. Ist die Displayfläche deckend, muss sie **ausgeknockt** werden, damit der
|
||
Screenshot dahinter liegen kann. Nur so bleiben Notch und Rundungen oben
|
||
3. Displayfläche ausmessen und als Prozentwerte eintragen — sie hängt am
|
||
konkreten Bild
|
||
4. **Kein Schatten im PNG** — die Posts sind schattenfrei (§5), ein eingebrannter
|
||
Schlagschatten im Mockup-Bild lässt sich nicht mehr abschalten
|
||
|
||
### Szenen-Mockup (Perspektive)
|
||
|
||
`assets/mockups/macbook-szene.png` — Macbook Air + iPad Pro, freigestellt aus
|
||
einem Pixeden-PSD. Anders als `laptop.png` steht die Szene schräg, die
|
||
Displayflächen sind also **Vierecke, keine Rechtecke**.
|
||
|
||
Damit reicht die übliche Prozent-Box nicht. Der Screenshot wird per
|
||
`matrix3d` in die Fläche projiziert:
|
||
|
||
| | Macbook | iPad |
|
||
|---|---|---|
|
||
| Design-Größe | 3456 × 2234 | 1438 × 2164 |
|
||
| Format | 1,547 | **0,6645** |
|
||
| Eckradius | 0 | 57 px |
|
||
| Viereck aus | Smart-Object | vermessene Glasfläche |
|
||
|
||
**Zulieferung: ein iPad-Screenshot muss 0,6645 haben** (1 : 1,505), also z. B.
|
||
1440 × 2167. Beim Macbook 1,547, dort verschwinden die unteren 15 % hinter der
|
||
Tastaturschale.
|
||
|
||
Die Matrizen bilden **Design-Pixel auf Bildpixel des 5742 px breiten
|
||
Originals** ab; `--sk` skaliert von dort auf die Breite in der Slide und wird
|
||
in `applyEdits()` gesetzt, damit eine Größenänderung mitläuft. Die Eckpunkte
|
||
stammen aus dem `transform_box` der Smart-Objekte im PSD, sind also gemessen
|
||
und nicht angepasst.
|
||
|
||
**Beide Displays sind ausgeknockt**, der Ausschnitt am Macbook endet dort, wo
|
||
das iPad davor liegt. Sonst läge der Screenshot vor dem Tablet. Der Ausschnitt
|
||
kommt aus der Deckung der Screen-Ebene, **nicht** aus dem Design-Rechteck: das
|
||
reicht hinter das Gehäuse und würde die Tastaturschale anschneiden.
|
||
|
||
**Die Geräte standen auf einer Glasplatte.** Mit dem Hintergrund fällt die
|
||
Platte weg, ihre Spiegelung bleibt aber als zweiter Körper stehen, und der
|
||
Spalt dazwischen wird auf hellem Grund zur weißen Linie.
|
||
|
||
**Der Spalt bekommt keinen festen Farbwert, sondern wird spaltenweise
|
||
interpoliert.** Für jede Bildspalte: Medianfarbe der fünf Pixel über dem
|
||
Spalt, Medianfarbe der fünf darunter, linear dazwischen. Am Display bleibt so
|
||
Schwarz, am Gehäuse das Metallic, an der Kante der Verlauf. Es gibt keinen
|
||
Farbsprung mehr und damit auch keinen Streifen.
|
||
|
||
> Ein einheitlicher Ton reicht nicht. Der Spalt läuft quer über Displayrand
|
||
> **und** Aluminium, also über zwei völlig verschiedene Töne. Jeder feste Wert
|
||
> passt an einer Stelle und sitzt an der anderen als Fremdkörper.
|
||
|
||
> Nicht wegschneiden: die Spiegelung hängt am Fuß des iPads, ein Schnitt nach
|
||
> Zusammenhangskomponenten trennt den mit ab. Das war der erste Versuch.
|
||
|
||
Die Füllung läuft über die **volle Breite**, auch unter dem iPad: dessen
|
||
eigener Spalt ist sonst als weiße Linie unter dem Rahmen zu sehen. Mit einem
|
||
festen Ton ging das nicht, da saß dort ein harter dunkler Block auf dem
|
||
Aluminium; mit der Interpolation passt es.
|
||
|
||
**Das iPad-Viereck kommt nicht aus dem Smart-Object.** Das sitzt im PSD zu
|
||
hoch und lässt unten einen dreimal so breiten schwarzen Streifen stehen wie
|
||
oben. Statt dessen wird die schwarze Glasfläche vermessen:
|
||
|
||
1. Randpunkte der vier Kanten sammeln, **nur im mittleren Drittel** — an den
|
||
Ecken liegen sie auf den Rundungen
|
||
2. Ausgleichsgerade je Kante, benachbarte schneiden → die **echten** Ecken.
|
||
Über Extrempunkte kommt ein zu kleines, verdrehtes Viereck heraus, weil
|
||
die Extrema auf den Rundungen sitzen
|
||
3. Eckradius aus dem Abstand Ecke zu Glas: bei einem rechten Winkel liegt der
|
||
Scheitel der Rundung bei `r · (√2 − 1)` → **Glas 1514 × 2240, r = 95**
|
||
4. **38 px** Rand nach innen (2,5 % der Breite) → Display 1438 × 2164
|
||
5. Innenradius **konzentrisch**: `95 − 38 = 57`. Nur so bleibt der schwarze
|
||
Rand auch in den Ecken gleich breit
|
||
|
||
Der Ausschnitt wird als gerundetes Rechteck im Design-Raum gezeichnet und dann
|
||
durch die Homographie geschoben, im CSS macht `border-radius: 57px` mit
|
||
`overflow: hidden` dasselbe. Ohne das stößt eine scharfe Bildecke in die runde
|
||
Glasecke, und genau das sieht man sofort.
|
||
|
||
Beim Macbook stimmt das Smart-Object, dort bleibt es dabei.
|
||
|
||
Belegung: Kanban-Board auf dem Macbook, Projekt-Chat hochkant auf dem iPad.
|
||
|
||
### Vom Macbook-Display ist nicht alles zu sehen
|
||
|
||
Oben verdeckt die Blende des Deckels samt Kameraaussparung, unten die
|
||
Tastaturschale, rechts das davorstehende iPad. Die Ecken des Smart-Objekts
|
||
sagen darüber nichts: daraus geschätzt kommt oben 3,8 % heraus, tatsächlich
|
||
sind es **6,9 %**.
|
||
|
||
Deshalb wird gemessen statt geschätzt:
|
||
|
||
1. Deckung der Screen-Ebene per **Umkehr-Homographie in den Design-Raum**
|
||
zurückrechnen — dort ist die Verdeckung eine Maske, kein Rätsel
|
||
2. Darin das **größte achsenparallele Rechteck** suchen (Histogramm-Verfahren
|
||
je Zeile) → **2726 × 1682 bei (74, 154)**, also 78,9 % der Breite und
|
||
75,3 % der Höhe
|
||
3. Aufnahme darin zentriert einpassen, `contain`, **nie** `cover`
|
||
|
||
Daher `width: 78.877%` und der Versatz. **Nichts wird abgeschnitten und nichts
|
||
gezoomt** — was frei bleibt, trägt den aus den Bildecken gemessenen
|
||
Hintergrundton der Aufnahme (`#F5F4F4` Board, `#FAFAFA` Chat). Schon fünf
|
||
Stufen Unterschied zeichnen die freie Fläche als Rand nach.
|
||
|
||
Das Zielformat für eine Macbook-Aufnahme ist damit **1,62** statt 1,547; wer
|
||
näher dran liegt, füllt mehr Fläche. Passen muss es nicht, es wird ohnehin
|
||
eingepasst.
|
||
**Beide liegen originalgetreu auf ihrem Display, ohne Zoom.** Sie sind flacher
|
||
als der Bildschirm, es bleibt also Fläche frei; die trägt mit `#FAFAFA` den
|
||
Hintergrundton der Anwendung und ist deshalb nicht als Rand zu erkennen.
|
||
|
||
> **Das Board rückt 5,15 % nach unten.** Das Smart-Object des PSD ragt oben
|
||
> hinter die Blende des Deckels. Ohne Versatz verschwindet genau die Kopfzeile
|
||
> der Anwendung, und der Screen sieht angeschnitten aus, obwohl gar nicht
|
||
> gezoomt wurde. Unten wird der Screen von der Tastaturschale beschnitten —
|
||
> das ist so gewollt und im Pixeden-Original genauso.
|
||
|
||
> **Lizenz beachten.** Pixeden-Ressourcen sind für kommerzielle Projekte frei
|
||
> und ohne Namensnennung nutzbar, dürfen aber **nicht weitergegeben** werden.
|
||
> Das PSD gehört deshalb nicht ins Repo, nur das für uns abgeleitete PNG.
|
||
|
||
### Nachgebaute UI-Bausteine
|
||
|
||
Manche Produkt-Elemente liegen **nicht** als Screenshot, sondern als HTML im
|
||
Generator. Bisher: der **Notizzettel** auf der Workflow-Slide.
|
||
|
||
Nachbau aus `hivegrid-pitch/ui/components/StickyNote/StickyNote.tsx`,
|
||
Variante `purple`:
|
||
|
||
| Teil | Wert |
|
||
|---|---|
|
||
| Kopfleiste | `#ce93d8` |
|
||
| Fläche | `#f3e5f5` |
|
||
| Text | `#424242`, Gewicht 400, Zeilenhöhe 1,5 |
|
||
| Menü | drei Punkte (FeatherMoreVertical), kein Balken |
|
||
| Anfasser | zwei Ecken unten rechts, 45 % und 65 % Deckkraft |
|
||
|
||
Die Vorlage ist 200 px breit bei 14 px Text; auf dem 1080er Blatt sind alle
|
||
Maße rund 1,6× hochskaliert. **Farbwerte nicht neu erfinden** — sie stammen aus
|
||
der Software und müssen wiedererkennbar bleiben.
|
||
|
||
Warum HTML und nicht Screenshot: Kanten und Schrift bleiben beim 3×-Export
|
||
genauso scharf wie der Rest der Slide, der Text ist austauschbar, und es gibt
|
||
keine Scrollbalken oder Cursor-Reste im Bild.
|
||
|
||
---
|
||
|
||
## 7. Export & Bildqualität
|
||
|
||
### Der Export behält die gerenderte Auflösung
|
||
|
||
**Gespeichert wird in 1080·k × 1350·k**, wobei k die Renderqualität ist. Bei 3×
|
||
sind das 3240 × 4050. Die Datei ist damit so groß wie das, was tatsächlich
|
||
gerendert wurde.
|
||
|
||
```
|
||
Renderqualität 1× → 1080 × 1350
|
||
Renderqualität 2× → 2160 × 2700
|
||
Renderqualität 3× → 3240 × 4050
|
||
```
|
||
|
||
**Früher stand hier das Gegenteil**, und die Begründung war das Upload-Verhalten
|
||
von Instagram: Breiten von 320 bis 1080 bleiben unangetastet, alles darüber wird
|
||
von Instagram verkleinert und neu komprimiert, also lieber selbst sauber auf
|
||
1080 herunterrechnen. Der Preis dafür war aber, dass jede gelieferte Aufnahme
|
||
auf die Breite ihres Rahmens im 1080er Blatt eingedampft wurde — eine Vorlage
|
||
mit 2390 px kam mit 916 px an, obwohl sie intern schon in 2748 px vorlag.
|
||
|
||
**Das wiegt schwerer als der Upload-Pfad.** Wer eine hoch aufgelöste Aufnahme
|
||
liefert, muss sie im Ergebnis wiederfinden. Kleiner rechnen kann jedes
|
||
Zielsystem selbst, und wer bewusst exakt 1080 × 1350 will, stellt die
|
||
Renderqualität auf 1×.
|
||
|
||
### Warum das Supersampling hier hilft
|
||
|
||
Nicht wegen des Textes — den rastert der Browser bei nativer Größe ohnehin
|
||
sauber, teils sogar schärfer als supersampled. Der Gewinn liegt woanders:
|
||
|
||
- **Transformationen** — der gekippte Laptop, gedrehte Formen, Chamfer-Kanten
|
||
und Hexagon-Clips werden bei doppelter Auflösung gerastert und danach
|
||
gemittelt. Das ist echtes Anti-Aliasing statt Treppenkanten
|
||
- **Screenshots** — unsere Quellen sind ~2000 px breit und werden auf ~1090 px
|
||
gezeigt. Bei 2× zeichnet der Browser sie näher an ihrer nativen Auflösung, das
|
||
anschließende Downsampling ist unser eigenes und damit besser
|
||
- **Marker und Wabenraster** — SVG-Kanten profitieren identisch
|
||
|
||
> Bei reinen Textslides ohne Mockup ist der Unterschied gering bis null. Deshalb
|
||
> ist die Stufe umschaltbar (1× / 2× / 3×), nicht fest verdrahtet. **2× ist der
|
||
> Standard**, 3× kostet spürbar Zeit und Speicher bei kaum sichtbarem Gewinn.
|
||
|
||
### Falls doch einmal verkleinert werden muss
|
||
|
||
Nicht in einem Sprung. Browser skalieren Canvas standardmäßig schnell statt gut:
|
||
Pixel werden direkt übernommen, Nachbarinformationen gehen verloren, das
|
||
Ergebnis rauscht. Zwei Gegenmaßnahmen, beide nötig:
|
||
|
||
```js
|
||
ctx.imageSmoothingEnabled = true;
|
||
ctx.imageSmoothingQuality = "high"; // Default ist "low"
|
||
```
|
||
|
||
Und **in Halbierungsschritten** verkleinern. Ein direkter Sprung von 3240 auf
|
||
1080 überspringt Zwischenpixel; zweimal halbieren mittelt jeweils über 2 × 2 und
|
||
kommt einer echten Flächenmittelung nahe.
|
||
|
||
`createImageBitmap()` mit `resizeQuality` wäre der elegantere Weg, ist aber in
|
||
Browsern uneinheitlich umgesetzt — teils wird die Option ignoriert. `drawImage`
|
||
mit `imageSmoothingQuality` ist der verlässliche Pfad.
|
||
|
||
### Format
|
||
|
||
**PNG, nicht JPEG.** Instagram konvertiert am Ende ohnehin zu JPEG — aber unsere
|
||
Slides sind Text, flache Flächen und UI-Screenshots mit harten Kanten. Genau die
|
||
zerlegt JPEG-Kompression zuerst. Wer als PNG hochlädt, gibt Instagram ein
|
||
verlustfreies Original und zahlt nur **einmal** Kompression statt zweimal.
|
||
|
||
Für Fotoinhalte wäre JPEG bei 85–95 % die bessere Wahl. Haben wir hier nicht.
|
||
|
||
### Farbraum
|
||
|
||
**sRGB, ausschließlich.** Instagram wirft andere Profile weg und konvertiert
|
||
nach sRGB. Wer in Adobe RGB oder P3 exportiert, bekommt Farbverschiebungen bei
|
||
dieser Konvertierung. Canvas-Exporte aus dem Browser sind standardmäßig sRGB —
|
||
also nichts zu tun, solange niemand ein Farbprofil anfasst.
|
||
|
||
### Weiteres
|
||
|
||
- Auf `document.fonts.ready` warten, sonst rendert der erste Export in einer
|
||
Fallback-Schrift
|
||
- Dateinamen mit `01-`…`05-` präfixen: Instagram übernimmt beim Hochladen die
|
||
Dateisortierung
|
||
- Dateigröße im Auge behalten. Für JPEG gilt die Faustregel „unter 1 MB", sonst
|
||
komprimiert Instagram härter. Für unsere PNGs mit Screenshots ist das nicht zu
|
||
halten und auch nicht nötig
|
||
- Vom Desktop hochladen, wenn möglich — gilt als schonender als der mobile Upload
|
||
|
||
### Quellenlage, ehrlich
|
||
|
||
**Von Meta dokumentiert:** die unterstützten Seitenverhältnisse (1.91:1 bis 4:5),
|
||
die Auflösungsgrenze 320–1080, der Beschnitt außerhalb, und dass alle
|
||
Carousel-Slides auf die erste beschnitten werden.
|
||
|
||
**Community-Konsens, nicht offiziell:** die konkreten Kompressionsprozente, die
|
||
1-MB-Faustregel, Desktop-vs-Mobile, und PNG-vs-JPEG-Empfehlungen. Plausibel und
|
||
breit reproduziert, aber von Meta nirgends bestätigt. Im Zweifel selbst
|
||
gegentesten: dieselbe Slide in zwei Varianten hochladen und vergleichen.
|
||
|
||
### Vier Dinge, die den Export brechen
|
||
|
||
**1. Tailwind- und `oklch()`-Farben.** Die Export-Bibliothek kann sie nicht
|
||
serialisieren. Der Slide-Baum benutzt deshalb ausschließlich Hex und `rgba()`.
|
||
|
||
**2. PNGs im `foreignObject`.** Der Export serialisiert die Slide in eine
|
||
`data:image/svg+xml`-URL und malt sie als **ein** Bild. Raster-Kinder dekodieren
|
||
asynchron und verpassen diesen Paint regelmäßig — der Screenshot fehlt dann,
|
||
während der Rest stimmt. SVGs dekodieren synchron und sind immer da.
|
||
|
||
> Deshalb ist der Export **verifiziert statt gehofft**: Jede Screenshot-Fläche
|
||
> wird nach dem Rendern abgetastet. Eine solche Fläche ist nie glatt, sie trägt
|
||
> UI und Farbe. Liegt die Luminanz-Spanne unter 12, fehlt das Bild und es wird
|
||
> neu gerendert, bis zu fünf Versuche.
|
||
|
||
**3. Browser-Downloads.** `data:`-URLs öffneten einen leeren Tab, `blob:`-URLs
|
||
das Bild im Tab — das `download`-Attribut wurde beide Male ignoriert. Der
|
||
Generator schickt die Bytes deshalb per POST an **`/save`**, und `serve.py`
|
||
schreibt direkt nach `~/Downloads`.
|
||
|
||
**4. Stille Skriptfehler.** Ein Fehler auf oberster Ebene (Syntax, Temporal Dead
|
||
Zone) tötet das ganze Skript: Das Panel rendert aus statischem HTML weiter, aber
|
||
nichts wird gebaut. Beide Seiten haben einen globalen `error`-Listener, der so
|
||
etwas ins Statusfeld schreibt. **Nicht entfernen.**
|
||
|
||
---
|
||
|
||
## 8. Fallen, in die wir schon getappt sind
|
||
|
||
| Symptom | Ursache |
|
||
|---|---|
|
||
| Logo unsichtbar auf Weiß | Sichtbarkeitsregel vertauscht — auf Hell wurde `logo-white.svg` gezeigt |
|
||
| Logo bildschirmbreit statt zentriert | `.laptop-screen img` galt auch fürs Logo und hat es überschrieben. Solche Regeln **immer auf eine Klasse eingrenzen** |
|
||
| Element eine Zeile zu tief | `max-width` erzwang einen zusätzlichen Umbruch. Bei gesetzten `<br>` gehört `white-space: nowrap` dazu |
|
||
| Farbe „stimmt nicht" | Deckkraft der Ebene lag darüber. Eine Farbe ist nur exakt, wenn nichts sie multipliziert |
|
||
| Muster wirkt zufällig hingeworfen | Formen liefen quer durch den Text. Dekoration bleibt in den Rändern, verankert und angeschnitten |
|
||
| Nichts lädt, Bühne leer | Konstante vor ihrer Deklaration benutzt (TDZ) |
|
||
| „Sanitaer“ auf dem fertigen Beitrag | Wörtlich aus der Quelle übernommen, die es selbst falsch schreibt. §4 |
|
||
|
||
---
|
||
|
||
## 9. Ordner
|
||
|
||
```
|
||
posts/
|
||
├── serve.py / serve.sh Statischer Server + /save-Endpunkt (Port 4321)
|
||
├── post1.html Post 1 · Lösung (Carousel, 5 Slides)
|
||
├── rules.md diese Datei
|
||
├── assets/
|
||
│ ├── logo.svg, logo-white.svg, cta-honeycomb.svg
|
||
│ ├── laptop.png Mockup, Display ausgeknockt
|
||
│ ├── fonts/ Manrope, lokal
|
||
│ └── screens/ Software-Screenshots
|
||
└── vendor/ html-to-image, JSZip (lokal, kein CDN)
|
||
```
|
||
|
||
Alles läuft **offline**. Keine CDN-Verweise, keine Google-Fonts zur Laufzeit —
|
||
sonst hängt der Export an einer Netzwerkverbindung und an fremden Origins.
|