Files
posts/rules.md
T
dejanandClaude Opus 5 5fe1d2945f Theme modern: Metallband, Icon-Satz und echte G-Marke
Das Band laeuft als durchgehende Zeichnung ueber den ganzen Streifen,
jede Slide zeigt ihren Ausschnitt ueber --slide-i. An der Naht laeuft es
weiter statt neu anzusetzen -- der Uebergang, den ein Carousel sonst
nicht hat.

Es ist ein Rohr, kein Farbband: vierzig Streifen aus einer Rampe, die
Glanzstelle eingerueckt bei 20 % und fast weiss, der Kernschatten bei
78 % statt an der Unterkante, darunter Licht vom Grund zurueck. Der
erste Anlauf mit vier weichen Streifen sah nach Bonbon aus.

Damit das Band quer durch die Komposition laufen darf, liegt jede
Visualisierung jetzt auf einer deckenden Karte. Linienwerk auf
Transparenz verschwindet darin. Slide 5 musste dafuer von 988 auf 896
schrumpfen, Slide 6 bekam links Einzug.

Ausserdem:

- Feather-Icons aus @subframe/core statt selbst gezeichneter Zeichen,
  auf Slide 2 bis 5. Slide 6 behaelt ihre Nachbauten aus WaitlistNext.
- Die echte G-Marke auf dem Ring, als Polygon statt als Bilddatei.
- Die 01/02/03 auf Slide 6 sind raus.
- Slide 1 uebernimmt den Rahmen der Ueber-uns-Seite: Radius 38 auf
  teal-hellem Grund, Foto 4,2 % eingerueckt.
- Der Grund ist wieder #FFFFFF, wie Post 1 und Post 2. Den Kontrast
  tragen die Karten jetzt ueber die Kante (#D5DBE1).
- Slide 4 und 5 hiessen abweichend vom Original; Beschriftungen und
  Fliesstext gleichen jetzt post2.html.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-13 20:19:08 +02:00

37 KiB
Raw Blame History

Post-Generator — Regeln & Konventionen

Arbeitsgrundlage für alles, was in diesem Repo an Social-Grafiken entsteht. Stand: 31.07.2026

Der Generator läuft lokal: ./serve.sh → http://127.0.0.1:4321/post1.html (Post 1, Carousel).


1. Wo die Wahrheit steht

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

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

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 ihre 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. Auf der Website reicht #E5E5E5, weil dort ein Schatten mitträgt; hier nicht (§7).

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.
  • Der Querschnitt ist ein Rohr, kein Farbband. Vierzig schmale Streifen, versetzt entlang derselben Bahn, eingefärbt aus der Rampe BAND_RAMPE. Was Metall von Bonbon unterscheidet, steht in diesen vier Punkten:
    • Die Glanzstelle liegt nicht an der Oberkante, sondern eingerückt bei 20 %, und sie ist schmal und fast weiß. Ein breites weiches Hellgelb obenauf ist genau der Bonbon-Eindruck.
    • Der dunkelste Punkt liegt nicht an der Unterkante, sondern bei 78 %. Darunter kommt Licht vom weißen Grund zurück.
    • Der Tonumfang ist groß: #6E4205 bis fast Weiß. Metall hat Kontrast, lackierter Kunststoff nicht.
    • Beide Silhouetten sind aufgehellt (Fresnel), dazu ein paar Längsstreifen als Schliff.
  • Kein SVG-Verlauf. Nicht weil Verläufe verboten wären — verboten sind sie nur beim Marker (§5) —, sondern weil ein linearGradient immer geradlinig durch die Bounding-Box läuft. Das Band wellt sich, der Verlauf würde also quer statt längs schattieren. Versetzte Streifen folgen der Kurve von selbst.
  • Der Versatz ist senkrecht, nicht rechtwinklig zur Bahn. Dadurch wird das Band an den Flanken um bis zu 14 % schmaler. Das liest sich als Verkürzung und ist erwünscht; echte Parallelkurven bekommen an engen Radien Selbstschnitte.
  • Volle Deckkraft. Jede Abschwächung nimmt dem Band genau das wieder weg, was es zum Körper macht.

Das Band läuft quer durch die Komposition, nicht am Rand entlang. Es liegt in .bg und damit unter allem. Damit das trägt, gilt für das Theme modern:

Jede Visualisierung liegt auf einer deckenden Karte. Linienwerk auf Transparenz verschwindet im Band. Alle sechs Slides von Post 2 sind deshalb auf chamferFlaeche(0, 0, W, H, KARTE, 22, true) gesetzt — das Band taucht dahinter ab und kommt daneben wieder hoch, wie der Schlauch in der Vorlage von awork. Eine weiße Ummantelung um einzelne Linien war der erste Versuch und sah nach Panne aus.

Daraus folgt: visW darf den Satzspiegel nicht überschreiten. Solange eine Visualisierung Linienwerk war, durfte sie breiter sein als 896; als Karte muss sie auf die 92er Kante. .vis svg skaliert über --vis-w mit, die Innenmaße bleiben also unangetastet — bei Slide 5 genügte visW: 988 → 896.

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, aktuell ungenutzt
--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.

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:

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.


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.

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

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

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.

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

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:

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)

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.