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) <noreply@anthropic.com>
This commit is contained in:
1 parent
8daa1ac3a6
commit
94dbd5c7d4
1 file changed
+155
-26
+155
-26
@@ -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 @@
|
||||
<kbd>E</kbd> Bearbeiten · 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>
|
||||
<kbd>D</kbd> Slide laden · <kbd>A</kbd> alle als ZIP
|
||||
</div>
|
||||
</details>
|
||||
@@ -976,24 +981,6 @@
|
||||
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: "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,
|
||||
},
|
||||
},
|
||||
{
|
||||
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,<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,
|
||||
},
|
||||
},
|
||||
{
|
||||
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.
|
||||
|
||||
Reference in new issue
Block a user