Post 2: die Geschichte, mit gebauten Visualisierungen

Sechs Slides von der Baustelle bis zur Warteliste. Statt Screenshots traegt
jede Slide eine Visualisierung des Stichworts aus ihrem Text: drei Schritte
fest auf einer Schiene, der schwere Bestand mit dem durchgestrichenen Pfeil,
vier lose Kacheln deren Verbindungen ins Leere laufen, das ERP als Sechseck
mit den Neuen daran, ein Feld mit Knopf.

Alles HTML, kein Bildmaterial ausser Noahs Karte. Damit bleibt es beim
3-fachen Export so scharf wie die Schrift, und jede Visualisierung laesst
sich im Canvas verschieben und in der Breite aendern wie jedes andere
Element. Das Kreuz auf der Wechsel-Slide liegt auf dem Pfeil, nicht auf dem
Ziel: der Wechsel scheitert, nicht das neue System.

Die Karte ist der aktive Zustand von der Ueber-uns-Seite, Werte aus
TeamCards.tsx. Ohne die weichgezeichnete Ellipse des Originals: die haengt an
backdrop-filter, und was der Export daraus macht, ist nicht verlaesslich.

Die Texte sind bewusst NICHT die von der Website. Dort stehen ganze Absaetze,
auf einer Slide muss eine Zeile im Vorbeiscrollen sitzen; der volle Text
kommt unter den Beitrag. Aus den gemessenen Breiten folgt: bei 74 px und
-0,03em passen rund 26 Zeichen in eine Zeile. Keine hier ist laenger als 22.

Der Stand von post2 ist neu geschrieben, er zeigte noch auf die Slides von
Post 1.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
dejanandClaude Opus 5 committed 2026-08-08 00:28:52 +02:00
1 parent 276cced03c
commit bd06f77a60
5 files changed
+392 -243

No files matched your search

+13
View File
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg id="Ebene_1" data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1074.12 930.23">
<defs>
<style>
.cls-1 {
fill: #f1b729;
}
</style>
</defs>
<g id="Ebene_2" data-name="Ebene 2">
<polygon class="cls-1" points="1074.12 465.12 939.87 232.54 537.06 465.12 805.59 465.12 671.34 697.66 402.78 697.66 268.53 465.12 402.78 232.54 939.87 232.54 832.69 46.91 805.59 0 268.53 0 0 465.12 268.53 930.23 805.59 930.23 1074.12 465.12"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 532 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

