diff --git a/assets/mockups/macbook-board.png b/assets/mockups/macbook-board.png new file mode 100644 index 0000000..41e754b Binary files /dev/null and b/assets/mockups/macbook-board.png differ diff --git a/assets/mockups/macbook-szene.png b/assets/mockups/macbook-szene.png deleted file mode 100644 index 2e6b96d..0000000 Binary files a/assets/mockups/macbook-szene.png and /dev/null differ diff --git a/post1.html b/post1.html index 148fa7a..aaf847b 100644 --- a/post1.html +++ b/post1.html @@ -651,70 +651,13 @@ width: 430px; height: auto; } - /* ─── Szenen-Mockup ────────────────────────────────────────────────────── - Macbook Air + iPad Pro in Perspektive (Pixeden-PSD, Hintergrund entfernt, - beide Displays ausgeknockt). Anders als beim frontalen Laptop reicht kein - Rechteck: die Displayflächen sind Vierecke. Die Screens werden deshalb - per matrix3d hineinprojiziert. Die Matrizen wurden aus den Eckpunkten der - Smart-Objekte im PSD gerechnet und bilden Design-Pixel auf Bildpixel des - 5742 px breiten Originals ab; --sk skaliert von dort auf die Breite in - der Slide und wird beim Anwenden der Größe gesetzt. */ - .szene { position: relative; width: 980px; } - .szene-dev { width: 100%; display: block; position: relative; z-index: 2; } - .szene-screen { - position: absolute; top: 0; left: 0; z-index: 1; - overflow: hidden; background: #fff; - transform-origin: 0 0; - } - .szene-mb { - width: 3456px; height: 2234px; - transform: scale(var(--sk, .1707)) - matrix3d(0.853327,-0.145151,0,-0.000016242, - -0.025684,1.027061,0,-0.000007324, - 0,0,1,0, 1614.15,493.94,0,1); - } - /* Das iPad-Viereck stammt aus der Glasflaeche, nicht aus dem Smart-Object - des PSD: das sass zu hoch und liess unten einen dreimal so breiten - schwarzen Streifen stehen. Die Glasecken sind ueber Ausgleichsgeraden auf - den vier Kanten bestimmt und geschnitten, nicht ueber Extrempunkte: die - lagen auf den Rundungen und ergaben ein zu kleines, schiefes Viereck. - Glas 1514 x 2240 mit Eckradius 95, davon 38 px Rand nach innen, also - Display 1438 x 2164 mit Radius 57. Der Radius ist konzentrisch, aussen - minus Randbreite, damit der schwarze Rand ueberall gleich breit bleibt. */ - .szene-ip { - width: 1438px; height: 2164px; - border-radius: 57px; overflow: hidden; - transform: scale(var(--sk, .1707)) - matrix3d(0.852061,-0.170328,0,-0.000024605, - 0.005789,0.984772,0,0.000001055, - 0,0,1,0, 4244.78,536.57,0,1); - } - /* Die Aufnahme fuellt das Display randlos, wie auf einem echten Bildschirm. - Vorher stand sie als kleineres Rechteck an per Hand gemessenen - Prozentwerten darin (Breite 78,877 %, Versatz 2,715 % / 14,645 %, - abgeleitet aus einem angenommenen "groessten vollstaendig sichtbaren - Rechteck" von 2726 x 1682 bei 74/154). Zwei Fehler steckten darin: - Die Konstanten stammten nicht aus der Glasflaeche des Mockups, deshalb - blieben 656 px rechts und 470 px unten leer waehrend die Werkzeugleiste - des Boards oben unter der Deckelblende verschwand. Und Prozentwerte in - translate() beziehen sich auf die Bildkante, nicht auf das Display — - Vorschau und Export loesten sie unterschiedlich auf, die Luecke war im - Export groesser als im Canvas. - object-fit uebernimmt das jetzt: kein Versatz, der auseinanderlaufen - koennte, und die Blende beschneidet von selbst. */ - .szene-screen > img { - position: absolute; top: 0; left: 0; - width: 100%; height: 100%; - object-fit: cover; object-position: center; - transform-origin: top left; - } - /* Der Hintergrundton bleibt als Rueckfall stehen, falls eine Aufnahme - einmal nicht ankommt — dann faellt es als Flaeche auf, nicht als Loch. */ - .szene-mb { background: #F5F4F4; } - .szene-ip { background: #FAFAFA; } - /* Das Board wird beim Fuellen seitlich beschnitten. Was wegfaellt, soll - rechts wegfallen: dort steht ohnehin das iPad davor. */ - .szene-mb > img { object-position: left center; } + /* ─── Laptop mit Board ─────────────────────────────────────────────────── + Fertiges Mockup: der Screen ist Teil des Bildes. Kein ausgeknocktes + Display, keine Projektion per matrix3d, kein zweiter Screenshot dahinter. + Damit fallen auch die gemessenen Vierecke und Eckradien weg, die das + vorherige Szenen-Mockup gebraucht hat. */ + .szene { position: relative; width: 720px; } + .szene-dev { width: 100%; height: auto; display: block; } .laptop-missing { width: 940px; padding: 80px 40px; text-align: center; @@ -1027,15 +970,15 @@ const SLIDES = [ { 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: "Szene" }], + 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 Handwerker
f\u00fcr Handwerker', sub: "Die Software, die deine Branchenprogramme nicht ersetzt, sondern verbindet.", }, { key: "projektmanagement", type: "feature", layouts: [{ id: "standard", label: "Standard" }], - eyebrow: "Projektmanagement", - head: 'Du siehst auf einen Blick,
wo jeder Auftrag steht.', + eyebrow: "Vertrieb", + head: 'Du siehst auf einen Blick,
wo alle Auftr\u00e4ge stehen.', 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 @@ -1052,7 +995,7 @@ { key: "workflow-engine", type: "feature", layouts: [{ id: "standard", label: "Standard" }], - eyebrow: "Workflow-Engine", + eyebrow: "Automation & KI", head: 'Wenn-dann
statt Handarbeit.', body: "Auslöser wählen, Aktionen dranhängen, fertig. Alles automatisch und ohne eine Zeile Code.", img: "./assets/screens/workflow.png", @@ -1073,15 +1016,18 @@ { key: "chat", type: "feature", layouts: [{ id: "standard", label: "Standard" }], - eyebrow: "Team-Chat", - // "automatisch seinen Kanal." misst mit Marker 933 px bei 896 - // verfuegbaren und bricht deshalb in eine dritte Zeile. "von selbst" - // sagt dasselbe und bleibt mit 846 px in der Zeile. - head: 'Jedes Projekt bekommt
von selbst seinen Kanal.', + eyebrow: "Chat", + // Zweizeilig, aber knapp: Zeile 2 misst mit Marker 880 px bei 896 + // verfuegbaren. Ein laengeres Wort bricht sofort um. + head: 'Projekte bekommen
automatisch ihren Kanal.', 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", @@ -1091,7 +1037,7 @@ { key: "form-builder", type: "feature", layouts: [{ id: "standard", label: "Standard" }], - eyebrow: "Form Builder", + eyebrow: "Formulare", head: 'Formulare selbst bauen.
Per Drag-and-drop.', 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", @@ -1115,7 +1061,7 @@ "logo-full": { label: "Logo", size: 560, prop: "width" }, "screen-logo": { label: "Logo im Screen", size: 430, prop: "width" }, "laptop": { label: "Laptop", size: 960, prop: "width" }, - "szene": { label: "Szene", size: 980, prop: "width" }, + "szene": { label: "Laptop · Board", size: 720, 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" }, @@ -1158,7 +1104,7 @@ const STORE = "hivegrid.projekt." + PROJECT; const STATE_URL = "./state/" + PROJECT + ".json"; - const STATE_V = 2; + const STATE_V = 3; function currentState() { return { @@ -1175,16 +1121,25 @@ der seinen Stand im Speicher und schreibt jede Änderung an der Datei beim nächsten Klick wieder zu. Im Ladepfad kann er das nicht. */ function migrateState(d) { - if (!d || (d.v || 1) >= 2) return false; - const feat = SLIDES.map((s, i) => (s.type === "feature" ? i : -1)).filter(i => i >= 0); - const ref = feat.length && Array.isArray(d.edits) ? (d.edits[feat[0]] || {}).badge : null; - if (ref) { - for (const i of feat.slice(1)) { - const e = d.edits[i] || (d.edits[i] = {}); - e.badge = Object.assign({}, e.badge, { dx: ref.dx || 0, dy: ref.dy || 0 }); + if (!d || (d.v || 1) >= STATE_V) return false; + const alt = d.v || 1; + if (alt < 2) { + const feat = SLIDES.map((s, i) => (s.type === "feature" ? i : -1)).filter(i => i >= 0); + const ref = feat.length && Array.isArray(d.edits) ? (d.edits[feat[0]] || {}).badge : null; + if (ref) { + for (const i of feat.slice(1)) { + const e = d.edits[i] || (d.edits[i] = {}); + e.badge = Object.assign({}, e.badge, { dx: ref.dx || 0, dy: ref.dy || 0 }); + } } } - d.v = 2; + // Stand 3: Das Titel-Mockup ist ein anderes Bild mit anderem Format + // geworden. Breite und Versatz von vorher passen dazu nicht mehr und + // wuerden das Geraet ueber die Slide hinaus schieben. + if (alt < 3 && Array.isArray(d.edits)) { + for (const e of d.edits) { if (e) delete e.szene; } + } + d.v = STATE_V; return true; } @@ -1426,16 +1381,10 @@ } const logo = variant === "dunkel" ? assets["./assets/brand/logo-white.svg"] : assets["./assets/brand/logo.svg"]; if (layoutState[i] === "szene") { - const dev = assets["./assets/mockups/macbook-szene.png"] - ? '
' + - '
' + - '
' + - '' + - '
' - : '
Szenen-Mockup fehlt.
Das freigestellte PNG als assets/mockups/macbook-szene.png ablegen.
'; + const dev = assets["./assets/mockups/macbook-board.png"] + ? '
' + : '
Mockup fehlt.
Das freigestellte PNG als assets/mockups/macbook-board.png ablegen.
'; return '
' + '

Vom Handwerker
für Handwerker

' + '' + @@ -1527,20 +1476,8 @@ e.size ? e.size + "px" : ""; } }); - // Die Perspektiv-Matrizen der Szene rechnen in Bildpixeln des Originals. - // Der Faktor auf die Breite in der Slide haengt also an der Geraetebreite - // und muss jede Groessenaenderung mitmachen. - const sz = slideEl.querySelector(".szene"); - if (sz) { - const w = (set["szene"] || {}).size || STANDARD["szene"].size; - sz.style.setProperty("--sk", w / SZENE_PX); - } } - // Breite des freigestellten Mockups im Originalmassstab. Die matrix3d-Werte - // sind darauf bezogen, deshalb steht die Zahl hier und nicht im CSS. - const SZENE_PX = 5742; - // Eine Seite = der Inhalt genau einer Slide, unabhängig davon, in welcher // Slide sie gerade mitgerendert wird. function buildPage(j) { @@ -1560,9 +1497,14 @@ if (sl.mockW) mock.style.setProperty("--mock-w", sl.mockW + "px"); if (sl.mockAR) mock.style.setProperty("--mock-ar", sl.mockAR); if (sl.mockLeft) mock.style.setProperty("--mock-left", sl.mockLeft + "px"); - // Hinterstes Blatt bekommt den Screen der nächsten Feature-Slide. + /* Hinterstes Blatt bekommt standardmässig den Screen der nächsten + Feature-Slide. Das kann danebengehen: der nächste Screen ist + womöglich stark gezoomt und damit unscharf, und er zeigt Bedienung, + die zum Feature dieser Slide gar nicht passt. Deshalb ist er über + "back" pro Slide überschreibbar. */ const withImg = SLIDES.filter(x => x.img); - const other = withImg[(withImg.findIndex(x => x.img === sl.img) + 1) % withImg.length]; + const auto = withImg[(withImg.findIndex(x => x.img === sl.img) + 1) % withImg.length]; + const other = sl.back || { img: auto.img, imgScale: auto.imgScale, imgX: auto.imgX, imgY: auto.imgY }; mock.innerHTML = '
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"); // Optional: the layout falls back to a hint if the file is not there yet. - const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-szene.png"]); - paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-szene.png"); + const optional = new Set(["./assets/mockups/laptop.png", "./assets/mockups/macbook-board.png"]); + paths.push("./assets/mockups/laptop.png", "./assets/mockups/macbook-board.png"); for (const path of [...new Set(paths)]) { const res = await fetch(path, { cache: "no-store" }).catch(() => null); if (!res || !res.ok) { @@ -1851,7 +1794,7 @@ Geräterahmen selbst. Die Rahmen fehlten in dieser Liste, deshalb blieb ein Export ohne Macbook unbemerkt. */ const SHOT_BOXES = - ".mock-inner, .laptop-screen, .hex-shot, .szene-screen, .szene-dev, .laptop-frame"; + ".mock-inner, .laptop-screen, .hex-shot, .szene-dev, .laptop-frame"; const MAX_TRIES = 5; // Ab hier gilt eine Fläche als leer. Da nur noch das Innere des Vierecks // abgetastet wird, ist kein Beiwerk mehr dabei, das die Spanne aufbläht. @@ -1859,9 +1802,9 @@ /* Die vier Ecken eines Kindes in Slide-Koordinaten. Vorher stand hier das achsenparallele Umrechteck. Das war der Fehler: - .szene-mb und .szene-ip stehen per matrix3d schräg im Raum, ihr Umrechteck - umfasst Raster, Hintergrund und das Nachbargerät. Darin fand die Prüfung - immer genug Helligkeitsspanne und winkte eine leere Displayfläche durch — + ein gedrehtes oder perspektivisch gestelltes Bild hat ein Umrechteck, das + Raster, Hintergrund und Nachbarelemente mit einschliesst. Darin fand die + Prüfung immer genug Helligkeitsspanne und winkte eine leere Fläche durch — genau so kam ein Export ohne Screenshots durch die Kontrolle. getBoxQuads gäbe die echten Ecken, kennt Chrome aber nicht. Also werden vier 1-px-Marken in die Ecken gesetzt und ausgemessen: die Layout-Engine diff --git a/state/post1-merk.json b/state/post1-merk.json index 5b13637..a1e7a10 100644 --- a/state/post1-merk.json +++ b/state/post1-merk.json @@ -34,11 +34,6 @@ "dx": -40, "dy": 597 }, - "szene": { - "dx": 1, - "dy": -88, - "size": 1020 - }, "wabe-claim": { "dx": 4, "dy": -891 @@ -67,7 +62,7 @@ }, "mock": { "dx": 96, - "dy": 18, + "dy": 15, "size": 706 }, "mock-2": { @@ -91,7 +86,7 @@ }, "mock": { "dx": -36, - "dy": -371, + "dy": -357, "size": 965 }, "mock-2": { @@ -120,7 +115,7 @@ }, "mock": { "dx": -20, - "dy": -232, + "dy": -222, "size": 931 }, "mock-2": { @@ -143,12 +138,12 @@ }, "mock": { "dx": -24, - "dy": 0, + "dy": -8, "size": 584 } } ], - "frameStyle": "stapel", + "frameStyle": "rund", "layoutState": [ "szene", "standard", @@ -159,7 +154,7 @@ "superSample": 3, "tagOut": "an", "tilt": "aus", - "v": 2, + "v": 3, "variant": "hell", "view": "streifen" }