From 94dbd5c7d45d0ffceb03c6242063ed39f6427787 Mon Sep 17 00:00:00 2001 From: DeeCee03 Date: Fri, 7 Aug 2026 01:13:57 +0200 Subject: [PATCH] Rueckgaengig, Slide-Tausch und ausgeblendete Tags Der Commit buendelt zwei Arbeitsstraenge, die in derselben Datei entstanden sind und sich nicht sauber trennen liessen. Aus der parallelen Sitzung: Rueckgaengig und Wiederherstellen ueber Cmd/Strg+Z, angehaengt an saveState statt an die 25 Stellen, die etwas aendern. Ziehen speichert erst beim Loslassen, eine Bewegung ist also ein Schritt und nicht hundert. Dazu der Tausch von Automation & KI und Vertrieb samt Stand 4: der Stand haengt an der Position, nicht am Slide-Schluessel, ohne Tausch laegen Positionen, Muster und Layout an der falschen Slide. Neu hier: die Tags sind ausgeblendet, nicht entfernt. Beim ersten Versuch waren sie geloescht, worauf Ueberschrift und Text nach oben rutschten - das Tag steht im Textfluss und belegt Hoehe. visibility haelt die Box, display none haette sie ebenso aufgegeben. Der Umschalter fuer die Ausrueckung und sein Kuerzel bleiben deshalb bestehen. Stand 5 raeumt den gespeicherten Tag-Eintrag: seine Stelle ist ohne Wirkung, und die Werte standen zuletzt bis 141 px oberhalb der Slide. Co-Authored-By: Claude Opus 5 (1M context) --- post1.html | 181 +++++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 155 insertions(+), 26 deletions(-) diff --git a/post1.html b/post1.html index 451c939..6cbd351 100644 --- a/post1.html +++ b/post1.html @@ -275,7 +275,6 @@ sein TEXT dadurch um die 20 px Innenabstand eingerückt gegenüber der Headline. Das negative Margin holt den Text auf dieselbe Linie. Zum Vergleichen umschaltbar. */ - .slide[data-tagout="an"] .badge { margin-left: -20px; } .sh { position: absolute; } @@ -427,18 +426,23 @@ /* Section badge, straight from the site: .chamfer-badge in globals.css with accent-yellow-light on accent-yellow-dark. Scaled from the 12px web size to - this canvas; the 5px corner cut scales with it. */ + this canvas; the 5px corner cut scales with it. + Ausgeblendet statt entfernt: das Tag steht im Textfluss, ohne seine Box + ruecken Ueberschrift und Text nach oben. visibility haelt den Platz, + display: none wuerde ihn aufgeben. */ .badge { display: inline-block; margin-bottom: 38px; font-size: 26px; font-weight: 600; letter-spacing: 0.1em; padding: 9px 20px; background: var(--accent-yellow-light); color: var(--accent-yellow-dark); + visibility: hidden; clip-path: polygon( 11px 0, calc(100% - 11px) 0, 100% 11px, 100% calc(100% - 11px), calc(100% - 11px) 100%, 11px 100%, 0 calc(100% - 11px), 0 11px ); } + .slide[data-tagout="an"] .badge { margin-left: -20px; } .headline { font-size: 74px; font-weight: 800; @@ -913,6 +917,7 @@ E Bearbeiten · Pfeile verschieben (⇧ = 10 px)
+ − 0 Zoom · ⌘/Strg + Rad
Leer + ziehen verschiebt die Fläche
+ ⌘/Strg+Z Rückgängig (⇧ = wiederherstellen)
D Slide laden · A alle als ZIP @@ -976,24 +981,6 @@ 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: "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 - // 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, - }, - }, { key: "workflow-engine", type: "feature", layouts: [{ id: "standard", label: "Standard" }], @@ -1015,6 +1002,24 @@ w: 320, left: 44, bottom: 150, z: 3, rot: -3, }, }, + { + key: "projektmanagement", type: "feature", + layouts: [{ id: "standard", label: "Standard" }], + 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 + // 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, + }, + }, { key: "chat", type: "feature", layouts: [{ id: "standard", label: "Standard" }], @@ -1106,7 +1111,7 @@ const STORE = "hivegrid.projekt." + PROJECT; const STATE_URL = "./state/" + PROJECT + ".json"; - const STATE_V = 3; + const STATE_V = 5; function currentState() { return { @@ -1141,6 +1146,23 @@ if (alt < 3 && Array.isArray(d.edits)) { for (const e of d.edits) { if (e) delete e.szene; } } + // Stand 4: Automation & KI steht jetzt vor Vertrieb. Der Stand haengt an der + // Position, nicht am Slide-Schluessel — ohne diesen Tausch laegen die + // gezogenen Positionen, das Muster und das Layout an der falschen Slide. + if (alt < 4) { + const tausch = arr => { + if (Array.isArray(arr) && arr.length > 2) { const t = arr[1]; arr[1] = arr[2]; arr[2] = t; } + }; + tausch(d.edits); tausch(d.bgState); tausch(d.layoutState); + if (d.current === 1) d.current = 2; + else if (d.current === 2) d.current = 1; + } + // Stand 5: Das Tag ist ausgeblendet, seine gespeicherte Stelle also ohne + // Wirkung. Der Eintrag wird geraeumt, damit keine Werte stehenbleiben, + // die niemand mehr nachvollziehen kann. + if (alt < 5 && Array.isArray(d.edits)) { + for (const e of d.edits) { if (e) delete e.badge; } + } d.v = STATE_V; return true; } @@ -1164,9 +1186,96 @@ + "damit die vorhandene Datei nicht ueberschrieben wird.", "err"); } + /* ─── Rueckgaengig ─────────────────────────────────────────────────────── + Haengt an saveState statt an den 25 Stellen, die etwas aendern: was + gespeichert wird, ist genau das, was rueckgaengig gemacht werden soll. + Ziehen speichert erst beim Loslassen, eine Bewegung ist also bereits ein + Schritt und nicht hundert. */ + const UNDO_MAX = 100; + let undoStack = [], redoStack = [], lastSnap = null, restoring = false; + + // Tiefe Kopie: currentState() gibt die laufenden Arrays heraus, und beim + // Ziehen wird in edits[i][key] direkt hineingeschrieben. Ohne Kopie wuerde + // sich ein abgelegter Stand nachtraeglich mitveraendern. + const kopie = x => JSON.parse(JSON.stringify(x)); + + // Vergleich mit sortierten Schluesseln, damit die Reihenfolge, in der dx und + // dy angelegt wurden, keinen Unterschied vortaeuscht. + function stabil(x) { + if (Array.isArray(x)) return x.map(stabil); + if (x && typeof x === "object") { + const o = {}; + for (const k of Object.keys(x).sort()) o[k] = stabil(x[k]); + return o; + } + return x; + } + + // Ein Slide-Wechsel allein ist nichts, was man rueckgaengig machen will. + // show() speichert bei jedem Wechsel; ohne diese Ausnahme bestuende der + // Stapel zur Haelfte aus Auswahlwechseln. + function nurAuswahlAnders(a, b) { + const ohne = s => { const c = Object.assign({}, s); delete c.current; return JSON.stringify(stabil(c)); }; + return ohne(a) === ohne(b); + } + + function merkeFuerUndo(state) { + const snap = kopie(state); + if (lastSnap && !nurAuswahlAnders(lastSnap, snap)) { + undoStack.push(lastSnap); + if (undoStack.length > UNDO_MAX) undoStack.shift(); + redoStack.length = 0; // neuer Zweig: das Wiederherstellen verfaellt + } + lastSnap = snap; + } + + /* Zurueckspielen laeuft ueber takeState, also denselben Weg wie das Laden, + und zieht danach die gesamte Oberflaeche nach. Von Hand jeden Schalter + einzeln zu setzen waere die Stelle, an der man einen vergisst. */ + function restoreState(d) { + restoring = true; + try { + takeState(kopie(d)); + deck.dataset.view = view; + press("variants", "v", variant); + press("tilts", "t", tilt); + press("quality", "q", String(superSample)); + press("conts", "c", continuous); + press("tagouts", "o", tagOut); + press("frames", "f", frameStyle); + press("views", "w", view); + SLIDES.forEach((s, i) => { if (s.type === "title") rebuildPage(i); }); + applyAll(); + applyEditsAll(); + show(current); + if (picked) pick(picked.slide, picked.key); else syncEditPanel(); + } finally { restoring = false; } + // Der zurueckgespielte Stand ist ab jetzt der Bezugspunkt, sonst legt der + // naechste Speichervorgang ihn gleich wieder als Schritt ab. + lastSnap = kopie(currentState()); + saveState(); + } + + function undo() { + if (stateLocked) return; + if (!undoStack.length) { setStatus("Nichts mehr rueckgaengig zu machen", "err"); return; } + redoStack.push(kopie(currentState())); + restoreState(undoStack.pop()); + setStatus("Rueckgaengig · noch " + undoStack.length + " Schritte", "ok"); + } + + function redo() { + if (stateLocked) return; + if (!redoStack.length) { setStatus("Nichts wiederherzustellen", "err"); return; } + undoStack.push(kopie(currentState())); + restoreState(redoStack.pop()); + setStatus("Wiederhergestellt · noch " + redoStack.length + " Schritte", "ok"); + } + function saveState() { if (stateLocked) return; const state = currentState(); + if (!restoring) merkeFuerUndo(state); try { localStorage.setItem(STORE, JSON.stringify(state)); } catch (err) { console.warn("Speichern fehlgeschlagen", err); } markSaved(); @@ -1222,6 +1331,14 @@ // sonst bliebe ein ueber Wochen gewachsener Stand ungeteilt liegen. // Nach einer Angleichung ebenso, damit sie nicht bei jedem Laden erneut greift. if (!fromFile || migrated) setTimeout(() => pushState(currentState()), 0); + takeState(d); + return true; + } + + /* Einen Stand in die laufenden Variablen uebernehmen. Steht bewusst als + eigene Funktion da: der Ladepfad und das Rueckgaengigmachen gehen beide + hier durch, sonst zieht der eine ein Feld nach, das der andere vergisst. */ + function takeState(d) { // Nur übernehmen, was zur aktuellen Slide-Anzahl passt: Änderungen am // Aufbau dürfen keinen kaputten Stand einspielen. if (Array.isArray(d.layoutState) && d.layoutState.length === SLIDES.length) { @@ -1241,7 +1358,6 @@ tagOut = d.tagOut || tagOut; frameStyle = d.frameStyle || frameStyle; current = Number.isInteger(d.current) ? d.current : 0; - return true; } let savedTimer = null; @@ -2532,10 +2648,6 @@ document.getElementById("std-list").innerHTML = html; })(); - document.getElementById("tagouts").addEventListener("click", e => { - const b = e.target.closest("button"); - if (b) { tagOut = b.dataset.o; press("tagouts", "o", tagOut); applyAll(); saveState(); } - }); document.getElementById("views").addEventListener("click", e => { const b = e.target.closest("button"); if (b) { view = b.dataset.w; deck.dataset.view = view; press("views", "w", view); zoom = null; show(current); saveState(); } @@ -2648,6 +2760,10 @@ const b = e.target.closest("button"); if (b) { bgState[current].int = b.dataset.i; syncBgButtons(); applyAll(); saveState(); } }); + document.getElementById("tagouts").addEventListener("click", e => { + const b = e.target.closest("button"); + if (b) { tagOut = b.dataset.o; press("tagouts", "o", tagOut); applyAll(); saveState(); } + }); document.getElementById("frames").addEventListener("click", e => { const b = e.target.closest("button"); if (b) { frameStyle = b.dataset.f; press("frames", "f", frameStyle); applyAll(); saveState(); } @@ -2666,6 +2782,19 @@ document.getElementById("dl-all").addEventListener("click", guard(downloadAll)); const BG_CYCLE = ["formen", "formen-g", "nur-g", "waben", "punkte", "raster", "aus"]; + /* ⌘/Strg + Z. Eigener Zuhoerer, weil der allgemeine unten alles mit + Sondertaste durchlaesst. In Eingabefeldern hat das Zurueckgehen des + Browsers Vorrang, sonst kaeme man aus einem Tippfehler nicht mehr heraus. */ + document.addEventListener("keydown", e => { + if (!(e.metaKey || e.ctrlKey) || e.altKey) return; + const k = e.key.toLowerCase(); + if (k !== "z" && k !== "y") return; + const t = e.target; + if (t && (t.tagName === "INPUT" || t.tagName === "TEXTAREA" || t.isContentEditable)) return; + e.preventDefault(); + if (k === "y" || e.shiftKey) redo(); else undo(); + }); + document.addEventListener("keydown", e => { if (e.metaKey || e.ctrlKey || e.altKey) return; // In einem Eingabefeld gehoeren die Tasten dem Feld, nicht den Kuerzeln.