Verwaltung uebersichtlicher, Bildlaufleiste dunkel

Rueckmeldung 22.08.2026 zur Anfrage-Detailansicht: "das ganze soll viel
uebersichtlicher sein und nicht so durcheinander" und "die leiste rechts
zum hoch und runter, kannst du die mal geil aussehen lassen anstatt
einfach so kake weiss."

1. DAS DURCHEINANDER HATTE EINE URSACHE

Die Kaesten standen in "columns: 2" -- Zeitungsspalten. Die fuellen sich
von selbst: erst Spalte eins bis oben voll, dann Spalte zwei. Wo ein
Kasten landet, haengt allein davon ab, wie lang die vor ihm sind. Bei
einer Anfrage stand "Interne Notizen" oben rechts, bei der naechsten
unten links. Es gab schlicht keine Ordnung, der man haette folgen koennen.

Jetzt ein festes Raster mit einer Aussage:
  LINKS  = was der Kunde geschickt hat   (lesen)
  RECHTS = was du damit machst           (handeln)

Immer gleich. Nach der zweiten Anfrage weiss man, wo man hinschaut, ohne
zu suchen. Rechts ist schmaler (Knoepfe brauchen weniger Platz als
laufender Text) und klebt beim Scrollen mit -- die Handlungen sind der
Grund, warum man die Ansicht oeffnet, sie duerfen nicht aus dem Bild
wandern.

Reihenfolge rechts korrigiert: "Anfrage uebernehmen" steht jetzt oben.
Es ist der Knopf, den man bei einer neuen Anfrage druecken WILL -- er
stand unter den Notizen und damit ausserhalb des sichtbaren Bereichs.

2. "KEINE ANGABE" WAR DIE HALBE ANSICHT

Jede fehlende Angabe bekam eine eigene Zeile. Der Kasten "Umfang"
enthielt zweimal nichts und war trotzdem so gross wie einer mit Inhalt.

Leere Felder sind aber keine Information, sondern deren Fehlen. Sie
stehen jetzt als EINE leise Zeile am Fuss: "Ohne Angabe: Bereiche,
Funktionen". Aus sechs Zeilen wird eine. Weggelassen werden sie nicht --
man muss sehen, wonach gefragt wurde und was unbeantwortet blieb, genau
daraus entstehen die Rueckfragen.

Neu darueber: "Auf einen Blick" mit Paket, Budget, Wunschtermin und
Alter. Die vier Fragen, die man immer zuerst hat, standen vorher auf drei
Kaesten verteilt. Das Alter als "vor 3 Tagen" statt als Datum -- die
Frage ist nie "welcher Tag war das", sondern "wie lange liegt das schon
hier", und die beantwortet ein Datum erst nach Kopfrechnen.

3. BILDLAUFLEISTE -- und ein Fehler, der fast durchgegangen waere

Auf einer durchgehend dunklen Seite ist eine weisse Bildlaufleiste der
einzige grelle Streifen im Bild. Sie zieht den Blick dorthin, wo nichts
Wichtiges steht, und blendet. Verstoesst gegen die Dauervorgabe
"augenschonend".

Zwei Wege noetig, weil kein Browser beide versteht: color-scheme: dark
fuer Firefox/Safari (wirkt zusaetzlich auf Auswahl- und Datumsfelder, die
sonst weiss aufblitzen), ::-webkit-scrollbar fuer Chrome/Edge.
scrollbar-width/-color bewusst NICHT gesetzt -- sobald es dasteht,
ignoriert Chrome die feineren ::-webkit-Regeln.

Beim ersten Versuch stand dort nur ".wd ::-webkit-scrollbar" -- MIT
Leerzeichen. Das trifft nur Elemente INNERHALB der Seite, nicht den body
selbst. Alle Messungen sahen gut aus; an der einen Leiste, ueber die sich
jemand beschwert hatte, haette sich nichts geaendert. Jetzt beide
Fassungen, mit einem Warnhinweis im CSS.

GEPRUEFT

Neue Datei pruef-detail.mjs: echter Browser, 1440x900 und 390x844, mit
einer absichtlich sehr knappen Anfrage -- genau die sah vorher schlecht
aus. 20 Pruefungen, alle bestanden: Spalten nebeneinander bzw. auf dem
Handy untereinander, rechte schmaler als linke, keine Ueberlappung, kein
waagerechtes Schieben, nichts ragt aus dem Fenster, keine einzelne
"keine Angabe"-Zeile mehr, keine Fehler im Protokoll.

Die Bildlaufleiste liess sich nur in einem ECHTEN Browserfenster pruefen:
Headless-Chromium blendet Leisten grundsaetzlich ueber dem Inhalt ein und
meldet deshalb immer 0 px Breite. Mit sichtbarem Fenster gemessen: 12 px,
alle sechs Regeln vom Browser akzeptiert.

