initial, vibe-coded version, mostly frontend
This commit is contained in:
+61
@@ -0,0 +1,61 @@
|
|||||||
|
# === Betriebssystem ===
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
Desktop.ini
|
||||||
|
|
||||||
|
# === IntelliJ IDEA / JetBrains (D13) ===
|
||||||
|
# Projektdateien lokal halten; geteilte Einstellungen ggf. gezielt wieder einchecken.
|
||||||
|
.idea/
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
out/
|
||||||
|
|
||||||
|
# === Editoren ===
|
||||||
|
.vscode/
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
|
||||||
|
# === Backend: Gradle / Kotlin / JVM (D13) ===
|
||||||
|
backend/.gradle/
|
||||||
|
backend/build/
|
||||||
|
backend/**/build/
|
||||||
|
!backend/**/src/**/build/
|
||||||
|
.gradle/
|
||||||
|
build/
|
||||||
|
!src/**/build/
|
||||||
|
# Gradle-Wrapper einchecken, aber lokale Overrides ignorieren
|
||||||
|
!gradle/wrapper/gradle-wrapper.jar
|
||||||
|
!gradle/wrapper/gradle-wrapper.properties
|
||||||
|
gradle-app.setting
|
||||||
|
|
||||||
|
# Spring Boot / lokale Laufzeit
|
||||||
|
*.log
|
||||||
|
logs/
|
||||||
|
application-local.properties
|
||||||
|
application-local.yml
|
||||||
|
application-local.yaml
|
||||||
|
|
||||||
|
# === Java-Artefakte ===
|
||||||
|
*.class
|
||||||
|
*.jar
|
||||||
|
*.war
|
||||||
|
*.ear
|
||||||
|
hs_err_pid*
|
||||||
|
replay_pid*
|
||||||
|
!gradle-wrapper.jar
|
||||||
|
|
||||||
|
# === Frontend (Vanilla HTML/CSS/JS, D11) ===
|
||||||
|
# Derzeit build-frei; falls später Tooling dazukommt:
|
||||||
|
frontend/node_modules/
|
||||||
|
frontend/dist/
|
||||||
|
|
||||||
|
# === Geheimnisse / lokale Umgebung ===
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
*.local
|
||||||
|
|
||||||
|
# === Temporäres ===
|
||||||
|
tmp/
|
||||||
|
*.tmp
|
||||||
|
*.bak
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# Werkbaum
|
||||||
|
|
||||||
|
Monorepo für Werkbaum: eine textuelle WBS-Notation mit Und/Oder-Zerlegung
|
||||||
|
(„all of" nebeneinander, „any of" untereinander) samt Editor und geplantem
|
||||||
|
Integrations-Backend.
|
||||||
|
|
||||||
|
## Struktur
|
||||||
|
- `frontend/` — Editor (Vanilla HTML/CSS/JS). Eigene Regeln: frontend/CLAUDE.md
|
||||||
|
- `backend/` — Kotlin/Spring-Boot-Anwendung (Taiga-Integration, Persistenz).
|
||||||
|
Eigene Regeln: backend/CLAUDE.md
|
||||||
|
- `docs/` — Projektdokumente · `brand/` — Marke (brand/BRAND.md)
|
||||||
|
|
||||||
|
## Verbindliche Referenzen
|
||||||
|
- Sprachdefinition: @docs/SPEC.md — die Notation ist der gemeinsame Vertrag
|
||||||
|
von Frontend und Backend. Syntaxänderungen: SPEC **zuerst**, dann Code.
|
||||||
|
- Entscheidungen: @docs/DECISIONS.md — respektieren; Abweichungen als neuen
|
||||||
|
Eintrag begründen, alte Einträge nie löschen. Besonders D13 (Backend-Stack)
|
||||||
|
und D14 (Parser-Hoheit) beachten.
|
||||||
|
- Ziele: docs/ROADMAP.md · Offene Arbeit: docs/TASKS.md (Checkboxen pflegen).
|
||||||
|
|
||||||
|
## Querschnitts-Konventionen
|
||||||
|
- UI-Texte und Doku auf Deutsch.
|
||||||
|
- Keine neuen Laufzeit-Abhängigkeiten ohne Rückfrage (gilt in beiden Teilen).
|
||||||
|
- Der Notationstext ist das führende Datenformat; kein Teil erfindet ein
|
||||||
|
eigenes Speicherformat für die Struktur.
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
<p align="left">
|
||||||
|
<img src="brand/logo.svg" width="72" alt="Werkbaum-Logo">
|
||||||
|
</p>
|
||||||
|
|
||||||
|
# Werkbaum
|
||||||
|
|
||||||
|
Eine textuelle, Markdown-artige Notation für Projektstrukturpläne
|
||||||
|
(Work Breakdown Structure) mit Und/Oder-Zerlegung — und ein Live-Editor,
|
||||||
|
der sie als Diagramm rendert.
|
||||||
|
|
||||||
|
```
|
||||||
|
[~] Werkbaum (XL) https://wiki.example.de/relaunch
|
||||||
|
- [~] Dokumentenspeicher
|
||||||
|
| [x] Textdatei mit Copy+Paste im Frontend (S)
|
||||||
|
- [x] Parser
|
||||||
|
- [x] Texteingabefeld im Frontend
|
||||||
|
| [ ] Backend
|
||||||
|
- [~] Darstellung/Rendern (XL)
|
||||||
|
- [/] H (S) @anna
|
||||||
|
- [ ] CMS-Anbindung (M)
|
||||||
|
| [ ] WordPress
|
||||||
|
| [?] Headless CMS
|
||||||
|
```
|
||||||
|
|
||||||
|
`-` = Pflicht-Teilpaket (all of, im Diagramm nebeneinander) ·
|
||||||
|
`|` = Alternative (any of, untereinander) · `[…]` = Status ·
|
||||||
|
`(M)` = T-Shirt-Aufwand · `@name` = Zuständigkeit · `%%` = Kommentar.
|
||||||
|
|
||||||
|
## Nutzung
|
||||||
|
|
||||||
|
`frontend/index.html` im Browser öffnen — links Text bearbeiten, rechts entsteht das
|
||||||
|
Diagramm live. Toggles: transponierte (schmale) Darstellung, verworfene
|
||||||
|
Elemente einblenden.
|
||||||
|
|
||||||
|
## Projektdokumente
|
||||||
|
|
||||||
|
- `frontend/` — Editor · `backend/` — Kotlin/Spring (Gerüst folgt, siehe backend/README.md)
|
||||||
|
- `docs/SPEC.md` — verbindliche Sprachdefinition
|
||||||
|
- `docs/DECISIONS.md` — Design-Entscheidungen mit Begründung
|
||||||
|
- `docs/ROADMAP.md` — Mermaid-Plugin, Taiga-Integration, Tenzu
|
||||||
|
- `docs/TASKS.md` — offene Aufgaben (Checkboxen)
|
||||||
|
- `docs/brand/BRAND.md` — Logo, Wortbild, Anwendungsregeln
|
||||||
|
- `docs/design/` — Design-Herleitung der Marke
|
||||||
|
- `CLAUDE.md` — Projektkontext für Claude Code
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# Werkbaum · Backend
|
||||||
|
|
||||||
|
Kotlin/Spring-Boot-Anwendung. Aufgaben: Persistenz der Notationstexte,
|
||||||
|
Taiga-Integration (REST-API, `#ref`-Auflösung, Status-Sync), später
|
||||||
|
Tenzu-Adapter. Noch nicht bootstrapped — siehe README.md hier.
|
||||||
|
|
||||||
|
## Konventionen
|
||||||
|
- Kotlin, Spring Boot, Gradle (Kotlin DSL), JDK 21.
|
||||||
|
- Paketwurzel `de.werkbaum`; Schichten: `api` (Controller/DTOs),
|
||||||
|
`domain`, `integration.taiga` (Client, Mapping), `persistence`.
|
||||||
|
- Tests mit JUnit 5 + Kotest-Assertions; Taiga-Client gegen
|
||||||
|
aufgezeichnete Antworten (WireMock), nie gegen Live-Instanzen.
|
||||||
|
- Konfiguration über `application.yml` + Umgebungsvariablen;
|
||||||
|
keine Zugangsdaten im Repository.
|
||||||
|
|
||||||
|
## Wichtig (D14 — Parser-Hoheit)
|
||||||
|
Das Backend parst die Notation **nicht**. Es speichert den Text als Ganzes
|
||||||
|
und arbeitet mit expliziten Metadaten. Sollte Backend-Parsen doch nötig
|
||||||
|
werden: zuerst DECISIONS ergänzen, dann gegen die gemeinsamen Fixtures aus
|
||||||
|
docs/SPEC.md §10 testen — niemals eine zweite, abweichende Grammatik pflegen.
|
||||||
|
|
||||||
|
## Taiga-Mapping (Vorgabe aus docs/ROADMAP.md)
|
||||||
|
- `#123` referenziert Epic/User Story/Task/Issue; Auflösung liefert Titel,
|
||||||
|
URL, Status. Status-Mapping Taiga-Workflow → Notation konfigurierbar
|
||||||
|
(Default: „New"→`[ ]`, „In progress"→`[~]`, „Ready for test"→`[/]`,
|
||||||
|
„Done"→`[x]`, „Archived"→`[^]`).
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
# Werkbaum Backend
|
||||||
|
|
||||||
|
Noch nicht bootstrapped. Vorgesehener Weg:
|
||||||
|
|
||||||
|
1. Gerüst über https://start.spring.io erzeugen: Kotlin · Gradle (Kotlin DSL) ·
|
||||||
|
JDK 21 · Abhängigkeiten: Spring Web, Spring Data JPA, Validation.
|
||||||
|
2. Inhalt dieses Ordners (CLAUDE.md, README.md) beibehalten, Gerüst
|
||||||
|
hineinlegen, Paketwurzel `de.werkbaum`.
|
||||||
|
3. Konventionen: siehe CLAUDE.md in diesem Ordner.
|
||||||
@@ -0,0 +1,85 @@
|
|||||||
|
# Entscheidungen (ADR-Kurzform)
|
||||||
|
|
||||||
|
Festgehaltene Design-Entscheidungen samt Begründung. Bei Änderungen: Eintrag
|
||||||
|
ergänzen, nicht löschen.
|
||||||
|
|
||||||
|
## D1 — `|` für Oder-Zerlegung, `-` für Und-Zerlegung
|
||||||
|
`|` bedeutet in Regex und BNF-Grammatiken bereits „oder“ und ist damit
|
||||||
|
selbsterklärend. `-` ist das gewohnte Markdown-Listenzeichen für „normale“
|
||||||
|
Teilpakete. Das Gate steckt im Aufzählungszeichen der Kinder, nicht in einer
|
||||||
|
Annotation am Parent — lokal lesbar, trivial parsebar.
|
||||||
|
|
||||||
|
## D2 — Status als erweiterte Markdown-Task-Checkbox
|
||||||
|
GitHub-Markdown kennt `[ ]`/`[x]`; die Erweiterung auf `[?] [~] [/] [^] [-]`
|
||||||
|
wirkt sofort vertraut. Mnemonik: `?` vage, leeres Kästchen = todo, `~` gängige
|
||||||
|
In-progress-Konvention, `/` = halbes `x`, `x` = erledigt, `^` = „nach oben
|
||||||
|
deployed“, `-` = durchgestrichen.
|
||||||
|
|
||||||
|
## D3 — Begriff „Durchstich“ für den Zwischenstatus
|
||||||
|
Zustand zwischen „in Arbeit“ und „fertig“: durchgängig funktionsfähig und
|
||||||
|
vorführbar, Feinarbeiten offen. „Durchstich“ ist als Fachbegriff (Tunnelbau,
|
||||||
|
End-to-End-Slice in der Softwarearchitektur) etabliert. Verworfene
|
||||||
|
Alternativen: „Feinschliff“, „Richtfest“, „vorführbereit“, „Beta“.
|
||||||
|
|
||||||
|
## D4 — Begriff „verworfen“ statt „gestrichen“
|
||||||
|
Beschreibt die bewusste Entscheidung gegen eine Option — besonders passend
|
||||||
|
für Any-of-Alternativen. Default ausgeblendet, Toggle „verworfene einblenden“.
|
||||||
|
|
||||||
|
## D5 — Farb-Logik folgt dem Risiko, nicht dem Fortschritt
|
||||||
|
Rosé = Kosten investiert, Risiko hoch (in Arbeit). Pastellgelb = läuft,
|
||||||
|
Restrisiko klein (Durchstich). Flieder = geplant (Absicht, nichts investiert,
|
||||||
|
zwischen Grau der Idee und Rosé der Arbeit). Ergebnis-Skala:
|
||||||
|
grau → flieder → rosé → gelb → grün → blau.
|
||||||
|
|
||||||
|
## D6 — Größe in Klammern, URL nackt, Personen mit `@`
|
||||||
|
`(M)` liest sich wie ein Kleidungsetikett. URLs werden ohne Link-Syntax
|
||||||
|
erkannt (einfach einfügbar); die URL wird **vor** den `@`-Tags extrahiert,
|
||||||
|
damit `https://user@host/…` nicht kollidiert. `@` ist die universelle
|
||||||
|
Mention-Konvention (GitHub, Slack, Jira).
|
||||||
|
|
||||||
|
## D7 — `%%` als Kommentarzeichen
|
||||||
|
Mermaid-Konvention; hält den Weg zum Mermaid-Plugin frei. `//` scheidet wegen
|
||||||
|
URLs aus, `#` bleibt für Referenzen/Tags reserviert, `<!-- -->` ist zu sperrig.
|
||||||
|
|
||||||
|
## D8 — Untergliederungspflicht ab M sichtbar machen
|
||||||
|
Fehlende Zerlegung wird nicht nur gemeldet, sondern **gezeigt**: Geister-Knoten
|
||||||
|
„Untergliederung fehlt“ hängt genau dort, wo der Ast weitergehen müsste.
|
||||||
|
Verworfene Elemente sind ausgenommen. Werden alle Kinder eines M+-Elements
|
||||||
|
verworfen und ausgeblendet, erscheint der Platzhalter wieder (es braucht eine
|
||||||
|
neue Zerlegung) — gewollt.
|
||||||
|
|
||||||
|
## D9 — Transponierte Darstellung mit unterschiedlichen Austrittsseiten
|
||||||
|
Im vertikalen Modus tritt all of **rechts** aus dem Parent aus (LR-Baum),
|
||||||
|
any of **unten links**. Austrittsseite + Linienstil (durchgezogen/gestrichelt)
|
||||||
|
codieren das Gate doppelt; Beschriftungen werden nie rotiert.
|
||||||
|
|
||||||
|
## D10 — Abzweige zielen auf Knotenmitte, nicht Teilbaummitte
|
||||||
|
Feste Knoten-Zeilenhöhe macht die Anschlusshöhe deterministisch (23 px).
|
||||||
|
Behebt „ins Leere laufende“ Linien bei eingerückten Unterbäumen.
|
||||||
|
|
||||||
|
## D11 — Technologie: Vanilla HTML/CSS/JS, eine Datei als Prototyp
|
||||||
|
Keine Frameworks, kein Build-Zwang. Parser ~30 Zeilen, Renderer erzeugt
|
||||||
|
verschachtelte `ul.and`/`ul.or`; Linien via CSS-Pseudo-Elemente. Modularisierung
|
||||||
|
(Parser/Renderer/UI getrennt + Tests) ist der geplante nächste Schritt.
|
||||||
|
|
||||||
|
## D12 — Name „Werkbaum" und Marke aus der Notation
|
||||||
|
Name: „Werk" + „Baum" = wörtlich der WBS-Baum; funktioniert als npm-Paket,
|
||||||
|
CLI-Befehl und Mermaid-Schlüsselwort (`werkbaum`, kleingeschrieben in
|
||||||
|
Code-Kontexten). Verworfene Kandidaten: Astrein, undoder, Aufriss, Gabelung.
|
||||||
|
Marke: Miniatur des Diagramms (K2) in zwei Orientierungen analog zu den
|
||||||
|
Darstellungsmodi — Hochformat als Primärzeichen, transponiert als
|
||||||
|
Sekundärzeichen. Nur Tinte + Petrol; durchgezogen = und, gestrichelt = oder;
|
||||||
|
Pastelltöne bleiben Statusfarben. Details: brand/BRAND.md.
|
||||||
|
|
||||||
|
## D13 — Backend in Kotlin/Spring Boot, Monorepo-Struktur
|
||||||
|
Backend als Kotlin/Spring-Boot-Anwendung (Gradle Kotlin DSL, JDK 21),
|
||||||
|
entwickelt in IntelliJ IDEA Ultimate. Monorepo mit `frontend/` und `backend/`;
|
||||||
|
CLAUDE.md dreistufig: Wurzel für Querschnitt, je eine pro Teilprojekt
|
||||||
|
(wird von Claude Code pfadbezogen geladen). Kein Node- oder Python-Backend.
|
||||||
|
|
||||||
|
## D14 — Parser-Hoheit liegt beim Frontend, SPEC ist normativ
|
||||||
|
Der Notationstext ist das führende Datenformat. Das Backend parst die
|
||||||
|
Notation nicht; es speichert den Text als Ganzes und bedient Integrationen
|
||||||
|
über explizite Metadaten. Wird Backend-Parsen später doch erforderlich,
|
||||||
|
gilt: docs/SPEC.md ist normativ, beide Parser testen gegen dieselben
|
||||||
|
Fixtures (SPEC §10) — es darf keine zweite, abweichende Grammatik entstehen.
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
# Roadmap
|
||||||
|
|
||||||
|
## Nahziel: tragfähige Codebasis
|
||||||
|
- Single-HTML-Prototyp (`index.html`) in Module zerlegen: `parser`, `model`,
|
||||||
|
`render`, `app`. Parser und Renderer müssen headless (ohne DOM-Editor)
|
||||||
|
nutzbar sein — Voraussetzung für alle Integrationen.
|
||||||
|
- Testsuite gegen `docs/SPEC.md` (kanonisches Beispiel als Fixture).
|
||||||
|
|
||||||
|
## Mermaid-Plugin
|
||||||
|
Ziel: ```wbs```-Blöcke in Mermaid-Umgebungen rendern.
|
||||||
|
- Offizieller Mechanismus: `mermaid.registerExternalDiagrams()` mit
|
||||||
|
Detektor (Schlüsselwort `wbs`), Parser, DiagramDB, Renderer.
|
||||||
|
Präzedenzfälle: ZenUML (extern), Mindmap (ursprünglich extern, ebenfalls
|
||||||
|
einrückungsbasierte Syntax).
|
||||||
|
- Hauptaufwand: **SVG-Renderer** mit eigener Layout-Berechnung
|
||||||
|
(Knotenmaße messen, Positionen, Verbinder als Pfade) inkl. Mischlayout
|
||||||
|
und transponiertem Modus. Toggles werden zu Syntax-Optionen
|
||||||
|
(z. B. `wbs LR`, Direktiven).
|
||||||
|
- Einschränkungen: wirkt nur in selbst initialisierten Mermaid-Instanzen
|
||||||
|
(nicht GitHub/GitLab/Notion); Lazy-Loading externer Diagramme galt zuletzt
|
||||||
|
als experimentell. Für universelle Verfügbarkeit: PR als eingebautes
|
||||||
|
Diagramm (Vorbild Mindmap/Kanban).
|
||||||
|
|
||||||
|
## Taiga-Integration
|
||||||
|
Ziel: Knoten mit Taiga-Objekten verlinken, Status automatisch synchronisieren.
|
||||||
|
- Architektur nach **Seedtime-Vorbild**: Companion-Ansatz — der Editor
|
||||||
|
(frontend/) plus ein Kotlin/Spring-Backend (backend/, D13) sprechen per
|
||||||
|
REST-API mit Taiga; optional dünnes
|
||||||
|
contrib-Frontend-Plugin (Menüpunkt im Projekt) und Backend-Paket
|
||||||
|
(Speicherung in Taigas DB statt Wiki-Seite).
|
||||||
|
- Syntax-Erweiterung `#123`: Referenz auf Epic/User Story/Task/Issue;
|
||||||
|
App löst Titel, Link und Status per API auf. Status-Mapping
|
||||||
|
Taiga-Workflow → Notation (z. B. „In progress“ → `[~]`, „Done“ → `[x]`).
|
||||||
|
- Aktualisierung: API-Abfrage beim Öffnen; Push via Webhooks.
|
||||||
|
Rückrichtung (Status im WBS ändern → Taiga) möglich.
|
||||||
|
- Einschränkungen: Plugins nur self-hosted (nicht taiga.io-Cloud);
|
||||||
|
Taiga 6 im Wartungsmodus, Frontend AngularJS-Altbestand — daher
|
||||||
|
Companion-App bevorzugen.
|
||||||
|
|
||||||
|
## Tenzu (Beobachten)
|
||||||
|
Nachfolger von „Taiga Next“; seit Ende Juli 2024 von der französischen
|
||||||
|
Genossenschaft Biru entwickelt (Kaleidos fokussiert Penpot). Open Source,
|
||||||
|
in laufender Entwicklung, Integrationsfähigkeit erklärtes Ziel — noch zu
|
||||||
|
jung als Plattform-Ziel. Companion-App so schneiden, dass ein späterer
|
||||||
|
Umzug Taiga → Tenzu nur den API-Adapter betrifft.
|
||||||
|
|
||||||
|
## Kleinere Ideen
|
||||||
|
- Deterministische Pastellfarbe pro `@name` (Personen wiedererkennen).
|
||||||
|
- SVG/PNG-Export für Präsentationen.
|
||||||
|
- Sichtbare Anmerkungen am Knoten (eigene Syntax, getrennt von `%%`).
|
||||||
|
- Aufwands-Rollup: Größen der Kinder aggregieren und mit Parent vergleichen.
|
||||||
|
- Attribut-Syntax für Termine/Meilensteine.
|
||||||
+140
@@ -0,0 +1,140 @@
|
|||||||
|
# WBS-Notation – Spezifikation
|
||||||
|
|
||||||
|
Textuelle Notation für Projektstrukturpläne (Work Breakdown Structure) mit
|
||||||
|
Und/Oder-Zerlegung. Diese Datei ist die verbindliche Sprachdefinition.
|
||||||
|
Syntaxänderungen werden zuerst hier dokumentiert, dann implementiert.
|
||||||
|
|
||||||
|
## 1. Zeilenformat
|
||||||
|
|
||||||
|
```
|
||||||
|
[Einrückung][Zeichen] [Statusbox] Label (Größe) URL @tag … %% Kommentar
|
||||||
|
```
|
||||||
|
|
||||||
|
Alle Bestandteile außer dem Label sind optional. Die Extraktion erfolgt in
|
||||||
|
dieser Reihenfolge (wichtig für Kollisionsfreiheit):
|
||||||
|
|
||||||
|
1. Kommentar entfernen: alles ab `%%` bis Zeilenende.
|
||||||
|
2. Einrückung, Zeichen (`-` / `|`) und Statusbox `[…]` per Zeilen-Regex.
|
||||||
|
3. URL: erstes Token, das auf `https?://\S+` passt (dadurch stören `@` in URLs nicht).
|
||||||
|
4. Größe: erstes `(XS|S|M|L|XL|XXL)`, Groß-/Kleinschreibung egal.
|
||||||
|
5. Tags: alle `@name`-Vorkommen.
|
||||||
|
6. Rest, whitespace-normalisiert = Label. Leeres Label ⇒ Zeile ignorieren.
|
||||||
|
|
||||||
|
Referenz-Regex der Implementierung:
|
||||||
|
|
||||||
|
```
|
||||||
|
^([ \t]*)([-|])?\s*(?:\[([ ?~xX^/-])\]\s*)?(.*)$
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Hierarchie
|
||||||
|
|
||||||
|
- Die Einrückung bestimmt die Ebene. Es gibt keine feste Schrittweite:
|
||||||
|
Elternknoten ist die nächste vorangehende Zeile mit **kleinerer**
|
||||||
|
Einrückungsbreite (Tab zählt als 2 Leerzeichen).
|
||||||
|
- Zeilen ohne Zeichen (`-`/`|`) sind Wurzelknoten. Mehrere Wurzeln = mehrere
|
||||||
|
Bäume nebeneinander.
|
||||||
|
|
||||||
|
## 3. Zerlegungsart (Gate)
|
||||||
|
|
||||||
|
| Zeichen | Bedeutung | Semantik |
|
||||||
|
|---|---|---|
|
||||||
|
| `-` | all of (Und-Zerlegung) | Alle Teilpakete sind erforderlich. |
|
||||||
|
| `\|` | any of (Oder-Zerlegung) | Mindestens eine Alternative wird gewählt. |
|
||||||
|
|
||||||
|
- Das Gate ist eine Eigenschaft der Geschwistergruppe; alle Geschwister sollen
|
||||||
|
dasselbe Zeichen tragen.
|
||||||
|
- Gemischte Geschwister: Darstellung nach dem **ersten** Kind, plus Warnung
|
||||||
|
mit Zeilennummer.
|
||||||
|
|
||||||
|
## 4. Status
|
||||||
|
|
||||||
|
Codiert als Checkbox nach dem Zeichen (Erweiterung der Markdown-Task-Syntax):
|
||||||
|
|
||||||
|
| Code | Key | Name | Bedeutung | Hintergrund | Rahmen |
|
||||||
|
|---|---|---|---|---|---|
|
||||||
|
| `[?]` | idee | Idee | vage Idee | `#EBEDEF` (grau) | `#A2ABB5` |
|
||||||
|
| `[ ]` | geplant | geplant | beschlossen, nichts investiert | `#EBE4F6` (flieder) | `#A991D4` |
|
||||||
|
| `[~]` | arbeit | in Arbeit | Kosten investiert, Risiko hoch | `#FADDE4` (rosé) | `#D897A8` |
|
||||||
|
| `[/]` | durchstich | Durchstich | funktionsbereit/vorführbar, Feinarbeiten offen | `#FBF2CE` (pastellgelb) | `#D9BE63` |
|
||||||
|
| `[x]` | fertig | fertig | abgeschlossen | `#DCF1DE` (pastellgrün) | `#86C293` |
|
||||||
|
| `[^]` | prod | in Produktion | deployed/live | `#DBEAF8` (pastellblau) | `#85ACD7` |
|
||||||
|
| `[-]` | verworfen | verworfen | bewusst nicht weiterverfolgt | `#F1F2F4`, gestrichelter Rahmen, Text durchgestrichen | `#B3BAC2` |
|
||||||
|
|
||||||
|
- Ohne Statusbox: neutraler Knoten (weiß).
|
||||||
|
- `x` auch als `X` zulässig.
|
||||||
|
- Verworfene Knoten (inkl. Teilbaum) sind per Default **ausgeblendet**;
|
||||||
|
Toggle „verworfene einblenden“ zeigt sie.
|
||||||
|
|
||||||
|
## 5. Aufwand (T-Shirt-Größen)
|
||||||
|
|
||||||
|
- Werte: `XS < S < M < L < XL < XXL`, notiert in Klammern, z. B. `(L)`.
|
||||||
|
- **Untergliederungsregel:** Ab `(M)` muss ein Element weiter zerlegt sein.
|
||||||
|
Ein Element ≥ M **ohne Kinder** erhält:
|
||||||
|
- gestrichelte orange Umrandung (`--warn`, `#B45309`) und
|
||||||
|
- einen Geister-Knoten „Untergliederung fehlt“ an gestrichelter Linie darunter.
|
||||||
|
- Ausnahme: verworfene Elemente lösen die Regel nie aus.
|
||||||
|
- Anzeige: dunkles Badge oben rechts an der Knoten-Ecke.
|
||||||
|
|
||||||
|
## 6. Links
|
||||||
|
|
||||||
|
- Ein nacktes `https://…`-Token macht den ganzen Knoten klickbar
|
||||||
|
(neuer Tab, `rel="noopener"`); Kennzeichnung mit ↗ hinter dem Label.
|
||||||
|
|
||||||
|
## 7. Personen-Tags
|
||||||
|
|
||||||
|
- `@name` mit `name` aus Unicode-Buchstaben, Ziffern, `.`, `_`, `-`.
|
||||||
|
- Mehrere Tags pro Zeile möglich, Position im Text egal.
|
||||||
|
- Anzeige: helle Pillen unten rechts an der Knoten-Ecke.
|
||||||
|
|
||||||
|
## 8. Kommentare
|
||||||
|
|
||||||
|
- `%%` leitet einen Kommentar ein — ganze Zeile oder ab Zeilenmitte.
|
||||||
|
- Konvention aus Mermaid übernommen; `%%{` vermeiden (dort Direktiven-Syntax).
|
||||||
|
|
||||||
|
## 9. Darstellung
|
||||||
|
|
||||||
|
### Normalmodus
|
||||||
|
- **all of:** Kinder nebeneinander, klassischer Organigramm-Fächer,
|
||||||
|
durchgezogene Linien (Tinte `#41556E`).
|
||||||
|
- **any of:** Alternativen untereinander; durchgezogene vertikale Linie links
|
||||||
|
unterhalb des Parents, **gestrichelte** Abzweige zu den Alternativen
|
||||||
|
(Petrol `#0F766E`). Alternative-Knoten mit petrolfarbenem Rahmen.
|
||||||
|
|
||||||
|
### Transponierter Modus (Toggle „schmal (vertikal)“)
|
||||||
|
- **all of:** Linie tritt **rechts auf halber Höhe** aus dem Parent aus
|
||||||
|
(entspricht Richtung LR), vertikale Sammelleiste, durchgezogene Abzweige;
|
||||||
|
Kinder rechts eingerückt untereinander.
|
||||||
|
- **any of:** unverändert — Austritt **unten links**, gestrichelte Abzweige.
|
||||||
|
- Merkregel: Austrittsseite codiert das Gate (rechts = und, unten = oder),
|
||||||
|
Linienstil bestätigt es.
|
||||||
|
|
||||||
|
### Geometrie-Invarianten
|
||||||
|
- Knoten haben feste Zeilenhöhe (`line-height: 1.3`), damit Abzweige
|
||||||
|
deterministisch auf Knotenmitte liegen (Offset 23 px = 5 px Listenabstand
|
||||||
|
+ halbe Knotenhöhe). Abzweige zielen auf den **Knoten**, nie auf die Mitte
|
||||||
|
des Teilbaums.
|
||||||
|
|
||||||
|
## 10. Beispiel (kanonisch)
|
||||||
|
|
||||||
|
```
|
||||||
|
%% Projektstruktur – Stand Sprint 14
|
||||||
|
[~] Website-Relaunch (XL) https://wiki.example.de/relaunch
|
||||||
|
- [x] Konzeption (M)
|
||||||
|
- [x] Zielgruppenanalyse (S)
|
||||||
|
- [x] Sitemap (XS)
|
||||||
|
- [~] Umsetzung (XL)
|
||||||
|
- [/] Frontend (S) https://git.example.de/frontend @anna
|
||||||
|
- [ ] Backend (L) @ben @carla
|
||||||
|
- [ ] CMS-Anbindung (M)
|
||||||
|
| [ ] WordPress
|
||||||
|
| [?] Headless CMS
|
||||||
|
| [-] Eigenentwicklung %% Aufwand zu hoch
|
||||||
|
- [?] Hosting (M)
|
||||||
|
| Cloud
|
||||||
|
| On-Premise
|
||||||
|
```
|
||||||
|
|
||||||
|
## 11. Reservierte Erweiterungen (noch nicht implementiert)
|
||||||
|
|
||||||
|
- `#123` — Referenz auf externe Tickets (geplant für Taiga-Integration).
|
||||||
|
- `#tag` — freie Schlagworte (deshalb `#` nicht anderweitig verwenden).
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
# Aufgaben
|
||||||
|
|
||||||
|
Abhaken beim Erledigen; neue Aufgaben unten anfügen.
|
||||||
|
|
||||||
|
## Phase 1 — Modularisierung & Tests
|
||||||
|
- [ ] Projektgerüst: `src/` (parser.js, model.js, render.js, app.js),
|
||||||
|
`tests/`, `index.html` bindet Module ein; weiterhin ohne Build nutzbar
|
||||||
|
(ES-Module) oder mit minimalem Setup (Vite) — Entscheidung dokumentieren.
|
||||||
|
- [ ] Parser extrahieren; Verhalten exakt wie in `docs/SPEC.md` §1–§8.
|
||||||
|
- [ ] Unit-Tests für den Parser (Vitest): kanonisches Beispiel aus SPEC §10
|
||||||
|
als Fixture; Randfälle: gemischte Gates, Tabs/ungleichmäßige Einrückung,
|
||||||
|
URL mit `@`, mehrere Wurzeln, leere Labels, `%%` am Zeilenanfang/-ende.
|
||||||
|
- [ ] Renderer extrahieren (HTML-String-Erzeugung), Snapshot-Tests für
|
||||||
|
Normal- und Vertikalmodus sowie „Untergliederung fehlt“.
|
||||||
|
- [ ] Warnungs-Modell vereinheitlichen (Zeilennummern, Typen).
|
||||||
|
|
||||||
|
## Phase 2 — Qualität
|
||||||
|
- [ ] Barrierefreiheit: Fokusreihenfolge, aria-Labels für Status/Größe/Tags.
|
||||||
|
- [ ] Druck-Stylesheet (Diagramm ohne Editor-Panel).
|
||||||
|
- [ ] Fehlertolerantes Parsen weiter ausbauen (unbekannte Statuszeichen melden).
|
||||||
|
|
||||||
|
## Phase 3 — Integrationen (siehe docs/ROADMAP.md)
|
||||||
|
- [ ] Backend-Gerüst per Spring Initializr in `backend/` anlegen
|
||||||
|
(Kotlin, Gradle Kotlin DSL, JDK 21; Konventionen: backend/CLAUDE.md).
|
||||||
|
- [ ] SVG-Renderer (Layout-Engine) als gemeinsame Basis für Export und
|
||||||
|
Mermaid-Plugin.
|
||||||
|
- [ ] Mermaid-Plugin-Spike: Detektor + Registrierung, ein Minimalbaum.
|
||||||
|
- [ ] Taiga-Spike: `#ref`-Syntax parsen, Status via REST-API auflösen
|
||||||
|
(read-only), Mapping konfigurierbar.
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
# Werkbaum — Marke
|
||||||
|
|
||||||
|
Leitidee: Die Marke ist aus den Strichen der Notation gebaut —
|
||||||
|
**durchgezogen = und**, **gestrichelt = oder**. Das Zeichen ist eine Miniatur
|
||||||
|
des Diagramms; es existiert in zwei Orientierungen, analog zu den beiden
|
||||||
|
Darstellungsmodi des Produkts.
|
||||||
|
|
||||||
|
## Dateien (`brand/`)
|
||||||
|
|
||||||
|
| Datei | Zweck |
|
||||||
|
|---|---|
|
||||||
|
| `logo.svg` | Primärzeichen, Hochformat (Variante A), helle Flächen |
|
||||||
|
| `logo-dark.svg` | Primärzeichen für dunkle Flächen |
|
||||||
|
| `logo-quer.svg` | Sekundärzeichen, transponiert (Variante B), für breite/niedrige Flächen (Header-Leisten, Banner) |
|
||||||
|
| `logo-quer-dark.svg` | Sekundärzeichen für dunkle Flächen |
|
||||||
|
| `favicon.svg` | vereinfachte Zwei-Ebenen-Form für 16–32 px |
|
||||||
|
|
||||||
|
Design-Herleitung und Alternativen: `docs/design/logo-konzepte.html` und
|
||||||
|
`docs/design/logo-k2-varianten.html`.
|
||||||
|
|
||||||
|
## Farben
|
||||||
|
|
||||||
|
- Tinte `#243447`, Petrol `#0F766E` (Oder-Anteile).
|
||||||
|
- Auf dunklem Grund: Papier `#F3F6F9`, Petrol hell `#45C4B4`.
|
||||||
|
- Die Pastelltöne des Produkts sind **Statusfarben** und erscheinen nie im Logo.
|
||||||
|
- Einfarbige Variante: alles in Tinte; die Strichelung bleibt erhalten.
|
||||||
|
|
||||||
|
## Wortbild
|
||||||
|
|
||||||
|
- IBM Plex Sans SemiBold, Zweiteilung: **Werk** (Tinte) + **baum** (Petrol),
|
||||||
|
letter-spacing −0.02em.
|
||||||
|
- CLI/npm/Code-Kontexte: konsequent kleingeschrieben `werkbaum`
|
||||||
|
(IBM Plex Mono).
|
||||||
|
|
||||||
|
## Regeln
|
||||||
|
|
||||||
|
- Gestrichelte Anteile behalten Richtung und Strichmuster; nichts rotieren.
|
||||||
|
- Unter 24 px immer `favicon.svg` verwenden, nie das Primärzeichen verkleinern.
|
||||||
|
- Schutzraum: mindestens die Höhe des Wurzel-Kästchens rundum.
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Werkbaum · Favicon (vereinfachte Zwei-Ebenen-Form) -->
|
||||||
|
<rect x="18" y="4" width="28" height="16" rx="4" fill="#243447"/>
|
||||||
|
<path d="M32 20 V26 M12 26 H52 M12 26 V32 M52 26 V32" fill="none" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="2" y="32" width="24" height="16" rx="4" fill="#fff" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="38" y="32" width="24" height="16" rx="4" fill="#fff" stroke="#0F766E" stroke-width="5"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 502 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Werkbaum · Primärzeichen (Variante A, Hochformat) · für dunkle Flächen -->
|
||||||
|
<rect x="22" y="5" width="20" height="12" rx="3.5" fill="#F3F6F9"/>
|
||||||
|
<path d="M32 17 V22 M14 22 H50 M14 22 V27 M50 22 V27" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="4" y="27" width="20" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="40" y="27" width="20" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<path d="M50 39 V46" fill="none" stroke="#45C4B4" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="46" width="20" height="12" rx="3.5" fill="none" stroke="#45C4B4" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 728 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Werkbaum · Sekundärzeichen (Variante B, transponiert) · für dunkle Flächen -->
|
||||||
|
<rect x="4" y="26" width="20" height="12" rx="3.5" fill="#F3F6F9"/>
|
||||||
|
<path d="M24 32 H30 M30 14 V32 M30 14 H38 M30 32 H38" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<path d="M30 32 V50 M30 50 H38" fill="none" stroke="#45C4B4" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="38" y="8" width="22" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="38" y="26" width="22" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="38" y="44" width="22" height="12" rx="3.5" fill="none" stroke="#45C4B4" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 743 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Werkbaum · Sekundärzeichen (Variante B, transponiert) · hell -->
|
||||||
|
<rect x="4" y="26" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M24 32 H30 M30 14 V32 M30 14 H38 M30 32 H38" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M30 32 V50 M30 50 H38" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="38" y="8" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="38" y="26" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="38" y="44" width="22" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 727 B |
@@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||||
|
<!-- Werkbaum · Primärzeichen (Variante A, Hochformat) · hell -->
|
||||||
|
<rect x="22" y="5" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M32 17 V22 M14 22 H50 M14 22 V27 M50 22 V27" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="4" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="40" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M50 39 V46" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="46" width="20" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 712 B |
@@ -0,0 +1,213 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Werkbaum · K2 „Die Miniatur" — zwei Varianten</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--paper:#F3F6F9;
|
||||||
|
--grid:rgba(36,52,71,.055);
|
||||||
|
--ink:#243447;
|
||||||
|
--card:#FFFFFF;
|
||||||
|
--muted:#6B7A8C;
|
||||||
|
--or:#0F766E;
|
||||||
|
--or-dark:#45C4B4;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
body{
|
||||||
|
font-family:'IBM Plex Sans',system-ui,sans-serif;
|
||||||
|
color:var(--ink);
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(0deg,transparent 0 23px,var(--grid) 23px 24px),
|
||||||
|
repeating-linear-gradient(90deg,transparent 0 23px,var(--grid) 23px 24px),
|
||||||
|
var(--paper);
|
||||||
|
min-height:100vh;
|
||||||
|
padding:44px 24px 72px;
|
||||||
|
}
|
||||||
|
main{max-width:980px;margin:0 auto}
|
||||||
|
.eyebrow{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.68rem;
|
||||||
|
letter-spacing:.12em;text-transform:uppercase;color:var(--or);
|
||||||
|
}
|
||||||
|
h1{font-size:1.5rem;font-weight:600;letter-spacing:-.01em;margin-top:6px}
|
||||||
|
header p{margin-top:8px;color:var(--muted);font-size:.92rem;max-width:66ch;line-height:1.6}
|
||||||
|
header p b{color:var(--ink);font-weight:500}
|
||||||
|
|
||||||
|
.variant{
|
||||||
|
margin-top:28px;
|
||||||
|
background:var(--card);
|
||||||
|
border:1px solid rgba(36,52,71,.14);
|
||||||
|
border-radius:16px;
|
||||||
|
padding:24px;
|
||||||
|
}
|
||||||
|
.v-head{display:flex;align-items:baseline;gap:12px;margin-bottom:18px}
|
||||||
|
.v-head h2{font-size:1.05rem;font-weight:600}
|
||||||
|
.v-head span{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.62rem;
|
||||||
|
letter-spacing:.08em;text-transform:uppercase;color:var(--muted);
|
||||||
|
}
|
||||||
|
.stage{display:flex;gap:16px;flex-wrap:wrap;align-items:center}
|
||||||
|
.swatch{
|
||||||
|
border-radius:14px;display:flex;align-items:center;justify-content:center;flex:0 0 auto;
|
||||||
|
}
|
||||||
|
.sw-paper{width:140px;height:140px;background:var(--paper);border:1px solid rgba(36,52,71,.12)}
|
||||||
|
.sw-ink{width:140px;height:140px;background:var(--ink)}
|
||||||
|
.minis{display:flex;flex-direction:column;gap:8px;align-items:center}
|
||||||
|
.minis .swatch{background:var(--paper);border:1px solid rgba(36,52,71,.12);border-radius:7px}
|
||||||
|
.m32{width:48px;height:48px}
|
||||||
|
.m16{width:28px;height:28px}
|
||||||
|
.minis span{font-family:'IBM Plex Mono',monospace;font-size:.58rem;color:var(--muted)}
|
||||||
|
.lockup{
|
||||||
|
display:flex;align-items:center;gap:14px;
|
||||||
|
background:var(--paper);border:1px solid rgba(36,52,71,.12);
|
||||||
|
border-radius:14px;padding:18px 24px;flex:1 1 260px;min-width:250px;
|
||||||
|
}
|
||||||
|
.wordmark{font-size:1.75rem;font-weight:600;letter-spacing:-.02em;line-height:1}
|
||||||
|
.wordmark .w1{color:var(--ink)}
|
||||||
|
.wordmark .w2{color:var(--or)}
|
||||||
|
.variant p{margin-top:16px;font-size:.88rem;color:var(--muted);line-height:1.7;max-width:76ch}
|
||||||
|
.variant p b{color:var(--ink);font-weight:500}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<span class="eyebrow">Markenentwurf · K2 ausgearbeitet</span>
|
||||||
|
<h1>Die Miniatur — zwei Varianten</h1>
|
||||||
|
<p>Beide Varianten zeigen denselben Baum: gefüllte Wurzel, Und-Fächer mit
|
||||||
|
durchgezogenen Linien, ein Oder-Anschluss gestrichelt in Petrol. Sie
|
||||||
|
entsprechen den <b>zwei Darstellungsmodi des Produkts</b> — Hochformat wie
|
||||||
|
die normale Ansicht, Querformat wie die transponierte.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="variant">
|
||||||
|
<div class="v-head"><h2>Variante A · Hochformat</h2><span>Primärzeichen</span></div>
|
||||||
|
<div class="stage">
|
||||||
|
<div class="swatch sw-paper">
|
||||||
|
<svg width="112" height="112" viewBox="0 0 64 64">
|
||||||
|
<rect x="22" y="5" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M32 17 V22 M14 22 H50 M14 22 V27 M50 22 V27" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="4" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="40" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M50 39 V46" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="46" width="20" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch sw-ink">
|
||||||
|
<svg width="112" height="112" viewBox="0 0 64 64">
|
||||||
|
<rect x="22" y="5" width="20" height="12" rx="3.5" fill="#F3F6F9"/>
|
||||||
|
<path d="M32 17 V22 M14 22 H50 M14 22 V27 M50 22 V27" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="4" y="27" width="20" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="40" y="27" width="20" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<path d="M50 39 V46" fill="none" stroke="#45C4B4" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="46" width="20" height="12" rx="3.5" fill="none" stroke="#45C4B4" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="minis">
|
||||||
|
<div class="swatch m32">
|
||||||
|
<svg width="36" height="36" viewBox="0 0 64 64">
|
||||||
|
<rect x="22" y="5" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M32 17 V22 M14 22 H50 M14 22 V27 M50 22 V27" fill="none" stroke="#243447" stroke-width="4"/>
|
||||||
|
<rect x="4" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="4"/>
|
||||||
|
<rect x="40" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="4"/>
|
||||||
|
<path d="M50 39 V46" fill="none" stroke="#0F766E" stroke-width="4" stroke-dasharray="5 5"/>
|
||||||
|
<rect x="40" y="46" width="20" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch m16">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 64 64">
|
||||||
|
<rect x="18" y="4" width="28" height="16" rx="4" fill="#243447"/>
|
||||||
|
<path d="M32 20 V26 M12 26 H52 M12 26 V32 M52 26 V32" fill="none" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="2" y="32" width="24" height="16" rx="4" fill="#fff" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="38" y="32" width="24" height="16" rx="4" fill="#fff" stroke="#0F766E" stroke-width="5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span>32 / 16 px</span>
|
||||||
|
</div>
|
||||||
|
<div class="lockup">
|
||||||
|
<svg width="46" height="46" viewBox="0 0 64 64">
|
||||||
|
<rect x="22" y="5" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M32 17 V22 M14 22 H50 M14 22 V27 M50 22 V27" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="4" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="40" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M50 39 V46" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="46" width="20" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
<span class="wordmark"><span class="w1">Werk</span><span class="w2">baum</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Drei Ebenen erzählen die ganze Geschichte: Wurzel, Und-Fächer, und aus dem
|
||||||
|
rechten Paket hängt gestrichelt eine Alternative. <b>Einsatz:</b> Website,
|
||||||
|
Doku-Titel, App-Icon, README. Für 16 px die vereinfachte Form — zwei Ebenen,
|
||||||
|
der Oder-Anteil wandert als petrolfarbener Rahmen ins rechte Kind.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="variant">
|
||||||
|
<div class="v-head"><h2>Variante B · Transponiert</h2><span>Sekundärzeichen für breite Flächen</span></div>
|
||||||
|
<div class="stage">
|
||||||
|
<div class="swatch sw-paper">
|
||||||
|
<svg width="112" height="112" viewBox="0 0 64 64">
|
||||||
|
<rect x="4" y="26" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M24 32 H30 M30 14 V32 M30 14 H38 M30 32 H38" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M30 32 V50 M30 50 H38" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="38" y="8" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="38" y="26" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="38" y="44" width="22" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch sw-ink">
|
||||||
|
<svg width="112" height="112" viewBox="0 0 64 64">
|
||||||
|
<rect x="4" y="26" width="20" height="12" rx="3.5" fill="#F3F6F9"/>
|
||||||
|
<path d="M24 32 H30 M30 14 V32 M30 14 H38 M30 32 H38" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<path d="M30 32 V50 M30 50 H38" fill="none" stroke="#45C4B4" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="38" y="8" width="22" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="38" y="26" width="22" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="38" y="44" width="22" height="12" rx="3.5" fill="none" stroke="#45C4B4" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="minis">
|
||||||
|
<div class="swatch m32">
|
||||||
|
<svg width="36" height="36" viewBox="0 0 64 64">
|
||||||
|
<rect x="4" y="26" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M24 32 H30 M30 14 V32 M30 14 H38 M30 32 H38" fill="none" stroke="#243447" stroke-width="4"/>
|
||||||
|
<path d="M30 32 V50 M30 50 H38" fill="none" stroke="#0F766E" stroke-width="4" stroke-dasharray="5 5"/>
|
||||||
|
<rect x="38" y="8" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="4"/>
|
||||||
|
<rect x="38" y="26" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="4"/>
|
||||||
|
<rect x="38" y="44" width="22" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch m16">
|
||||||
|
<svg width="18" height="18" viewBox="0 0 64 64">
|
||||||
|
<rect x="2" y="24" width="24" height="16" rx="4" fill="#243447"/>
|
||||||
|
<path d="M26 32 H32 M32 14 V32 M32 14 H40 M32 32 H40" fill="none" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="40" y="6" width="22" height="16" rx="4" fill="#fff" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="40" y="24" width="22" height="16" rx="4" fill="#fff" stroke="#0F766E" stroke-width="5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span>32 / 16 px</span>
|
||||||
|
</div>
|
||||||
|
<div class="lockup">
|
||||||
|
<svg width="46" height="46" viewBox="0 0 64 64">
|
||||||
|
<rect x="4" y="26" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M24 32 H30 M30 14 V32 M30 14 H38 M30 32 H38" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M30 32 V50 M30 50 H38" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="38" y="8" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="38" y="26" width="22" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="38" y="44" width="22" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
<span class="wordmark"><span class="w1">Werk</span><span class="w2">baum</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<p>Die Wurzel links, die Struktur wächst nach rechts — exakt die Logik des
|
||||||
|
transponierten Modus: die Und-Linie tritt <b>rechts</b> aus der Wurzel aus,
|
||||||
|
der Oder-Zweig hängt gestrichelt <b>unten</b> an der Sammelleiste.
|
||||||
|
<b>Einsatz:</b> breite Flächen wie Header-Leisten, Social-Banner,
|
||||||
|
Doku-Fußzeilen — überall dort, wo das Hochformat zu viel Höhe bräuchte.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,317 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Werkbaum · Logo & Wortbild</title>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--paper:#F3F6F9;
|
||||||
|
--grid:rgba(36,52,71,.055);
|
||||||
|
--ink:#243447;
|
||||||
|
--line:#41556E;
|
||||||
|
--card:#FFFFFF;
|
||||||
|
--muted:#6B7A8C;
|
||||||
|
--or:#0F766E;
|
||||||
|
--or-dark:#45C4B4;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
body{
|
||||||
|
font-family:'IBM Plex Sans',system-ui,sans-serif;
|
||||||
|
color:var(--ink);
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(0deg,transparent 0 23px,var(--grid) 23px 24px),
|
||||||
|
repeating-linear-gradient(90deg,transparent 0 23px,var(--grid) 23px 24px),
|
||||||
|
var(--paper);
|
||||||
|
min-height:100vh;
|
||||||
|
padding:44px 24px 72px;
|
||||||
|
}
|
||||||
|
main{max-width:980px;margin:0 auto}
|
||||||
|
header{margin-bottom:14px}
|
||||||
|
.eyebrow{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.68rem;
|
||||||
|
letter-spacing:.12em;text-transform:uppercase;color:var(--or);
|
||||||
|
}
|
||||||
|
h1{font-size:1.5rem;font-weight:600;letter-spacing:-.01em;margin-top:6px}
|
||||||
|
header p{margin-top:8px;color:var(--muted);font-size:.92rem;max-width:64ch;line-height:1.6}
|
||||||
|
header p b{color:var(--ink);font-weight:500}
|
||||||
|
|
||||||
|
section{margin-top:40px}
|
||||||
|
h2{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.72rem;font-weight:500;
|
||||||
|
letter-spacing:.12em;text-transform:uppercase;color:var(--muted);
|
||||||
|
border-bottom:1px solid rgba(36,52,71,.14);padding-bottom:8px;margin-bottom:18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.concepts{display:grid;grid-template-columns:repeat(auto-fit,minmax(280px,1fr));gap:16px}
|
||||||
|
.concept{
|
||||||
|
background:var(--card);
|
||||||
|
border:1px solid rgba(36,52,71,.14);
|
||||||
|
border-radius:14px;
|
||||||
|
padding:20px;position:relative;
|
||||||
|
}
|
||||||
|
.concept.reco{border-color:var(--or)}
|
||||||
|
.reco-tag{
|
||||||
|
position:absolute;top:-9px;left:16px;
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.6rem;letter-spacing:.08em;
|
||||||
|
background:var(--or);color:#fff;border-radius:999px;padding:2px 9px;
|
||||||
|
}
|
||||||
|
.stage{
|
||||||
|
display:flex;gap:14px;align-items:center;margin-bottom:14px;
|
||||||
|
}
|
||||||
|
.swatch{
|
||||||
|
border-radius:14px;display:flex;align-items:center;justify-content:center;
|
||||||
|
flex:0 0 auto;
|
||||||
|
}
|
||||||
|
.sw-paper{width:112px;height:112px;background:var(--paper);border:1px solid rgba(36,52,71,.12)}
|
||||||
|
.sw-ink{width:112px;height:112px;background:var(--ink)}
|
||||||
|
.minis{display:flex;flex-direction:column;gap:8px;align-items:center;justify-content:center}
|
||||||
|
.minis .swatch{background:var(--paper);border:1px solid rgba(36,52,71,.12);border-radius:7px}
|
||||||
|
.m32{width:44px;height:44px}
|
||||||
|
.m16{width:26px;height:26px}
|
||||||
|
.minis span{font-family:'IBM Plex Mono',monospace;font-size:.58rem;color:var(--muted)}
|
||||||
|
.concept h3{font-size:.98rem;font-weight:600}
|
||||||
|
.concept p{margin-top:6px;font-size:.85rem;color:var(--muted);line-height:1.6}
|
||||||
|
.concept p b{color:var(--ink);font-weight:500}
|
||||||
|
|
||||||
|
.wordmarks{display:flex;flex-direction:column;gap:14px}
|
||||||
|
.wm{
|
||||||
|
background:var(--card);border:1px solid rgba(36,52,71,.14);border-radius:14px;
|
||||||
|
padding:22px 24px;display:flex;align-items:center;gap:22px;flex-wrap:wrap;
|
||||||
|
}
|
||||||
|
.wm .label{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.62rem;letter-spacing:.08em;
|
||||||
|
text-transform:uppercase;color:var(--muted);flex:0 0 118px;
|
||||||
|
}
|
||||||
|
.lockup{display:flex;align-items:center;gap:13px}
|
||||||
|
.lockup svg{flex:0 0 auto}
|
||||||
|
.wordmark{
|
||||||
|
font-size:1.9rem;font-weight:600;letter-spacing:-.02em;line-height:1;
|
||||||
|
}
|
||||||
|
.wordmark .w1{color:var(--ink)}
|
||||||
|
.wordmark .w2{color:var(--or)}
|
||||||
|
.wm.dark{background:var(--ink);border-color:var(--ink)}
|
||||||
|
.wm.dark .wordmark .w1{color:#F3F6F9}
|
||||||
|
.wm.dark .wordmark .w2{color:var(--or-dark)}
|
||||||
|
.wm.dark .label{color:#8CA0B5}
|
||||||
|
.cli{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:1.05rem;color:var(--ink);
|
||||||
|
background:var(--paper);border:1px solid rgba(36,52,71,.14);
|
||||||
|
border-radius:8px;padding:10px 16px;
|
||||||
|
}
|
||||||
|
.cli .brk{color:var(--or)}
|
||||||
|
.cli .dim{color:var(--muted)}
|
||||||
|
|
||||||
|
.rules{
|
||||||
|
background:var(--card);border:1px solid rgba(36,52,71,.14);border-radius:14px;
|
||||||
|
padding:20px 24px;font-size:.88rem;line-height:1.75;color:var(--muted);max-width:74ch;
|
||||||
|
}
|
||||||
|
.rules b{color:var(--ink);font-weight:500}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<main>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<span class="eyebrow">Markenentwurf</span>
|
||||||
|
<h1>Werkbaum — Logo & Wortbild</h1>
|
||||||
|
<p>Leitidee: Die Marke wird aus den Strichen der Notation selbst gebaut —
|
||||||
|
<b>durchgezogen = und</b>, <b>gestrichelt = oder</b>.
|
||||||
|
Wer das Diagramm kennt, liest das Logo; wer das Logo kennt, versteht das Diagramm.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Bildmarken</h2>
|
||||||
|
<div class="concepts">
|
||||||
|
|
||||||
|
<div class="concept reco">
|
||||||
|
<span class="reco-tag">Empfehlung</span>
|
||||||
|
<div class="stage">
|
||||||
|
<div class="swatch sw-paper">
|
||||||
|
<svg width="88" height="88" viewBox="0 0 64 64">
|
||||||
|
<path d="M32 11 L32 28 L19 49" fill="none" stroke="#243447" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 28 L45 49" fill="none" stroke="#0F766E" stroke-width="5" stroke-linecap="round" stroke-dasharray="6 7"/>
|
||||||
|
<circle cx="32" cy="10" r="4.6" fill="#243447"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch sw-ink">
|
||||||
|
<svg width="88" height="88" viewBox="0 0 64 64">
|
||||||
|
<path d="M32 11 L32 28 L19 49" fill="none" stroke="#F3F6F9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 28 L45 49" fill="none" stroke="#45C4B4" stroke-width="5" stroke-linecap="round" stroke-dasharray="6 7"/>
|
||||||
|
<circle cx="32" cy="10" r="4.6" fill="#F3F6F9"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="minis">
|
||||||
|
<div class="swatch m32">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 64 64">
|
||||||
|
<path d="M32 11 L32 28 L19 49" fill="none" stroke="#243447" stroke-width="6" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 28 L45 49" fill="none" stroke="#0F766E" stroke-width="6" stroke-linecap="round" stroke-dasharray="7 8"/>
|
||||||
|
<circle cx="32" cy="10" r="5.2" fill="#243447"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch m16">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 64 64">
|
||||||
|
<path d="M32 11 L32 28 L19 49" fill="none" stroke="#243447" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 28 L45 49" fill="none" stroke="#0F766E" stroke-width="8" stroke-linecap="round" stroke-dasharray="9 10"/>
|
||||||
|
<circle cx="32" cy="10" r="6" fill="#243447"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span>32 / 16 px</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>K1 · Die Gabelung</h3>
|
||||||
|
<p>Wurzelpunkt, Stamm, eine Und-Gabel, ein Oder-Zweig — die ganze Notation
|
||||||
|
in einer Geste. <b>Skaliert bis zum Favicon</b>, funktioniert einfarbig
|
||||||
|
und ist als App-Kachel sofort erkennbar.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="concept">
|
||||||
|
<div class="stage">
|
||||||
|
<div class="swatch sw-paper">
|
||||||
|
<svg width="88" height="88" viewBox="0 0 64 64">
|
||||||
|
<rect x="20" y="6" width="24" height="13" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M32 19 V24 M14 24 H50 M14 24 V29 M50 24 V29" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="4" y="29" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="40" y="29" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M50 41 V47" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="47" width="20" height="11" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch sw-ink">
|
||||||
|
<svg width="88" height="88" viewBox="0 0 64 64">
|
||||||
|
<rect x="20" y="6" width="24" height="13" rx="3.5" fill="#F3F6F9"/>
|
||||||
|
<path d="M32 19 V24 M14 24 H50 M14 24 V29 M50 24 V29" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="4" y="29" width="20" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<rect x="40" y="29" width="20" height="12" rx="3.5" fill="none" stroke="#F3F6F9" stroke-width="3"/>
|
||||||
|
<path d="M50 41 V47" fill="none" stroke="#45C4B4" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="47" width="20" height="11" rx="3.5" fill="none" stroke="#45C4B4" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="minis">
|
||||||
|
<div class="swatch m32">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 64 64">
|
||||||
|
<rect x="20" y="6" width="24" height="13" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M32 19 V24 M14 24 H50 M14 24 V29 M50 24 V29" fill="none" stroke="#243447" stroke-width="4"/>
|
||||||
|
<rect x="4" y="29" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="4"/>
|
||||||
|
<rect x="40" y="29" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="4"/>
|
||||||
|
<path d="M50 41 V47" fill="none" stroke="#0F766E" stroke-width="4" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="47" width="20" height="11" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="4"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch m16">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 64 64">
|
||||||
|
<rect x="18" y="4" width="28" height="16" rx="4" fill="#243447"/>
|
||||||
|
<path d="M32 20 V26 M12 26 H52 M12 26 V32 M52 26 V32" fill="none" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="2" y="32" width="24" height="15" rx="4" fill="#fff" stroke="#243447" stroke-width="5"/>
|
||||||
|
<rect x="38" y="32" width="24" height="15" rx="4" fill="#fff" stroke="#243447" stroke-width="5"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span>32 / 16 px</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>K2 · Die Miniatur</h3>
|
||||||
|
<p>Der Projektstrukturplan selbst als Zeichen: gefüllte Wurzel, Und-Fächer,
|
||||||
|
gestrichelter Oder-Anschluss. <b>Am erklärendsten</b> — ideal für
|
||||||
|
Website-Header und Doku; fürs Favicon braucht es die vereinfachte Form.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="concept">
|
||||||
|
<div class="stage">
|
||||||
|
<div class="swatch sw-paper">
|
||||||
|
<svg width="88" height="88" viewBox="0 0 64 64">
|
||||||
|
<rect x="20" y="27" width="24" height="24" rx="5.5" fill="#fff" stroke="#243447" stroke-width="4"/>
|
||||||
|
<path d="M27 45.5 L37 32.5" stroke="#0F766E" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M32 27 V16 M32 16 L23 8" fill="none" stroke="#243447" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 16 L41 8" fill="none" stroke="#0F766E" stroke-width="4" stroke-linecap="round" stroke-dasharray="5 6"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch sw-ink">
|
||||||
|
<svg width="88" height="88" viewBox="0 0 64 64">
|
||||||
|
<rect x="20" y="27" width="24" height="24" rx="5.5" fill="none" stroke="#F3F6F9" stroke-width="4"/>
|
||||||
|
<path d="M27 45.5 L37 32.5" stroke="#45C4B4" stroke-width="4" stroke-linecap="round"/>
|
||||||
|
<path d="M32 27 V16 M32 16 L23 8" fill="none" stroke="#F3F6F9" stroke-width="4" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 16 L41 8" fill="none" stroke="#45C4B4" stroke-width="4" stroke-linecap="round" stroke-dasharray="5 6"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="minis">
|
||||||
|
<div class="swatch m32">
|
||||||
|
<svg width="32" height="32" viewBox="0 0 64 64">
|
||||||
|
<rect x="18" y="26" width="28" height="27" rx="6" fill="#fff" stroke="#243447" stroke-width="5"/>
|
||||||
|
<path d="M26 47 L38 32" stroke="#0F766E" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<path d="M32 26 V15 M32 15 L22 7" fill="none" stroke="#243447" stroke-width="5" stroke-linecap="round"/>
|
||||||
|
<path d="M32 15 L42 7" fill="none" stroke="#0F766E" stroke-width="5" stroke-linecap="round" stroke-dasharray="6 7"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<div class="swatch m16">
|
||||||
|
<svg width="16" height="16" viewBox="0 0 64 64">
|
||||||
|
<rect x="16" y="26" width="32" height="30" rx="7" fill="#fff" stroke="#243447" stroke-width="6"/>
|
||||||
|
<path d="M25 48 L39 33" stroke="#0F766E" stroke-width="6" stroke-linecap="round"/>
|
||||||
|
<path d="M32 26 V14 M32 14 L21 5 M32 14 L43 5" fill="none" stroke="#243447" stroke-width="6" stroke-linecap="round"/>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<span>32 / 16 px</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<h3>K3 · Der Spross</h3>
|
||||||
|
<p>Aus der Status-Checkbox <b>[/]</b> (Durchstich) wächst ein Zweig — Und
|
||||||
|
solid, Oder gestrichelt. Erzählt die Herkunft aus der Markdown-Task-Welt;
|
||||||
|
der verspielteste der drei Entwürfe.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Wortbild</h2>
|
||||||
|
<div class="wordmarks">
|
||||||
|
|
||||||
|
<div class="wm">
|
||||||
|
<span class="label">Primär-Lockup</span>
|
||||||
|
<div class="lockup">
|
||||||
|
<svg width="42" height="42" viewBox="0 0 64 64">
|
||||||
|
<path d="M32 11 L32 28 L19 49" fill="none" stroke="#243447" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 28 L45 49" fill="none" stroke="#0F766E" stroke-width="5" stroke-linecap="round" stroke-dasharray="6 7"/>
|
||||||
|
<circle cx="32" cy="10" r="4.6" fill="#243447"/>
|
||||||
|
</svg>
|
||||||
|
<span class="wordmark"><span class="w1">Werk</span><span class="w2">baum</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wm dark">
|
||||||
|
<span class="label">Auf Tinte</span>
|
||||||
|
<div class="lockup">
|
||||||
|
<svg width="42" height="42" viewBox="0 0 64 64">
|
||||||
|
<path d="M32 11 L32 28 L19 49" fill="none" stroke="#F3F6F9" stroke-width="5" stroke-linecap="round" stroke-linejoin="round"/>
|
||||||
|
<path d="M32 28 L45 49" fill="none" stroke="#45C4B4" stroke-width="5" stroke-linecap="round" stroke-dasharray="6 7"/>
|
||||||
|
<circle cx="32" cy="10" r="4.6" fill="#F3F6F9"/>
|
||||||
|
</svg>
|
||||||
|
<span class="wordmark"><span class="w1">Werk</span><span class="w2">baum</span></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="wm">
|
||||||
|
<span class="label">CLI / npm</span>
|
||||||
|
<div class="cli"><span class="dim">$</span> <span class="brk">[/]</span> werkbaum <span class="dim">render projekt.wbs</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section>
|
||||||
|
<h2>Anwendungsregeln</h2>
|
||||||
|
<div class="rules">
|
||||||
|
Die Marke nutzt genau zwei Farben: <b>Tinte #243447</b> und <b>Petrol #0F766E</b>
|
||||||
|
(auf dunklem Grund #F3F6F9 und #45C4B4). Die Pastelltöne bleiben ausschließlich
|
||||||
|
<b>Statusfarben im Produkt</b> und erscheinen nie im Logo — so bleibt die Marke
|
||||||
|
ruhig und die Status-Semantik eindeutig. Der gestrichelte Zweig zeigt immer nach
|
||||||
|
rechts unten und behält sein Strichmuster; einfarbige Variante: alles in Tinte,
|
||||||
|
Strichelung bleibt. Wortbild in IBM Plex Sans SemiBold mit der Zweiteilung
|
||||||
|
<b>Werk</b> / <b style="color:var(--or)">baum</b>; für CLI, npm und
|
||||||
|
Code-Kontexte konsequent <b>kleingeschrieben</b>: <span style="font-family:'IBM Plex Mono',monospace">werkbaum</span>.
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
# Werkbaum · Frontend
|
||||||
|
|
||||||
|
Editor: `index.html` ist der funktionierende Prototyp (Text links,
|
||||||
|
Diagramm rechts, Toggles für transponierte Ansicht und verworfene Elemente).
|
||||||
|
|
||||||
|
## Konventionen
|
||||||
|
- Vanilla HTML/CSS/JS, ES-Module; keine Frameworks. Testwerkzeug (Vitest) ok.
|
||||||
|
- Parser und Renderer müssen headless (ohne Editor-UI) nutzbar bleiben —
|
||||||
|
Basis für SVG-Export und Mermaid-Plugin (docs/ROADMAP.md).
|
||||||
|
- Design: Farben/Typografie beibehalten (CSS-Variablen, IBM Plex);
|
||||||
|
Statusfarben sind in SPEC §4 normiert. Marke nach ../brand/BRAND.md;
|
||||||
|
Pastelltöne nie im Logo.
|
||||||
|
|
||||||
|
## Stolperfallen
|
||||||
|
- Abzweig-Linien zielen auf die **Knotenmitte** (fester 23-px-Offset,
|
||||||
|
`line-height: 1.3`), nicht auf die Mitte des Teilbaums — bei Layout-
|
||||||
|
Änderungen beide Modi (normal + „schmal (vertikal)") prüfen.
|
||||||
|
- Extraktionsreihenfolge im Parser nicht umstellen: Kommentar → Zeichen/
|
||||||
|
Status → URL → Größe → Tags (sonst kollidiert `@` in URLs).
|
||||||
|
- „verworfen" ist per Default ausgeblendet; Filterlogik steckt in
|
||||||
|
`visibleChildren()` und muss bei Renderer-Umbauten erhalten bleiben.
|
||||||
@@ -0,0 +1,494 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Werkbaum · WBS-Editor</title>
|
||||||
|
<link rel="icon" type="image/svg+xml" href="../docs/brand/favicon.svg">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Sans:wght@400;500;600&family=IBM+Plex+Mono:wght@400;500&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
:root{
|
||||||
|
--paper:#F3F6F9;
|
||||||
|
--grid:rgba(36,52,71,.055);
|
||||||
|
--ink:#243447;
|
||||||
|
--line:#41556E;
|
||||||
|
--card:#FFFFFF;
|
||||||
|
--muted:#6B7A8C;
|
||||||
|
--or:#0F766E;
|
||||||
|
--warn:#B45309;
|
||||||
|
--r:8px;
|
||||||
|
}
|
||||||
|
*{box-sizing:border-box;margin:0;padding:0}
|
||||||
|
body{
|
||||||
|
font-family:'IBM Plex Sans',system-ui,sans-serif;
|
||||||
|
color:var(--ink);
|
||||||
|
background:
|
||||||
|
repeating-linear-gradient(0deg,transparent 0 23px,var(--grid) 23px 24px),
|
||||||
|
repeating-linear-gradient(90deg,transparent 0 23px,var(--grid) 23px 24px),
|
||||||
|
var(--paper);
|
||||||
|
min-height:100vh;
|
||||||
|
padding:32px 20px 56px;
|
||||||
|
}
|
||||||
|
header{max-width:1200px;margin:0 auto 24px}
|
||||||
|
h1{font-size:1.25rem;font-weight:600;letter-spacing:-.01em}
|
||||||
|
header p{margin-top:4px;color:var(--muted);font-size:.9rem}
|
||||||
|
|
||||||
|
.app{
|
||||||
|
max-width:1200px;margin:0 auto;
|
||||||
|
display:grid;grid-template-columns:360px 1fr;gap:20px;
|
||||||
|
align-items:start;
|
||||||
|
}
|
||||||
|
@media (max-width:820px){ .app{grid-template-columns:1fr} }
|
||||||
|
|
||||||
|
.panel{
|
||||||
|
background:var(--card);
|
||||||
|
border:1px solid rgba(36,52,71,.14);
|
||||||
|
border-radius:12px;
|
||||||
|
overflow:hidden;
|
||||||
|
}
|
||||||
|
.panel-head{
|
||||||
|
padding:10px 16px;
|
||||||
|
display:flex;justify-content:space-between;align-items:center;gap:14px;
|
||||||
|
font-family:'IBM Plex Mono',monospace;
|
||||||
|
font-size:.7rem;letter-spacing:.08em;text-transform:uppercase;
|
||||||
|
color:var(--muted);
|
||||||
|
border-bottom:1px solid rgba(36,52,71,.1);
|
||||||
|
flex-wrap:wrap;
|
||||||
|
}
|
||||||
|
.panel-head>span{margin-right:auto}
|
||||||
|
.switch{
|
||||||
|
display:flex;align-items:center;gap:7px;cursor:pointer;
|
||||||
|
text-transform:none;letter-spacing:0;
|
||||||
|
font-family:'IBM Plex Sans',sans-serif;font-size:.78rem;color:var(--muted);
|
||||||
|
}
|
||||||
|
.switch input{position:absolute;opacity:0;width:0;height:0}
|
||||||
|
.switch i{
|
||||||
|
width:30px;height:17px;border-radius:999px;background:#C6CFD8;
|
||||||
|
position:relative;transition:background .15s;flex:0 0 auto;
|
||||||
|
}
|
||||||
|
.switch i::before{
|
||||||
|
content:'';position:absolute;top:2px;left:2px;width:13px;height:13px;
|
||||||
|
border-radius:50%;background:#fff;transition:left .15s;
|
||||||
|
box-shadow:0 1px 2px rgba(36,52,71,.25);
|
||||||
|
}
|
||||||
|
.switch input:checked + i{background:var(--or)}
|
||||||
|
.switch input:checked + i::before{left:15px}
|
||||||
|
textarea{
|
||||||
|
display:block;width:100%;min-height:380px;
|
||||||
|
border:0;outline:none;resize:vertical;
|
||||||
|
padding:14px 16px;
|
||||||
|
font-family:'IBM Plex Mono',monospace;
|
||||||
|
font-size:.85rem;line-height:1.7;
|
||||||
|
color:var(--ink);background:var(--card);
|
||||||
|
tab-size:2;
|
||||||
|
}
|
||||||
|
.hint{
|
||||||
|
padding:12px 16px;border-top:1px dashed rgba(36,52,71,.18);
|
||||||
|
font-size:.8rem;color:var(--muted);line-height:1.7;
|
||||||
|
}
|
||||||
|
.hint code{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.75rem;
|
||||||
|
background:var(--paper);border:1px solid rgba(36,52,71,.12);
|
||||||
|
border-radius:4px;padding:0 5px;
|
||||||
|
}
|
||||||
|
.hint .or-code{color:var(--or);border-color:rgba(15,118,110,.4)}
|
||||||
|
|
||||||
|
.diagram{
|
||||||
|
padding:28px 16px 24px;
|
||||||
|
overflow-x:auto;
|
||||||
|
background:transparent;
|
||||||
|
}
|
||||||
|
.panel.right{background:rgba(255,255,255,.6)}
|
||||||
|
.warnings{padding:0 16px 14px;font-size:.8rem;color:var(--warn)}
|
||||||
|
.warnings div{margin-top:4px}
|
||||||
|
.empty{color:var(--muted);font-size:.85rem;padding:8px 4px}
|
||||||
|
|
||||||
|
/* ---------- Baum ---------- */
|
||||||
|
.tree{width:max-content;margin:0 auto;list-style:none}
|
||||||
|
.tree ul{list-style:none}
|
||||||
|
.tree li{display:flex;flex-direction:column;align-items:center;position:relative}
|
||||||
|
.tree > li + li{margin-top:36px}
|
||||||
|
|
||||||
|
.node{
|
||||||
|
position:relative;
|
||||||
|
color:var(--ink);
|
||||||
|
background:var(--card);
|
||||||
|
border:1.5px solid var(--line);
|
||||||
|
border-radius:var(--r);
|
||||||
|
padding:7px 14px;
|
||||||
|
font-size:.88rem;font-weight:500;line-height:1.3;
|
||||||
|
white-space:nowrap;
|
||||||
|
box-shadow:0 1px 2px rgba(36,52,71,.08);
|
||||||
|
}
|
||||||
|
a.node{text-decoration:none;cursor:pointer}
|
||||||
|
a.node:hover{box-shadow:0 2px 8px rgba(36,52,71,.22)}
|
||||||
|
.ext{font-size:.72em;margin-left:5px;opacity:.55}
|
||||||
|
.size{
|
||||||
|
position:absolute;top:-9px;right:-9px;
|
||||||
|
font-family:'IBM Plex Mono',monospace;
|
||||||
|
font-size:.58rem;font-weight:500;letter-spacing:.02em;
|
||||||
|
background:var(--ink);color:#fff;
|
||||||
|
border:1.5px solid var(--card);
|
||||||
|
border-radius:999px;padding:1px 6px;line-height:1.4;
|
||||||
|
}
|
||||||
|
.node.needs{outline:2px dashed var(--warn);outline-offset:2px}
|
||||||
|
.tags{
|
||||||
|
position:absolute;right:-9px;bottom:-9px;
|
||||||
|
display:flex;gap:3px;
|
||||||
|
}
|
||||||
|
.tag{
|
||||||
|
font-family:'IBM Plex Mono',monospace;
|
||||||
|
font-size:.58rem;font-weight:500;letter-spacing:.02em;
|
||||||
|
background:#EEF1F4;color:var(--ink);
|
||||||
|
border:1.5px solid var(--line);
|
||||||
|
border-radius:999px;padding:1px 6px;line-height:1.4;
|
||||||
|
box-shadow:0 0 0 1.5px var(--card);
|
||||||
|
}
|
||||||
|
.ghost-node{
|
||||||
|
position:relative;margin-top:14px;
|
||||||
|
border:1.5px dashed var(--warn);
|
||||||
|
color:var(--warn);background:rgba(180,83,9,.06);
|
||||||
|
border-radius:var(--r);padding:4px 12px;
|
||||||
|
font-size:.74rem;font-style:italic;white-space:nowrap;
|
||||||
|
}
|
||||||
|
.ghost-node::before{
|
||||||
|
content:'';position:absolute;top:-14px;left:50%;height:14px;
|
||||||
|
border-left:2px dashed var(--warn);
|
||||||
|
}
|
||||||
|
.root-node{background:var(--ink);border-color:var(--ink);color:#fff;font-weight:600;padding:9px 20px}
|
||||||
|
|
||||||
|
/* --- ALL OF: Kinder nebeneinander, durchgezogener Verteiler --- */
|
||||||
|
ul.and{display:flex;align-items:flex-start;position:relative;padding-top:14px}
|
||||||
|
ul.and::before{
|
||||||
|
content:'';position:absolute;top:0;left:50%;height:14px;border-left:2px solid var(--line);
|
||||||
|
}
|
||||||
|
ul.and>li{padding:16px 12px 0}
|
||||||
|
ul.and>li::before,
|
||||||
|
ul.and>li::after{
|
||||||
|
content:'';position:absolute;top:0;right:50%;
|
||||||
|
border-top:2px solid var(--line);width:50%;height:16px;
|
||||||
|
}
|
||||||
|
ul.and>li::after{right:auto;left:50%;border-left:2px solid var(--line)}
|
||||||
|
ul.and>li:only-child{padding-top:14px}
|
||||||
|
ul.and>li:only-child::before,ul.and>li:only-child::after{border-top:none}
|
||||||
|
ul.and>li:only-child::after{border-left:2px solid var(--line);height:14px}
|
||||||
|
ul.and>li:first-child::before,
|
||||||
|
ul.and>li:last-child::after{border:0 none}
|
||||||
|
ul.and>li:last-child::before{border-right:2px solid var(--line);border-radius:0 6px 0 0}
|
||||||
|
ul.and>li:first-child::after{border-radius:6px 0 0 0}
|
||||||
|
|
||||||
|
/* --- ANY OF: durchgezogene Linie links, gestrichelte Abzweige --- */
|
||||||
|
li.has-or{align-items:flex-start}
|
||||||
|
ul.or{
|
||||||
|
display:flex;flex-direction:column;
|
||||||
|
align-items:flex-start;
|
||||||
|
position:relative;
|
||||||
|
padding-top:14px;
|
||||||
|
}
|
||||||
|
ul.or::before{
|
||||||
|
content:'';position:absolute;top:0;left:9px;height:14px;border-left:2px solid var(--or);
|
||||||
|
}
|
||||||
|
ul.or>li{position:relative;align-items:flex-start;padding:5px 0 5px 30px}
|
||||||
|
/* Abzweig auf Höhe der Knoten-Mitte (5px li-Padding + halbe Knotenhöhe), nicht Mitte des Teilbaums */
|
||||||
|
ul.or>li::before{
|
||||||
|
content:'';position:absolute;left:9px;top:23px;width:21px;border-top:2px dashed var(--or);
|
||||||
|
}
|
||||||
|
ul.or>li::after{
|
||||||
|
content:'';position:absolute;left:9px;top:0;height:100%;border-left:2px solid var(--or);
|
||||||
|
}
|
||||||
|
ul.or>li:last-child::after{height:23px}
|
||||||
|
ul.or .node{
|
||||||
|
border-color:var(--or);
|
||||||
|
box-shadow:none;
|
||||||
|
font-weight:400;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Status (Pastell) ---------- */
|
||||||
|
.node.st-idee {background:#EBEDEF;border-color:#A2ABB5;color:var(--ink)}
|
||||||
|
.node.st-geplant {background:#EBE4F6;border-color:#A991D4;color:var(--ink)}
|
||||||
|
.node.st-arbeit {background:#FADDE4;border-color:#D897A8;color:var(--ink)}
|
||||||
|
.node.st-durchstich{background:#FBF2CE;border-color:#D9BE63;color:var(--ink)}
|
||||||
|
.node.st-fertig {background:#DCF1DE;border-color:#86C293;color:var(--ink)}
|
||||||
|
.node.st-prod {background:#DBEAF8;border-color:#85ACD7;color:var(--ink)}
|
||||||
|
.node.st-verworfen{
|
||||||
|
background:#F1F2F4;border-color:#B3BAC2;border-style:dashed;
|
||||||
|
color:var(--muted);text-decoration:line-through;box-shadow:none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chips{display:flex;flex-wrap:wrap;gap:6px;margin-top:8px}
|
||||||
|
.chip{
|
||||||
|
font-family:'IBM Plex Mono',monospace;font-size:.72rem;
|
||||||
|
border:1.5px solid;border-radius:6px;padding:2px 8px;
|
||||||
|
color:var(--ink);white-space:nowrap;
|
||||||
|
}
|
||||||
|
.chip.st-idee {background:#EBEDEF;border-color:#A2ABB5}
|
||||||
|
.chip.st-geplant {background:#EBE4F6;border-color:#A991D4}
|
||||||
|
.chip.st-arbeit {background:#FADDE4;border-color:#D897A8}
|
||||||
|
.chip.st-durchstich{background:#FBF2CE;border-color:#D9BE63}
|
||||||
|
.chip.st-fertig {background:#DCF1DE;border-color:#86C293}
|
||||||
|
.chip.st-prod {background:#DBEAF8;border-color:#85ACD7}
|
||||||
|
.chip.st-verworfen{background:#F1F2F4;border-color:#B3BAC2;border-style:dashed;color:var(--muted);text-decoration:line-through}
|
||||||
|
|
||||||
|
/* ---------- Vertikal-Modus (transponiert) ---------- */
|
||||||
|
.tree.vertical{margin:0}
|
||||||
|
.tree.vertical li{align-items:flex-start}
|
||||||
|
.tree.vertical ul.and{flex-direction:column;align-items:flex-start;padding-top:14px}
|
||||||
|
.tree.vertical ul.and::before{left:9px;height:14px}
|
||||||
|
.tree.vertical ul.and>li{padding:5px 0 5px 30px}
|
||||||
|
.tree.vertical ul.and>li::before{
|
||||||
|
left:9px;right:auto;top:23px;width:21px;height:0;
|
||||||
|
border:0;border-top:2px solid var(--line);border-radius:0;
|
||||||
|
}
|
||||||
|
.tree.vertical ul.and>li::after{
|
||||||
|
left:9px;right:auto;top:0;width:0;height:100%;
|
||||||
|
border:0;border-left:2px solid var(--line);border-radius:0;
|
||||||
|
}
|
||||||
|
.tree.vertical ul.and>li:last-child::after{height:23px}
|
||||||
|
|
||||||
|
/* All-of transponiert: Linie läuft rechts aus dem Überknoten heraus (wie Richtung LR),
|
||||||
|
Any-of bleibt unten-links — dadurch klar unterscheidbare Austrittsseiten */
|
||||||
|
.tree.vertical li.has-and{flex-direction:row;align-items:flex-start}
|
||||||
|
.tree.vertical li.has-and>ul.and{padding-top:0}
|
||||||
|
.tree.vertical li.has-and>ul.and::before{
|
||||||
|
left:0;top:17px;width:9px;height:0;
|
||||||
|
border-left:0;border-top:2px solid var(--line);
|
||||||
|
}
|
||||||
|
.tree.vertical li.has-and>ul.and>li:first-child::after{top:17px;height:calc(100% - 17px)}
|
||||||
|
.tree.vertical li.has-and>ul.and>li:only-child::after{top:17px;height:6px}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<header>
|
||||||
|
<h1 style="display:flex;align-items:center;gap:11px">
|
||||||
|
<svg width="34" height="34" viewBox="0 0 64 64" aria-hidden="true">
|
||||||
|
<rect x="22" y="5" width="20" height="12" rx="3.5" fill="#243447"/>
|
||||||
|
<path d="M32 17 V22 M14 22 H50 M14 22 V27 M50 22 V27" fill="none" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="4" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<rect x="40" y="27" width="20" height="12" rx="3.5" fill="#fff" stroke="#243447" stroke-width="3"/>
|
||||||
|
<path d="M50 39 V46" fill="none" stroke="#0F766E" stroke-width="3" stroke-dasharray="4 4"/>
|
||||||
|
<rect x="40" y="46" width="20" height="12" rx="3.5" fill="#fff" stroke="#0F766E" stroke-width="3"/>
|
||||||
|
</svg>
|
||||||
|
<span>Werk<span style="color:var(--or)">baum</span></span>
|
||||||
|
</h1>
|
||||||
|
<p>Links die textuelle Struktur bearbeiten — rechts entsteht das Diagramm live.</p>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<div class="app">
|
||||||
|
<div class="panel">
|
||||||
|
<div class="panel-head">Struktur (Text)</div>
|
||||||
|
<textarea id="src" spellcheck="false"></textarea>
|
||||||
|
<div class="hint">
|
||||||
|
Einrückung (2 Leerzeichen oder Tab) definiert die Hierarchie.<br>
|
||||||
|
<code>-</code> Teilpaket, alle erforderlich <em>(all of)</em><br>
|
||||||
|
<code class="or-code">|</code> Alternative, eine wählen <em>(any of)</em><br>
|
||||||
|
Zeile ohne Zeichen = Wurzelknoten. Geschwister sollten dasselbe Zeichen tragen.<br>
|
||||||
|
Status als Kästchen nach dem Zeichen, z. B. <code>- [~] Frontend</code>:
|
||||||
|
<div class="chips">
|
||||||
|
<span class="chip st-idee">[?] Idee</span>
|
||||||
|
<span class="chip st-geplant">[ ] geplant</span>
|
||||||
|
<span class="chip st-arbeit">[~] in Arbeit</span>
|
||||||
|
<span class="chip st-durchstich">[/] Durchstich</span>
|
||||||
|
<span class="chip st-fertig">[x] fertig</span>
|
||||||
|
<span class="chip st-prod">[^] in Produktion</span>
|
||||||
|
<span class="chip st-verworfen">[-] verworfen</span>
|
||||||
|
</div>
|
||||||
|
Aufwand als T-Shirt-Größe in Klammern, Link einfach als URL anhängen:<br>
|
||||||
|
<code>- [ ] Backend (L) https://…</code><br>
|
||||||
|
Ab <code>(M)</code> gilt: weiter untergliedern — fehlt die Untergliederung, erscheint ein Platzhalter im Diagramm.<br>
|
||||||
|
Kommentare mit <code>%%</code> — als ganze Zeile oder am Zeilenende.
|
||||||
|
Personen mit <code>@name</code> — erscheinen unten rechts am Knoten.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="panel right">
|
||||||
|
<div class="panel-head">
|
||||||
|
<span>Diagramm</span>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="showc"><i></i>verworfene
|
||||||
|
</label>
|
||||||
|
<label class="switch">
|
||||||
|
<input type="checkbox" id="vert"><i></i>vertikal
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="diagram"><ul class="tree" id="out"></ul></div>
|
||||||
|
<div class="warnings" id="warn"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const INITIAL = `%% Projektstruktur – Stand Sprint 14
|
||||||
|
[~] Website-Relaunch (XL) https://wiki.example.de/relaunch
|
||||||
|
- [x] Konzeption (M)
|
||||||
|
- [x] Zielgruppenanalyse (S)
|
||||||
|
- [x] Sitemap (XS)
|
||||||
|
- [~] Umsetzung (XL)
|
||||||
|
- [/] Frontend (S) https://git.example.de/frontend @anna
|
||||||
|
- [ ] Backend (L) @ben @carla
|
||||||
|
- [ ] CMS-Anbindung (M)
|
||||||
|
| [ ] WordPress
|
||||||
|
| [?] Headless CMS
|
||||||
|
| [-] Eigenentwicklung %% Aufwand zu hoch
|
||||||
|
- [?] Hosting (M)
|
||||||
|
| Cloud
|
||||||
|
| On-Premise`;
|
||||||
|
|
||||||
|
const SIZE_RANK = {XS:0, S:1, M:2, L:3, XL:4, XXL:5};
|
||||||
|
|
||||||
|
const STATUS_BY_CODE = {
|
||||||
|
'?': {key:'idee', name:'Idee'},
|
||||||
|
' ': {key:'geplant', name:'geplant'},
|
||||||
|
'~': {key:'arbeit', name:'in Arbeit'},
|
||||||
|
'/': {key:'durchstich', name:'Durchstich – funktionsbereit, Feinarbeiten offen'},
|
||||||
|
'x': {key:'fertig', name:'fertig'},
|
||||||
|
'^': {key:'prod', name:'in Produktion'},
|
||||||
|
'-': {key:'verworfen', name:'verworfen'}
|
||||||
|
};
|
||||||
|
|
||||||
|
const src = document.getElementById('src');
|
||||||
|
const out = document.getElementById('out');
|
||||||
|
const warnBox = document.getElementById('warn');
|
||||||
|
|
||||||
|
function esc(s){
|
||||||
|
return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Parser ---------- */
|
||||||
|
function parse(text){
|
||||||
|
const virtualRoot = {label:'', type:'and', children:[]};
|
||||||
|
const stack = [{node:virtualRoot, width:-1}];
|
||||||
|
const warnings = [];
|
||||||
|
|
||||||
|
text.split('\n').forEach((raw, i) => {
|
||||||
|
raw = raw.replace(/%%.*$/, ''); /* %%-Kommentare entfernen (Mermaid-Konvention) */
|
||||||
|
if(!raw.trim()) return;
|
||||||
|
const m = raw.match(/^([ \t]*)([-|])?\s*(?:\[([ ?~xX^\/-])]\s*)?(.*)$/);
|
||||||
|
const width = m[1].replace(/\t/g,' ').length;
|
||||||
|
const type = m[2] === '|' ? 'or' : 'and';
|
||||||
|
const status = m[3] ? STATUS_BY_CODE[m[3].toLowerCase()] : null;
|
||||||
|
|
||||||
|
let rest = m[4], url = null, size = null;
|
||||||
|
const tags = [];
|
||||||
|
rest = rest.replace(/https?:\/\/\S+/i, s => { url = s; return ''; });
|
||||||
|
rest = rest.replace(/\((XXL|XS|XL|S|M|L)\)/i, (s, g) => { size = g.toUpperCase(); return ''; });
|
||||||
|
rest = rest.replace(/@([\p{L}\p{N}._-]+)/gu, (s, g) => { tags.push(g); return ''; });
|
||||||
|
const label = rest.replace(/\s+/g, ' ').trim();
|
||||||
|
if(!label) return;
|
||||||
|
|
||||||
|
while(stack.length > 1 && stack[stack.length-1].width >= width) stack.pop();
|
||||||
|
const parent = stack[stack.length-1].node;
|
||||||
|
|
||||||
|
const node = {label, type, status, url, size, tags, children:[], line:i+1};
|
||||||
|
parent.children.push(node);
|
||||||
|
stack.push({node, width});
|
||||||
|
});
|
||||||
|
|
||||||
|
return {roots: virtualRoot.children, warnings};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------- Renderer ---------- */
|
||||||
|
function gateOf(children){
|
||||||
|
return children.length && children[0].type === 'or' ? 'or' : 'and';
|
||||||
|
}
|
||||||
|
|
||||||
|
function needsBreakdown(n){
|
||||||
|
if(n.status && n.status.key === 'verworfen') return false;
|
||||||
|
return !!n.size && SIZE_RANK[n.size] >= SIZE_RANK.M && !n.children.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
function visibleChildren(n){
|
||||||
|
if(showc.checked) return n.children;
|
||||||
|
return n.children.filter(k => !k.status || k.status.key !== 'verworfen');
|
||||||
|
}
|
||||||
|
|
||||||
|
const GHOST = '<div class="ghost-node">Untergliederung fehlt</div>';
|
||||||
|
|
||||||
|
function nodeHtml(n, extra){
|
||||||
|
const need = needsBreakdown(n);
|
||||||
|
const cls = ['node', extra || '', n.status ? 'st-' + n.status.key : '', need ? 'needs' : '']
|
||||||
|
.filter(Boolean).join(' ');
|
||||||
|
const title = n.status ? ` title="${n.status.name}"` : '';
|
||||||
|
const tagsHtml = n.tags && n.tags.length
|
||||||
|
? `<span class="tags">${n.tags.map(t => `<span class="tag">${esc(t)}</span>`).join('')}</span>`
|
||||||
|
: '';
|
||||||
|
const inner = esc(n.label) +
|
||||||
|
(n.url ? '<span class="ext">↗</span>' : '') +
|
||||||
|
(n.size ? `<span class="size">${n.size}</span>` : '') +
|
||||||
|
tagsHtml;
|
||||||
|
const html = n.url
|
||||||
|
? `<a class="${cls}" href="${esc(n.url).replace(/"/g,'"')}" target="_blank" rel="noopener"${title}>${inner}</a>`
|
||||||
|
: `<div class="${cls}"${title}>${inner}</div>`;
|
||||||
|
return html + (need ? GHOST : '');
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderChildren(node, warnings){
|
||||||
|
const kids = visibleChildren(node);
|
||||||
|
if(!kids.length) return '';
|
||||||
|
const types = new Set(kids.map(k => k.type));
|
||||||
|
if(types.size > 1){
|
||||||
|
warnings.push(`Zeile ${kids[0].line}: Unter „${esc(node.label)}“ sind - und | gemischt — dargestellt nach dem ersten Kind.`);
|
||||||
|
}
|
||||||
|
const gate = gateOf(kids);
|
||||||
|
const items = kids.map(k => {
|
||||||
|
const vk = visibleChildren(k);
|
||||||
|
const liCls = vk.length ? (gateOf(vk) === 'or' ? ' class="has-or"' : ' class="has-and"') : '';
|
||||||
|
return `<li${liCls}>` +
|
||||||
|
nodeHtml(k) +
|
||||||
|
renderChildren(k, warnings) +
|
||||||
|
`</li>`;
|
||||||
|
}).join('');
|
||||||
|
return `<ul class="${gate}">${items}</ul>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function render(){
|
||||||
|
let {roots} = parse(src.value);
|
||||||
|
const warnings = [];
|
||||||
|
if(!showc.checked){
|
||||||
|
roots = roots.filter(r => !r.status || r.status.key !== 'verworfen');
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!roots.length){
|
||||||
|
out.innerHTML = '<div class="empty">Noch keine Struktur — einfach lostippen.</div>';
|
||||||
|
warnBox.innerHTML = '';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.innerHTML = roots.map(root => {
|
||||||
|
const vk = visibleChildren(root);
|
||||||
|
const liCls = vk.length ? (gateOf(vk) === 'or' ? ' class="has-or"' : ' class="has-and"') : '';
|
||||||
|
return `<li${liCls}>` +
|
||||||
|
nodeHtml(root, 'root-node') +
|
||||||
|
renderChildren(root, warnings) +
|
||||||
|
`</li>`;
|
||||||
|
}).join('');
|
||||||
|
|
||||||
|
warnBox.innerHTML = warnings.map(w => `<div>⚠ ${w}</div>`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tab-Taste rückt ein statt den Fokus zu wechseln */
|
||||||
|
src.addEventListener('keydown', e => {
|
||||||
|
if(e.key === 'Tab'){
|
||||||
|
e.preventDefault();
|
||||||
|
const {selectionStart:s, selectionEnd:eEnd, value} = src;
|
||||||
|
src.value = value.slice(0, s) + ' ' + value.slice(eEnd);
|
||||||
|
src.selectionStart = src.selectionEnd = s + 2;
|
||||||
|
render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
src.addEventListener('input', render);
|
||||||
|
|
||||||
|
const vert = document.getElementById('vert');
|
||||||
|
vert.addEventListener('change', () => {
|
||||||
|
out.classList.toggle('vertical', vert.checked);
|
||||||
|
});
|
||||||
|
|
||||||
|
const showc = document.getElementById('showc');
|
||||||
|
showc.addEventListener('change', render);
|
||||||
|
|
||||||
|
src.value = INITIAL;
|
||||||
|
render();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
Reference in New Issue
Block a user