Compare commits

...
3 Commits
Author SHA1 Message Date
dejanandClaude Opus 5 0c08b18dd9 Uebergabe zu Post 2
Alles zum Beitrag vom 12.08. an einer Stelle: die sechs Slides mit Text und
Akzentwort, was die Visualisierungen zeigen und warum, die gemessenen Maße
samt Anleitung zum Nachmessen, der offene Rest und die Fallen, die schon
zugeschnappt sind.

Damit laesst sich ohne den Gespraechsverlauf weiterarbeiten. Die README
verweist darauf.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:29:24 +02:00
dejanandClaude Opus 5 ce56735077 Post 2: Visualisierungen fuellen das Format
Gemessen statt geschaetzt: der Inhaltsblock endet bei y 415 bis 460, unten
bleiben 92 px Rand, also rund 800 px freie Hoehe. Die Visualisierungen waren
115 bis 393 hoch, der Rest war tote Flaeche. Jetzt fuellen alle sechs das
Format mit 70 bis 133 px Luft zum Text. Slide 6 bleibt bewusst kleiner und
steht mittig: ein Formularfeld auf 700 px aufgeblasen sieht aufgeblasen aus.

Slide 2 hat eine echte Schiene mit gedrehter Beschriftung statt zweier
unerklaerlicher Balken. Der Riegel aus der alten Fassung war noch im Markup,
sein CSS aber schon weg. Die Schritte heissen jetzt "abtippen, noch mal
abtippen, und noch mal" statt dreimal "so wie immer".

Der gelbe Akzent auf "Chat" ist raus. Die vier Bausteine sind gleichwertig,
und Gelb bedeutet auf den anderen Slides etwas anderes.

Im Stand lagen Probier-Verschiebungen, die Noahs Karte auf 700 px vergroessert
und 250 px nach unten geschoben hatten. Deshalb war sie unten abgeschnitten,
obwohl die Vorgabe im Code stimmte.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:29:24 +02:00
dejanandClaude Opus 5 71a7ac79a9 Server verschluckt keine Fehler mehr
Der Filter in log_message pruefte args[0] mit "in", bekommt bei send_error aber
einen HTTPStatus statt der Anfragezeile. Der TypeError riss den Handler-Thread
mit, der Browser wartete auf eine Antwort, die nie kam, und die Seite blieb
ohne Meldung bei "Lade Screens ..." stehen.

args[0] wird jetzt in str() gewickelt, und alles, was keine gewoehnliche
Anfragezeile ist, wird geloggt statt verschluckt. Genau das haette die Suche
vorher abgekuerzt.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-11 19:29:02 +02:00
5 changed files with 313 additions and 136 deletions

No files matched your search