Versionsstempel auf allen 11 Seiten und Cache-Name des Service Workers
auf v3 -- sonst liefert der eigene Zwischenspeicher beim ersten Laden
weiter die alte Fassung aus.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
2026-08-22 23:05:47 +02:00
co-authored by Claude Opus 5
parent 60c1e7be94
commit 9186d4cc1a
14 changed files with 490 additions and 106 deletions
+83
View File
@@ -75,6 +75,89 @@
--wd-zeit-lang: .6s;
}
/* ---------------------------------------------------------------------
1b. BILDLAUFLEISTE
Rückmeldung 22.08.2026: "die leiste rechts zum hoch und runter, kannst
du die mal geil aussehen lassen anstatt einfach so kake weiß."
Zu Recht. Auf einer durchgehend dunklen Seite ist eine helle
Bildlaufleiste der einzige grelle Streifen im Bild — sie zieht den
Blick genau dorthin, wo nichts Wichtiges steht, und ist bei einer
dunklen Umgebung schlicht blendend. Das widerspricht der Dauervorgabe
"augenschonend".
Zwei Wege sind nötig, weil kein Browser beide versteht:
* color-scheme: dark -- Firefox und Safari. Ein einziges Wort, das
dem Browser sagt: das hier ist eine dunkle Oberfläche, nimm deine
dunklen Bedienelemente. Wirkt auch auf Auswahlfelder und
Datumsauswahl, die sonst weiß aufblitzen.
* ::-webkit-scrollbar -- Chrome, Edge, Opera. Feiner steuerbar,
aber ausschließlich dort verfügbar.
scrollbar-width/-color (die neue Norm) NICHT verwendet: Sobald es
gesetzt ist, ignoriert Chrome die ::-webkit-Regeln und man bekommt
überall die grobe Fassung. Zwei getrennte Wege ergeben in beiden
Browserfamilien das bessere Ergebnis.
Die Leiste ist bewusst zurückhaltend: gedeckt, erst beim Zeigen etwas
heller. Sie soll auffindbar sein, nicht leuchten.
--------------------------------------------------------------------- */
body.wd,
.wd input, .wd select, .wd textarea {
/* Wirkt zusätzlich auf Formularfelder — sonst bleibt die Datumsauswahl
in Firefox ein weißes Rechteck mitten in der dunklen Seite. */
color-scheme: dark;
}
/* ACHTUNG bei den Selektoren: "body.wd::-webkit-scrollbar" (ohne
Leerzeichen) und ".wd ::-webkit-scrollbar" (mit Leerzeichen) sind
zwei VERSCHIEDENE Dinge, und man braucht beide.
ohne Leerzeichen -> die Leiste des body selbst, also die grosse
rechts am Fensterrand. GENAU die war gemeint.
mit Leerzeichen -> die Leisten von Elementen INNERHALB der Seite,
z.B. im Detailfenster der Verwaltung.
Beim ersten Versuch stand hier nur die zweite Fassung. Alle Messungen
sahen gut aus — an der einzigen Leiste, ueber die sich jemand
beschwert hatte, aenderte sich dadurch nichts. */
body.wd::-webkit-scrollbar,
.wd ::-webkit-scrollbar { width: 12px; height: 12px; }
body.wd::-webkit-scrollbar-track,
.wd ::-webkit-scrollbar-track {
background: rgba(8, 14, 24, .6);
border-radius: 999px;
}
body.wd::-webkit-scrollbar-thumb,
.wd ::-webkit-scrollbar-thumb {
background: linear-gradient(180deg, rgba(127,208,232,.44), rgba(183,157,255,.36));
border-radius: 999px;
/* Der durchsichtige Rand lässt die Spur seitlich durchscheinen — der
Griff wirkt dadurch schmaler als die Trefferfläche. Bedienbar bleibt
die volle Breite, sichtbar ist nur ein feiner Strich. */
border: 3px solid transparent;
background-clip: content-box;
}
body.wd::-webkit-scrollbar-thumb:hover,
.wd ::-webkit-scrollbar-thumb:hover {
background: linear-gradient(180deg, rgba(127,208,232,.6), rgba(183,157,255,.5));
background-clip: content-box;
}
body.wd::-webkit-scrollbar-thumb:active,
.wd ::-webkit-scrollbar-thumb:active {
background: linear-gradient(180deg, rgba(127,208,232,.8), rgba(183,157,255,.7));
background-clip: content-box;
}
/* Die Ecke zwischen waagerechter und senkrechter Leiste — ohne diese
Zeile bleibt genau dort ein weißes Quadrat stehen. */
body.wd::-webkit-scrollbar-corner,
.wd ::-webkit-scrollbar-corner { background: transparent; }
/* ---------------------------------------------------------------------
2. GRUNDLAGEN
--------------------------------------------------------------------- */
+155
View File
@@ -0,0 +1,155 @@
/* Prüft die neue Detailansicht der Verwaltung im echten Browser.
Die Seite spricht mit dem internen Dienst, den es hier nicht gibt.
Deshalb werden alle Netzanfragen abgefangen und mit erfundenen, aber
REALISTISCHEN Daten beantwortet — insbesondere einer sehr knappen
Anfrage, denn genau die sah vorher schlecht aus (halbe Ansicht
"keine Angabe").
Geprüft wird nicht "sieht gut aus", sondern messbar:
- stehen die Spalten wirklich nebeneinander
- ist die rechte Spalte nicht breiter als die linke
- ragt nichts aus dem Fenster
- ist die Bildlaufleiste dunkel statt weiss
*/
import { chromium } from "playwright";
import { createServer } from "node:http";
import { readFileSync, existsSync } from "node:fs";
import { join, extname } from "node:path";
const WURZEL = process.cwd();
const TYPEN = { ".html": "text/html", ".css": "text/css", ".js": "text/javascript",
".json": "application/json", ".png": "image/png", ".jpg": "image/jpeg",
".svg": "image/svg+xml", ".webp": "image/webp", ".ico": "image/x-icon" };
const server = createServer((req, res) => {
const pfad = join(WURZEL, decodeURIComponent(req.url.split("?")[0]));
if (existsSync(pfad) && !pfad.endsWith("/")) {
res.writeHead(200, { "content-type": TYPEN[extname(pfad)] || "text/plain" });
return res.end(readFileSync(pfad));
}
res.writeHead(404).end("nix");
});
await new Promise((r) => server.listen(4321, r));
const ANFRAGE_KNAPP = {
id: "t1", nummer: "A-2608-0002", name: "Selbsttest", email: "[email protected]",
telefon: null, kontaktweg: "email", sprache: "de", paket: "onepager",
ziel: "TESTANFRAGE aus dem Selbsttest - bitte archivieren.",
zielgruppe: null, seiten: null, funktionen: [], details: {},
hat_texte: null, hat_bilder: null, hat_logo: null, hat_domain: null,
budget: null, termin: null, bemerkung: null, status: "neu",
archiviert: 0, kunde_id: null,
erstellt_am: new Date(Date.now() - 3 * 86400000).toISOString(),
};
const browser = await chromium.launch();
const ergebnisse = [];
function pruefe(name, ok, zusatz) {
ergebnisse.push({ name, ok, zusatz });
console.log((ok ? " ok " : " FEHL ") + name + (zusatz ? " -> " + zusatz : ""));
}
for (const [bezeichnung, breite, hoehe] of [["Computer", 1440, 900], ["Handy", 390, 844]]) {
const seite = await browser.newPage({ viewport: { width: breite, height: hoehe } });
await seite.route("**/webdesign/admin/**", (route) => {
const u = route.request().url();
if (u.includes("/anfragen/t1")) {
return route.fulfill({ json: { ok: true, anfrage: ANFRAGE_KNAPP, notizen: [] } });
}
if (u.includes("/anfragen")) {
return route.fulfill({ json: { ok: true, anfragen: [ANFRAGE_KNAPP] } });
}
return route.fulfill({ json: { ok: true, kunden: [], projekte: [] } });
});
await seite.route("**/auth/**", (r) => r.fulfill({ json: { ok: true, user: { name: "Test" } } }));
await seite.addInitScript(() => {
sessionStorage.setItem("wd-vw-token", "testtoken");
sessionStorage.setItem("wd-sitzung-aktiv", "1");
});
const fehlerImProtokoll = [];
seite.on("pageerror", (e) => fehlerImProtokoll.push(e.message));
await seite.goto("http://127.0.0.1:4321/webdesign/verwaltung.html");
await seite.waitForTimeout(900);
console.log("\n=== " + bezeichnung + " (" + breite + "x" + hoehe + ") ===");
const karte = seite.locator(".vw-karte").first();
if (await karte.count()) {
await karte.click();
await seite.waitForTimeout(700);
}
const da = await seite.locator(".vw-detail-spalten").count();
pruefe(bezeichnung + ": Detailansicht öffnet sich", da === 1);
if (!da) { await seite.close(); continue; }
const mass = await seite.evaluate(() => {
const li = document.querySelector(".vw-dt-links");
const re = document.querySelector(".vw-dt-rechts");
const blatt = document.querySelector(".vw-detail-blatt");
const leere = document.querySelectorAll(".vw-ohne");
const blick = document.querySelectorAll(".vw-blick-feld");
const keineAngabe = Array.from(document.querySelectorAll(".vw-paar dd.vw-leer")).length;
return {
li: li?.getBoundingClientRect(), re: re?.getBoundingClientRect(),
blatt: blatt?.getBoundingClientRect(),
leereZeilen: leere.length, blickFelder: blick.length, keineAngabe,
fensterBreite: innerWidth, fensterHoehe: innerHeight,
ueberlauf: blatt ? blatt.scrollWidth > blatt.clientWidth + 1 : false,
};
});
if (breite >= 900) {
pruefe(bezeichnung + ": Spalten stehen NEBENeinander",
Math.abs(mass.li.top - mass.re.top) < 40, `links y=${Math.round(mass.li.top)}, rechts y=${Math.round(mass.re.top)}`);
pruefe(bezeichnung + ": rechte Spalte ist schmaler als die linke",
mass.re.width < mass.li.width, `${Math.round(mass.li.width)} vs ${Math.round(mass.re.width)}`);
pruefe(bezeichnung + ": Spalten überlappen sich nicht",
mass.li.right <= mass.re.left + 1, `links endet ${Math.round(mass.li.right)}, rechts beginnt ${Math.round(mass.re.left)}`);
} else {
pruefe(bezeichnung + ": Spalten stehen UNTEReinander",
mass.re.top > mass.li.top, `links y=${Math.round(mass.li.top)}, rechts y=${Math.round(mass.re.top)}`);
}
pruefe(bezeichnung + ": Fenster ragt nicht über den Bildschirm",
mass.blatt.left >= -1 && mass.blatt.right <= mass.fensterBreite + 1,
`${Math.round(mass.blatt.left)} … ${Math.round(mass.blatt.right)} bei ${mass.fensterBreite}`);
pruefe(bezeichnung + ": kein waagerechtes Schieben nötig", !mass.ueberlauf);
pruefe(bezeichnung + ": „Auf einen Blick“ zeigt vier Felder", mass.blickFelder === 4, "n=" + mass.blickFelder);
pruefe(bezeichnung + ": leere Angaben sind zusammengefasst", mass.leereZeilen >= 3, "n=" + mass.leereZeilen);
pruefe(bezeichnung + ": keine einzelne „keine Angabe“-Zeile mehr", mass.keineAngabe === 0, "n=" + mass.keineAngabe);
pruefe(bezeichnung + ": keine Fehler im Protokoll", fehlerImProtokoll.length === 0, fehlerImProtokoll.join(" | "));
await seite.screenshot({ path: `pruef-detail-${bezeichnung.toLowerCase()}.png`, fullPage: false });
await seite.close();
}
/* Bildlaufleiste: dunkel statt weiss? */
const s2 = await browser.newPage({ viewport: { width: 1200, height: 700 } });
await s2.goto("http://127.0.0.1:4321/webdesign/verwaltung.html");
await s2.waitForTimeout(400);
const bl = await s2.evaluate(() => {
const stil = getComputedStyle(document.body);
const d = document.createElement("div");
d.style.cssText = "overflow:auto;height:40px";
d.innerHTML = "<div style='height:400px'></div>";
document.body.appendChild(d);
return { farbschema: stil.colorScheme, hatRegel: !!Array.from(document.styleSheets)
.flatMap((s) => { try { return Array.from(s.cssRules); } catch { return []; } })
.find((r) => r.selectorText && r.selectorText.includes("scrollbar-thumb")) };
});
console.log("\n=== Bildlaufleiste ===");
pruefe("color-scheme steht auf dark (Firefox/Safari)", bl.farbschema === "dark", bl.farbschema);
pruefe("eigene Regel für den Griff vorhanden (Chrome/Edge)", bl.hatRegel);
await s2.close();
await browser.close();
server.close();
const fehler = ergebnisse.filter((e) => !e.ok).length;
console.log(`\n===== ${ergebnisse.length - fehler} bestanden, ${fehler} fehlgeschlagen =====`);
process.exitCode = fehler ? 1 : 0;
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
/* Zeitstrahl: die Linie verbindet die Nummernkreise. Sie liegt hinter
den Kreisen (z-index) und endet beim letzten Schritt, damit sie nicht
@@ -183,7 +183,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-ablauf.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-ablauf.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
</body>
</html>
+5 -5
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
/* ---------- Schrittanzeige ----------
Zeigt alle vier Schritte gleichzeitig statt nur "3 von 4". Man sieht
@@ -418,8 +418,8 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-anfrage.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/wd-anfrage.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-anfrage.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
<script src="/assets/js/wd-anfrage.js?v=20260822wd3"></script>
</body>
</html>
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
.fq-thema { scroll-margin-top: 90px; }
.fq-thema + .fq-thema { margin-top: clamp(2rem, 4vw, 3rem); }
@@ -195,7 +195,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-faq.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-faq.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
</body>
</html>
+4 -4
View File
@@ -16,8 +16,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
/* ---------- Nur auf der Startseite gebraucht ---------- */
@@ -407,8 +407,8 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-index.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-index.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
<script>
/* ---------------------------------------------------------------------
Marken-Intro (Masterplan S.3, Schritt 02: "Nach erfolgreichem Code
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
/* Sprungmarken.
Frueher eine waagerecht wischbare Leiste. Rueckmeldung 22.08.2026:
@@ -331,7 +331,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-leistungen.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-leistungen.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
</body>
</html>
+5 -5
View File
@@ -12,8 +12,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
.po-login { max-width: 440px; margin: clamp(1.5rem,6vh,4rem) auto; }
@@ -134,8 +134,8 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-portal.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/wd-portal.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-portal.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
<script src="/assets/js/wd-portal.js?v=20260822wd3"></script>
</body>
</html>
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
.pfo-projekt { overflow: hidden; padding: 0; margin-bottom: clamp(1.4rem, 3vw, 2.2rem); }
.pfo-bild {
@@ -192,7 +192,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-portfolio.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-portfolio.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
</body>
</html>
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
.pr-preis-gross { font-family: var(--font-head); font-weight: 800; color: var(--wd-blau); white-space: nowrap; }
@@ -196,7 +196,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-preise.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-preise.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
</body>
</html>
+1 -1
View File
@@ -34,7 +34,7 @@
Start alle alten Zwischenspeicher weg. Muss bei jeder Änderung an den
Dateien unten hochgezählt werden, sonst hängen Nutzer auf einem alten
Stand fest. */
const CACHE_NAME = "dogfather-webdesign-v2";
const CACHE_NAME = "dogfather-webdesign-v3";
/* Bausteine, die die Oberfläche zum Anzeigen braucht. Bewusst KEINE
HTML-Datei in dieser Liste. */
+4 -4
View File
@@ -13,8 +13,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
.ue-kopf { display: grid; grid-template-columns: minmax(0, .85fr) minmax(0, 1.15fr); gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
/* Auf dem Handy zuerst der Text, dann das Bild: sonst schiebt ein
@@ -127,7 +127,7 @@
<div id="wd-fuss"></div>
<script src="/assets/js/i18n-wd-ueber.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-ueber.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
</body>
</html>
+209 -63
View File
@@ -11,8 +11,8 @@
<link rel="apple-touch-icon" href="/assets/img/wd-app-180.png" />
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-title" content="DF Verwaltung" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
/* =====================================================================
Verwaltungsbereich — bewusst NUR AUF DEUTSCH.
@@ -186,25 +186,110 @@
z-index: 2;
}
/* Zwei Spalten ab Tablet: die Abschnitte sind unterschiedlich hoch,
deshalb Mauerwerk statt Raster — sonst entstehen grosse Luecken. */
@media (min-width: 780px) {
.vw-detail-spalten { columns: 2; column-gap: 1.4rem; }
.vw-detail-spalten > .vw-abschnitt {
break-inside: avoid; -webkit-column-break-inside: avoid;
display: inline-block; width: 100%;
/* ---------- Zwei Spalten MIT BEDEUTUNG ----------
Vorher "columns: 2" — also Zeitungsspalten. Die füllen sich von
selbst: erst Spalte eins bis oben voll, dann Spalte zwei. Welcher
Kasten wo landet, hängt allein davon ab, wie lang die davor sind.
Bei einer Anfrage stand "Interne Notizen" oben rechts, bei der
nächsten unten links. Genau das war das Durcheinander
(Rückmeldung 22.08.2026) — es gab schlicht keine Ordnung, der man
hätte folgen können.
Jetzt ein festes Raster mit einer Aussage:
LINKS = was der Kunde geschickt hat (lesen)
RECHTS = was du damit machst (handeln)
Diese Trennung ist immer gleich. Man weiss nach der zweiten Anfrage,
wo man hinschauen muss, ohne zu suchen.
Die rechte Spalte ist schmaler: Sie enthaelt Knoepfe und kurze Texte,
die linke laufende Angaben. Gleich breit wuerde rechts Luft und
links Enge erzeugen.
Und sie klebt beim Scrollen mit. Die Handlungen sind der Grund, warum
man diese Ansicht ueberhaupt oeffnet — sie duerfen nicht nach unten
aus dem Bild wandern, waehrend man oben die Angaben liest. */
@media (min-width: 900px) {
.vw-detail-spalten {
display: grid;
grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
gap: 0 1.3rem;
align-items: start;
}
.vw-dt-links, .vw-dt-rechts { min-width: 0; }
.vw-dt-rechts {
position: sticky;
/* Abstand zur oben klebenden Kopfzeile, sonst schiebt sich der
erste Kasten darunter. */
top: 6.2rem;
}
}
/* Unter 900px untereinander — in der Reihenfolge lesen, dann handeln. */
.vw-dt-links, .vw-dt-rechts { display: flex; flex-direction: column; }
/* ---------- Auf einen Blick ----------
Die vier Fragen, die man IMMER zuerst hat: Was will der? Wie viel
Geld? Bis wann? Wie alt ist die Anfrage? Sie standen bisher verteilt
in drei verschiedenen Kaesten weiter unten. Jetzt einmal oben,
gross, ohne Suchen. */
.vw-blick {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(115px, 1fr));
gap: .5rem;
margin-bottom: 1.1rem;
}
.vw-blick-feld {
padding: .7rem .8rem;
border-radius: 12px;
background: rgba(127,208,232,.055);
border: 1px solid var(--wd-rand);
min-width: 0;
}
.vw-blick-was {
display: block; font-size: .68rem; letter-spacing: .11em;
text-transform: uppercase; color: var(--wd-text-soft); margin-bottom: .22rem;
}
.vw-blick-wert {
display: block; font-family: var(--font-head); font-weight: 700;
font-size: .98rem; color: var(--wd-text); line-height: 1.25;
overflow-wrap: break-word;
}
.vw-blick-wert.vw-leer { font-family: var(--font-body); font-weight: 400; opacity: .5; font-size: .88rem; }
/* ---------- Nicht Angegebenes ----------
Vorher bekam jede fehlende Angabe eine eigene Zeile mit "keine
Angabe". Bei einer knappen Anfrage bestand die halbe Ansicht daraus —
der Kasten "Umfang" enthielt nichts als zweimal diesen Text und war
trotzdem so gross wie ein Kasten mit Inhalt.
Leere Felder sind aber keine Information, sondern deren Fehlen. Sie
stehen jetzt als EINE zurueckhaltende Zeile am Fuss des Kastens.
Weggelassen werden sie nicht: Man muss sehen koennen, wonach gefragt
wurde und was unbeantwortet blieb — genau daraus entstehen die
Rueckfragen. */
.vw-ohne {
margin-top: .55rem; padding-top: .5rem;
border-top: 1px dashed var(--wd-rand);
font-size: .8rem; color: var(--wd-text-soft); line-height: 1.5;
}
.vw-ohne b { font-weight: 600; color: var(--wd-text-soft); }
/* Ein Kasten, in dem GAR NICHTS ausgefuellt wurde, tritt als Ganzes
zurueck — er soll nicht dieselbe Aufmerksamkeit bekommen wie einer
mit Inhalt. */
.vw-abschnitt--leer { opacity: .62; }
.vw-abschnitt--leer:hover { opacity: 1; }
/* Jeder Abschnitt wird eine eigene Flaeche statt nur einer Linie. */
.vw-detail-spalten > .vw-abschnitt {
.vw-detail-spalten .vw-abschnitt {
padding: .95rem 1.1rem 1rem;
border-radius: 14px;
background: rgba(16, 26, 41, .5);
border: 1px solid var(--wd-rand);
margin-bottom: 1.1rem;
}
.vw-detail-spalten > .vw-abschnitt > h3 { border-bottom: none; padding-bottom: 0; margin-bottom: .7rem; }
.vw-detail-spalten .vw-abschnitt > h3 { border-bottom: none; padding-bottom: 0; margin-bottom: .7rem; }
/* "keine Angabe" tritt zurueck: Es ist die Abwesenheit einer Information
und darf nicht so aussehen wie eine. */
@@ -439,7 +524,7 @@
<div class="vw-detail-blatt" id="vw-detail-blatt"></div>
</div>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
<script>
/* =====================================================================
Verwaltung: Anfragen ansehen, Status ändern, Notizen, Archivieren.
@@ -793,6 +878,52 @@
(leer ? "keine Angabe" : s(roh ? roh : wert)) + "</dd></div>";
}
/* Baut einen Kasten aus Paaren [Bezeichnung, Wert] und trennt dabei
Ausgefuelltes von Leerem: gefuellte Angaben als vollwertige Zeilen,
alle fehlenden zusammen als eine leise Zeile darunter.
Der Unterschied in der Praxis: Aus sechs Zeilen "keine Angabe"
untereinander wird "Ohne Angabe: Telefon, Zielgruppe, Termin" — eine
Zeile statt sechs, und man liest auf einen Blick, was noch fehlt,
statt es sich aus Luecken zusammenzusuchen. */
function abschnitt(titel, paare, anhang) {
var gefuellt = paare.filter(function (p) { return p[1]; });
var fehlend = paare.filter(function (p) { return !p[1]; }).map(function (p) { return p[0]; });
var innen = gefuellt.map(function (p) { return paar(p[0], p[1]); }).join("");
/* Anhang (Knoepfe, Zusatzfragen) VOR die Fehlt-Zeile. Die gehoert an
den Fuss des Kastens — ein Knopf darunter sah aus, als beziehe er
sich auf die fehlenden Angaben. */
innen += anhang || "";
if (fehlend.length) {
innen += '<div class="vw-ohne"><b>Ohne Angabe:</b> ' + s(fehlend.join(", ")) + "</div>";
}
return '<div class="vw-abschnitt' + (gefuellt.length ? "" : " vw-abschnitt--leer") + '">' +
"<h3>" + s(titel) + "</h3>" + innen + "</div>";
}
function blickFeld(was, wert) {
var leer = !wert;
return '<div class="vw-blick-feld"><span class="vw-blick-was">' + s(was) + "</span>" +
'<span class="vw-blick-wert' + (leer ? " vw-leer" : "") + '">' +
(leer ? "offen" : s(wert)) + "</span></div>";
}
/* "vor 3 Tagen" statt eines Datums. Beim Sichten der Anfragen ist die
Frage nie "welcher Tag war das", sondern "wie lange liegt das schon
hier" — und die beantwortet ein Datum erst nach Kopfrechnen. */
function alter(iso) {
var ms = Date.now() - new Date(iso).getTime();
var tage = Math.floor(ms / 86400000);
if (isNaN(tage)) return "";
if (tage <= 0) return "heute";
if (tage === 1) return "gestern";
if (tage < 7) return "vor " + tage + " Tagen";
if (tage < 14) return "vor 1 Woche";
if (tage < 60) return "vor " + Math.floor(tage / 7) + " Wochen";
return "vor " + Math.floor(tage / 30) + " Monaten";
}
async function oeffneDetail(id) {
offeneId = id;
detail.classList.add("vw-auf");
@@ -816,46 +947,80 @@
'<span class="vw-status vw-status--' + s(a.status) + '">' + s(STATUS_TEXT[a.status] || a.status) + "</span></div>" +
'<button class="vw-zu" type="button" aria-label="Schließen">✕</button>' +
"</div>" +
/* Auf einen Blick — die vier Fragen, die man immer zuerst hat. */
'<div class="vw-blick">' +
blickFeld("Paket", PAKET_TEXT[a.paket] || a.paket) +
blickFeld("Budget", BUDGET_TEXT[a.budget] || a.budget) +
blickFeld("Wunschtermin", a.termin) +
blickFeld("Eingegangen", alter(a.erstellt_am)) +
"</div>" +
'<div class="vw-detail-spalten">' +
'<div class="vw-abschnitt"><h3>Kontakt</h3>' +
paar("E-Mail", a.email) +
paar("Telefon", a.telefon) +
paar("Bevorzugt", a.kontaktweg === "telefon" ? "Telefon" : "E-Mail") +
paar("Sprache", a.sprache) +
/* ----- LINKS: was der Kunde geschickt hat (lesen) ----- */
'<div class="vw-dt-links">' +
abschnitt("Kontakt", [
["E-Mail", a.email],
["Telefon", a.telefon],
["Bevorzugt", a.kontaktweg === "telefon" ? "Telefon" : "E-Mail"],
["Sprache", (a.sprache || "").toUpperCase()],
],
'<div class="wd-btn-reihe" style="margin-top:.7rem">' +
'<a class="wd-btn wd-btn--zweit wd-btn--klein" href="mailto:' + s(a.email) +
"?subject=" + encodeURIComponent("Deine Anfrage " + a.nummer + " bei Dogfather Webdesign") +
'">Antwort schreiben</a></div>' +
"</div>" +
'">Antwort schreiben</a></div>') +
'<div class="vw-abschnitt"><h3>Vorhaben</h3>' +
paar("Paket", PAKET_TEXT[a.paket] || a.paket) +
paar("Ziel", a.ziel) +
paar("Zielgruppe", a.zielgruppe) +
"</div>" +
abschnitt("Vorhaben", [
["Ziel", a.ziel],
["Zielgruppe", a.zielgruppe],
]) +
'<div class="vw-abschnitt"><h3>Umfang</h3>' +
paar("Bereiche", a.seiten) +
paar("Funktionen", funktionen) +
details +
"</div>" +
/* "details" sind die Zusatzfragen aus dem Formular — sie kommen
schon fertig gebaut und immer gefuellt, deshalb als Anhang. */
abschnitt("Umfang", [
["Bereiche", a.seiten],
["Funktionen", funktionen],
], details) +
'<div class="vw-abschnitt"><h3>Vorhandene Inhalte</h3>' +
paar("Texte", VORHANDEN_TEXT[a.hat_texte] || a.hat_texte) +
paar("Bilder", VORHANDEN_TEXT[a.hat_bilder] || a.hat_bilder) +
paar("Logo", VORHANDEN_TEXT[a.hat_logo] || a.hat_logo) +
paar("Domain", VORHANDEN_TEXT[a.hat_domain] || a.hat_domain) +
"</div>" +
'<div class="vw-abschnitt"><h3>Rahmen</h3>' +
paar("Budget", BUDGET_TEXT[a.budget] || a.budget) +
paar("Wunschtermin", a.termin) +
paar("Eingegangen", window.WD.datum(a.erstellt_am)) +
"</div>" +
abschnitt("Vorhandene Inhalte", [
["Texte", VORHANDEN_TEXT[a.hat_texte] || a.hat_texte],
["Bilder", VORHANDEN_TEXT[a.hat_bilder] || a.hat_bilder],
["Logo", VORHANDEN_TEXT[a.hat_logo] || a.hat_logo],
["Domain", VORHANDEN_TEXT[a.hat_domain] || a.hat_domain],
]) +
(a.bemerkung ? '<div class="vw-abschnitt"><h3>Bemerkung</h3>' + paar("", a.bemerkung) + "</div>" : "") +
"</div>" +
/* ----- RECHTS: was du damit machst (handeln) ----- */
'<div class="vw-dt-rechts">' +
/* Der wichtigste Knopf steht oben. Bei einer neuen Anfrage ist die
Uebernahme das, was man tun WILL — sie stand vorher unter den
Notizen und damit ausserhalb des Bildes. */
(a.kunde_id
? '<div class="vw-abschnitt"><h3>Kunde</h3>' +
'<p class="wd-klein wd-leise">Aus dieser Anfrage wurde bereits ein Kundenzugang erstellt. Du findest ihn oben unter „Kunden“.</p></div>'
: '<div class="vw-abschnitt"><h3>Weiter geht es</h3>' +
'<div class="vw-uebernahme">' +
"<h4>Anfrage übernehmen</h4>" +
"<p>Legt in einem Schritt den Kundenzugang UND das Projekt an — mit den Angaben aus dieser Anfrage. Du bekommst danach sofort den Einladungslink.</p>" +
'<dl class="vw-uebernahme-vorschau">' +
"<dt>Kunde</dt><dd>" + s(a.name) + "</dd>" +
"<dt>E-Mail</dt><dd>" + s(a.email) + "</dd>" +
"<dt>Projekt</dt><dd>" + s(PAKET_TEXT[a.paket] || a.paket) + " für " + s(a.name) + "</dd>" +
"<dt>Sprache</dt><dd>" + s((a.sprache || "de").toUpperCase()) + "</dd>" +
"</dl>" +
'<div class="wd-feld" style="margin-bottom:.8rem">' +
'<label for="vw-ue-preis">Gesamtpreis in Euro <span class="wd-leise">optional, später änderbar</span></label>' +
'<input type="number" id="vw-ue-preis" min="0" step="1" inputmode="numeric" placeholder="z. B. 1500" />' +
'<span class="wd-feld-hilfe" id="vw-ue-anzahlung">Die 30 % Anzahlung wird daraus berechnet.</span>' +
"</div>" +
'<button type="button" class="wd-btn wd-btn--haupt" id="vw-uebernehmen">Kunde und Projekt anlegen</button>' +
"</div></div>") +
'<div class="vw-abschnitt"><h3>Status ändern</h3>' +
'<div class="vw-status-wahl">' +
Object.keys(STATUS_TEXT).map(function (k) {
@@ -877,33 +1042,14 @@
'<button type="button" class="wd-btn wd-btn--zweit wd-btn--klein" id="vw-notiz-speichern">Notiz speichern</button>' +
"</div>" +
(a.kunde_id
? '<div class="vw-abschnitt"><h3>Kunde</h3>' +
'<p class="wd-klein wd-leise">Aus dieser Anfrage wurde bereits ein Kundenzugang erstellt. Du findest ihn oben unter „Kunden“.</p></div>'
: '<div class="vw-abschnitt"><h3>Weiter geht es</h3>' +
'<div class="vw-uebernahme">' +
"<h4>Anfrage übernehmen</h4>" +
"<p>Legt in einem Schritt den Kundenzugang UND das Projekt an — mit den Angaben aus dieser Anfrage. Du bekommst danach sofort den Einladungslink.</p>" +
'<dl class="vw-uebernahme-vorschau">' +
"<dt>Kunde</dt><dd>" + s(a.name) + "</dd>" +
"<dt>E-Mail</dt><dd>" + s(a.email) + "</dd>" +
"<dt>Projekt</dt><dd>" + s(PAKET_TEXT[a.paket] || a.paket) + " für " + s(a.name) + "</dd>" +
"<dt>Sprache</dt><dd>" + s((a.sprache || "de").toUpperCase()) + "</dd>" +
"</dl>" +
'<div class="wd-feld" style="margin-bottom:.8rem">' +
'<label for="vw-ue-preis">Gesamtpreis in Euro <span class="wd-leise">optional, später änderbar</span></label>' +
'<input type="number" id="vw-ue-preis" min="0" step="1" inputmode="numeric" placeholder="z. B. 1500" />' +
'<span class="wd-feld-hilfe" id="vw-ue-anzahlung">Die 30 % Anzahlung wird daraus berechnet.</span>' +
"</div>" +
'<button type="button" class="wd-btn wd-btn--haupt" id="vw-uebernehmen">Kunde und Projekt anlegen</button>' +
"</div></div>") +
'<div class="vw-abschnitt"><h3>Aufräumen</h3>' +
'<p class="wd-klein wd-leise">Archivieren löscht nichts — die Anfrage bleibt vollständig erhalten und lässt sich jederzeit zurückholen.</p>' +
'<button type="button" class="wd-btn wd-btn--still wd-btn--klein" id="vw-archivieren">' +
(a.archiviert ? "Aus dem Archiv zurückholen" : "Archivieren") + "</button>" +
"</div>" +
"</div>";
"</div>" + /* Ende rechte Spalte */
"</div>"; /* Ende Spaltenraster */
blatt.querySelector(".vw-zu").addEventListener("click", schliesseDetail);
+4 -4
View File
@@ -21,8 +21,8 @@
<meta name="apple-mobile-web-app-capable" content="yes" />
<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
<meta name="apple-mobile-web-app-title" content="Dogfather Webdesign" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd2" />
<link rel="stylesheet" href="/assets/css/main.css?v=20260822wd3" />
<link rel="stylesheet" href="/assets/css/webdesign.css?v=20260822wd3" />
<style>
/* =====================================================================
Zugangswand — nur hier gebraucht, deshalb bewusst NICHT in
@@ -177,8 +177,8 @@
</div>
</main>
<script src="/assets/js/i18n-wd-zugang.js?v=20260822wd2"></script>
<script src="/assets/js/wd-core.js?v=20260822wd2"></script>
<script src="/assets/js/i18n-wd-zugang.js?v=20260822wd3"></script>
<script src="/assets/js/wd-core.js?v=20260822wd3"></script>
<script>
(function () {
"use strict";