+348 -119
View File
@@ -3,7 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>HiveGrid Post 2 (Carousel)</title>
<title>PostGrid Post 2</title>
<link rel="stylesheet" href="./assets/fonts/manrope.css">
<style>
/* ─── HiveGrid design tokens (mirrored from app/globals.css) ─────────────
@@ -249,7 +249,10 @@
Klar abgesetzt von der Sidebar: eigener Grund, eigener Scroll, eigener
Zoom. Was hier passiert, lässt die Bedienleiste unberührt. */
.stage {
flex: 1; position: relative; height: 100vh; overflow: hidden;
/* Nicht 100vh: darueber sitzt die Menueleiste. Mit voller
Fensterhoehe rutscht die Fussleiste samt Zoom und Merkzettel
unter den unteren Rand. */
flex: 1; position: relative; height: 100%; overflow: hidden;
background-color: #101116;
background-image: radial-gradient(circle, #282B35 1.6px, transparent 1.6px);
background-size: 24px 24px;
@@ -353,6 +356,166 @@
opacity: calc(var(--pat-strength) * var(--int-mul, 1));
}
/* ─── Visualisierungen ───────────────────────────────────────────────────
Post 2 zeigt keine Screenshots, sondern das Stichwort aus dem Text als
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. */
.vis {
position: absolute; z-index: 1;
left: var(--vis-left, 92px);
bottom: var(--vis-bottom, 96px);
width: var(--vis-w, 900px);
color: var(--head-color);
}
.vis-kasten {
position: relative; padding: 22px 26px;
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;
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
);
}
.vis-warteliste .adresse {
margin-top: 18px; font-size: 21px; font-weight: 700; letter-spacing: .02em;
color: var(--body-color);
}
/* ─── Teamkarte ──────────────────────────────────────────────────────────
Nachbau der Karte von der Ueber-uns-Seite im aktiven Zustand: Hintergrund
entsaettigt und abgedunkelt, die Person in voller Farbe, das gelbe
Namensschild sichtbar. Werte aus TeamCards.tsx uebernommen.
Ohne die weichgezeichnete Ellipse aus dem Original: die haengt an
backdrop-filter, und was der Export daraus macht, ist nicht verlaesslich. */
.karte {
position: relative; aspect-ratio: 4/5; overflow: hidden;
border-radius: 38px; background: #0f1a2e; isolation: isolate;
}
.karte-grund {
position: absolute; inset: 4.2%; z-index: 1; overflow: hidden;
border-radius: 27px; background: #2b2d2a;
filter: grayscale(.04) saturate(.74) brightness(.73);
}
.karte-grund img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.karte-wash {
position: absolute; inset: 0;
background: linear-gradient(180deg, rgba(15,26,46,.01), rgba(15,26,46,.12));
}
.karte-person {
position: absolute; inset: 0; z-index: 4;
width: 100%; height: 100%; object-fit: cover;
filter: saturate(1) contrast(1.01);
}
.karte-name {
position: absolute; left: 6%; bottom: 5.7%; z-index: 5;
display: flex; align-items: baseline; gap: 13px;
padding: 13px 17px; background: var(--accent-yellow); color: #101010;
}
.karte-name span { font-size: 27px; font-weight: 700; letter-spacing: -.03em; line-height: 1.15; }
.karte-name small { font-size: 12px; font-weight: 700; letter-spacing: .14em; text-transform: uppercase; }
/* ─── Bearbeitung im Canvas ───────────────────────────────────────────────
Verschieben läuft über --ex/--ey, also über transform statt über den
Textfluss: So verschiebt ein Element nie seine Nachbarn und lässt sich
@@ -755,7 +918,7 @@
<body>
<div class="menubar" id="menubar">
<span class="marke">HiveGrid</span>
<span class="marke">PostGrid</span>
<div class="menu" data-menu="projekt">
<button type="button">Projekt</button>
@@ -786,9 +949,6 @@
<button type="button" data-tun="undo">Rückgängig<span class="kurz">⌘Z</span></button>
<button type="button" data-tun="redo">Wiederherstellen<span class="kurz">⇧⌘Z</span></button>
<hr>
<button type="button" data-opt="editmodes:e:an">Bearbeiten an<span class="kurz">E</span></button>
<button type="button" data-opt="editmodes:e:aus">Bearbeiten aus</button>
<hr>
<button type="button" data-klick="reset-one">Element auf Standard</button>
<button type="button" data-klick="reset-all">Alles auf Standard</button>
</div>
@@ -809,6 +969,8 @@
<div class="menu" data-menu="ansicht">
<button type="button">Ansicht</button>
<div class="liste">
<button type="button" data-tun="vorschau">Vorschau<span class="kurz">P</span></button>
<hr>
<button type="button" data-opt="views:w:streifen">Streifen<span class="kurz">W</span></button>
<button type="button" data-opt="views:w:einzeln">Einzeln</button>
<hr>
@@ -860,29 +1022,31 @@
</div>
</details>
<details class="group" data-grp="layout-nur-diese-slide">
<summary>Layout (nur diese Slide)</summary>
<!-- Nicht mehr in der Seitenleiste: Layout und Perspektive sind
post-spezifisch, Vorschau steht oben unter Ansicht, der Tag ist
ausgeblendet. Die Schalter bleiben trotzdem im Dokument, weil die
Menueleiste, die Tastenkuerzel und die Abgleich-Funktionen sie
betaetigen. Sonst gaebe es zwei Wahrheiten ueber denselben Zustand. -->
<div hidden>
<div class="opts" id="layouts"></div>
</details>
<details class="group" data-grp="tag-optisch-ausger-ckt">
<summary>Tag optisch ausgerückt</summary>
<div class="opts" id="tagouts">
<button data-o="an" aria-pressed="true">An</button>
<button data-o="aus" aria-pressed="false">Aus</button>
</div>
</details>
<details class="group" data-grp="bearbeiten">
<summary>Bearbeiten</summary>
<div class="opts" id="editmodes">
<button data-e="an" aria-pressed="false">An</button>
<button data-e="aus" aria-pressed="true">Aus</button>
<div class="opts" id="vorschau">
<button data-p="aus" aria-pressed="true">Aus</button>
<button data-p="an" aria-pressed="false">An</button>
</div>
<div class="hint" style="margin-top:8px">Ein angeklicktes Element öffnet
sein eigenes Feld rechts.</div>
<button class="btn secondary" id="reset-all" style="width:100%;margin-top:8px">Alles auf Standard</button>
</details>
<div class="opts" id="ints">
<button data-i="dezent" aria-pressed="true">Dezent</button>
<button data-i="kraeftig" aria-pressed="false">Kräftig</button>
</div>
<div class="opts" id="tilts">
<button data-t="an" aria-pressed="true">Gekippt</button>
<button data-t="aus" aria-pressed="false">Gerade</button>
</div>
<button id="reset-all">Alles auf Standard</button>
</div>
<details class="std">
<summary>Standardwerte</summary>
@@ -897,14 +1061,6 @@
</div>
</details>
<details class="group" data-grp="intensit-t">
<summary>Intensität</summary>
<div class="opts" id="ints">
<button data-i="dezent" aria-pressed="true">Dezent</button>
<button data-i="kraeftig" aria-pressed="false">Kräftig</button>
</div>
</details>
<details class="group" data-grp="bildrahmen">
<summary>Bildrahmen</summary>
<div class="opts three" id="frames">
@@ -915,14 +1071,6 @@
</div>
</details>
<details class="group" data-grp="mockup-perspektive">
<summary>Mockup-Perspektive</summary>
<div class="opts" id="tilts">
<button data-t="an" aria-pressed="true">Gekippt</button>
<button data-t="aus" aria-pressed="false">Gerade</button>
</div>
</details>
<details class="group" data-grp="renderqualit-t">
<summary>Renderqualität</summary>
<div class="opts three" id="quality">
@@ -932,12 +1080,13 @@
</div>
</details>
<div class="group">
<label>Download</label>
<div class="row" style="margin-bottom:8px">
<button id="dl-png" disabled>Diese Slide</button>
</div>
<button id="dl-all" class="btn secondary" style="width:100%" disabled>Alle 5 Slides als ZIP</button>
<!-- Der Export steht oben unter Datei. Die Knoepfe bleiben trotzdem im
Dokument: die Menueleiste betaetigt sie, und sie tragen den Zustand
"noch nicht bereit", solange die Schrift nicht geladen ist. Ohne sie
muesste beides doppelt gefuehrt werden. -->
<div hidden>
<button id="dl-png" disabled>Diese Slide</button>
<button id="dl-all" disabled>Alle Slides als ZIP</button>
</div>
<details class="group" style="margin-bottom:0" data-grp="tastatur">
@@ -945,10 +1094,10 @@
<div class="keys">
<kbd>←</kbd><kbd>→</kbd> Slide · <kbd>V</kbd> Variante<br>
<kbd>L</kbd> Layout · <kbd>I</kbd> Intensität<br>
<kbd>W</kbd> Ansicht · <kbd>G</kbd> Tag<br>
<kbd>W</kbd> Ansicht<br>
<kbd>R</kbd> Bildrahmen<br>
<kbd>T</kbd> Perspektive<br>
<kbd>E</kbd> Bearbeiten · Pfeile verschieben (<kbd>⇧</kbd> = 10 px)<br>
<kbd>P</kbd> Vorschau · Pfeile verschieben (<kbd>⇧</kbd> = 10 px)<br>
<kbd>+</kbd> <kbd>−</kbd> <kbd>0</kbd> Zoom · ⌘/Strg + Rad<br>
<kbd>Leer</kbd> + ziehen verschiebt die Fläche<br>
<kbd>⌘/Strg</kbd>+<kbd>Z</kbd> Rückgängig (<kbd>⇧</kbd> = wiederherstellen)<br>
@@ -1017,82 +1166,70 @@
Website say exactly the same thing; the headlines are new and short,
because a full sentence does not work as a carousel hook.
img-scale/x/y frame each capture on its telling region. */
/* ─── Die Geschichte ──────────────────────────────────────────────────────
Post 2 erzaehlt die Herkunft, nicht die Funktionen. Statt Screenshots
traegt jede Slide eine gebaute Visualisierung: der Text nennt ein
Stichwort, das Bild zeigt es. Alle sind aus derselben Formensprache wie
die Slides selbst gebaut, also chamferte Kaesten, Sechsecke und der eine
gelbe Akzent. Kein Bildmaterial ausser Noahs Karte. */
/* ─── Die Geschichte ──────────────────────────────────────────────────────
Post 2 erzaehlt die Herkunft, nicht die Funktionen. Statt Screenshots
traegt jede Slide eine gebaute Visualisierung: der Text nennt ein
Stichwort, das Bild zeigt es.
Die Texte sind NICHT die von der Website. Dort stehen ganze Absaetze, hier
muss eine Zeile im Vorbeiscrollen sitzen. Der volle Text kommt unter den
Beitrag, die Slide traegt nur die Zuspitzung. Faustregel aus den
gemessenen Breiten: bei 74 px und -0,03em passen rund 26 Zeichen in eine
Zeile, mit Marker etwas weniger. Keine Zeile hier ist laenger als 22. */
const VORLAGEN = [
{
key: "titel", type: "title",
layouts: [{ id: "klassisch", label: "Klassisch" }, { id: "wabe", label: "Wabe" }, { id: "laptop", label: "Laptop" }, { id: "laptop-logo", label: "Laptop · Logo" }, { id: "laptop-blur", label: "Laptop · Blur" }, { id: "laptop-footer", label: "Laptop · Logo unten" }, { id: "szene", label: "Laptop · Board" }],
head: 'Vom <span class="a">Handwerker</span><br>f\u00fcr Handwerker',
sub: "Die Software, die deine Branchenprogramme nicht ersetzt, sondern verbindet.",
key: "titel", type: "feature",
layouts: [{ id: "standard", label: "Standard" }],
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,
},
{
key: "workflow-engine", type: "feature",
key: "prozesse", type: "feature",
layouts: [{ id: "standard", label: "Standard" }],
eyebrow: "Automation & KI",
head: '<span class="a">Wenn-dann</span><br>statt Handarbeit.',
body: "Auslöser wählen, Aktionen dranhängen, fertig. Alles automatisch und ohne eine Zeile Code.",
img: "./assets/screens/workflow.png",
mockW: 940, mockAR: 2.366,
imgScale: 1, imgX: "0%", imgY: "0%",
// Die Bausteinliste ragt über den Ablauf hinaus: sie zeigt, woraus er entsteht.
media2: {
img: "./assets/screens/workflow-bausteine.png",
w: 340, ar: 0.8326, left: 700, bottom: 96, z: 2, rot: 0,
},
// Der Zettel liegt bewusst schief und leicht über der linken Blattkante:
// er gehört zum Ablauf, ist aber sichtbar von Hand dazugelegt.
note: {
text: "Eigene Notizen direkt am Ablauf hinterlegen.",
w: 320, left: 44, bottom: 150, z: 3, rot: -3,
},
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,
},
{
key: "projektmanagement", type: "feature",
key: "wechseln", type: "feature",
layouts: [{ id: "standard", label: "Standard" }],
eyebrow: "Vertrieb",
head: 'Du siehst auf einen Blick,<br><span class="a">wo alle Auftr\u00e4ge stehen.</span>',
body: "Projekte, Kunden, Angebote, F\u00f6rderantr\u00e4ge. Kein Suchen, kein Nachfragen, kein Zettelchaos.",
img: "./assets/screens/pipelines.png",
// Nur die linke Bildhälfte: Zoom 2 bei Versatz 0 zeigt exakt den Anteil
// 0 bis 0,5. Das Rahmenverhältnis ist die halbierte Bildbreite.
mockW: 580, mockAR: 0.8855,
imgScale: 2, imgX: "0%", imgY: "0%",
// Detailspalte eines Projekts vor dem Board: zeigt, was hinter einer
// Karte steckt, ohne das Board zu verdecken.
media2: {
img: "./assets/screens/pipelines-detail.png",
w: 300, ar: 0.781, left: 500, bottom: 52, z: 2, rot: -2,
},
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,
},
{
key: "chat", type: "feature",
key: "mehr", type: "feature",
layouts: [{ id: "standard", label: "Standard" }],
eyebrow: "Chat",
// Zweizeilig, aber knapp: Zeile 2 misst mit Marker 880 px bei 896
// verfuegbaren. Ein laengeres Wort bricht sofort um.
head: 'Projekte bekommen<br>automatisch <span class="a">ihren Kanal.</span>',
body: "Foto von der Baustelle rein, alle sehen's sofort. Aus jeder Nachricht wird mit einem Klick eine Aufgabe.",
img: "./assets/screens/chat.png",
mockW: 1030, mockAR: 1.703,
imgScale: 1, imgX: "0%", imgY: "0%",
// Das hinterste Blatt zeigt sonst den Form Builder bei 3,8-fachem Zoom:
// unscharf, und die Kopieren- und Papierkorb-Symbole gehören zu einem
// anderen Feature. Stattdessen der Chat im Hochformat, ungezoomt.
back: { img: "./assets/screens/chat-tablet.png", imgScale: 1, imgX: "0%", imgY: "0%" },
// Das Anhang-Menü zeigt, was aus einer Nachricht alles werden kann.
media2: {
img: "./assets/screens/chat-menue.png",
w: 300, ar: 0.866, left: 120, bottom: 55, z: 2, rot: -3,
},
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,
},
{
key: "form-builder", type: "feature",
key: "verbinden", type: "feature",
layouts: [{ id: "standard", label: "Standard" }],
eyebrow: "Formulare",
head: 'Formulare selbst bauen.<br><span class="a">Per Drag-and-drop.</span>',
body: "Von der Kontaktanfrage bis zum Aufmaß mit Unterschrift vor Ort. Teil sie per Link oder QR-Code, jede Einsendung wird automatisch zu Lead, Kontakt oder Auftrag.",
img: "./assets/screens/form-builder.png",
mockW: 540, mockAR: 0.75, mockLeft: 270,
imgScale: 3.8, imgX: "-36.9%", imgY: "-10%",
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,
},
{
key: "warteliste", type: "feature",
layouts: [{ id: "standard", label: "Standard" }],
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,
},
];
@@ -1130,6 +1267,73 @@
{ pfad: "./assets/screens/form-builder.png", name: "Form Builder" },
];
/* ─── Visualisierungen ───────────────────────────────────────────────────
Jede Slide nennt ueber "vis" ihr Bild. Gebaut wird es hier aus HTML, nicht
aus einer Aufnahme: so bleibt es beim Export scharf und laesst sich wie
jedes andere Element im Canvas verschieben. */
const kasten = (titel, zeile, marke) =>
'<div class="vis-kasten' + (marke ? " vis-marke" : "") + '">' +
'<div class="vis-titel">' + titel + '</div>' +
(zeile ? '<div class="vis-zeile">' + zeile + '</div>' : '') +
'</div>';
const VIS = {
karte: () =>
'<div class="karte">' +
'<div class="karte-grund"><img alt="" src="' + assets["./assets/team/noah-bg.webp"] + '">' +
'<span class="karte-wash"></span></div>' +
'<img class="karte-person" alt="Noah Geerkens" src="' +
assets["./assets/team/noah-person.webp"] + '">' +
'<div class="karte-name"><span>Noah</span><small>CEO</small></div>' +
'</div>',
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>',
ersetzen: () =>
'<div class="vis-ersetzen">' +
'<div class="schwer vis-stapel">' +
kasten("Stammdaten", "seit Jahren gepflegt") +
kasten("Aufträge", "die ganze Historie") +
kasten("Buchhaltung", "und die Leute, die es kennen") +
'</div>' +
'<div class="pfeil"><div class="vis-kreuz"><i></i><i></i></div></div>' +
'<div class="leer">alles noch mal von vorn</div>' +
'</div>',
lose: () =>
'<div class="vis-lose">' +
'<div class="feld">' + kasten("KI", "fehlt") + '<span class="stummel"></span></div>' +
'<div class="feld">' + kasten("CRM", "fehlt") + '<span class="stummel"></span></div>' +
'<div class="feld">' + kasten("Automation", "fehlt") + '<span class="stummel"></span></div>' +
'<div class="feld">' + kasten("ERP", "läuft, redet aber mit niemandem") + '<span class="stummel"></span></div>' +
'</div>',
verbinden: () =>
'<div class="vis-verbinden">' +
'<div class="saeule">' + kasten("KI") + kasten("CRM") + '</div>' +
'<div class="mitte">' +
'<span class="quer li"></span>' +
'<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>',
warteliste: () =>
'<div class="vis-warteliste">' +
'<div class="feldzeile">' +
'<div class="eingabe">deine@firma.de</div>' +
'<div class="knopf">Auf die Warteliste</div>' +
'</div>' +
'<div class="adresse">hivegrid.de</div>' +
'</div>',
};
/* ─── Das Deck ────────────────────────────────────────────────────────────
Welche Slides der Post hat, steht nicht mehr fest. Jeder Eintrag ist
entweder eine mitgelieferte Vorlage (ref) oder eine selbst angelegte, und
@@ -1191,6 +1395,7 @@
"screen-logo": { label: "Logo im Screen", size: 430, prop: "width" },
"laptop": { label: "Laptop", size: 960, prop: "width" },
"szene": { label: "Laptop · Board", size: 880, prop: "width" },
"vis": { label: "Visualisierung", size: null, prop: "width" },
"mock": { label: "Bild", size: null, prop: "width" },
"mock-2": { label: "Zweites Bild", size: null, prop: "width" },
"note": { label: "Notizzettel", size: null, prop: "width" },
@@ -1205,6 +1410,7 @@
if (key === "mock") return SLIDES[i].mockW || 1090;
if (key === "mock-2") return (SLIDES[i].media2 || {}).w || 400;
if (key === "note") return (SLIDES[i].note || {}).w || 320;
if (key === "vis") return SLIDES[i].visW || 900;
return (STANDARD[key] || {}).size;
}
@@ -1222,7 +1428,8 @@
let edits = SLIDES.map(() => ({}));
let picked = null; // { slide, key }
let editMode = "aus";
// Bearbeiten ist immer an. "aus" heisst Vorschau: alles festgelegt.
let editMode = "an";
let current = 0;
let variant = "dunkel", tilt = "an", superSample = 2, view = "streifen", tagOut = "an", frameStyle = "chamfer";
@@ -1731,6 +1938,16 @@
content.className = "content";
page.appendChild(content);
if (sl.vis && VIS[sl.vis]) {
const v = document.createElement("div");
v.className = "vis vis-" + sl.vis;
v.style.cssText =
"--vis-w:" + (sl.visW || 900) + "px;--vis-left:" + (sl.visLeft || 92) +
"px;--vis-bottom:" + (sl.visBottom || 96) + "px";
v.innerHTML = VIS[sl.vis]();
page.appendChild(v);
}
if (sl.type === "feature" && assets[sl.img]) {
const mock = document.createElement("div");
mock.className = "mock";
@@ -1882,7 +2099,9 @@
.concat(SLIDES.filter(s => s.img).map(s => s.img))
.concat(SLIDES.filter(s => s.back).map(s => s.back.img))
.concat(SLIDES.filter(s => s.media2).map(s => s.media2.img));
paths.push("./assets/screens/pipelines.png", "./assets/screens/chat-tablet.png");
// Noahs Karte von der Ueber-uns-Seite. Zwei Bilder, weil die Karte die
// Person freigestellt vor ihren eigenen Hintergrund legt.
paths.push("./assets/team/noah-bg.webp", "./assets/team/noah-person.webp");
/* Alle Screens, nicht nur die gerade benutzten: eine selbst angelegte
Slide kann jeden davon waehlen, und ein Bild, das erst beim Auswaehlen
geladen wuerde, fehlt im Export. */
@@ -3115,18 +3334,24 @@
})();
document.getElementById("forget").addEventListener("click", forgetProject);
document.getElementById("editmodes").addEventListener("click", e => {
document.getElementById("vorschau").addEventListener("click", e => {
const b = e.target.closest("button");
if (!b) return;
editMode = b.dataset.e;
press("editmodes", "e", editMode);
setVorschau(b.dataset.p === "an");
});
/* In der Vorschau ist alles festgelegt: keine Auswahl, kein Ziehen, keine
Rahmen. Blaettern und Zoomen bleiben, das ist der Sinn der Sache. */
function setVorschau(an) {
editMode = an ? "aus" : "an";
press("vorschau", "p", an ? "an" : "aus");
deck.querySelectorAll(".slide").forEach(el => { el.dataset.editmode = editMode; });
if (editMode === "aus") {
if (an) {
picked = null;
deck.querySelectorAll("[data-edit]").forEach(n => n.classList.remove("picked"));
syncEditPanel();
}
});
}
/* ─── Angleichen über alle Slides ────────────────────────────────────────
Das gewählte Element ist die Vorlage, alle gleichnamigen auf den anderen
@@ -3286,7 +3511,7 @@
const menues = [...bar.querySelectorAll(".menu")];
const zu = () => menues.forEach(m => delete m.dataset.offen);
const AKTION = { undo, redo };
const AKTION = { undo, redo, vorschau: () => setVorschau(editMode === "an") };
// Haken und graue Eintraege erst beim Oeffnen setzen: vorher weiss die
// Leiste nicht, was gerade gilt.
@@ -3299,6 +3524,11 @@
b.insertAdjacentHTML("afterbegin",
'<span class="haken">' + (an ? "✓" : "") + "</span>");
});
m.querySelectorAll('button[data-tun="vorschau"]').forEach(b => {
b.querySelector(".haken")?.remove();
b.insertAdjacentHTML("afterbegin",
'<span class="haken">' + (editMode === "aus" ? "✓" : "") + "</span>");
});
m.querySelectorAll("button[data-klick]").forEach(b => {
const q = document.getElementById(b.dataset.klick);
b.disabled = !q || q.disabled;
@@ -3545,13 +3775,12 @@
else if (e.key === "-") { setZoom((zoom || autoFit()) / 1.25); }
else if (e.key === "0") { zoom = null; fitStrip(); }
else if (k === "w") { view = view === "streifen" ? "einzeln" : "streifen"; deck.dataset.view = view; press("views", "w", view); zoom = null; show(current); }
else if (k === "e") { document.querySelector('#editmodes button[data-e="' + (editMode === "an" ? "aus" : "an") + '"]').click(); }
else if (k === "p") { setVorschau(editMode === "an"); }
else if (k === "r") {
const cyc = ["chamfer", "rund", "stapel", "aus"];
frameStyle = cyc[(cyc.indexOf(frameStyle) + 1) % cyc.length];
press("frames", "f", frameStyle); applyAll(); saveState();
}
else if (k === "g") { tagOut = tagOut === "an" ? "aus" : "an"; press("tagouts", "o", tagOut); applyAll(); saveState(); }
else if (k === "d") guard(downloadOne)();
else if (k === "a") guard(downloadAll)();
});
+31 -124
View File
@@ -14,7 +14,11 @@
},
{
"bg": "raster",
"int": "kraeftig"
"int": "dezent"
},
{
"bg": "raster",
"int": "dezent"
},
{
"bg": "raster",
@@ -28,159 +32,62 @@
"werte": {}
},
{
"ref": "workflow-engine",
"ref": "prozesse",
"werte": {}
},
{
"ref": "projektmanagement",
"ref": "wechseln",
"werte": {}
},
{
"ref": "chat",
"ref": "mehr",
"werte": {}
},
{
"ref": "form-builder",
"ref": "verbinden",
"werte": {}
},
{
"ref": "warteliste",
"werte": {}
}
],
"edits": [
{
"laptop": {
"dx": -15,
"dy": -39,
"size": 827
},
"logo-full": {
"dx": -40,
"dy": 597
},
"szene": {
"dx": 129,
"dy": -286,
"size": 1816
},
"wabe-claim": {
"dx": 1214,
"dy": -1110
},
"wabe-logo": {
"dx": 215,
"dy": 831
},
"wabe-tagline": {
"dx": 141,
"dy": -33,
"size": 88
"vis": {
"dx": -100,
"dy": 250,
"size": 700
}
},
{
"badge": {
"dx": 311,
"dy": -77
},
"body": {
"dx": 2,
"dy": -55
},
"headline": {
"dx": 0,
"dy": -109
},
"mock": {
"dx": -38,
"dy": -383,
"size": 965
},
"mock-2": {
"dx": -75,
"dy": 1,
"size": 354
},
"note": {
"dx": 150,
"dy": -244,
"size": 320
"vis": {
"dx": -7,
"dy": -19
}
},
{
"badge": {
"dx": -42,
"dy": -68
},
"body": {
"dx": 2,
"dy": -55
},
"headline": {
"dx": 0,
"dy": -109
},
"mock": {
"dx": 107,
"dy": 1,
"size": 706
},
"mock-2": {
"dx": 159,
"dy": 8,
"size": 360
"vis": {
"dx": 19,
"dy": -27
}
},
{
"badge": {
"dx": -42,
"dy": -68
},
"body": {
"dx": 2,
"dy": -55
},
"headline": {
"dx": 0,
"dy": -109
},
"mock": {
"dx": -20,
"dy": -222,
"size": 931
},
"mock-2": {
"dx": -69,
"dy": -139
}
},
{
"badge": {
"dx": -42,
"dy": -68
},
"body": {
"dx": 2,
"dy": -55
},
"headline": {
"dx": 0,
"dy": -109
},
"mock": {
"dx": -24,
"dy": -8,
"size": 584
}
}
{},
{},
{}
],
"frameStyle": "rund",
"frameStyle": "chamfer",
"layoutState": [
"szene",
"standard",
"standard",
"standard",
"standard",
"standard",
"standard"
],
"superSample": 3,
"superSample": 2,
"tagOut": "an",
"tilt": "aus",
"tilt": "an",
"v": 6,
"variant": "hell",
"view": "streifen"