K24–K26 sind seit dem Webshop-Livegang (main) vergeben; der Mail-Lader sucht die Vorlage über den Dateianfang, doppelte Nummern hätten die falsche Mail geschickt. Das Feld ist noch nicht in der Produktion angelegt (zeiten-setup.mjs lief noch nicht), daher jetzt ein sprechender Name. Spec und Plan behalten die alten Nummern (Hinweis oben im Dokument). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
16 KiB
Öffnungszeiten live auf der Kundenwebsite einbauen
Anleitung für das WEBklar-Team. „Öffnungszeiten live“ zeigt die Zeiten, die der Kunde im Portal einträgt (Tab „Funktionen“, Kachel „Öffnungszeiten live“), auf seiner Website: als Tabelle, als Live-Status und als Kurzanzeige, dazu eine Hinweis-Leiste, auf dem Handy eine Kontakt-Leiste und Google-Angaben (JSON-LD) im Seitencode. Den siteKey (16 Zeichen, a–z und 2–7) findest Du im WOMS: Kunde → Reiter „Funktionen“, Zeile „Öffnungszeiten live“ (mit Kopierknopf). Im Einrichtungs-Ticket „Öffnungszeiten live einbauen – “ stehen Einbau-Zeile, Ergebnis des automatischen Einbaus (einbau), Beispiele der Platzhalter mit den Plan-Kurznamen und die Prüfliste. Dieses Dokument ist die Referenz dahinter. Die Datenschutz-Absätze für die Kundenseite stehen in docs/funktionen/website-datenschutz-absatz.md.
Die Spec ist docs/superpowers/specs/2026-10-04-oeffnungszeiten-live-design.md (Abschnitte 4.2, 4.3, 4.5, 4.6).
1. Was automatisch geschieht und was Du tust
| Teil | Wer | Wie |
|---|---|---|
| Einbau-Zeile (Skript) | Runner, automatisch | beim nächsten Veröffentlichen in jede HTML-Datei vor </head>, nur wenn die Funktion in setup oder active steht |
| Hinweis-Leiste, Kontakt-Leiste, JSON-LD | Skript | kommen mit den Daten, ohne Platzhalter |
| Platzhalter (Tabelle, Status, Badge) mit Ersatz-HTML | Du | an die Stellen setzen, an denen feste Zeiten stehen, und die festen Zeiten ersetzen |
| Datenschutz-Absatz auf der Website | Du | Abschnitt 8, Punkt 5 |
Einbau-Zeile bei einbau: manuell oder wenn die Seite woanders liegt |
Du | Abschnitt 9 |
Der Einbau ist ein Einbau je Website und ist im Preis (30 € oder im Wartungs-Abo ohne Aufpreis) enthalten. Er wird nicht auf das Stundenkontingent angerechnet. Sonderwünsche (zusätzliche Stellen, Sondergestaltung) gehen über ein Angebot oder, mit Abo, über Stunden.
2. Die Einbau-Zeile
<script data-wk-zeiten src="https://project.webklar.com/embed/zeiten.js" data-site="SITEKEY" defer></script>
- Ins
<head>jeder HTML-Seite, einmal pro Seite. Der Runner setzt sie vor</head>ein (liegt kein</head>vor, nach<body>); eine schon vorhandene Zeile mitdata-wk-zeitenersetzt er, der Einbau ist also wiederholbar und doppelt nichts. data-siteist dersiteKey. Optionaldata-api="https://project.webklar.com"(nur am Script-Tag, nie an einem Platzhalter); ohne Angabe nimmt das Skript die Herkunft seiner eigenen Adresse (das klappt nur bei einem normalen<script src>; bei Tag-Manager oder nachgeladenem Skript istdata-apiPflicht). Den siteKey darf auch ein Platzhalter tragen (data-webklar-zeiten="SITEKEY").- Die Adresse des Skripts kommt aus der Runner-Variable
ZEITEN_PUBLIC_BASE(Vorgabehttps://project.webklar.com/embed/, muss mithttps://beginnen und mit/enden). Für die Produktion nichts ändern. - Das Skript hat keine Abhängigkeiten, setzt keine Cookies (keine Cookies, kein
localStorage, keinsessionStorage), fragt nurproject.webklar.comab und lädt nichts von Dritten. Kundentexte setzt es nur als Text ein, nie als HTML. Es wird 5 Minuten gecacht und darf von jeder Domain geladen werden. Ein Abruf je Seite für alle Teile, danach höchstens alle 10 Minuten. - Hat die Seite eine Content-Security-Policy, muss
project.webklar.cominscript-srcundconnect-srcstehen, undstyle-srcmuss Inline-Stile erlauben ('unsafe-inline'): das Skript legt ein<style>-Element an (Tabelle, Status, Leisten). Ohne diese Erlaubnis erscheinen sie ohne Gestaltung.
3. Platzhalter
Ein Platzhalter ist ein Element mit dem Attribut data-webklar-zeiten. Innen steht das Ersatz-HTML (Abschnitt 4).
<div data-webklar-zeiten data-modus="tabelle" data-plan="oeffnungszeiten">…Ersatz-HTML…</div>
<div data-webklar-zeiten data-modus="status" data-plan="oeffnungszeiten">Mo–Fr 9–18 Uhr</div>
<span data-webklar-zeiten data-modus="badge" data-plan="oeffnungszeiten">Mo–Fr 9–18 Uhr</span>
data-modus |
Zeigt | Gedacht für |
|---|---|---|
tabelle |
Wochentabelle (echtes <table> mit Caption und Zeilenköpfen, heutiger Tag mit aria-current="date"), darunter „Abweichungen in den nächsten 14 Tagen“ |
Kontaktseite, Footer |
status |
Punkt und Text, zum Beispiel „Jetzt geöffnet · schließt um 18 Uhr“, ohne aria-live |
Kopfbereich, Hero |
badge |
Kurzform „Geöffnet“, „Geschlossen“, „Nach Vereinbarung“ | Menü, Header |
data-planist der Kurzname des Zeitplans (Kleinbuchstaben, Ziffern und Bindestrich, 1–24 Zeichen, nach dem Anlegen unveränderlich). Der Hauptplan heißtoeffnungszeiten. Weitere Pläne (zum Beispielkueche,lieferung) hat der Kunde im Editor angelegt; die Kurznamen stehen im Editor und im Einrichtungs-Ticket („Pläne (Kurznamen für data-plan)“). Ohnedata-plangilt der Hauptplan.- Einzelseiten-Apps (Vite/React): Das Skript beobachtet
<body>und füllt Platzhalter, die erst nach einem Seitenwechsel auf die Seite kommen, nach etwa 0,2 Sekunden (auch einen wieder eingehängten Knoten). Setze Platzhalter nicht in Teile, die die App beim Rendern entfernt oder neu aufbaut: ein neu gebauter Knoten startet wieder mit seinem Ersatz-HTML, und was das Skript hineingeschrieben hat, kann die App überschreiben. - Ein unbekannter
data-planoderdata-moduslässt das Ersatz-HTML stehen und schreibt eine Warnung in die Konsole. Es gibt kein stilles Umschalten auf den Hauptplan. Bei Fehlern, ohne JavaScript, bei Daten älter als 24 Stunden und nach dem Abschalten bleibt ebenfalls das Ersatz-HTML. - Das Skript füllt die Platzhalter ab
setup, damit Du vor dem Schließen des Tickets prüfen kannst. - Gestaltung: Light DOM, alle Klassen beginnen mit
wkz-, die Stile stehen in@layer wkz. Jede Regel der Kundenseite (ohne Layer) gewinnt also gegen die Standard-Stile. Schrift und Farben erbt die Seite. Variablen (alle optional, am Platzhalter oder auf:root):--wkz-offen,--wkz-zu,--wkz-unklar(Farben der Punkte),--wkz-offen-hell,--wkz-zu-hell,--wkz-unklar-hell,--wkz-offen-text,--wkz-zu-text,--wkz-unklar-text(Badge),--wkz-rand,--wkz-heute. Kontrast mindestens 4,5 : 1 prüfen. - Hat das Theme globale Regeln wie
table{…}odertd{…}, greifen sie auch in der Tabelle. Prüfe das bei der Abnahme und überschreibe gezielt, zum Beispiel[data-webklar-zeiten] table{ … }.
4. Ersatz-HTML
Jeder Platzhalter behält eine statische Ersatz-Tabelle: Sie steht ohne JavaScript, bei einem Fehler und nach der Abschaltung. Ohne sie wäre die Stelle leer.
- WOMS → Funktionen → Ersatz-HTML kopieren (je Plan ein Knopf; bei mehreren Plänen mit Namen dahinter). Erst nach dem ersten Speichern des Kunden; vorher meldet das WOMS „Der Kunde hat noch keine Zeiten eingetragen“ (die Schnittstelle antwortet 409:
GET /api/team/funktionen/:customerId/zeiten/ersatz?projectId=…&plan=<kurz>). - In jeden Platzhalter einsetzen. Das Ersatz-HTML ist eine schlichte Wochentabelle als reines ASCII mit Entities (
ä,–), also in jeder Dateikodierung sicher. Ein Fensterende um Mitternacht steht als00:00(„18:00–00:00“), ein Fenster von 00:00 bis 24:00 als „rund um die Uhr“. - Die Tabelle zeigt den Wochenplan zum Zeitpunkt des Einbaus. Ändert der Kunde seinen Wochenplan später, bleibt das Ersatz-HTML zunächst stehen (mit Skript unsichtbar).
- Beim Abschalten der Funktion, wenn der aktuelle Wochenplan des Hauptplans vom Stand beim Einbau abweicht, entsteht ein WOMS-Ticket „Ersatz-Tabelle aktualisieren – “ mit dem aktuellen Ersatz-HTML. Ersetze die Tabelle in allen Platzhaltern (gehört zum enthaltenen Einbau, Leistungsbeschreibung Nr. 4 Abs. 3). Die Mail K30 sagt dem Kunden, dass wieder feste Zeiten auf der Seite stehen. Liegt die Seite woanders, schicke dem Kunden den Text.
5. Header-Selektor (navSelektor)
Die Hinweis-Leiste steht als erstes Kind von <body> im Fluss und schiebt den Inhalt nach unten. Feste und klebende Elemente mit top: 0 rückt das Skript, solange die Leiste sichtbar ist, um max(0, Leistenhöhe − Scrollposition) nach unten (Inline-top, beim Entfernen der Leiste zurückgesetzt) und erhöht scroll-padding-top.
Überlappt die Leiste trotzdem den Kopf der Seite (zum Beispiel ein fester Header mit top: 12px, mit transform oder in einem Wrapper), setze im WOMS bei Kunde → Funktionen → „Öffnungszeiten live“ das Feld Header-Selektor (Team-Einstellung navSelektor):
- ein CSS-Selektor des Headers, höchstens 120 Zeichen, erlaubt sind nur
A–Z a–z 0–9, Leerzeichen und# . - _ > : [ ] = " ', nicht erlaubt<,{,}und;. Beispiel:header.site-headeroder#nav. - Das Skript wendet ihn mit
querySelectorAllin einemtry/catchan; ein falscher Selektor bricht nichts, er bewirkt nur nichts. - Leer lassen = Standard (nur
top: 0). Speichern mitteamVersion(bei 409 „Inzwischen geändert – bitte neu laden“). - Prüfe nach dem Setzen: oben auf der Seite, beim Scrollen (die Leiste scrollt weg, der Header klebt wieder oben) und auf dem Handy.
6. Leisten ausschalten
Im selben Bereich (WOMS → Funktionen → „Öffnungszeiten live“) gibt es zwei Haken: kontaktleisteAus („Kontakt-Leiste aus“) und hinweisleisteAus („Hinweis-Leiste aus“). Sie gelten je Website.
- Die Kontakt-Leiste erscheint nur bei
(max-width: 768px), unten fest mitz-index: 40, Knöpfe „Anrufen“, „Route“ und, wenn der Kunde sie eingerichtet hat, „WhatsApp“. Hat die Seite selbst ein festes Element am unteren Rand mit einema[href^="tel:"], bleibt sie automatisch aus. Setze den Haken, wenn die Seite eine eigene Anruf-Leiste hat, die das Skript nicht erkennt, oder der Kunde die Leiste nicht möchte. - WhatsApp und die Route (Google Maps) öffnen erst beim Klick. Den Haken „Ich habe den WhatsApp-Hinweis in meine Datenschutzerklärung aufgenommen“ setzt der Kunde im Portal; in der Kundenansicht setzt ihn kein Mitarbeiter.
- Die Hinweis-Leiste zeigt zu jedem Zeitpunkt den gültigen Hinweis mit dem höchsten Rang (Wichtig, Urlaub, Aktion, Info). Sie hat keinen Schließen-Knopf (das Skript speichert nichts im Browser).
7. JSON-LD
Das Skript pflegt die Öffnungszeiten im Seitencode: Gibt es einen application/ld+json-Block mit einem Knoten vom Typ LocalBusiness oder einem Untertyp (auch in @graph), ersetzt es dort nur openingHoursSpecification und specialOpeningHoursSpecification (alles andere im Block bleibt); unlesbare Blöcke bleiben unberührt. Gibt es keinen, legt es einen eigenen Block [data-wkz-jsonld] mit name, address und url an. Prüfe auf der Seite, ob JSON-LD mit festen Öffnungszeiten an einer anderen Stelle steht (zum Beispiel als Mikrodaten im Footer); das würde dem Skript widersprechen.
8. Prüfliste
Das Einrichtungs-Ticket nennt dieselben fünf Punkte:
- Feste Zeiten im Repo suchen und ersetzen:
grep -ril "öffnungszeit" .und Ähnliches (geöffnet,Mo-Fr,Uhr), auch Footer, Kontaktseite,<meta>und eigene Status-Skripte. Jede Stelle wird ein Platzhalter mit Ersatz-HTML oder fällt weg. - Header: Überlappt die Hinweis-Leiste den Kopf der Seite? Sonst
navSelektorsetzen (Abschnitt 5). - Anruf-Leiste: Hat die Seite schon eine eigene feste Anruf-Leiste? Dann
kontaktleisteAus(Abschnitt 6), damit sie nicht doppelt erscheint. - Vorhandenes JSON-LD (Öffnungszeiten der Seite) prüfen, damit nichts doppelt oder widersprüchlich steht (Abschnitt 7).
- Datenschutz-Absatz auf der Website: den Absatz „Öffnungszeiten auf dieser Website“ aus
docs/funktionen/website-datenschutz-absatz.mdeinsetzen (Variante A bei WEBklar-Hosting, Variante B bei Fremdhosting). Die Bausteine „Route“ und „WhatsApp“ nur, wenn die Kontakt-Leiste die Knöpfe zeigt. Liegt die Datenschutzerklärung in der von WEBklar betreuten Website, setzt Du den Absatz ein (gehört zum Einbau, nicht auf das Stundenkontingent anrechnen); liegt sie woanders (externer Generator), schicke dem Kunden den Textbaustein, er fügt ihn selbst ein. Die Texte sind Entwurf, bis die Rechtsrunde sie freigibt.
Zur Abnahme außerdem: Seite in der Vorschau öffnen und mit ausgeschaltetem JavaScript ansehen (Ersatz-Tabelle muss stehen), Konsole auf Warnungen zu data-plan prüfen, auf einem Handy (höchstens 768 px Breite) die Kontakt-Leiste, den Regler „Uhrzeit testen“ im Editor gegen die Anzeige halten, Kontrast und Tippflächen (mindestens 44 px) ansehen.
9. Sperrliste ZEITEN_KEIN_NEUBAU und Seiten ohne automatischen Einbau
Neubau und Einsetzen. Bei jedem Übergang in setup oder active (Einschalten im Abo, Zahlung, Aktivierung durch das Team, Rückkehr des Abos, Behalten) stößt das Portal einen Neubau der Seite an (höchstens einer gleichzeitig). Das Ergebnis steht in einbau: angestossen (Auftrag angenommen, die Zeile erscheint nach dem Veröffentlichen) oder einbau: manuell. Änderungen an Zeiten und Leisten brauchen keinen Neubau.
einbau: manuell heißt: kein Repo, Vorschau aus, Dev-Modus, Projekt auf der Sperrliste, Fehler oder Runner ausgelastet. Dann setzt Du die Einbau-Zeile von Hand ins Repo.
Sperrliste. ZEITEN_KEIN_NEUBAU (Umgebungsvariable des Portals, Liste von Subdomains, durch Komma getrennt): Für diese Projekte gibt es weder einen angestoßenen Neubau noch das automatische Einsetzen bei normalen Deploys; die Funktion zählt sie immer als manuell. Vorgabe ist emmas-schatzkiste (Emmas Seiten, deren Dateien teilweise nicht in Git liegen). Achtung: Setzt Du die Variable, ersetzt sie die Vorgabe. Willst Du eine Seite zusätzlich sperren, nenne emmas-schatzkiste mit in der Liste. Änderung in compose und .env des Portals, danach das Portal mit docker compose -f /home/webklar/apps/docker-compose.yml up -d --no-deps kundenbereich-server neu erstellen (kein docker restart: es liest geänderte Werte nicht neu ein). Für gesperrte Seiten setzt Du die Zeile und die Platzhalter von Hand ein (im Repo, soweit die Dateien dort liegen, sonst direkt auf dem Server) und prüfst nach jedem Deploy, ob sie noch da sind.
Seite liegt woanders (anderer Hoster, keine WEBklar-Veröffentlichung): Das Ticket enthält die Einbau-Zeile; Du setzt sie ein, soweit WEBklar Zugriff auf die Seite hat. Hat WEBklar keinen Zugriff, schicke dem Kunden im Chat Einbau-Zeile, Platzhalter-Beispiele und Ersatz-HTML und bitte ihn, sie selbst oder durch seinen Anbieter einzufügen. Schließe das Ticket erst, wenn die Anzeige auf der Live-Seite stimmt: Öffne die Seite, sieh in den Netzwerk-Aufrufen nach der Antwort von /api/public/zeiten/v1/<siteKey> ("aktiv": true) und nach der gefüllten Tabelle.
10. Freischalten und Ende
- Freischalten: Der Job setzt
active(und sendet K16), wenn das Ticket geschlossen ist und der Kunde Zeiten eingetragen hat (Hauptplan gespeichert, mindestens ein Tag nicht „zu“). Sonst bleibtsetup, das WOMS zeigt „wartet auf die Zeiten des Kunden“. Solange der Kunde nichts eingetragen hat, kannst Du die Tabelle nicht einbauen. - Ende des Abos (Gratisweg): K29 mit Frist zum Behalten (30 Tage), danach
disabledund K30, Eingaben noch 30 Tage, dann gelöscht. Das Ticket „Ersatz-Tabelle aktualisieren“ gehört zum Abschalten (Abschnitt 4). - Abschalten durch das Team (Grund
team): keine automatische Löschung.
11. Häufige Fehlerbilder
| Symptom | Meist liegt es an |
|---|---|
| Es erscheint nur das Ersatz-HTML | Funktion nicht in setup/active, einbau: manuell und Zeile fehlt, unbekannter data-plan, kein JavaScript, Daten älter als 24 Stunden, CSP blockiert project.webklar.com |
| Hinweis-Leiste verdeckt den Header | navSelektor nicht gesetzt (Abschnitt 5) |
| Zwei Anruf-Leisten auf dem Handy | kontaktleisteAus setzen (Abschnitt 6) |
| Kontakt-Leiste fehlt auf dem Handy | Seite hat schon eine feste Anruf-Leiste unten, oder Breite über 768 px, oder kontaktleisteAus |
| Öffnungszeiten doppelt im Suchergebnis oder Test | festes JSON-LD an anderer Stelle (Abschnitt 7) |
| Tabelle sieht fremd aus | globale Regeln des Themes (Abschnitt 3) |