Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
cef710d5db | ||
|
|
434e89f779 |
No files matched your search
@@ -4,9 +4,13 @@ Lokaler CMS-Prototyp für PHP-Webhosting mit erstem visuellen Editor. Symfony 7.
|
||||
|
||||
Die übernommenen Originaldokumente sind `AGENTS.md` und `ENTWICKLUNGSPLAN.md`. Den tatsächlichen Stand enthält `docs/STATUS.md`, Entscheidungen und Datenmodell stehen in `docs/ARCHITECTURE.md`.
|
||||
|
||||
## Slider-Steuerung auf dem Bild (ab 1.11.4)
|
||||
|
||||
Die Steuerung liegt jetzt auf dem Slider, wie beim Hero-Slider von kant-it.de: runde weiße Pfeile links und rechts in der Mitte, Punkte unten rechts mit dem aktiven Punkt als Strich in der Akzentfarbe, ein kleiner Pause-Knopf daneben, kein Zähler mehr (der bleibt für Vorlesesoftware). Auf Bildschirmen bis 600 px verschwinden die Pfeile, gewischt wird mit dem Finger. Unter **Design → Slider-Steuerung** gibt es zusätzlich „Aktiver Punkt“.
|
||||
|
||||
## Farben der Slider-Steuerung (ab 1.11.3)
|
||||
|
||||
Slider auswählen → **Design → Slider-Steuerung**: Farbe der Punkte, Farbe von Pfeilen und Zähler sowie Hintergrund der Knöpfe, je Gerät und mit Palettenfarben. Ohne eigene Werte bleiben die Punkte in der Textfarbe der Sektion und die Knöpfe weiß mit dunkler Schrift. Die Farben gelten auch für die skriptfreien Punkte, die der Browser selbst zeichnet.
|
||||
Slider auswählen → **Design → Slider-Steuerung**: Farbe der Punkte, Farbe von Pfeilen und Zähler sowie Hintergrund der Knöpfe, je Gerät und mit Palettenfarben. Ohne eigene Werte sind die Punkte halbtransparent weiß und die Pfeile weiß mit Akzentfarbe. Die Farben gelten auch für die skriptfreien Punkte, die der Browser selbst zeichnet.
|
||||
|
||||
## Slider auf dem iPhone (ab 1.11.2)
|
||||
|
||||
|
||||
+29
-1
@@ -1,12 +1,40 @@
|
||||
# Projektstatus
|
||||
|
||||
## 1.11.4 – Slider-Steuerung auf dem Bild wie bei kant-it.de (14.09.2026)
|
||||
|
||||
**Anlass:** Umut wollte nicht die Knopfleiste unter dem Slider, sondern das Design des Hero-Sliders auf www.kant-it.de (eigenes Plugin, kein Flatsome-Slider): runde weiße Pfeile auf dem Bild, Punkte unten rechts mit aktivem Strich in der Akzentfarbe, keine Zählerleiste, Pfeile auf dem Handy aus.
|
||||
|
||||
**Umgesetzt:** `slider.js` legt Slider und Steuerung in eine `.slider-shell` (beim Aufräumen wieder entpackt) und benennt Pfeile und Pause (`slider-prev/next/play`, `data-playing`); `site.css` zeichnet die Steuerung als Ebene über dem Bild: Pfeile 50 px rund mit Chevron, Punkte 9 px (aktiv 28 px Strich), Pause 32 px unten rechts, Zähler nur noch für Vorlesesoftware, Pfeile unter 600 px ausgeblendet. Neue Gestaltungseigenschaft „Aktiver Punkt“ (`sliderDotActiveColor`, `--slider-dot-active`). Der Compiler-Selektor `#id + .slider-controls` gilt in der Shell weiterhin.
|
||||
|
||||
**Geprüft:** Playwright auf der Pilotkopie bei 1600 und 390 px: Pfeile mittig links/rechts (Desktop) bzw. ausgeblendet (Mobil), Punkte und Pause unten rechts innerhalb des Bildes, Screenshots `reports/editor/slider-design-*.png` visuell geprüft. Zwei gezielte PHP-Tests, 33 JavaScript-Tests. Editor-Browserprüfung siehe Veröffentlichung.
|
||||
|
||||
## Elektro Bernhard: Abgleich mit dem bestehenden Auftritt, SEO nachgezogen (14.09.2026)
|
||||
|
||||
**Anlass:** Umut bat darum, den Nachbau gegen die laufende Website `elektro-bernhard.de` zu halten und SEO nachzuholen.
|
||||
|
||||
**Dabei zwei Falschangaben gefunden und korrigiert.** Das gelieferte HTML-Paket ordnete zwei Google-Rezensionen falsch zu: „Super Firma und einfach tolle Mitarbeiter“ stammt von **Redzepi Samir** (26.10.2024), nicht von Philipp Bender; Philipp Bender (26.11.2024) hat die Architekten-Rezension geschrieben. Der dritte Eintrag des Pakets war eine selbst formulierte Zusammenfassung statt der tatsächlichen Worte. Die Zuordnung ist am DOM der Live-Seite geprüft (`.wprevpro_t1_DIV_1`: erst Text, dann Name, dann Datum), nicht aus der flachen Textfassung geraten. Jetzt stehen drei Rezensionen im Originalwortlaut mit richtigem Namen und Datum; der Hinweis darunter nennt den Stand und sagt ausdrücklich, dass kein Gesamtdurchschnitt ausgewiesen wird.
|
||||
|
||||
**Aus dem bestehenden Auftritt nachgeholt:** die sechs Stärken („Kundenzufriedenheit“, „Qualität“, „Erfahrung“, „Flexibel“, „Professionell“, „Schnell & sauber“), die das Paket zu drei Absätzen Fließtext zusammengezogen hatte – jetzt als Symbolkarten auf der Leistungsübersicht, die dadurch von 8 auf 23 Bausteine wächst. Dazu der Handlungsaufruf „Projekt geplant? Wir sind bereit.“ als `cta`-Baustein auf der Übersicht und auf allen vier Leistungsseiten. Das Einsatzgebiet nennt jetzt auch Kuppenheim, Gaggenau, Gernsbach und Muggensturm – Orte, die der bestehende Auftritt in seinen eigenen Keywords führt.
|
||||
|
||||
**SEO:** `sameAs` mit den drei Profilen, die die Live-Seite selbst verlinkt (Google-Unternehmensprofil, Facebook, Instagram) – sie stehen jetzt in den strukturierten Daten. Unternehmensbeschreibung auf Leistungen, Notdienstnummer und Bürozeiten erweitert; sie ist zugleich die Zusammenfassungszeile in `llms.txt`. Vorschaubild beim Teilen gesetzt (Startseite Teamfoto, Leistungsseiten Installationsmotiv), womit `og:image` und `twitter:card: summary_large_image` ausgeliefert werden statt einer Textkarte. Meta-Beschreibungen liegen zwischen 110 und 159 Zeichen.
|
||||
|
||||
`llms.txt` gab es schon seit dem Aufbau (Einstellung `llmsTxt`); es listet alle zehn indexierbaren Seiten mit Beschreibung.
|
||||
|
||||
**Bewusst nicht per Skript gesetzt:** der Auszug (`excerpt`) je Seite. Das Feld erscheint in der Verwaltung nur bei Beiträgen; ein per SQL geschriebener Wert wäre unsichtbar und würde beim nächsten Speichern der Seitendaten stillschweigend gelöscht. `llms.txt` nimmt ersatzweise die Seitenbeschreibung, und die ist im Editor bearbeitbar.
|
||||
|
||||
**Offene Lücke gegenüber dem HTML-Paket:** dessen JSON-LD enthielt `openingHoursSpecification`, `areaServed`, einen `contactPoint` für den Notdienst sowie `FAQPage` und `Service`. Das CMS kennt diese Felder nicht – es liefert `LocalBusiness` (Name, Adresse, Telefon, E-Mail, `sameAs`) und `BreadcrumbList`. Das nachzurüsten ist eine Kernerweiterung (Einstellungen, `SiteSettings::organisation()`, FAQ aus Akkordeons ableiten) mit eigenem Release, nicht eine Änderung an dieser Website. Noch nicht umgesetzt, nicht beauftragt.
|
||||
|
||||
**Geprüft:** Alle zehn Adressen 200, `/gibtsnicht` 404, `llms.txt` und `sitemap.xml` ausgeliefert; strukturierte Daten, `og:image` und `twitter:card` im Quelltext kontrolliert; Leistungsübersicht, Leistungsseite und Kundenstimmen in der App-Vorschau angesehen; keine Konsolenfehler. Das größte Dokument (Startseite) liegt bei 117 Bausteinen und 27 KiB – Grenze sind 300 und 256 KiB.
|
||||
|
||||
## 1.11.3 – Farben der Slider-Steuerung (14.09.2026)
|
||||
|
||||
**Anlass:** Umut will die Punktfarben am Slider einstellen; auf dem dunklen Hero waren die Standardfarben (dunkle Punkte, weiße Knöpfe) nicht anpassbar.
|
||||
|
||||
**Umgesetzt:** Drei Gestaltungseigenschaften in der neuen Gruppe „Slider-Steuerung“ (`sliderDotColor`, `sliderControlColor`, `sliderControlBackground`) mit dem Ziel `controls`: der Compiler schreibt sie als `--slider-dot`, `--slider-ink`, `--slider-bg` auf den Slider und auf die vom Skript dahinter eingefügte Steuerleiste (`#id,#id + .slider-controls{…}`); `site.css` liest die Variablen für Punkte, Pfeile, Zähler, Knopfhintergrund und die skriptfreien `::scroll-marker`. Die Gruppe erscheint nur an Slidern (`relevantProperty`).
|
||||
|
||||
**Geprüft:** Neuer Test `testSliderControlsTakeTheirColoursFromTheSliderStyle` (Ausgabe mit Hex- und Palettenfarbe). Playwright im Editor der Pilotkopie: Gruppe am Hero-Slider vorhanden, Punktfarbe `#ff0000` gesetzt, Regel in der Vorschau angekommen, Punkt bei „Bewegung abspielen“ rot gezeichnet. 33 JavaScript-Tests. Editor-Browserprüfung siehe Veröffentlichung.
|
||||
**Geprüft:** Neuer Test `testSliderControlsTakeTheirColoursFromTheSliderStyle` (Ausgabe mit Hex- und Palettenfarbe). Playwright im Editor der Pilotkopie: Gruppe am Hero-Slider vorhanden, Punktfarbe `#ff0000` gesetzt, Regel in der Vorschau angekommen, Punkt bei „Bewegung abspielen“ rot gezeichnet. 33 JavaScript-Tests, Editor-Browserprüfung vollständig erfolgreich.
|
||||
|
||||
**Veröffentlicht:** Commit `6dc30a4` und Tag `v1.11.3` auf `origin`; [Release 1.11.3](https://git.kant-it.de/kantarci/KantSites/releases/tag/v1.11.3) mit ZIP (3548 Dateien), SHA-256-Datei und `install.php` als neuestes stabiles Release; Anhänge anonym heruntergeladen und bytegenau verglichen, SHA-256 `a9536289…f45a24`. Der Commit enthält auch den parallel entstandenen Statuseintrag zur Elektro-Bernhard-Referenzwebsite; deren Skripte sind noch nicht committed.
|
||||
|
||||
## Zweite Referenzwebsite: Elektro Bernhard in eigener Datenbank (14.09.2026)
|
||||
|
||||
|
||||
+26
-2
@@ -203,8 +203,32 @@ body.consent-open{padding-bottom:260px}
|
||||
.slider-dots{position:relative}
|
||||
@supports selector(::scroll-marker){.slider-dots:not(.slider-controlled){scroll-marker-group:after;scroll-behavior:smooth;padding-bottom:0;scrollbar-width:none;scrollbar-gutter:auto}.slider-dots:not(.slider-controlled)::-webkit-scrollbar{display:none}.slider-dots:not(.slider-controlled)>*::scroll-marker{content:'';width:12px;height:12px;border-radius:50%;background:color-mix(in srgb,var(--slider-dot,currentColor) 30%,transparent);margin:0 6px;cursor:pointer}.slider-dots>*::scroll-marker:target-current{background:var(--slider-dot,var(--accent))}.slider-dots::scroll-marker-group{display:flex;justify-content:center;align-items:center;padding:14px 0 2px}}
|
||||
.slider-controlled{scrollbar-width:none;scrollbar-gutter:auto}.slider-controlled::-webkit-scrollbar{display:none}
|
||||
.slider-controls{display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:8px;min-height:48px;margin-top:8px;padding:4px;font:14px/1.4 system-ui,sans-serif}.slider-controls button{display:inline-flex;align-items:center;justify-content:center;min-width:44px;min-height:40px;padding:8px 12px;border:1px solid color-mix(in srgb,var(--slider-ink,#193c2d) 35%,transparent);border-radius:7px;background:var(--slider-bg,#fff);color:var(--slider-ink,#193c2d);cursor:pointer;font:inherit}.slider-controls button:focus-visible{outline:3px solid #b55b12;outline-offset:3px}.slider-controls button:hover{background:color-mix(in srgb,var(--slider-bg,#fff) 85%,var(--slider-ink,#193c2d))}.slider-controls button:disabled{opacity:.6;cursor:default}.slider-position{font-variant-numeric:tabular-nums;min-width:44px;text-align:center;color:var(--slider-ink,inherit)}.slider-control-dots{display:flex;flex-wrap:wrap;gap:3px}.slider-control-dots button{min-width:32px;padding:0;border:0;background:transparent;color:var(--slider-dot,var(--slider-ink,inherit));font-size:0}.slider-control-dots button::before{content:'';width:10px;height:10px;border-radius:50%;border:2px solid currentColor;background:transparent;transition:background-color .2s}.slider-control-dots button[aria-pressed=true]::before{background:currentColor}.slider-control-dots button:hover::before{background:color-mix(in srgb,currentColor 50%,transparent)}.slider-controls[hidden]{display:none}
|
||||
@media(max-width:650px){.slider-controls button{min-height:44px;min-width:44px}}
|
||||
/* Die Steuerung liegt auf dem Bild, wie beim Hero-Slider von kant-it.de: runde Pfeile links und
|
||||
rechts, Punkte unten rechts (der aktive als Strich in der Akzentfarbe), Pause klein unten links,
|
||||
kein Zähler. Farben kommen aus der Gestaltung des Sliders (--slider-*). */
|
||||
.slider-shell{position:relative}
|
||||
.slider-controls{position:absolute;inset:0;margin:0;padding:0;pointer-events:none;font:14px/1 system-ui,sans-serif}
|
||||
.slider-controls button{pointer-events:auto;cursor:pointer;font:inherit;padding:0;border:0;background:transparent;color:inherit}
|
||||
.slider-controls button:focus-visible{outline:3px solid var(--slider-dot-active,var(--accent));outline-offset:3px}
|
||||
.slider-controls .slider-prev,.slider-controls .slider-next{position:absolute;top:50%;transform:translateY(-50%);width:50px;height:50px;border-radius:50%;background:var(--slider-bg,rgba(255,255,255,.9));color:var(--slider-ink,var(--accent));display:flex;align-items:center;justify-content:center;font-size:0;box-shadow:0 6px 18px rgba(0,0,0,.18);transition:background-color .2s,color .2s,transform .2s}
|
||||
.slider-prev{left:24px}.slider-next{right:24px}
|
||||
.slider-prev::before,.slider-next::before{content:'';width:12px;height:12px;border-left:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);margin-left:5px}
|
||||
.slider-next::before{transform:rotate(-135deg);margin-left:-5px}
|
||||
.slider-controls .slider-prev:hover,.slider-controls .slider-next:hover{background:var(--slider-dot-active,var(--accent));color:#fff}
|
||||
.slider-prev:disabled,.slider-next:disabled{opacity:.4;cursor:default}
|
||||
.slider-position{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
|
||||
.slider-control-dots{position:absolute;right:70px;bottom:20px;display:flex;gap:3px}
|
||||
.slider-control-dots button{display:flex;align-items:center;justify-content:center;width:24px;height:24px;font-size:0;transition:width .25s}
|
||||
.slider-control-dots button::before{content:'';width:9px;height:9px;border-radius:99px;background:var(--slider-dot,rgba(255,255,255,.55));transition:width .25s,background-color .25s}
|
||||
.slider-control-dots button[aria-pressed=true]{width:40px}
|
||||
.slider-control-dots button[aria-pressed=true]::before{width:28px;background:var(--slider-dot-active,var(--accent))}
|
||||
.slider-control-dots button:hover::before{background:var(--slider-dot-active,var(--accent))}
|
||||
.slider-controls .slider-play{position:absolute;right:24px;bottom:16px;width:32px;height:32px;border-radius:50%;background:rgba(0,0,0,.35);color:#fff;font-size:0;display:flex;align-items:center;justify-content:center}
|
||||
.slider-play::before{content:'';width:0;height:0;border-left:9px solid currentColor;border-top:6px solid transparent;border-bottom:6px solid transparent;margin-left:2px}
|
||||
.slider-play[data-playing=true]::before{width:8px;height:11px;border:0;border-left:3px solid currentColor;border-right:3px solid currentColor;margin-left:0}
|
||||
.slider-play:disabled{opacity:.4;cursor:default}
|
||||
.slider-controls[hidden]{display:none}
|
||||
@media(max-width:600px){.slider-controls .slider-prev,.slider-controls .slider-next{display:none}.slider-control-dots{right:54px;bottom:14px}.slider-controls .slider-play{right:14px;bottom:12px}}
|
||||
|
||||
.slide-desktop-1{grid-auto-columns:100%}.slide-desktop-2{grid-auto-columns:calc(50% - 10px)}
|
||||
.slide-desktop-3{grid-auto-columns:calc(33.333% - 14px)}.slide-desktop-4{grid-auto-columns:calc(25% - 15px)}
|
||||
|
||||
+10
-5
@@ -56,9 +56,9 @@ export function initSliders(doc = document) {
|
||||
go(direction > 0 ? (last ? visible[0] : visible[Math.min(visible.length - 1, position + 1)]) : (first ? visible.at(-1) : visible[Math.max(0, position - 1)]));
|
||||
};
|
||||
const manual = action => { playing = false; clearTimeout(timer); update(); action(); };
|
||||
const previous = button('←', 'Vorherige Folie', () => manual(() => step(-1)));
|
||||
const previous = button('←', 'Vorherige Folie', () => manual(() => step(-1))); previous.className = 'slider-prev';
|
||||
const count = doc.createElement('span'); count.className = 'slider-position'; controls.append(count);
|
||||
const next = button('→', 'Nächste Folie', () => manual(() => step(1)));
|
||||
const next = button('→', 'Nächste Folie', () => manual(() => step(1))); next.className = 'slider-next';
|
||||
const dots = [];
|
||||
if (slider.classList.contains('slider-dots')) {
|
||||
const group = doc.createElement('div'); group.className = 'slider-control-dots'; controls.append(group);
|
||||
@@ -67,6 +67,7 @@ export function initSliders(doc = document) {
|
||||
});
|
||||
}
|
||||
const play = seconds >= 3 ? button('', 'Automatische Wiedergabe pausieren', () => { playing = !playing; update(); schedule(); }) : null;
|
||||
if (play) play.className = 'slider-play';
|
||||
const update = () => {
|
||||
const visible = available();
|
||||
const fits = visible.length < 2 || slider.scrollWidth <= slider.clientWidth + 2;
|
||||
@@ -74,7 +75,7 @@ export function initSliders(doc = document) {
|
||||
const index = active(); count.textContent = `${Math.max(0, visible.indexOf(index) + 1)} / ${visible.length}`;
|
||||
dots.forEach((dot, i) => { dot.hidden = !visible.includes(i); dot.setAttribute('aria-pressed', String(i === index)); });
|
||||
if (play) {
|
||||
play.textContent = playing ? 'Pause' : 'Wiedergabe';
|
||||
play.textContent = playing ? 'Pause' : 'Wiedergabe'; play.dataset.playing = String(playing);
|
||||
play.setAttribute('aria-label', playing ? 'Automatische Wiedergabe pausieren' : 'Automatische Wiedergabe starten');
|
||||
play.disabled = reduced(); play.title = reduced() ? 'Automatik ist bei reduzierter Bewegung deaktiviert.' : '';
|
||||
}
|
||||
@@ -84,7 +85,11 @@ export function initSliders(doc = document) {
|
||||
if (playing && !reduced()) timer = setTimeout(() => { if (playing && !hover && visible && !doc.hidden) step(1); schedule(); }, seconds * 1000);
|
||||
};
|
||||
const pause = () => { playing = false; update(); clearTimeout(timer); };
|
||||
slider.after(controls); slider.classList.add('slider-controlled'); update();
|
||||
// Slider and controls share a positioned shell, so arrows and dots can lie on the picture
|
||||
// the way the Kant-IT hero slider draws them.
|
||||
const shell = doc.createElement('div'); shell.className = 'slider-shell';
|
||||
slider.replaceWith(shell); shell.append(slider, controls);
|
||||
slider.classList.add('slider-controlled'); update();
|
||||
slider.addEventListener('scroll', update, { passive: true, signal });
|
||||
view.addEventListener('resize', update, { passive: true, signal });
|
||||
slider.addEventListener('pointerenter', () => { hover = true; }, { signal });
|
||||
@@ -98,7 +103,7 @@ export function initSliders(doc = document) {
|
||||
const resize = new view.ResizeObserver(update); resize.observe(slider);
|
||||
preference.addEventListener('change', () => { if (reduced()) pause(); update(); }, { signal });
|
||||
schedule();
|
||||
cleanups.push(() => { clearTimeout(timer); clearInterval(settle); slider.style.scrollSnapType = ''; events.abort(); observer.disconnect(); resize.disconnect(); controls.remove(); slider.classList.remove('slider-controlled'); delete slider.dataset.enhanced; });
|
||||
cleanups.push(() => { clearTimeout(timer); clearInterval(settle); slider.style.scrollSnapType = ''; events.abort(); observer.disconnect(); resize.disconnect(); controls.remove(); shell.replaceWith(slider); slider.classList.remove('slider-controlled'); delete slider.dataset.enhanced; });
|
||||
});
|
||||
return () => cleanups.forEach(cleanup => cleanup());
|
||||
}
|
||||
|
||||
@@ -207,6 +207,7 @@ final class StyleSchema
|
||||
// The slider controls are built by the script next to the slider, so their colours
|
||||
// travel as custom properties onto the slider and its controls (target "controls").
|
||||
'sliderDotColor' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-dot', 'label' => 'Punkte', 'target' => 'controls'],
|
||||
'sliderDotActiveColor' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-dot-active', 'label' => 'Aktiver Punkt', 'target' => 'controls'],
|
||||
'sliderControlColor' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-ink', 'label' => 'Pfeile und Zähler', 'target' => 'controls'],
|
||||
'sliderControlBackground' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-bg', 'label' => 'Hintergrund der Knöpfe', 'target' => 'controls'],
|
||||
];
|
||||
|
||||
Reference in new issue
Block a user