Regel: Oberflaechen immer aus den Produkt-Komponenten

rules.md §0 macht es zur Pflicht, komponenten.md ist das Nachschlagewerk
mit Tokens, Komponenten, Bausteinen und Vorgehen, CLAUDE.md weist jeden
Agenten darauf hin.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 5.5 committed 2026-10-20 18:03:33 +02:00
1 parent 35e68db15b
commit 4dd2ea1d18
3 files changed
+257 -7

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
# posts (PostGrid)
Hier entstehen die Bild- und Animationsposts von HiveGrid. Regeln in `rules.md`.
**PFLICHT, ohne Ausnahme:** Jede Oberfläche in einem Post wird aus den echten Komponenten von `hivegrid-cloud`
nachgebaut, mit deren Maßen, Farben, Schrift, Icons und Beschriftungen, und bildet einen Screen nach, den es im Produkt
gibt. Keine ausgedachten, „vibecoded“ Grafiken. Vor jedem neuen Post `komponenten.md` lesen und die Bausteine aus
`post8.html` übernehmen (rules.md §0).
- Code von hivegrid-cloud immer von `origin/main` lesen (`git show origin/main:<pfad>`), die Arbeitskopie ist veraltet
und wird nicht angefasst.
- PostGrid starten: `./serve.sh`, dann http://127.0.0.1:4321/. Der Export (`node animation.mjs`) lädt immer von Port 4321.
- Texte: schlichtes Deutsch, keine Geviertstriche.
+209
View File
@@ -0,0 +1,209 @@
# 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
1. **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 unter
`apps/web/app/routes/app.$tenant/`, die Texte in `translations.json`.
2. **Immer `origin/main` lesen, 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).
3. **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.
4. **Mit den Bausteinen zeichnen** (Abschnitt 3). Sie stehen in `post8.html` und werden in den neuen Post kopiert, weil
jeder Post eine einzelne Datei ist.
5. **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>`.
---
## 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:
1. den `@font-face`-Block im `<head>`
2. beim Start `document.fonts.load(...)` für 400, 500 und 600, bevor zum ersten Mal gemessen wird
3. `instrumentSansCss()`, angehängt an `fontEmbedCss`, 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-sm` 4 px (Eingabefelder) · `rounded-md` **6 px** (Knöpfe, Karten, Tabs) · `rounded-lg` 8 px (Widgets) ·
`rounded-xl` 12 px. In `theme.css` steht 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
1. **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()` an `fontEmbedCss` hängen. Prüfen, indem man
Export und Vorschau übereinanderlegt.
2. **Messen vor dem Laden.** `pMess()` speichert Breiten zwischen. Wird vor dem Laden der Schrift gemessen, bleibt die
Breite der Ersatzschrift hängen. Darum `document.fonts.load` am Anfang von `boot()`.
3. **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.
4. **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.
5. **Höhe der Karte.** Zu hohe Karten ragen in den Text der Slide. Lieber Schritte oder Hilfetexte weglassen als die
Komponenten zu verkleinern.
6. **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.
+35 -7
View File
@@ -1,13 +1,41 @@
# Post-Generator — Regeln & Konventionen
Arbeitsgrundlage für alles, was in diesem Repo an Social-Grafiken entsteht.
**Stand:** 15.08.2026
**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
@@ -27,7 +55,7 @@ Der Generator läuft lokal: `./serve.sh` → <http://127.0.0.1:4321/post1.html>
>
> | Was | Wert |
> |---|---|
> | Schrift | Instrument Sans (heute hier: Manrope) |
> | 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 |
@@ -36,12 +64,12 @@ Der Generator läuft lokal: `./serve.sh` → <http://127.0.0.1:4321/post1.html>
> | 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` (heute hier: Phosphor) |
> | Symbole | Feather über `@subframe/core`: in Oberflächen als Lucide-Formen (§0), Phosphor nur noch in älteren Posts |
>
> Drei Punkte widersprechen den bisherigen Regeln und sind noch nicht
> entschieden: die Schatten (`--shadow-md`, `--shadow-lg`) gegen §5 „Keine
> Schatten“, der Rahmen `#E5E5E5` gegen `#D5DBE1` aus §1a, Feather gegen
> Phosphor. Die Werte sind hier noch nicht gespiegelt.
> 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**: