Fertiges Laptop-Mockup auf der Titel-Slide, Tags umbenannt
Das Szenen-Mockup aus Macbook Air und iPad Pro faellt weg. Das neue Bild bringt seinen Screen bereits mit, damit fallen auch die ausgeknockten Displays, die beiden matrix3d-Projektionen, die eingespielten Screenshots und die dafuer gemessenen Vierecke, Eckradien und Skalenfaktoren weg. Rund 3500 Zeichen CSS werden zu 460. Standardbreite 720 px. Das ist gerechnet: bei 1409 zu 1494 ergibt das 763 px Hoehe, zusammen mit Kopfblock und Claim 1138 von 1166 px Inhaltshoehe. Bei 760 px reisst es die Slide. Stand 3 verwirft den gespeicherten Wert fuer das Geraet. Er stammt vom alten Mockup mit anderem Format; 1020 px Breite waeren hier 1082 px hoch. Tags heissen jetzt Vertrieb, Automation & KI, Chat und Formulare. Das & steht blank, nicht als Entitaet: der Text laeuft ueber innerHTML in Tag und Navigation, aber ueber textContent in die Statuszeile. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
1923fbbb15
commit
46cce993ca
3 files changed
+41
-107
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
Binary file not shown.
|
Before Width: | Height: | Size: 1011 KiB |
+41
-107
@@ -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,14 +970,14 @@
|
||||
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 <span class="a">Handwerker</span><br>f\u00fcr Handwerker',
|
||||
sub: "Die Software, die deine Branchenprogramme nicht ersetzt, sondern verbindet.",
|
||||
},
|
||||
{
|
||||
key: "projektmanagement", type: "feature",
|
||||
layouts: [{ id: "standard", label: "Standard" }],
|
||||
eyebrow: "Projektmanagement",
|
||||
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",
|
||||
@@ -1052,7 +995,7 @@
|
||||
{
|
||||
key: "workflow-engine", type: "feature",
|
||||
layouts: [{ id: "standard", label: "Standard" }],
|
||||
eyebrow: "Workflow-Engine",
|
||||
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",
|
||||
@@ -1073,7 +1016,7 @@
|
||||
{
|
||||
key: "chat", type: "feature",
|
||||
layouts: [{ id: "standard", label: "Standard" }],
|
||||
eyebrow: "Team-Chat",
|
||||
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>',
|
||||
@@ -1094,7 +1037,7 @@
|
||||
{
|
||||
key: "form-builder", type: "feature",
|
||||
layouts: [{ id: "standard", label: "Standard" }],
|
||||
eyebrow: "Form Builder",
|
||||
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",
|
||||
@@ -1118,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" },
|
||||
@@ -1161,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 {
|
||||
@@ -1178,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;
|
||||
}
|
||||
|
||||
@@ -1429,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"]
|
||||
? '<div class="szene">' +
|
||||
'<div class="szene-screen szene-mb"><img alt="" src="' +
|
||||
assets["./assets/screens/pipelines.png"] + '"></div>' +
|
||||
'<div class="szene-screen szene-ip"><img alt="" src="' +
|
||||
assets["./assets/screens/chat-tablet.png"] + '"></div>' +
|
||||
'<img class="szene-dev" alt="" src="' +
|
||||
assets["./assets/mockups/macbook-szene.png"] + '">' +
|
||||
'</div>'
|
||||
: '<div class="laptop-missing">Szenen-Mockup fehlt.<br>Das freigestellte PNG als <b>assets/mockups/macbook-szene.png</b> ablegen.</div>';
|
||||
const dev = assets["./assets/mockups/macbook-board.png"]
|
||||
? '<div class="szene"><img class="szene-dev" alt="" src="' +
|
||||
assets["./assets/mockups/macbook-board.png"] + '"></div>'
|
||||
: '<div class="laptop-missing">Mockup fehlt.<br>Das freigestellte PNG als <b>assets/mockups/macbook-board.png</b> ablegen.</div>';
|
||||
return '<div class="title-top">' +
|
||||
'<p class="wabe-tagline">Vom Handwerker<br>für <span class="a">Handwerker</span></p>' +
|
||||
'<img class="wabe-logo" alt="HiveGrid" src="' + logo + '">' +
|
||||
@@ -1530,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) {
|
||||
@@ -1707,8 +1641,8 @@
|
||||
.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) {
|
||||
@@ -1860,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.
|
||||
@@ -1868,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
|
||||
|
||||
Reference in new issue
Block a user