+156
View File
@@ -0,0 +1,156 @@
# Post 2 · Die Geschichte
**Erscheint Mi 12.08.2026, 12:00 Uhr.** Carousel, 6 Slides, 1080 × 1350 px.
Zweiter Beitrag des Kontos `hivegrid.de` (nach „Vom Handwerker für Handwerker").
Diese Datei ist die Übergabe: alles, was zum Beitrag bekannt ist, damit man ohne
den Gesprächsverlauf weiterarbeiten kann. Der Redaktionsplan drumherum steht in
[`redaktionsplan/`](./redaktionsplan/).
---
## 1. Worum es geht
Die Herkunft, nicht die Funktionen. Warum es HiveGrid gibt: ein SHK-Betrieb in
vierter Generation, der sein Branchenprogramm nicht wechseln konnte und deshalb
drumherum gebaut hat.
**Vorlage ist der Text von der Website** (`HiveGrid-Website`,
`app/playground/PlaygroundClient.tsx`, Abschnitt „Warum es HiveGrid gibt").
Der vollständige Text gehört **unter** den Beitrag, nicht auf die Slides.
> Auf den Slides steht die Zuspitzung, in der Bildunterschrift der ganze Text.
> Ein früherer Stand hatte die Website-Absätze wörtlich auf den Slides. Das war
> falsch: Auf einer Slide muss eine Zeile im Vorbeiscrollen sitzen.
---
## 2. Die sechs Slides
| # | Schlüssel | Überschrift | Fließtext | Visualisierung |
|---|---|---|---|---|
| 1 | `titel` | Die Geschichte fängt **auf der Baustelle an.** | Noah Geerkens, SHK-Betrieb in vierter Generation. | `karte` |
| 2 | `prozesse` | Nicht sinnvoll. **Nur so gewachsen.** | Die Software gibt den Takt vor. Nicht der Betrieb. | `schiene` |
| 3 | `wechseln` | Wechseln? **Kannst du vergessen.** | Jahre an Daten und Wissen hängen drin. Am Ende dasselbe, nur woanders. | `ersetzen` |
| 4 | `mehr` | Wir wollten nicht weg. **Wir wollten mehr.** | KI, CRM, Automatisierung. Und Tools, die miteinander reden. | `lose` |
| 5 | `verbinden` | Dein ERP bleibt. **Der Rest kommt dazu.** | Nichts wird ersetzt. Alles wird verbunden. | `verbinden` |
| 6 | `warteliste` | Gebaut im Betrieb. **Für den Betrieb.** | Setz dich auf die Warteliste. | `warteliste` |
Fett = das mit dem Marker hervorgehobene Akzentwort (`<span class="a">`).
**Zeilenlängen sind geprüft.** Bei 74 px und `-0.03em` passen rund 26 Zeichen in
eine Zeile, mit Marker etwas weniger. Keine Zeile hier ist länger als 22, es
bricht also nichts um und alle Überschriften bleiben zweizeilig. Gemessen wurde
mit der echten Manrope (variables Latin-Subset, Gewicht 800) über die
Vorschubbreiten, nicht geschätzt.
---
## 3. Die Visualisierungen
**Kein Bildmaterial außer Noahs Karte.** Alles andere ist HTML und CSS. Damit
bleibt es beim 3-fachen Export so scharf wie die Schrift, und jede
Visualisierung lässt sich im Canvas verschieben und in der Breite ändern wie
jedes andere Element. Sie heißt dort **„Visualisierung"**.
Gebaut aus derselben Formensprache wie die Slides: chamferte Kästen, Sechsecke,
der eine gelbe Akzent. Durchgezogene Balken heißen verbunden, gestrichelte offen.
| Name | Was zu sehen ist | Gedanke dahinter |
|---|---|---|
| `karte` | Noahs Teamkarte von der Über-uns-Seite | Ein Gesicht, kein Symbol |
| `schiene` | Drei Schritte an einer Schiene mit der Beschriftung „seit Jahren so" | Die Schiene gibt die Reihenfolge vor, nicht der Betrieb |
| `ersetzen` | Schwerer Bestand, durchgestrichener Pfeil, leeres Ziel | Das Kreuz liegt auf dem **Pfeil**, nicht auf dem Ziel: der Wechsel scheitert, nicht das neue System |
| `lose` | Vier Kacheln, deren gestrichelte Stummel ins Leere laufen | KI, CRM, Automation, ERP wissen nichts voneinander |
| `verbinden` | ERP als Sechseck in der Mitte, das Neue drumherum | Der Kern bleibt, außen kommt dazu |
| `warteliste` | Eingabefeld, Knopf, Adresse | Der Abschluss ist ein Feld, kein Bild |
### Noahs Karte
Nachbau der Karte von der Über-uns-Seite **im aktiven Zustand**. Werte direkt aus
`HiveGrid-Website/app/ueber-uns/TeamCards.tsx`:
```
Hintergrund grayscale(.04) saturate(.74) brightness(.73)
Person saturate(1) contrast(1.01)
Kartengrund #0f1a2e Radius außen 38 px, innen 27 px
Namensschild gelb, links unten, "Noah" + "CEO"
```
Bilder liegen unter `assets/team/noah-bg.webp` und `noah-person.webp`.
> **Ohne die weichgezeichnete Ellipse des Originals.** Die hängt an
> `backdrop-filter`, und was `html-to-image` daraus macht, ist nicht verlässlich.
---
## 4. Maße, gemessen
Der Inhaltsblock (Überschrift plus Fließtext) endet je nach Textlänge bei
**y 415 bis 460**. Unten bleiben 92 px Rand. Für die Visualisierung stehen also
rund **800 px Höhe** zur Verfügung, und die soll sie auch füllen.
| Slide | Visualisierung: oben | Höhe | Luft zum Text | Abstand nach unten |
|---|---|---|---|---|
| 1 | 499 | 775 | 84 | 76 |
| 2 | 548 | 682 | 133 | 120 |
| 3 | 566 | 664 | 106 | 120 |
| 4 | 502 | 700 | 70 | 120 |
| 5 | 470 | 732 | 83 | 120 |
| 6 | 603 | 466 | 188 | 281 |
Slide 6 ist bewusst kleiner und steht dafür mittig in der freien Fläche. Ein
Formularfeld auf 700 px aufzublasen sieht aufgeblasen aus.
**Wie nachmessen:** Eine Kopie von `post2.html` anlegen, vor `</body>` ein
Skript hängen, das `getBoundingClientRect` der Elemente in `document.title`
schreibt, und mit
`Google\ Chrome --headless --dump-dom` auslesen. Auf `.holder .slide` warten,
die Seite baut asynchron auf.
---
## 5. Stand der Dinge
**Fertig:** Alle sechs Slides sind gebaut, die Texte auf Slide-Länge, die
Visualisierungen füllen das Format, Noahs Karte sitzt.
**Offen:**
1. **Die Bildunterschrift.** Noahs vollständiger Text von der Website, plus
Hashtags. Vorgaben aus dem Redaktionsplan: Haken in den ersten 125 Zeichen,
300 bis 800 Zeichen gesamt, 5 bis 10 Hashtags ganz unten, Suchwörter
zusätzlich im Fließtext. Die Bildunterschrift von Post 1 steht als Muster im
Gesprächsverlauf und folgt genau diesem Aufbau.
2. **Export und Kontrolle.** Über `Datei → Alle Slides als ZIP`, 3-fach.
Danach jede Slide ansehen: der Export hat schon zweimal Bilder verschluckt,
die Prüfung meldet das inzwischen, aber ein Blick schadet nicht.
3. **Mitgestalter.** Falls Noahs Betriebsprofil mit drauf soll, **beim Posten**
einladen. Nachträglich geht es zwar, aber der Beitrag erscheint dann erst
nach der Annahme bei dessen Followern und die Ausspielung ist vorbei.
---
## 6. Fallen, die schon zugeschnappt sind
- **Der offene Tab überschreibt die Datei.** `state/post2.json` gewinnt nur
**beim Laden**. Wer die Datei von außen ändert und dann im offenen Tab klickt,
bekommt den alten Stand zurückgeschrieben. Also erst neu laden, dann klicken.
Änderungen, die sicher ankommen müssen, gehören in den Ladepfad (`migrateState`).
- **Probier-Verschiebungen bleiben im Stand.** Slide 1 war unten abgeschnitten,
weil die Karte auf 700 px vergrößert und 250 px nach unten geschoben war. Wenn
eine Vorgabe im Code nicht wirkt, zuerst `edits` im Stand prüfen.
- **Der Server verschluckt Fehler.** Ein Filter in `log_message` ist über
`HTTPStatus` gestolpert und hat den Thread mitgerissen; die Seite blieb dann
ohne Meldung bei „Lade Screens ..." stehen. Behoben, aber wenn die Seite
wieder hängt: zuerst `serve.py` neu starten und in die Ausgabe schauen.
- **Texte von der Website gehören nicht auf Slides.** Siehe oben.
---
## 7. Was danach kommt
Der Redaktionsplan sieht als nächsten Beitrag **Fr 14.08. „Verbinden statt
ersetzen"** vor, ein Einzelbild mit der Kernaussage. Der Satz „Verbinden statt
ersetzen" ist dafür reserviert und taucht deshalb in keiner Bildunterschrift
vorher wörtlich auf.
+7 -3
View File
@@ -18,9 +18,13 @@ Downloads landen direkt in `~/Downloads`, ohne Browser-Dialog.
## Projekte
| Projekt | Datei | Inhalt |
|---|---|---|
| Post 1 · Lösung | `post1.html` | Carousel, Anzahl frei |
| Projekt | Datei | Inhalt | Übergabe |
|---|---|---|---|
| Post 1 · Lösung | `post1.html` | Carousel, Anzahl frei | |
| Post 2 · Die Geschichte | `post2.html` | Carousel, 6 Slides, erscheint 12.08. | [`POST-2.md`](./POST-2.md) |
Der Redaktionsplan für alle weiteren Beiträge liegt in
[`redaktionsplan/`](./redaktionsplan/), gebaut mit `python3 redaktionsplan/bauen.py`.
### Slides anlegen und wegnehmen
+137 -111
View File
@@ -362,125 +362,147 @@
Bild. Gebaut aus derselben Formensprache wie die Slides: chamferte
Kaesten, Sechsecke, der eine gelbe Akzent. Alles HTML, damit es beim
3-fachen Export genauso scharf bleibt wie die Schrift und sich im Canvas
verschieben laesst wie jedes andere Element. */
verschieben laesst wie jedes andere Element.
Die Maße sind gemessen, nicht geschaetzt: der Inhaltsblock endet bei
y 415 bis 460, unten bleiben 92 px Rand. Rund 800 px Hoehe stehen also
zur Verfuegung, und die soll eine Visualisierung auch fuellen. */
.vis {
position: absolute; z-index: 1;
left: var(--vis-left, 92px);
bottom: var(--vis-bottom, 96px);
width: var(--vis-w, 900px);
width: var(--vis-w, 896px);
color: var(--head-color);
}
.vis-kasten {
position: relative; padding: 22px 26px;
position: relative; padding: 30px 34px;
background: var(--frame-bg);
display: flex; flex-direction: column; justify-content: center;
clip-path: polygon(
18px 0, calc(100% - 18px) 0, 100% 18px, 100% calc(100% - 18px),
calc(100% - 18px) 100%, 18px 100%, 0 calc(100% - 18px), 0 18px
);
}
.vis-titel { font-size: 38px; font-weight: 800; letter-spacing: -0.02em; line-height: 1.1; }
.vis-zeile { margin-top: 8px; font-size: 24px; font-weight: 500; line-height: 1.35; color: var(--body-color); }
.vis-marke { background: var(--accent-yellow); color: #3E2C0A; }
.vis-marke .vis-zeile { color: #6B4E12; }
/* Slide 2: die drei Schritte haengen an einer Schiene. Sie stehen nicht
dort, weil es sinnvoll waere, sondern weil die Schiene es vorgibt. */
.vis-schiene { display: flex; align-items: stretch; gap: 40px; }
.vis-schiene .rail {
flex: none; width: 74px; position: relative;
background: var(--shape-fill);
clip-path: polygon(
14px 0, 60px 0, 74px 14px, 74px calc(100% - 14px),
60px 100%, 14px 100%, 0 calc(100% - 14px), 0 14px
);
}
.vis-schiene .rail span {
position: absolute; left: 50%; top: 50%; white-space: nowrap;
transform: translate(-50%, -50%) rotate(-90deg);
color: #fff; font-size: 21px; font-weight: 700; letter-spacing: .13em;
text-transform: uppercase; opacity: .82;
}
.vis-schiene .stufen { flex: 1; display: flex; flex-direction: column; }
.vis-schiene .vis-kasten { min-height: 190px; }
.vis-schiene .verbund {
width: 8px; height: 56px; margin-left: 58px; background: var(--shape-line);
}
/* Slide 3: der Bestand ist schwer, das Ziel leer. Das Kreuz liegt auf dem
Pfeil, nicht auf dem Ziel: der Wechsel scheitert, nicht das System. */
.vis-ersetzen { display: flex; align-items: stretch; gap: 30px; }
.vis-ersetzen .schwer { flex: 1.25; display: grid; gap: 14px; }
.vis-ersetzen .schwer .vis-kasten { min-height: 212px; }
.vis-ersetzen .leer {
flex: 1; padding: 30px; border: 8px dashed var(--shape-line);
display: flex; align-items: center; justify-content: center; text-align: center;
color: var(--body-color); font-size: 26px; font-weight: 600; line-height: 1.35;
}
.vis-ersetzen .pfeil {
flex: none; align-self: center; position: relative;
width: 104px; height: 8px; background: var(--shape-line);
}
.vis-ersetzen .pfeil::after {
content: ""; position: absolute; right: -2px; top: -14px;
border: 18px solid transparent; border-left-color: var(--shape-line);
}
.vis-kreuz {
position: absolute; left: 50%; top: 50%; width: 92px; height: 92px;
transform: translate(-50%, -50%);
}
.vis-kreuz i {
position: absolute; left: 0; top: 42px; width: 100%; height: 10px;
background: var(--accent-yellow);
}
.vis-kreuz i:first-child { transform: rotate(45deg); }
.vis-kreuz i:last-child { transform: rotate(-45deg); }
/* Slide 4: vier Kacheln, die nichts voneinander wissen. Die gestrichelten
Stummel laufen ins Leere. */
.vis-lose { display: grid; grid-template-columns: repeat(2, 1fr); gap: 32px 96px; }
.vis-lose .feld { position: relative; }
.vis-lose .feld .vis-kasten { min-height: 320px; }
.vis-lose .stummel {
position: absolute; top: 50%; width: 72px; height: 0;
border-top: 8px dashed var(--shape-line);
}
.vis-lose .feld:nth-child(odd) .stummel { right: -84px; }
.vis-lose .feld:nth-child(even) .stummel { left: -84px; }
/* Slide 5: das ERP bleibt in der Mitte, das Neue haengt drumherum. */
.vis-verbinden { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; }
.vis-verbinden .saeule { display: grid; gap: 24px; }
.vis-verbinden .saeule .vis-kasten { min-height: 340px; }
.vis-verbinden .mitte { position: relative; width: 452px; padding: 0 30px; }
.vis-nabe {
position: relative; width: 392px; height: 340px; margin: 0 auto;
background: var(--shape-fill);
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
display: flex; flex-direction: column; align-items: center; justify-content: center;
color: #fff; text-align: center;
}
.vis-nabe b { font-size: 34px; font-weight: 800; letter-spacing: -0.02em; }
.vis-nabe small { margin-top: 6px; font-size: 21px; font-weight: 600; opacity: .74; }
.vis-verbinden .quer { position: absolute; top: 50%; width: 30px; height: 8px; background: var(--shape-line); }
.vis-verbinden .quer.li { left: 0; }
.vis-verbinden .quer.re { right: 0; }
/* Slide 6: der Abschluss ist ein Feld, kein Bild. */
.vis-warteliste {
border: 6px solid var(--shape-line); padding: 54px 48px; text-align: center;
clip-path: polygon(
22px 0, calc(100% - 22px) 0, 100% 22px, 100% calc(100% - 22px),
calc(100% - 22px) 100%, 22px 100%, 0 calc(100% - 22px), 0 22px
);
}
.vis-warteliste .oben {
font-size: 21px; font-weight: 700; letter-spacing: .16em; text-transform: uppercase;
color: var(--body-color);
}
.vis-warteliste .feldzeile { display: flex; align-items: stretch; gap: 18px; margin-top: 26px; }
.vis-warteliste .eingabe {
flex: 1; padding: 30px 32px; text-align: left;
font-size: 30px; font-weight: 500; color: var(--body-color);
background: var(--frame-bg);
clip-path: polygon(
16px 0, calc(100% - 16px) 0, 100% 16px, 100% calc(100% - 16px),
calc(100% - 16px) 100%, 16px 100%, 0 calc(100% - 16px), 0 16px
);
}
.vis-titel { font-size: 27px; font-weight: 700; letter-spacing: -0.01em; }
.vis-zeile { margin-top: 6px; font-size: 21px; font-weight: 500; color: var(--body-color); }
.vis-marke { background: var(--accent-yellow); color: #3E2C0A; }
.vis-marke .vis-zeile { color: #6B4E12; }
/* Verbindungen: 6 px starke Balken in der Rahmenfarbe. Durchgezogen heisst
verbunden, gestrichelt heisst offen. */
.vis-linie { position: absolute; height: 6px; background: var(--shape-line); }
.vis-linie.senk { width: 6px; height: auto; }
.vis-offen { border-top: 6px dashed var(--shape-line); background: none; height: 0; }
/* Slide 2: drei Schritte fest auf einer Schiene. Sie sitzen nicht dort,
weil es sinnvoll waere, sondern weil die Schiene es vorgibt. */
.vis-schiene { display: flex; align-items: stretch; gap: 0; }
.vis-schiene .vis-kasten { flex: 1; }
.vis-schiene .verbund { flex: none; width: 54px; align-self: center; height: 6px; background: var(--shape-line); }
.vis-riegel {
margin-top: 26px; height: 14px; background: var(--shape-fill); opacity: .28;
clip-path: polygon(7px 0, calc(100% - 7px) 0, 100% 7px, 100% 100%, 0 100%, 0 7px);
}
/* Slide 3: der Bestand ist schwer, der Wechsel daneben leer. Das Kreuz
liegt auf dem Pfeil, nicht auf dem Ziel. */
.vis-ersetzen { display: flex; align-items: center; gap: 34px; }
.vis-ersetzen .schwer { flex: 1.35; }
.vis-ersetzen .leer {
flex: 1; padding: 22px 26px; min-height: 168px;
border: 6px dashed var(--shape-line);
display: flex; align-items: center; justify-content: center;
color: var(--body-color); font-size: 21px; font-weight: 600;
}
.vis-ersetzen .pfeil { flex: none; position: relative; width: 96px; height: 6px; background: var(--shape-line); }
.vis-ersetzen .pfeil::after {
content: ""; position: absolute; right: -2px; top: -11px;
border: 14px solid transparent; border-left-color: var(--shape-line);
}
.vis-kreuz {
position: absolute; left: 50%; top: 50%; width: 74px; height: 74px;
transform: translate(-50%, -50%);
}
.vis-kreuz i {
position: absolute; left: 0; top: 34px; width: 100%; height: 8px;
background: var(--accent-yellow);
}
.vis-kreuz i:first-child { transform: rotate(45deg); }
.vis-kreuz i:last-child { transform: rotate(-45deg); }
.vis-stapel { display: grid; gap: 10px; }
/* Slide 4: vier Kacheln, die nichts voneinander wissen. Die gestrichelten
Stummel laufen ins Leere. */
.vis-lose { display: grid; grid-template-columns: repeat(2, 1fr); gap: 30px 74px; }
.vis-lose .feld { position: relative; }
.vis-lose .stummel {
position: absolute; top: 50%; width: 54px; height: 0;
border-top: 6px dashed var(--shape-line);
}
.vis-lose .feld:nth-child(odd) .stummel { right: -60px; }
.vis-lose .feld:nth-child(even) .stummel { left: -60px; }
/* Slide 5: das ERP bleibt in der Mitte, HiveGrid liegt als Sechseck
drumherum und haelt die drei Neuen daran. */
.vis-verbinden { display: grid; grid-template-columns: 1fr auto 1fr; align-items: center; gap: 0; }
.vis-verbinden .saeule { display: grid; gap: 20px; }
.vis-verbinden .mitte { position: relative; width: 300px; padding: 0 26px; }
.vis-nabe {
position: relative; width: 248px; height: 215px; margin: 0 auto;
background: var(--shape-fill);
clip-path: polygon(25% 0%, 75% 0%, 100% 50%, 75% 100%, 25% 100%, 0% 50%);
display: flex; flex-direction: column; align-items: center; justify-content: center;
color: var(--text-white);
}
.slide[data-variant="hell"] .vis-nabe { color: #fff; }
.vis-nabe b { font-size: 25px; font-weight: 800; letter-spacing: -0.01em; }
.vis-nabe small { margin-top: 4px; font-size: 17px; font-weight: 600; opacity: .72; }
.vis-verbinden .quer { position: absolute; top: 50%; width: 26px; height: 6px; background: var(--shape-line); }
.vis-verbinden .quer.li { left: 0; }
.vis-verbinden .quer.re { right: 0; }
/* Slide 6: der Abschluss ist ein Feld, kein Bild. */
.vis-warteliste { text-align: center; }
.vis-warteliste .feldzeile {
display: flex; align-items: stretch; gap: 14px; margin-top: 22px;
}
.vis-warteliste .eingabe {
flex: 1; padding: 20px 24px; text-align: left;
font-size: 22px; font-weight: 500; color: var(--body-color);
border: 3px solid var(--shape-line);
clip-path: polygon(
14px 0, calc(100% - 14px) 0, 100% 14px, 100% calc(100% - 14px),
calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px), 0 14px
);
}
.vis-warteliste .knopf {
flex: none; padding: 20px 32px; font-size: 22px; font-weight: 800;
flex: none; display: flex; align-items: center; padding: 30px 40px;
font-size: 30px; font-weight: 800;
background: var(--accent-yellow); color: #3E2C0A;
clip-path: polygon(
14px 0, calc(100% - 14px) 0, 100% 14px, 100% calc(100% - 14px),
calc(100% - 14px) 100%, 14px 100%, 0 calc(100% - 14px), 0 14px
16px 0, calc(100% - 16px) 0, 100% 16px, 100% calc(100% - 16px),
calc(100% - 16px) 100%, 16px 100%, 0 calc(100% - 16px), 0 16px
);
}
.vis-warteliste .adresse {
margin-top: 18px; font-size: 21px; font-weight: 700; letter-spacing: .02em;
color: var(--body-color);
margin-top: 30px; font-size: 30px; font-weight: 800; letter-spacing: -0.01em;
color: var(--head-color);
}
/* ─── Teamkarte ──────────────────────────────────────────────────────────
@@ -1189,7 +1211,7 @@
eyebrow: "Titel",
head: 'Die Geschichte fängt<br><span class="a">auf der Baustelle an.</span>',
body: "Noah Geerkens, SHK-Betrieb in vierter Generation.",
vis: "karte", visW: 520, visLeft: 280, visBottom: 130,
vis: "karte", visW: 620, visLeft: 230, visBottom: 76,
},
{
key: "prozesse", type: "feature",
@@ -1197,7 +1219,7 @@
eyebrow: "Prozesse",
head: 'Nicht sinnvoll.<br><span class="a">Nur so gewachsen.</span>',
body: "Die Software gibt den Takt vor. Nicht der Betrieb.",
vis: "schiene", visW: 900, visLeft: 92, visBottom: 210,
vis: "schiene", visW: 896, visLeft: 92, visBottom: 120,
},
{
key: "wechseln", type: "feature",
@@ -1205,7 +1227,7 @@
eyebrow: "Wechseln",
head: 'Wechseln?<br><span class="a">Kannst du vergessen.</span>',
body: "Jahre an Daten und Wissen hängen drin. Am Ende dasselbe, nur woanders.",
vis: "ersetzen", visW: 900, visLeft: 92, visBottom: 190,
vis: "ersetzen", visW: 896, visLeft: 92, visBottom: 120,
},
{
key: "mehr", type: "feature",
@@ -1213,7 +1235,7 @@
eyebrow: "Mehr",
head: 'Wir wollten nicht weg.<br><span class="a">Wir wollten mehr.</span>',
body: "KI, CRM, Automatisierung. Und Tools, die miteinander reden.",
vis: "lose", visW: 900, visLeft: 92, visBottom: 200,
vis: "lose", visW: 896, visLeft: 92, visBottom: 120,
},
{
key: "verbinden", type: "feature",
@@ -1221,7 +1243,7 @@
eyebrow: "Verbinden",
head: 'Dein ERP bleibt.<br><span class="a">Der Rest kommt dazu.</span>',
body: "Nichts wird ersetzt. Alles wird verbunden.",
vis: "verbinden", visW: 880, visLeft: 100, visBottom: 170,
vis: "verbinden", visW: 896, visLeft: 92, visBottom: 120,
},
{
key: "warteliste", type: "feature",
@@ -1229,7 +1251,7 @@
eyebrow: "Warteliste",
head: 'Gebaut im Betrieb.<br><span class="a">Für den Betrieb.</span>',
body: "Setz dich auf die Warteliste.",
vis: "warteliste", visW: 820, visLeft: 130, visBottom: 220,
vis: "warteliste", visW: 820, visLeft: 130, visBottom: 281,
},
];
@@ -1289,10 +1311,13 @@
schiene: () =>
'<div class="vis-schiene">' +
kasten("Anfrage", "so wie immer") + '<div class="verbund"></div>' +
kasten("Angebot", "so wie immer") + '<div class="verbund"></div>' +
kasten("Rechnung", "so wie immer") +
'</div><div class="vis-riegel"></div>',
'<div class="rail"><span>seit Jahren so</span></div>' +
'<div class="stufen">' +
kasten("Anfrage", "abtippen") + '<div class="verbund"></div>' +
kasten("Angebot", "noch mal abtippen") + '<div class="verbund"></div>' +
kasten("Rechnung", "und noch mal") +
'</div>' +
'</div>',
ersetzen: () =>
'<div class="vis-ersetzen">' +
@@ -1321,11 +1346,12 @@
'<div class="vis-nabe"><b>Dein ERP</b><small>bleibt, wo es ist</small></div>' +
'<span class="quer re"></span>' +
'</div>' +
'<div class="saeule">' + kasten("Automation") + kasten("Chat", "", true) + '</div>' +
'<div class="saeule">' + kasten("Automation") + kasten("Chat") + '</div>' +
'</div>',
warteliste: () =>
'<div class="vis-warteliste">' +
'<div class="oben">Frühzugang</div>' +
'<div class="feldzeile">' +
'<div class="eingabe">deine@firma.de</div>' +
'<div class="knopf">Auf die Warteliste</div>' +
+8 -1
View File
@@ -218,9 +218,16 @@ class Handler(http.server.SimpleHTTPRequestHandler):
super().end_headers()
def log_message(self, fmt, *args):
first = args[0] if args else ""
# args[0] ist normalerweise die Anfragezeile, bei send_error aber ein
# HTTPStatus. Ohne str() flog hier ein TypeError, der den Handler-Thread
# abbrach: der Browser wartete dann ewig auf eine Antwort, die nie kam,
# und die Seite blieb bei "Lade Screens ..." stehen.
first = str(args[0]) if args else ""
if "POST /save" in first or "DELETE" in first:
super().log_message(fmt, *args)
elif not first.startswith(("GET", "POST", "HEAD")):
# Fehler und Warnungen gehoeren gesehen, sonst sucht man lange.
super().log_message(fmt, *args)
class Server(socketserver.ThreadingTCPServer):
+5 -21
View File
@@ -2,7 +2,7 @@
"bgState": [
{
"bg": "raster",
"int": "dezent"
"int": "kraeftig"
},
{
"bg": "raster",
@@ -53,30 +53,14 @@
}
],
"edits": [
{
"vis": {
"dx": -100,
"dy": 250,
"size": 700
}
},
{
"vis": {
"dx": -7,
"dy": -19
}
},
{
"vis": {
"dx": 19,
"dy": -27
}
},
{},
{},
{},
{},
{},
{}
],
"frameStyle": "chamfer",
"frameStyle": "rund",
"layoutState": [
"standard",
"standard",