17 KiB
Produkt-Komponenten für Posts
Stand: 20.10.2026. Quelle: hivegrid-cloud, Stand origin/main vom 19.10.2026.
Referenz-Umsetzung: post8.html, Abschnitt „Post 8 · E-Mail-Marketing“.
Diese Datei ist das Nachschlagewerk für jede Zeichnung, die in einem Post die Software zeigt. Die Regel dazu steht in
rules.md §0 und gilt ohne Ausnahme.
1. Die Regel
Jede Oberfläche in einem Post wird aus den echten Komponenten von hivegrid-cloud nachgebaut, mit ihren Maßen, Farben, Schriften, Icons und Beschriftungen. Keine eigenen, ausgedachten Grafiken.
- Jede Zeichnung bildet einen Screen nach, den es im Produkt gibt. Im Kommentar steht die Datei, aus der er stammt.
- Beschriftungen stehen wörtlich in
apps/web/public/locales/de/translations.json. - Was im Produkt nicht existiert, wird nicht gezeichnet. Auch nicht, wenn es gut aussähe.
- Was nicht auf die Slide passt, wird weggelassen, nicht umgebaut. Im Kommentar steht, was fehlt und warum.
- Erfunden werden darf nur Demo-Inhalt: Namen, Betreffzeilen, Texte, Daten, Zahlen. Die Zahlen müssen aufgehen.
Warum: Die erste Fassung von Post 8 hatte ein eigenes Design (Pillen mit Rand, Häkchen-Chips, Reiter und Kacheln, die es im Produkt nicht gibt). Dejan: „sieht total vibecoded aus“. Nach dem Umbau auf die Komponenten: „sieht jetzt genau so aus wie die Software“ (20.10.2026).
2. So gehst du vor
- Screens finden. Für das Thema des Posts das Modul in hivegrid-cloud suchen:
apps/web/app/components/screens/<modul>/,apps/web/app/modules/<modul>/, die Routen unterapps/web/app/routes/app.$tenant/, die Texte intranslations.json. - Immer
origin/mainlesen, nie die Arbeitskopie. Die lokale Kopie von hivegrid-cloud ist veraltet und wird nicht angefasst (kein pull, kein checkout ohne Dejan).- Einzelne Datei:
git -C ../hivegrid-cloud show origin/main:<pfad> - Für viele Dateien ein Schnappschuss ins Scratchpad:
git -C ../hivegrid-cloud archive origin/main apps/web/app apps/web/public/locales/de | tar -x -C <scratchpad>/hg-main - Vorher
git -C ../hivegrid-cloud fetch(holt nur, ändert nichts).
- Einzelne Datei:
- Für jede Slide einen echten Screen wählen. Aufbau von oben nach unten aus dem Code lesen: Kopf, Knöpfe, Tabs, Felder, Tabellenspalten, welche Badge für welchen Status.
- Mit den Bausteinen zeichnen (Abschnitt 3). Sie stehen in
post8.htmlund werden in den neuen Post kopiert, weil jeder Post eine einzelne Datei ist. - Prüfen:
- Streifen-Ansicht
?nur=streifen: Die Karte darf nicht in den Text der Slide ragen. - PNG-Export: Die Oberfläche muss in Instrument Sans stehen, nicht in einer Ersatzschrift (siehe Fallen).
- MP4 mit
node animation.mjs postN.html <key>.
- Streifen-Ansicht
3. Bausteine in post8.html
Alle Werte sind Produkt-Pixel und werden mit PZ = 1.15 vergrößert, wie ein Browserzoom. Koordinaten sind Pixel der
Zeichnung. Nur Schriften, Icons und Komponentenhöhen laufen über pz().
| Baustein | Bildet nach | Aufruf |
|---|---|---|
P, P_TON, P_KNOPF, P_KARTE |
Rollen aus ui/theme.css |
Farbkonstanten |
pText(x, y, t, gr, gew, farbe, o) |
Text in Instrument Sans | o.anker für Ausrichtung |
pMess(t, gr, gew) |
Textbreite in Instrument Sans | |
pMitte(ym, gr) |
Grundlinie für Text mittig in einem Kasten | |
pIkon(name, cx, cy, gr, farbe) |
Feather/Lucide-Icon, Strich 2 auf 24 | Namen aus LUCIDE |
pKarte(x, y, b, h, art, radius) |
Card, InlineMessage |
art: std, weiss, neutral, info, warnung |
pBadge(x, ym, t, ton, klein) |
BadgeFeedback subtle |
ton: positiv, warnung, info, neutral |
pKnopf(x, y, t, art, o) |
Button |
art: primaer, sekundaer, tertiaer, geist, aus; o.ikon, o.ikonRechts, o.klein |
pFeld(x, y, b, o) |
TextInput, Select, TextArea |
o.label, o.wert, o.platzhalter, o.auswahl, o.hoch, o.hilfe |
pTabs(x, y, b, reiter, aktiv, innen) |
Tabs |
|
pUmschalter(xRechts, y, eintraege, aktiv) |
ToggleGroup klein |
Einträge [icon, text] |
pTabelle(x, y, spalten, zeilen, zh, zelle) |
Table |
Spalten [titel, icon, breite] |
pRahmen(h, inhalt, kopf) |
Seite mit PageHeader |
kopf.ikon, kopf.titel, kopf.badge, kopf.ikonKasten, kopf.rechts |
pKopfKnopf(t, art, o) |
Knopf rechts im Kopf | |
emKpi(...) |
WidgetRahmen mit KPICard |
Icons: LUCIDE enthält die Formen aus lucide-static@0.460.0. Subframes Feather*-Icons haben dieselben Formen.
Fehlt ein Icon, die SVG von https://cdn.jsdelivr.net/npm/lucide-static@0.460.0/icons/<name>.svg holen und das Innere
der <svg> in die Tabelle eintragen. Phosphor (ikon()) gehört nur noch in ältere Posts, nicht in Oberflächen.
Schrift: Die Dateien liegen in assets/fonts/instrument-sans-*.woff2, dieselben wie in
hivegrid-cloud/apps/web/public/fonts/. Der Post braucht dafür drei Dinge:
- den
@font-face-Block im<head> - beim Start
document.fonts.load(...)für 400, 500 und 600, bevor zum ersten Mal gemessen wird instrumentSansCss(), angehängt anfontEmbedCss, sonst fehlt die Schrift im PNG
Alles drei steht in post8.html.
4. Tokens, aufgelöst
Schrift
Instrument Sans, variabel 400 bis 700. Laufweite überall 0.
| Token | px / Zeilenhöhe / Gewicht | Wofür |
|---|---|---|
subtitle |
32/40/600 | Seitentitel im PageHeader, Wert in StatsCard |
heading- |
24/32/500 | Dialogtitel |
body-large |
20/24 (‑0: 26)/500 | Abschnitte wie „E-Mail gestalten“, EmptyState-Titel |
body-default-regular |
16/24 (‑0: 22)/400 | Knopfbeschriftung, Eingaben, Fließtext |
body-default-medium |
16/24/500 | Feldbeschriftungen |
body-default-semibold |
16/24/600 | Kartentitel |
body-small-regular |
14/21 (‑0: 18)/400 | Hilfetexte, Tabs, Seitenleiste |
body-small-medium |
14/21/500 | Badges, Menüeinträge, Checkbox-Beschriftung |
body-extra-small |
12/18 (‑0: 16)/400 bis 600 | Tabellenkopf (500), Tooltip |
text-heading-2 und text-heading-3 werden im Code benutzt, sind aber nirgends definiert. Sie rendern als 16/24/400.
Farben
Neutral: 0 #FAFAFA · 50 #F5F4F4 · 100 #F0F0F0 · 200 #E5E5E5 · 300 #CECECF · 400 #B5B5B7 · 500 #9D9DA0 ·
600 #858589 · 700 #6D6D71 · 800 #505057 · 850 #37373E · 900 #24242B · 950 #111114
| Rolle | Wert |
|---|---|
Seite (surface-neutral-primary) |
#F5F4F4 |
Karte, Kopfzeile, Tabellenzeile (secondary) |
#FAFAFA |
Tabellenkopf, Hover (tertiary) |
#F0F0F0 |
| Rand, Rand stark | #E5E5E5, #B5B5B7 (Eingabefelder) |
| Text primär, sekundär, tertiär | #24242B, #505057, #6D6D71 |
| Platzhalter, deaktiviert | #CECECF |
| Link | #08739C |
| Icons primär, sekundär, tertiär | #24242B, #505057, #858589 |
| Akzent für aktiv und ausgewählt | #E1F4FD (information subtle) |
| Sekundärknopf | #E3F1FF |
| Ton | 100 | 200 | 500 | 600 | 700 |
|---|---|---|---|---|---|
| Erfolg (grün) | #DCFCE4 |
#BAF8CB |
#20C54F |
#15A43E |
#148134 |
| Warnung (gelb, nicht Marke) | #FCF6C5 |
#FAEB8E |
#E1AB11 |
#C2840C |
#9B5E0D |
| Fehler (rot) | #FEE2E2 |
#FDCBCB |
#ED4646 |
#DA2828 |
#C52020 |
| Information (ocean) | #E1F4FD |
#BCEAFB |
#15B1E2 |
#0890C1 |
#08739C |
Kein Markengelb in der Oberfläche. Die Rolle „brand“ ist im Produkt auf Grau gemappt, Primärknöpfe sind #24242B.
Gelb gehört auf der Slide nur zur Überschrift und zu Marken-Elementen außerhalb der Oberfläche.
Radien und Schatten
rounded-sm4 px (Eingabefelder) ·rounded-md6 px (Knöpfe, Karten, Tabs) ·rounded-lg8 px (Widgets) ·rounded-xl12 px. Intheme.csssteht md = 8,globals.cssüberschreibt das danach mit 6.- Schatten im Produkt: sm
0 1px 2px rgb(0 0 0 / .08), md und lg größer. In den Zeichnungen lassen wir sie weg (rules.md §5). Der Stapel um die Karte hat seinen eigenen Schatten und bleibt.
5. Komponenten
| Komponente | Maße und Farben |
|---|---|
| Button | klein h32, px12, Abstand 8 · mittel h36, px16, Abstand 12 · groß h48. Radius md. Beschriftung 16/400, Icon 16. primär #24242B/#FAFAFA · sekundär #E3F1FF/#24242B · tertiär #F5F4F4 mit Rand #E5E5E5 · ghost transparent · information #0890C1 · destructive #DA2828 · deaktiviert #F0F0F0/#CECECF |
| IconButton | rund, quadratisch mit square (md). 24/32/36/48 Kasten, Icon 16/20/20/24 |
| BadgeFeedback (alle Status) | Pille ohne Rand, h24 (klein 20), px10, Abstand 8, Punkt 4 px, Schrift 14/500. positiv #DCFCE4, Text und Punkt #148134 · Warnung #FCF6C5, Text #24242B, Punkt #9B5E0D · information #E1F4FD, Text #24242B, Punkt #08739C · neutral #F0F0F0, Text und Punkt #24242B · negativ #FEE2E2, #C52020 |
| Badge (Farbtöne) | Pille h24 mit 1-px-Rand, sonst wie oben. Standard #F0F0F0, Rand #E5E5E5 |
| Tabs | Reiter px16, py10 (rund 40 hoch), oben rund, #FAFAFA, unten 2 px #E5E5E5, Schrift 14/400 #505057. Aktiv: Fläche #E1F4FD, unten 2 px #24242B, Schrift 14/500 #24242B |
| TextInput / Select | h36, px12, Radius 4, Rand #B5B5B7, Fläche #F5F4F4. Beschriftung 16/500 #505057, 6 px darüber. Wert 16 #505057, Platzhalter #CECECF, Hilfe 14 #6D6D71. Pfeil 16 #858589 |
| TextArea | wie oben, mindestens 96 hoch, Innenabstand 12×8 |
| Checkbox | 16, Radius 4, Rand 2 px #B5B5B7. An: #24242B mit Haken #FAFAFA. Beschriftung 14/500 |
| Switch | 36×22, rund. Aus: #F5F4F4 mit dunklem Knopf. An: #24242B mit hellem Knopf |
| Table | Außenrand #E5E5E5. Kopf h32, #F0F0F0, Schrift 12/500 #505057, Icon 12 #858589. Zeilen #FAFAFA mit Trennlinien #E5E5E5, Höhe aus dem Inhalt (rund 31) |
| Avatar | rund, 24/32/40/48/64. Ohne Bild #F0F0F0, Kürzel 16/500 #505057 |
| IconWithBackground | Radius 4. Kasten 22/24/28/32/40, Icon 14/16/20/24/32. information #E1F4FD mit #08739C |
| Card | Radius md, Rand #E5E5E5, Fläche #FAFAFA, Innenabstand 16 (lg 24). Varianten information, warning, negative, positive, neutral |
| InlineMessage | Card in Ton-Variante, px16, py12, Text 16 #24242B. Standard information |
| Alert | Radius md, Rand, px16, py20, Icon 24. Titel 16/500 im Ton, Text 16 #505057 |
Widget (WidgetRahmen) |
Radius lg, Rand, weiß. Kopf h44 mit Titel 13/600. KPI: Wert halbfett, Fußzeile 11 #6D6D71 über einer Linie |
| ToggleGroup | h36 (klein 32), Innenabstand 2, Rand, #F0F0F0. Aktiv: #24242B mit Schrift #FAFAFA |
| DropdownMenu | #FAFAFA, Rand, Radius md, Innenabstand 4. Einträge 14/500 #6D6D71, Hover #F0F0F0 |
| Tooltip | #505057, Schrift 12/500 #FAFAFA, Radius 4 |
| Progress | 6 px, rund, Spur #F0F0F0, Füllung #24242B |
| EmptyState | Titel 20/500, Text 16 #505057. Umrandet: Rand und #F5F4F4 |
| Dialog | weiß, Rand, Radius md, Overlay rgb(0 0 0 / .4). DecisionDialog 420 oder 560 breit, Titel 24/500 |
App-Rahmen
- Seite: Hintergrund
#F5F4F4. Kopfleiste h64,#FAFAFA, Linie unten. - PageHeader:
#FAFAFA, px24, py20, Linie unten. Icon 24 in#858589, Abstand 8, Titel 32/600#24242B. Knöpfe rechts mit Abstand 8. In den Zeichnungen Titel 24, weil die Karte schmaler ist als ein Bildschirm. - Filterleiste:
#F5F4F4, px24, py10. Suchfeld#FAFAFA, Facetten-Chips h24 mit Rand#B5B5B7. Aktiv#BCEAFB. - Seitenleiste: 240 breit,
#FAFAFA. Einträge h40, Icon 20 mit Strich 1.5, Schrift 14. Gewählt:#E1F4FD.
6. Fallen
- Schrift im PNG. html-to-image bettet nur die Schrift ein, die im HTML vorkommt, also Manrope. Instrument Sans
steckt nur im SVG und fehlte im Export. Lösung:
instrumentSansCss()anfontEmbedCsshängen. Prüfen, indem man Export und Vorschau übereinanderlegt. - Messen vor dem Laden.
pMess()speichert Breiten zwischen. Wird vor dem Laden der Schrift gemessen, bleibt die Breite der Ersatzschrift hängen. Darumdocument.fonts.loadam Anfang vonboot(). - Was in den Übersetzungen steht, ist nicht automatisch zu sehen. Beispiel: „Pre-Send-Prüfung“ und die sechs
Prüfpunkte stehen in
translations.json, angezeigt werden sie nirgends. Immer im Screen-Code nachsehen, ob ein Text wirklich gerendert wird. - Website und Produkt unterscheiden. Die Website zeigt manches, was es im Produkt nicht gibt (in Post 8 die Produktkacheln im Mail-Baukasten). Maßgeblich ist das Produkt.
- Höhe der Karte. Zu hohe Karten ragen in den Text der Slide. Lieber Schritte oder Hilfetexte weglassen als die Komponenten zu verkleinern.
- Feste Höhe im Bewegtbild. Die Höhe der Zeichnung darf nicht vom Zeitpunkt abhängen, sonst ändert sich der viewBox und alles springt.
7. Beispiel: E-Mail-Marketing (Post 8)
| Slide | Screen | Datei in apps/web/app/components/screens/email-marketing/ |
|---|---|---|
| Bewegtbild | Kampagnenbericht | EmailMarketingDetailScreens.tsx |
| Zielgruppe | Dialog „Zielgruppe bearbeiten“ | SegmentEditorDialog.tsx, SegmentFilterBuilder.tsx |
| Vorlagen | Vorlagenseite | EmailMarketingTemplateScreens.tsx |
| Rechtssicher | „Empfänger & Versand“ mit „Bereit zum Versand?“ | EmailMarketingCampaignEditorPage.tsx |
| Baukasten | Gestaltung | CampaignContentBuilder.tsx, CampaignBuilderSidebar.tsx, campaign-mail-look.ts |
Status-Badges dort: „Gesendet“ positiv · „Geplant“, „Pausiert“ Warnung · „Wird gesendet“ information · „Entwurf“, „Archiviert“ neutral. Empfänger: „Zugestellt“, „Geöffnet“, „Geklickt“ positiv · „Ausstehend“ Warnung · „Gebounced“ negativ. Vorlagen: „Standard“ information · „Eigen“ neutral. Versandbereitschaft: „Erledigt“ positiv · „Offen“ neutral.
8. Beispiel: Form Builder (Post 7)
Seit 20.10.2026 nach dem echten Editor gebaut. Vorher kam Post 7 aus dem Klickdummy des Pitch-Pakets
(S02EinfacherFragenEditor.tsx) und zeigte Dinge, die es nicht gibt.
| Slide | Screen | Datei in apps/web/app/components/screens/forms/ |
|---|---|---|
| Bewegtbild | Reiter „Fragen“, neue Frage klappt auf | editor/questions-tab/QuestionsTabView.tsx, SortableQuestionCard.tsx |
| Die Fragen | zugeklappte Fragekarten, Werkzeugleiste | SortableQuestionCard.tsx:121, QuestionsTabView.tsx |
| Bedingt sichtbar | offene Frage mit „Nur anzeigen, wenn“ | editor/questions-tab/QuestionEditors.tsx:123 |
| Lead | Ansicht „Felder zuweisen“ | editor/LeadBanner.tsx, editor/LeadMappingView.tsx |
- Kopf (
FormEditorPage.tsx:411): kein PageHeader, eine eigene Zeile mitclipboard-list, Name 20/500, Badge „Entwurf“ (neutral), „Veröffentlicht“ (positiv) oder „Änderungen nicht live“ (Warnung). Rechts klein: „Einbetten & QR“ (sekundär, nur extern), „Vorschau“ (tertiär), „Veröffentlichen“ (primär), ⋯. Autosave „Automatisch gespeichert“ steht dort und blendet nach 3 s aus. - Reiter: „Fragen“ (
list-checks), „Design“ (palette), „Antworten“ (inbox), gleich breit. - Fragekarte zu: Radius 8, px20 py16, Griff, Typ-Icon, Titel 16/500 mit rotem
*bei Pflicht, darunter der Typ und bei optionalen „ · optional“. Kopieren grau, Löschen rot#C52020. Bedingte Fragen: Badge „Bedingt sichtbar“ klein neutral. - Fragekarte offen: kein farbiger Rand, sondern 6-px-Streifen links in
#0791F0. Optionen nur hier. - Feldtypen: Text, Mehrzeiliger Text, Auswahl, Mehrfachauswahl, Checkbox, E-Mail, Telefon, Zahl, Datum, Uhrzeit,
Datum und Uhrzeit, Datei (
paperclip), Foto (Kamera, nur intern), Unterschrift. Nicht: „Kurzantwort“, „Absatz“, „Multiple-Choice-Frage“. - „Frage hinzufügen“: gestrichelte Fläche, 2 px
#B5B5B7, Icon 24, Schrift 20/500. Neue Frage: Typ „Text“, Name „Unbenannte Frage“, neue Optionen „Auswahl A“, „Auswahl B“. - Lead: Banner „Lead-Verknüpfung“ mit „Aktiv“, Text „Neue Antworten werden als Lead gespeichert.“, Knöpfe „Felder zuweisen“ (primär) und „Deaktivieren“ (tertiär). Eine Pipeline gibt es im Form Builder nicht.
- Gibt es nicht: eine Fußzeile, „Zuletzt gespeichert vor wenigen Sekunden“, „Bereit zum Veröffentlichen“.
Bausteine dazu in post7.html: fbRahmen, fbFrage, fbLeiste, fbNeu, fbFeld, fbOffenKopf, fbOffenKarte,
fbOption, fbOptionNeu, fbSchalter, pKurz (Text mit „…“ kürzen).
9. PostGrid-Eigenheit: gleiche Höhe im Carousel
Alle Grafiken eines Posts bekommen dieselbe SVG-Höhe, damit die Karten auf allen Slides oben auf einer Linie stehen.
Eine kürzere Karte lässt deshalb unten Platz frei. Das ist gewollt. Zu hoch darf keine werden: Sie ragt sonst in den
Text. Das Bewegtbild ohne Fließtext hat ein eigenes visBottom.