Weiter zum Inhalt
  • Home
  • Aktuell
  • Tags
  • 0 Ungelesen 0
  • Kategorien
  • Unreplied
  • Beliebt
  • GitHub
  • Docu
  • Hilfe
Skins
  • Hell
  • Brite
  • Cerulean
  • Cosmo
  • Flatly
  • Journal
  • Litera
  • Lumen
  • Lux
  • Materia
  • Minty
  • Morph
  • Pulse
  • Sandstone
  • Simplex
  • Sketchy
  • Spacelab
  • United
  • Yeti
  • Zephyr
  • Dunkel
  • Cyborg
  • Darkly
  • Quartz
  • Slate
  • Solar
  • Superhero
  • Vapor

  • Standard: (Kein Skin)
  • Kein Skin
Einklappen
ioBroker Logo

Community Forum

donate donate
  1. ioBroker Community Home
  2. Deutsch
  3. Visualisierung
  4. Google Kalender (ICS) für Material Design & Mini-Kalender

NEWS

  • Der neue Monatsrückblick für Mai und Juni 2026 ist online!
    BluefoxB
    Bluefox
    8
    1
    653

  • wichtiges UPDATE für controller 7.2.2 im stable
    HomoranH
    Homoran
    10
    1
    3.3k

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    16
    1
    5.2k

Google Kalender (ICS) für Material Design & Mini-Kalender

Geplant Angeheftet Gesperrt Verschoben Visualisierung
vis
68 Beiträge 7 Kommentatoren 783 Aufrufe 9 Beobachtet
  • Älteste zuerst
  • Neuste zuerst
  • Meiste Stimmen
Antworten
  • In einem neuen Thema antworten
Anmelden zum Antworten
Dieses Thema wurde gelöscht. Nur Nutzer mit entsprechenden Rechten können es sehen.
  • R Offline
    R Offline
    Rudi-Ment
    schrieb am zuletzt editiert von
    #48

    Ok.
    Mache ich

    1 Antwort Letzte Antwort
    0
    • Ro75R Online
      Ro75R Online
      Ro75
      schrieb am zuletzt editiert von
      #49

      Version 1.0.1 - 23.07.2026

      • Ganztagstermine, über mehrere Tage, werden korrekt dargestellt.

      Script in Post #1 wurde aktualisiert.

      Ro75.

      SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

      NashraN sigi234S 2 Antworten Letzte Antwort
      1
      • Ro75R Ro75

        Version 1.0.1 - 23.07.2026

        • Ganztagstermine, über mehrere Tage, werden korrekt dargestellt.

        Script in Post #1 wurde aktualisiert.

        Ro75.

        NashraN Offline
        NashraN Offline
        Nashra
        Most Active Forum Testing
        schrieb am zuletzt editiert von
        #50

        @Ro75 sagte:

        Version 1.0.1 - 23.07.2026

        • Ganztagstermine, über mehrere Tage, werden korrekt dargestellt.

        Script in Post #1 wurde aktualisiert.

        Ro75.

        Wird angezeigt 👍

        Gruß Ralf
        Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

        Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

        1 Antwort Letzte Antwort
        0
        • Ro75R Ro75

          Version 1.0.1 - 23.07.2026

          • Ganztagstermine, über mehrere Tage, werden korrekt dargestellt.

          Script in Post #1 wurde aktualisiert.

          Ro75.

          sigi234S Online
          sigi234S Online
          sigi234
          Forum Testing Most Active
          schrieb am zuletzt editiert von sigi234
          #51

          @Ro75 sagte:

          Version 1.0.1 - 23.07.2026

          • Ganztagstermine, über mehrere Tage, werden korrekt dargestellt.

          Script in Post #1 wurde aktualisiert.

          Ro75.

          Bei mir leider nicht.

          Screenshot (2971).png

          Screenshot (2972).png

          Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
          Immer Daten sichern!

          Ro75R 1 Antwort Letzte Antwort
          0
          • sigi234S sigi234

            @Ro75 sagte:

            Version 1.0.1 - 23.07.2026

            • Ganztagstermine, über mehrere Tage, werden korrekt dargestellt.

            Script in Post #1 wurde aktualisiert.

            Ro75.

            Bei mir leider nicht.

            Screenshot (2971).png

            Screenshot (2972).png

            Ro75R Online
            Ro75R Online
            Ro75
            schrieb zuletzt editiert von Ro75
            #52

            @sigi234 sagte:

            Bei mir leider nicht.

            Ich habe es gerade nochmal getestet. Bei mir kommt der Termin. Welchen Datenpunkt nimmst Du? M1 oder C1? Schon mal reingesehen was drin steht?

            Ro75.

            EDIT: Ich habe über die Android-App einen Ganztagstermin über 4 Tage gemacht - Ergebnis: OK. Dann habe ich einen Ganztagstermin über 14 Tage gemacht via Google im Browser - Ergebnis OK.

            Unbenannt2.png

            Zur Sicherheit habe ich meinen Code hier mit dem Code in Post #1 verglichen: passt. Prüfe bitte C1 und M1. Eventuell auch ein Fehler in der Darstellung?

            Unbenannt_anonymized.png

            SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

            1 Antwort Letzte Antwort
            0
            • NashraN Offline
              NashraN Offline
              Nashra
              Most Active Forum Testing
              schrieb zuletzt editiert von Nashra
              #53

              @ro75
              sigi234 nutzt wohl den kleinen m1.
              Habe auch mal getestet und da kommt nur ein Eintrag
              Screenshot 2026-07-24 135155.png Screenshot 2026-07-24 135241.png

              beim großen C1 ist alles ok
              Screenshot 2026-07-24 135320.png
              Screenshot 2026-07-24 135714.png

              Gruß Ralf
              Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

              Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

              1 Antwort Letzte Antwort
              0
              • Ro75R Online
                Ro75R Online
                Ro75
                schrieb zuletzt editiert von
                #54

                @nashra vielen Dank für den Hinweis. Ich habe das Problem gefunden. Ich passe das Script an. @sigi234 ich werde noch heute eine neue Scriptversion zur Verfügung stellen, damit ist das Problem dann auch behoben. Bitte noch etwas Geduld. Danke.

                Ro75.

                SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                1 Antwort Letzte Antwort
                1
                • NashraN Offline
                  NashraN Offline
                  Nashra
                  Most Active Forum Testing
                  schrieb zuletzt editiert von Nashra
                  #55

                  @ro75
                  ich habe da gerade noch was gefunden....
                  oben im Bild für den m1 fehlt ein Termin d.h. wird nicht gelistet.
                  Es ist wie im c1 der "In Memory Aponi" am 29.7.
                  taucht auch nicht in der m1.json auf

                    {
                      "img": "http://192.168.1.14:8082/vis.0/kalender/muell.png",
                      "zeit": "27.07.2026 - 27.07.2026",
                      "ereignis": "Wertstoffe",
                      "ort": ""
                    },
                    {
                      "img": "http://192.168.1.14:8082/vis.0/kalender/muell.png",
                      "zeit": "07.08.2026 - 07.08.2026",
                      "ereignis": "Restmüll",
                      "ort": ""
                    },
                  
                  

                  Gruß Ralf
                  Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

                  Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                  1 Antwort Letzte Antwort
                  0
                  • Ro75R Online
                    Ro75R Online
                    Ro75
                    schrieb zuletzt editiert von
                    #56

                    Verstehe ich gerade nicht. Wo fehlt er denn? M1 oder C1?

                    Ro75.

                    SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                    NashraN 1 Antwort Letzte Antwort
                    0
                    • Ro75R Ro75

                      Verstehe ich gerade nicht. Wo fehlt er denn? M1 oder C1?

                      Ro75.

                      NashraN Offline
                      NashraN Offline
                      Nashra
                      Most Active Forum Testing
                      schrieb zuletzt editiert von
                      #57

                      @Ro75 sagte:

                      Verstehe ich gerade nicht. Wo fehlt er denn? M1 oder C1?

                      Ro75.

                      In der m1, habe ich doch geschrieben

                      Gruß Ralf
                      Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

                      Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                      1 Antwort Letzte Antwort
                      0
                      • Ro75R Online
                        Ro75R Online
                        Ro75
                        schrieb zuletzt editiert von
                        #58

                        @nashra Problem gefunden und auch beseitigt. Bitte noch etwas Geduld bis ich das neue Script freigebe - teste noch etwas.

                        Ro75.

                        SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                        NashraN 1 Antwort Letzte Antwort
                        1
                        • Ro75R Ro75

                          @nashra Problem gefunden und auch beseitigt. Bitte noch etwas Geduld bis ich das neue Script freigebe - teste noch etwas.

                          Ro75.

                          NashraN Offline
                          NashraN Offline
                          Nashra
                          Most Active Forum Testing
                          schrieb zuletzt editiert von
                          #59

                          @Ro75 sagte:

                          @nashra Problem gefunden und auch beseitigt. Bitte noch etwas Geduld bis ich das neue Script freigebe - teste noch etwas.

                          Ro75.

                          Kein Problem, nutze den großen Kalender. Ist mir eigentlich auch nur zufällig aufgefallen bei dem kleinen.

                          Gruß Ralf
                          Mir egal, wer Dein Vater ist! Wenn ich hier angel, wird nicht übers Wasser gelaufen!!

                          Benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.

                          1 Antwort Letzte Antwort
                          0
                          • Ro75R Online
                            Ro75R Online
                            Ro75
                            schrieb zuletzt editiert von
                            #60

                            Version 1.0.2 - 24.07.2026

                            • Ganztagstermine, über mehrere Tage, werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt.
                            • "Spezialtermine" wie "In Memory" oder auch Feiertage, etc. werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt, inklusive Emoji

                            Script in Post #1 angepasst.

                            Ro75.

                            SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                            sigi234S 1 Antwort Letzte Antwort
                            1
                            • OliverIOO Offline
                              OliverIOO Offline
                              OliverIO
                              schrieb zuletzt editiert von OliverIO
                              #61

                              Ich habe mal was für den großen Kalender probiert, welcher nur auf dem jsonTemplate und dem m1 Datenpunkt basiert

                              <%
                              /* ==========================================================================
                                KONFIGURATION
                                ========================================================================== */
                              
                              const calendarConfig = {
                                 /* Startansicht: "month", "week" oder "day" */
                                 defaultView: "month",
                              
                                 /* Sprache für Datumsausgaben */
                                 locale: "de-DE",
                              
                                 /* Erster Wochentag: 1 = Montag, 0 = Sonntag */
                                 firstDayOfWeek: 1,
                              
                                 /* Wochenende hervorheben */
                                 highlightWeekend: true,
                              
                                 /* Kalenderwoche anzeigen */
                                 showWeekNumbers: true,
                              
                                 /* Bilder der Termine anzeigen */
                                 showImages: true,
                              
                                 /* Leere Orte ausblenden */
                                 hideEmptyLocation: true,
                              
                                 /*
                                  * Farbmodus:
                                  * "light" = helle Darstellung
                                  * "dark"  = dunkle Darstellung
                                  * "auto"  = Systemeinstellung des Browsers verwenden
                                  */
                                 theme: "auto",
                              
                                 /*
                                  * Methode zur automatischen Gruppierung und Farbzuordnung.
                                  *
                                  * "img":
                                  * Termine mit derselben Bild-URL erhalten dieselbe Farbe.
                                  *
                                  * "eventPrefix":
                                  * Der Teil des Ereignisnamens vor dem ersten Doppelpunkt wird verwendet.
                                  */
                                 colorGrouping: "img",
                              
                                 /*
                                  * Optional feste Farben vorgeben.
                                  *
                                  * matchField darf nur "ereignis" oder "img" sein.
                                  * match enthält einen Text, der im entsprechenden JSON-Feld gesucht wird.
                                  *
                                  * Regeln werden von oben nach unten geprüft.
                                  * Nicht passende Termine erhalten automatisch eine Farbe.
                                  */
                                 colorRules: [
                                     {
                                         matchField: "ereignis",
                                         match: "Altpapier",
                                         color: "#d32f2f"
                                     },
                                     {
                                         matchField: "ereignis",
                                         match: "Restmüll",
                                         color: "#546e7a"
                                     },
                                     {
                                         matchField: "ereignis",
                                         match: "Biotonne",
                                         color: "#2e7d32"
                                     },
                                     {
                                         matchField: "ereignis",
                                         match: "gelber Sack",
                                         color: "#f9a825"
                                     },
                                     {
                                         matchField: "ereignis",
                                         match: "Geburtstag",
                                         color: "#8e24aa"
                                     }
                                 ],
                              
                                 /*
                                  * Automatisch verwendete Farben.
                                  * Die Farben sollten einen ausreichenden Kontrast zu weißer Schrift haben.
                                  */
                                 autoColors: [
                                     "#1565c0",
                                     "#2e7d32",
                                     "#c62828",
                                     "#6a1b9a",
                                     "#00838f",
                                     "#ef6c00",
                                     "#4527a0",
                                     "#ad1457",
                                     "#00695c",
                                     "#283593",
                                     "#558b2f",
                                     "#5d4037"
                                 ],
                              
                                 /*
                                  * Optionale Oberflächenfarben.
                                  * Ein leerer Wert verwendet die Farbe des jeweiligen Themes.
                                  */
                                 colors: {
                                     light: {
                                         background: "",
                                         surface: "",
                                         surfaceSecondary: "",
                                         text: "",
                                         textSecondary: "",
                                         border: "",
                                         today: "",
                                         weekend: ""
                                     },
                                     dark: {
                                         background: "",
                                         surface: "",
                                         surfaceSecondary: "",
                                         text: "",
                                         textSecondary: "",
                                         border: "",
                                         today: "",
                                         weekend: ""
                                     }
                                 },
                              
                                 /* Beschriftungen */
                                 labels: {
                                     today: "Heute",
                                     month: "Monat",
                                     week: "Woche",
                                     day: "Tag",
                                     previous: "Zurück",
                                     next: "Weiter",
                                     allDay: "Ganztägig",
                                     noEvents: "Keine Termine",
                                     location: "Ort",
                                     calendarWeekShort: "KW",
                                     invalidDate: "Ungültiges Datum"
                                 }
                              };
                              
                              
                              /* ==========================================================================
                                DATEN GLOBAL FÜR DIESES WIDGET BEREITSTELLEN
                                ========================================================================== */
                              
                              const calendarGlobalStoreName = "__visJsontemplateCalendarWidgetStore";
                              
                              if (typeof window !== "undefined") {
                                 if (
                                     !window[calendarGlobalStoreName] ||
                                     typeof window[calendarGlobalStoreName] !== "object"
                                 ) {
                                     window[calendarGlobalStoreName] = Object.create(null);
                                 }
                              
                                 /*
                                  * Einen bereits vorhandenen Zustand nicht überschreiben.
                                  * Bei einer Datenaktualisierung wird das Template komplett neu erzeugt,
                                  * die globale window-Variable bleibt jedoch erhalten.
                                  */
                                 const existingWidgetStore =
                                     window[calendarGlobalStoreName][widgetid] &&
                                     typeof window[calendarGlobalStoreName][widgetid] === "object"
                                         ? window[calendarGlobalStoreName][widgetid]
                                         : {};
                              
                                 window[calendarGlobalStoreName][widgetid] = {
                                     data: data,
                                     config: calendarConfig,
                              
                                     state:
                                         existingWidgetStore.state &&
                                         typeof existingWidgetStore.state === "object"
                                             ? existingWidgetStore.state
                                             : {
                                                 view: calendarConfig.defaultView,
                                                 currentDate: null
                                             }
                                 };
                              }
                              %>
                              
                              <style>
                              #<%- widgetid %>
                              {
                                 --jscal-background-light: <%- calendarConfig.colors.light.background || "#f4f6f8" %>;
                                 --jscal-surface-light: <%- calendarConfig.colors.light.surface || "#ffffff" %>;
                                 --jscal-surface-secondary-light: <%- calendarConfig.colors.light.surfaceSecondary || "#f7f8fa" %>;
                                 --jscal-text-light: <%- calendarConfig.colors.light.text || "#17212b" %>;
                                 --jscal-text-secondary-light: <%- calendarConfig.colors.light.textSecondary || "#66717d" %>;
                                 --jscal-border-light: <%- calendarConfig.colors.light.border || "#d7dce2" %>;
                                 --jscal-today-light: <%- calendarConfig.colors.light.today || "#e3f2fd" %>;
                                 --jscal-weekend-light: <%- calendarConfig.colors.light.weekend || "#faf4f4" %>;
                              
                                 --jscal-background-dark: <%- calendarConfig.colors.dark.background || "#12171d" %>;
                                 --jscal-surface-dark: <%- calendarConfig.colors.dark.surface || "#1c232b" %>;
                                 --jscal-surface-secondary-dark: <%- calendarConfig.colors.dark.surfaceSecondary || "#252e38" %>;
                                 --jscal-text-dark: <%- calendarConfig.colors.dark.text || "#f1f4f7" %>;
                                 --jscal-text-secondary-dark: <%- calendarConfig.colors.dark.textSecondary || "#aeb8c2" %>;
                                 --jscal-border-dark: <%- calendarConfig.colors.dark.border || "#3b4652" %>;
                                 --jscal-today-dark: <%- calendarConfig.colors.dark.today || "#173b55" %>;
                                 --jscal-weekend-dark: <%- calendarConfig.colors.dark.weekend || "#271f24" %>;
                              
                                 --jscal-background: var(--jscal-background-light);
                                 --jscal-surface: var(--jscal-surface-light);
                                 --jscal-surface-secondary: var(--jscal-surface-secondary-light);
                                 --jscal-text: var(--jscal-text-light);
                                 --jscal-text-secondary: var(--jscal-text-secondary-light);
                                 --jscal-border: var(--jscal-border-light);
                                 --jscal-today: var(--jscal-today-light);
                                 --jscal-weekend: var(--jscal-weekend-light);
                              
                                 box-sizing: border-box;
                                 width: 100%;
                                 height: 100%;
                                 min-height: 320px;
                                 overflow: hidden;
                                 color: var(--jscal-text);
                                 background: var(--jscal-background);
                                 font-family: Arial, Helvetica, sans-serif;
                              }
                              
                              #<%- widgetid %> *,
                              #<%- widgetid %> *::before,
                              #<%- widgetid %> *::after
                              {
                                 box-sizing: border-box;
                              }
                              
                              #<%- widgetid %>[data-jscal-theme="dark"]
                              {
                                 --jscal-background: var(--jscal-background-dark);
                                 --jscal-surface: var(--jscal-surface-dark);
                                 --jscal-surface-secondary: var(--jscal-surface-secondary-dark);
                                 --jscal-text: var(--jscal-text-dark);
                                 --jscal-text-secondary: var(--jscal-text-secondary-dark);
                                 --jscal-border: var(--jscal-border-dark);
                                 --jscal-today: var(--jscal-today-dark);
                                 --jscal-weekend: var(--jscal-weekend-dark);
                              }
                              
                              @media (prefers-color-scheme: dark)
                              {
                                 #<%- widgetid %>[data-jscal-theme="auto"]
                                 {
                                     --jscal-background: var(--jscal-background-dark);
                                     --jscal-surface: var(--jscal-surface-dark);
                                     --jscal-surface-secondary: var(--jscal-surface-secondary-dark);
                                     --jscal-text: var(--jscal-text-dark);
                                     --jscal-text-secondary: var(--jscal-text-secondary-dark);
                                     --jscal-border: var(--jscal-border-dark);
                                     --jscal-today: var(--jscal-today-dark);
                                     --jscal-weekend: var(--jscal-weekend-dark);
                                 }
                              }
                              
                              #<%- widgetid %> .jscal
                              {
                                 display: flex;
                                 flex-direction: column;
                                 width: 100%;
                                 height: 100%;
                                 min-height: 320px;
                                 background: var(--jscal-background);
                              }
                              
                              #<%- widgetid %> .jscal-toolbar
                              {
                                 display: flex;
                                 flex: 0 0 auto;
                                 align-items: center;
                                 gap: 8px;
                                 min-height: 58px;
                                 padding: 8px 10px;
                                 border-bottom: 1px solid var(--jscal-border);
                                 background: var(--jscal-surface);
                              }
                              
                              #<%- widgetid %> .jscal-navigation,
                              #<%- widgetid %> .jscal-view-buttons
                              {
                                 display: flex;
                                 align-items: center;
                                 gap: 4px;
                              }
                              
                              #<%- widgetid %> .jscal-view-buttons
                              {
                                 margin-left: auto;
                              }
                              
                              #<%- widgetid %> .jscal-title
                              {
                                 min-width: 0;
                                 margin: 0 8px;
                                 overflow: hidden;
                                 color: var(--jscal-text);
                                 font-size: 18px;
                                 font-weight: 700;
                                 line-height: 1.2;
                                 text-align: center;
                                 text-overflow: ellipsis;
                                 white-space: nowrap;
                              }
                              
                              #<%- widgetid %> .jscal-button
                              {
                                 display: inline-flex;
                                 align-items: center;
                                 justify-content: center;
                                 min-width: 36px;
                                 height: 36px;
                                 padding: 0 10px;
                                 border: 1px solid var(--jscal-border);
                                 border-radius: 7px;
                                 color: var(--jscal-text);
                                 background: var(--jscal-surface-secondary);
                                 font: inherit;
                                 font-size: 13px;
                                 font-weight: 600;
                                 line-height: 1;
                                 cursor: pointer;
                                 user-select: none;
                                 -webkit-tap-highlight-color: transparent;
                              }
                              
                              #<%- widgetid %> .jscal-button:hover
                              {
                                 filter: brightness(0.96);
                              }
                              
                              #<%- widgetid %> .jscal-button:focus-visible
                              {
                                 outline: 2px solid #1976d2;
                                 outline-offset: 2px;
                              }
                              
                              #<%- widgetid %> .jscal-button.is-active
                              {
                                 border-color: #1976d2;
                                 color: #ffffff;
                                 background: #1976d2;
                              }
                              
                              #<%- widgetid %> .jscal-button-icon
                              {
                                 width: 36px;
                                 padding: 0;
                              }
                              
                              #<%- widgetid %> .jscal-button svg
                              {
                                 width: 20px;
                                 height: 20px;
                                 fill: currentColor;
                              }
                              
                              #<%- widgetid %> .jscal-content
                              {
                                 position: relative;
                                 flex: 1 1 auto;
                                 min-height: 0;
                                 overflow: auto;
                                 overscroll-behavior: contain;
                                 background: var(--jscal-background);
                              }
                              
                              #<%- widgetid %> .jscal-month
                              {
                                 display: grid;
                                 min-width: 640px;
                                 grid-template-columns: auto repeat(7, minmax(80px, 1fr));
                                 background: var(--jscal-border);
                                 gap: 1px;
                              }
                              
                              #<%- widgetid %> .jscal-month.no-week-numbers
                              {
                                 grid-template-columns: repeat(7, minmax(80px, 1fr));
                              }
                              
                              #<%- widgetid %> .jscal-week-header
                              {
                                 position: sticky;
                                 top: 0;
                                 z-index: 4;
                                 display: flex;
                                 align-items: center;
                                 justify-content: center;
                                 min-height: 36px;
                                 padding: 5px;
                                 color: var(--jscal-text-secondary);
                                 background: var(--jscal-surface-secondary);
                                 font-size: 12px;
                                 font-weight: 700;
                                 text-align: center;
                              }
                              
                              #<%- widgetid %> .jscal-week-number
                              {
                                 display: flex;
                                 align-items: center;
                                 justify-content: center;
                                 min-height: 112px;
                                 padding: 4px;
                                 color: var(--jscal-text-secondary);
                                 background: var(--jscal-surface-secondary);
                                 font-size: 11px;
                                 font-weight: 700;
                              }
                              
                              #<%- widgetid %> .jscal-month-day
                              {
                                 position: relative;
                                 min-height: 112px;
                                 padding: 5px;
                                 overflow: hidden;
                                 background: var(--jscal-surface);
                              }
                              
                              #<%- widgetid %> .jscal-month-day.is-outside
                              {
                                 opacity: 0.52;
                              }
                              
                              #<%- widgetid %> .jscal-month-day.is-today
                              {
                                 background: var(--jscal-today);
                              }
                              
                              #<%- widgetid %> .jscal-month-day.is-weekend
                              {
                                 background: var(--jscal-weekend);
                              }
                              
                              #<%- widgetid %> .jscal-month-day.is-weekend.is-today
                              {
                                 background: var(--jscal-today);
                              }
                              
                              #<%- widgetid %> .jscal-day-number
                              {
                                 display: flex;
                                 align-items: center;
                                 justify-content: flex-end;
                                 height: 22px;
                                 margin-bottom: 3px;
                                 color: var(--jscal-text);
                                 font-size: 12px;
                                 font-weight: 700;
                              }
                              
                              #<%- widgetid %> .jscal-day-number span
                              {
                                 display: inline-flex;
                                 align-items: center;
                                 justify-content: center;
                                 min-width: 22px;
                                 height: 22px;
                                 padding: 0 4px;
                                 border-radius: 11px;
                              }
                              
                              #<%- widgetid %> .jscal-month-day.is-today .jscal-day-number span
                              {
                                 color: #ffffff;
                                 background: #1976d2;
                              }
                              
                              #<%- widgetid %> .jscal-events
                              {
                                 display: flex;
                                 flex-direction: column;
                                 gap: 3px;
                              }
                              
                              #<%- widgetid %> .jscal-event
                              {
                                 position: relative;
                                 display: flex;
                                 align-items: center;
                                 gap: 5px;
                                 min-width: 0;
                                 min-height: 24px;
                                 padding: 3px 6px;
                                 border: 0;
                                 border-left: 4px solid var(--jscal-event-color);
                                 border-radius: 4px;
                                 color: #ffffff;
                                 background: var(--jscal-event-color);
                                 box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.07);
                                 font: inherit;
                                 font-size: 11px;
                                 line-height: 1.25;
                                 text-align: left;
                                 cursor: pointer;
                                 -webkit-tap-highlight-color: transparent;
                              }
                              
                              #<%- widgetid %> .jscal-event:hover,
                              #<%- widgetid %> .jscal-event:focus-visible,
                              #<%- widgetid %> .jscal-event.is-selected
                              {
                                 filter: brightness(1.12);
                                 outline: none;
                              }
                              
                              #<%- widgetid %> .jscal-event-image
                              {
                                 flex: 0 0 auto;
                                 width: 17px;
                                 height: 17px;
                                 border-radius: 3px;
                                 object-fit: contain;
                                 background: rgba(255, 255, 255, 0.82);
                              }
                              
                              #<%- widgetid %> .jscal-event-time
                              {
                                 flex: 0 0 auto;
                                 font-weight: 700;
                                 white-space: nowrap;
                              }
                              
                              #<%- widgetid %> .jscal-event-title
                              {
                                 min-width: 0;
                                 overflow: hidden;
                                 text-overflow: ellipsis;
                                 white-space: nowrap;
                              }
                              
                              #<%- widgetid %> .jscal-more
                              {
                                 padding: 2px 5px;
                                 color: var(--jscal-text-secondary);
                                 font-size: 11px;
                                 font-weight: 700;
                              }
                              
                              #<%- widgetid %> .jscal-week
                              {
                                 display: grid;
                                 min-width: 720px;
                                 min-height: 100%;
                                 grid-template-columns: repeat(7, minmax(100px, 1fr));
                                 gap: 1px;
                                 background: var(--jscal-border);
                              }
                              
                              #<%- widgetid %> .jscal-week-day
                              {
                                 display: flex;
                                 flex-direction: column;
                              
                                 min-height: 100%;
                                 overflow: hidden;
                              
                                 background: var(--jscal-surface);
                              }
                              
                              #<%- widgetid %> .jscal-week-day.is-weekend
                              {
                                 background: var(--jscal-weekend);
                              }
                              
                              #<%- widgetid %> .jscal-week-day.is-today
                              {
                                 background: var(--jscal-today);
                              }
                              
                              #<%- widgetid %> .jscal-week-day-header
                              {
                                 position: sticky;
                                 top: 0;
                                 z-index: 3;
                                 display: flex;
                                 flex-direction: column;
                                 align-items: center;
                                 gap: 2px;
                                 min-height: 55px;
                                 padding: 7px 4px;
                                 border-bottom: 1px solid var(--jscal-border);
                                 background: inherit;
                              }
                              
                              #<%- widgetid %> .jscal-week-day-name
                              {
                                 color: var(--jscal-text-secondary);
                                 font-size: 11px;
                                 font-weight: 700;
                                 text-transform: uppercase;
                              }
                              
                              #<%- widgetid %> .jscal-week-day-date
                              {
                                 display: inline-flex;
                                 align-items: center;
                                 justify-content: center;
                                 min-width: 30px;
                                 height: 30px;
                                 padding: 0 5px;
                                 border-radius: 15px;
                                 color: var(--jscal-text);
                                 font-size: 16px;
                                 font-weight: 700;
                              }
                              
                              #<%- widgetid %> .jscal-week-day.is-today .jscal-week-day-date
                              {
                                 color: #ffffff;
                                 background: #1976d2;
                              }
                              
                              #<%- widgetid %> .jscal-week-events
                              {
                                 flex: 1;
                                 overflow: hidden;
                              }
                              
                              #<%- widgetid %> .jscal-week .jscal-event
                              {
                                 display: flex;
                                 align-items: flex-start;
                                 gap: 6px;
                              
                                 min-height: 36px;
                                 max-height: 54px;
                              
                                 padding: 6px;
                              
                                 overflow: hidden;
                              }
                              
                              #<%- widgetid %> .jscal-week .jscal-event-title
                              {
                                 flex: 1;
                                 min-width: 0;
                              
                                 overflow: hidden;
                                 text-overflow: ellipsis;
                              
                                 display: -webkit-box;
                                 -webkit-box-orient: vertical;
                                 -webkit-line-clamp: 2;
                              
                                 line-height: 1.2;
                                 white-space: normal;
                              
                                 overflow-wrap: anywhere;
                                 word-break: break-word;
                              }
                              
                              #<%- widgetid %> .jscal-empty
                              {
                                 padding: 14px 6px;
                                 color: var(--jscal-text-secondary);
                                 font-size: 12px;
                                 text-align: center;
                              }
                              
                              #<%- widgetid %> .jscal-day-view
                              {
                                 min-height: 100%;
                                 padding: 12px;
                              }
                              
                              #<%- widgetid %> .jscal-day-section
                              {
                                 margin-bottom: 16px;
                              }
                              
                              #<%- widgetid %> .jscal-day-section-title
                              {
                                 margin: 0 0 7px;
                                 padding: 0 2px 6px;
                                 border-bottom: 1px solid var(--jscal-border);
                                 color: var(--jscal-text-secondary);
                                 font-size: 12px;
                                 font-weight: 700;
                                 text-transform: uppercase;
                              }
                              
                              #<%- widgetid %> .jscal-day-event
                              {
                                 display: grid;
                                 grid-template-columns: 70px minmax(0, 1fr);
                                 margin-bottom: 8px;
                                 border: 1px solid var(--jscal-border);
                                 border-left: 5px solid var(--jscal-event-color);
                                 border-radius: 7px;
                                 overflow: hidden;
                                 background: var(--jscal-surface);
                              }
                              
                              #<%- widgetid %> .jscal-day-event-time
                              {
                                 display: flex;
                                 align-items: flex-start;
                                 justify-content: center;
                                 padding: 11px 6px;
                                 color: var(--jscal-text-secondary);
                                 background: var(--jscal-surface-secondary);
                                 font-size: 12px;
                                 font-weight: 700;
                                 text-align: center;
                              }
                              
                              #<%- widgetid %> .jscal-day-event-content
                              {
                                 display: flex;
                                 align-items: flex-start;
                                 gap: 9px;
                                 min-width: 0;
                                 padding: 9px;
                                 border: 0;
                                 color: var(--jscal-text);
                                 background: transparent;
                                 font: inherit;
                                 text-align: left;
                                 cursor: pointer;
                              }
                              
                              #<%- widgetid %> .jscal-day-event-content:hover,
                              #<%- widgetid %> .jscal-day-event-content:focus-visible
                              {
                                 background: var(--jscal-surface-secondary);
                                 outline: none;
                              }
                              
                              #<%- widgetid %> .jscal-day-event-image
                              {
                                 flex: 0 0 auto;
                                 width: 34px;
                                 height: 34px;
                                 border-radius: 5px;
                                 object-fit: contain;
                              }
                              
                              #<%- widgetid %> .jscal-day-event-text
                              {
                                 min-width: 0;
                              }
                              
                              #<%- widgetid %> .jscal-day-event-title
                              {
                                 color: var(--jscal-text);
                                 font-size: 14px;
                                 font-weight: 700;
                                 line-height: 1.35;
                              }
                              
                              #<%- widgetid %> .jscal-day-event-location
                              {
                                 margin-top: 4px;
                                 color: var(--jscal-text-secondary);
                                 font-size: 12px;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip
                              {
                                 position: fixed;
                                 z-index: 2147483000;
                                 display: none;
                                 width: min(320px, calc(100vw - 20px));
                                 max-height: min(360px, calc(100vh - 20px));
                                 padding: 12px;
                                 overflow: auto;
                                 border: 1px solid var(--jscal-border);
                                 border-radius: 9px;
                                 color: var(--jscal-text);
                                 background: var(--jscal-surface);
                                 box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
                                 pointer-events: none;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip.is-visible
                              {
                                 display: block;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip-header
                              {
                                 display: flex;
                                 align-items: flex-start;
                                 gap: 9px;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip-image
                              {
                                 flex: 0 0 auto;
                                 width: 42px;
                                 height: 42px;
                                 border-radius: 6px;
                                 object-fit: contain;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip-title
                              {
                                 min-width: 0;
                                 color: var(--jscal-text);
                                 font-size: 14px;
                                 font-weight: 700;
                                 line-height: 1.35;
                                 overflow-wrap: anywhere;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip-row
                              {
                                 display: grid;
                                 grid-template-columns: 62px minmax(0, 1fr);
                                 gap: 8px;
                                 margin-top: 10px;
                                 color: var(--jscal-text);
                                 font-size: 12px;
                                 line-height: 1.4;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip-label
                              {
                                 color: var(--jscal-text-secondary);
                                 font-weight: 700;
                              }
                              
                              #<%- widgetid %> .jscal-tooltip-value
                              {
                                 overflow-wrap: anywhere;
                              }
                              
                              #<%- widgetid %> .jscal-invalid
                              {
                                 margin: 6px;
                                 padding: 7px;
                                 border: 1px solid var(--jscal-border);
                                 border-left: 4px solid #c62828;
                                 border-radius: 5px;
                                 color: var(--jscal-text);
                                 background: var(--jscal-surface);
                                 font-size: 12px;
                              }
                              
                              @media (max-width: 700px)
                              {
                                 #<%- widgetid %> .jscal-toolbar
                                 {
                                     flex-wrap: wrap;
                                     gap: 6px;
                                 }
                              
                                 #<%- widgetid %> .jscal-title
                                 {
                                     order: -1;
                                     flex: 1 0 100%;
                                     width: 100%;
                                     margin: 0;
                                     padding: 3px;
                                     font-size: 16px;
                                 }
                              
                                 #<%- widgetid %> .jscal-view-buttons
                                 {
                                     margin-left: auto;
                                 }
                              
                                 #<%- widgetid %> .jscal-button
                                 {
                                     min-width: 34px;
                                     height: 34px;
                                     padding: 0 8px;
                                     font-size: 12px;
                                 }
                              
                                 #<%- widgetid %> .jscal-month-day
                                 {
                                     min-height: 96px;
                                 }
                              }
                              </style>
                              
                              <div class="jscal" data-jscal-container>
                                 <div class="jscal-toolbar">
                                     <div class="jscal-navigation">
                                         <button
                                             type="button"
                                             class="jscal-button jscal-button-icon"
                                             data-jscal-action="previous"
                                             aria-label="<%- calendarConfig.labels.previous %>"
                                             title="<%- calendarConfig.labels.previous %>"
                                         >
                                             <svg viewBox="0 0 24 24" aria-hidden="true">
                                                 <path d="M15.4 5.4 8.8 12l6.6 6.6-1.4 1.4L6 12l8-8z"></path>
                                             </svg>
                                         </button>
                              
                                         <button
                                             type="button"
                                             class="jscal-button"
                                             data-jscal-action="today"
                                         >
                                             <%- calendarConfig.labels.today %>
                                         </button>
                              
                                         <button
                                             type="button"
                                             class="jscal-button jscal-button-icon"
                                             data-jscal-action="next"
                                             aria-label="<%- calendarConfig.labels.next %>"
                                             title="<%- calendarConfig.labels.next %>"
                                         >
                                             <svg viewBox="0 0 24 24" aria-hidden="true">
                                                 <path d="M8.6 18.6 15.2 12 8.6 5.4 10 4l8 8-8 8z"></path>
                                             </svg>
                                         </button>
                                     </div>
                              
                                     <div class="jscal-title" data-jscal-title></div>
                              
                                     <div class="jscal-view-buttons">
                                         <button
                                             type="button"
                                             class="jscal-button"
                                             data-jscal-view="month"
                                         >
                                             <%- calendarConfig.labels.month %>
                                         </button>
                              
                                         <button
                                             type="button"
                                             class="jscal-button"
                                             data-jscal-view="week"
                                         >
                                             <%- calendarConfig.labels.week %>
                                         </button>
                              
                                         <button
                                             type="button"
                                             class="jscal-button"
                                             data-jscal-view="day"
                                         >
                                             <%- calendarConfig.labels.day %>
                                         </button>
                                     </div>
                                 </div>
                              
                                 <div class="jscal-content" data-jscal-content></div>
                              
                                 <div
                                     class="jscal-tooltip"
                                     data-jscal-tooltip
                                     role="tooltip"
                                 ></div>
                              </div>
                              
                              <script>
                              (function ()
                              {
                                 "use strict";
                                 const widgetId = "<%- widgetid %>";
                                 const globalStoreName = "__visJsontemplateCalendarWidgetStore";
                              
                                 const widgetStore =
                                 	window[globalStoreName] &&
                                 	window[globalStoreName][widgetId]
                                 		? window[globalStoreName][widgetId]
                                 		: null;
                              
                                 if (!widgetStore)
                                 {
                                 	console.warn(
                                 		"JSON-Template-Kalender: Keine globalen Daten für Widget gefunden:",
                                 		widgetId
                                 	);
                              
                                 	return;
                                 }
                              
                                 const rawData = widgetStore.data;
                                 const config = widgetStore.config;
                              
                                 const root = document.getElementById(widgetId);
                              
                                 if (!root)
                                 {
                                     return;
                                 }
                              
                                 if (typeof root.__jsonCalendarCleanup === "function")
                                 {
                                     root.__jsonCalendarCleanup();
                                 }
                              
                                 root.setAttribute("data-jscal-theme", config.theme || "auto");
                              
                                 const container = root.querySelector("[data-jscal-container]");
                                 const contentElement = root.querySelector("[data-jscal-content]");
                                 const titleElement = root.querySelector("[data-jscal-title]");
                                 const tooltipElement = root.querySelector("[data-jscal-tooltip]");
                              
                                 if (!container || !contentElement || !titleElement || !tooltipElement)
                                 {
                                     return;
                                 }
                              
                                 const storedState =
                                 widgetStore.state &&
                                 typeof widgetStore.state === "object"
                                     ? widgetStore.state
                                     : {};
                              
                                 const storedView = ["month", "week", "day"].includes(storedState.view)
                                 	? storedState.view
                                 	: null;
                              
                                 const storedCurrentDate =
                                 	typeof storedState.currentDate === "string"
                                 		? parseStoredDate(storedState.currentDate)
                                 		: null;
                              
                                 const state = {
                                 	view:
                                 		storedView ||
                                 		(
                                 			["month", "week", "day"].includes(config.defaultView)
                                 				? config.defaultView
                                 				: "month"
                                 		),
                              
                                 	currentDate:
                                 		storedCurrentDate ||
                                 		startOfDay(new Date()),
                              
                                 	selectedEventElement: null,
                                 	tooltipEventId: null
                                 };
                              
                                 const formatterCache = {};
                                 const groupColorMap = new Map();
                              
                                 const events = Array.isArray(rawData)
                                     ? rawData
                                         .map(function (item, index)
                                         {
                                             return normalizeEvent(item, index);
                                         })
                                         .filter(function (item)
                                         {
                                             return item !== null;
                                         })
                                     : [];
                              
                                 const invalidItems = Array.isArray(rawData)
                                     ? rawData
                                         .map(function (item, index)
                                         {
                                             return {
                                                 item: item,
                                                 index: index,
                                                 parsed: normalizeEvent(item, index)
                                             };
                                         })
                                         .filter(function (entry)
                                         {
                                             return entry.parsed === null;
                                         })
                                     : [];
                              
                                 function pad(value)
                                 {
                                     return String(value).padStart(2, "0");
                                 }
                              
                                 function escapeHtml(value)
                                 {
                                     return String(value === undefined || value === null ? "" : value)
                                         .replace(/&/g, "&amp;")
                                         .replace(/</g, "&lt;")
                                         .replace(/>/g, "&gt;")
                                         .replace(/"/g, "&quot;")
                                         .replace(/'/g, "&#039;");
                                 }
                              
                                 function escapeAttribute(value)
                                 {
                                     return escapeHtml(value);
                                 }
                              
                                 function createLocalDate(year, month, day, hour, minute)
                                 {
                                     const date = new Date(
                                         year,
                                         month - 1,
                                         day,
                                         hour || 0,
                                         minute || 0,
                                         0,
                                         0
                                     );
                              
                                     if (
                                         date.getFullYear() !== year ||
                                         date.getMonth() !== month - 1 ||
                                         date.getDate() !== day
                                     )
                                     {
                                         return null;
                                     }
                              
                                     return date;
                                 }
                              
                                 function parseStoredDate(value)
                                 {
                                 	if (typeof value !== "string")
                                 	{
                                 		return null;
                                 	}
                              
                                 	const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
                              
                                 	if (!match)
                                 	{
                                 		return null;
                                 	}
                              
                                 	return createLocalDate(
                                 		Number(match[1]),
                                 		Number(match[2]),
                                 		Number(match[3]),
                                 		0,
                                 		0
                                 	);
                                 }
                              
                              
                                 function parseDateTimeText(value)
                                 {
                                     if (typeof value !== "string")
                                     {
                                         return null;
                                     }
                              
                                     const normalized = value
                                         .trim()
                                         .replace(/\s+/g, " ");
                              
                                     const separatorMatch = normalized.match(/\s+-\s+/);
                              
                                     if (!separatorMatch)
                                     {
                                         return null;
                                     }
                              
                                     const separatorIndex = separatorMatch.index;
                                     const separatorLength = separatorMatch[0].length;
                                     const startText = normalized.slice(0, separatorIndex).trim();
                                     const endText = normalized
                                         .slice(separatorIndex + separatorLength)
                                         .trim();
                              
                                     const fullDatePattern = /^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}))?$/;
                                     const timeOnlyPattern = /^(\d{1,2}):(\d{2})$/;
                              
                                     const startMatch = startText.match(fullDatePattern);
                              
                                     if (!startMatch)
                                     {
                                         return null;
                                     }
                              
                                     const startDay = Number(startMatch[1]);
                                     const startMonth = Number(startMatch[2]);
                                     const startYear = Number(startMatch[3]);
                                     const startHasTime = startMatch[4] !== undefined;
                                     const startHour = startHasTime ? Number(startMatch[4]) : 0;
                                     const startMinute = startHasTime ? Number(startMatch[5]) : 0;
                              
                                     if (
                                         startHour < 0 ||
                                         startHour > 23 ||
                                         startMinute < 0 ||
                                         startMinute > 59
                                     )
                                     {
                                         return null;
                                     }
                              
                                     const start = createLocalDate(
                                         startYear,
                                         startMonth,
                                         startDay,
                                         startHour,
                                         startMinute
                                     );
                              
                                     if (!start)
                                     {
                                         return null;
                                     }
                              
                                     let end = null;
                                     let endHasTime = false;
                              
                                     const endFullDateMatch = endText.match(fullDatePattern);
                                     const endTimeOnlyMatch = endText.match(timeOnlyPattern);
                              
                                     if (endFullDateMatch)
                                     {
                                         const endDay = Number(endFullDateMatch[1]);
                                         const endMonth = Number(endFullDateMatch[2]);
                                         const endYear = Number(endFullDateMatch[3]);
                              
                                         endHasTime = endFullDateMatch[4] !== undefined;
                              
                                         const endHour = endHasTime ? Number(endFullDateMatch[4]) : 0;
                                         const endMinute = endHasTime ? Number(endFullDateMatch[5]) : 0;
                              
                                         if (
                                             endHour < 0 ||
                                             endHour > 23 ||
                                             endMinute < 0 ||
                                             endMinute > 59
                                         )
                                         {
                                             return null;
                                         }
                              
                                         end = createLocalDate(
                                             endYear,
                                             endMonth,
                                             endDay,
                                             endHour,
                                             endMinute
                                         );
                                     } else if (endTimeOnlyMatch)
                                     {
                                         endHasTime = true;
                              
                                         const endHour = Number(endTimeOnlyMatch[1]);
                                         const endMinute = Number(endTimeOnlyMatch[2]);
                              
                                         if (
                                             endHour < 0 ||
                                             endHour > 23 ||
                                             endMinute < 0 ||
                                             endMinute > 59
                                         )
                                         {
                                             return null;
                                         }
                              
                                         end = createLocalDate(
                                             startYear,
                                             startMonth,
                                             startDay,
                                             endHour,
                                             endMinute
                                         );
                                     }
                              
                                     if (!end)
                                     {
                                         return null;
                                     }
                              
                                     const allDay = !startHasTime && !endHasTime;
                              
                                     if (allDay)
                                     {
                                         end.setHours(23, 59, 59, 999);
                                     }
                              
                                     if (end.getTime() < start.getTime())
                                     {
                                         return null;
                                     }
                              
                                     return {
                                         start: start,
                                         end: end,
                                         allDay: allDay,
                                         sourceText: normalized
                                     };
                                 }
                              
                                 function normalizeEvent(item, index)
                                 {
                                     if (!item || typeof item !== "object")
                                     {
                                         return null;
                                     }
                              
                                     const parsedDate = parseDateTimeText(item.zeit);
                              
                                     if (!parsedDate)
                                     {
                                         return null;
                                     }
                              
                                     const event = {
                                         id: String(index),
                                         img: typeof item.img === "string"
                                             ? item.img.trim()
                                             : "",
                                         zeit: typeof item.zeit === "string"
                                             ? item.zeit.trim()
                                             : "",
                                         ereignis: typeof item.ereignis === "string"
                                             ? item.ereignis.trim()
                                             : "",
                                         ort: typeof item.ort === "string"
                                             ? item.ort.trim()
                                             : "",
                                         start: parsedDate.start,
                                         end: parsedDate.end,
                                         allDay: parsedDate.allDay
                                     };
                              
                                     event.color = getEventColor(event);
                              
                                     return event;
                                 }
                              
                                 function getGroupingKey(event)
                                 {
                                     if (config.colorGrouping === "eventPrefix")
                                     {
                                         const eventText = event.ereignis || "";
                                         const separatorPosition = eventText.indexOf(":");
                              
                                         if (separatorPosition >= 0)
                                         {
                                             return eventText.slice(0, separatorPosition).trim().toLowerCase();
                                         }
                              
                                         return eventText.trim().toLowerCase();
                                     }
                              
                                     return (event.img || event.ereignis || event.id).toLowerCase();
                                 }
                              
                                 function getEventColor(event)
                                 {
                                     if (Array.isArray(config.colorRules))
                                     {
                                         for (const rule of config.colorRules)
                                         {
                                             if (
                                                 !rule ||
                                                 !["ereignis", "img"].includes(rule.matchField) ||
                                                 typeof rule.match !== "string" ||
                                                 typeof rule.color !== "string"
                                             )
                                             {
                                                 continue;
                                             }
                              
                                             const fieldValue = String(event[rule.matchField] || "");
                              
                                             if (
                                                 fieldValue
                                                     .toLocaleLowerCase(config.locale)
                                                     .includes(rule.match.toLocaleLowerCase(config.locale))
                                             )
                                             {
                                                 return rule.color;
                                             }
                                         }
                                     }
                              
                                     const groupKey = getGroupingKey(event);
                              
                                     if (groupColorMap.has(groupKey))
                                     {
                                         return groupColorMap.get(groupKey);
                                     }
                              
                                     const palette = Array.isArray(config.autoColors) && config.autoColors.length > 0
                                         ? config.autoColors
                                         : ["#1565c0"];
                              
                                     const color = palette[groupColorMap.size % palette.length];
                              
                                     groupColorMap.set(groupKey, color);
                              
                                     return color;
                                 }
                              
                                 function startOfDay(date)
                                 {
                                     return new Date(
                                         date.getFullYear(),
                                         date.getMonth(),
                                         date.getDate(),
                                         0,
                                         0,
                                         0,
                                         0
                                     );
                                 }
                              
                                 function endOfDay(date)
                                 {
                                     return new Date(
                                         date.getFullYear(),
                                         date.getMonth(),
                                         date.getDate(),
                                         23,
                                         59,
                                         59,
                                         999
                                     );
                                 }
                              
                                 function addDays(date, amount)
                                 {
                                     const result = new Date(date);
                                     result.setDate(result.getDate() + amount);
                                     return result;
                                 }
                              
                                 function addMonths(date, amount)
                                 {
                                     const targetDay = date.getDate();
                                     const result = new Date(
                                         date.getFullYear(),
                                         date.getMonth() + amount,
                                         1
                                     );
                              
                                     const lastDay = new Date(
                                         result.getFullYear(),
                                         result.getMonth() + 1,
                                         0
                                     ).getDate();
                              
                                     result.setDate(Math.min(targetDay, lastDay));
                              
                                     return result;
                                 }
                              
                                 function startOfWeek(date)
                                 {
                                     const result = startOfDay(date);
                                     const currentWeekday = result.getDay();
                                     const firstDay = Number(config.firstDayOfWeek) === 0 ? 0 : 1;
                                     const difference = (currentWeekday - firstDay + 7) % 7;
                              
                                     result.setDate(result.getDate() - difference);
                              
                                     return result;
                                 }
                              
                                 function sameDay(first, second)
                                 {
                                     return (
                                         first.getFullYear() === second.getFullYear() &&
                                         first.getMonth() === second.getMonth() &&
                                         first.getDate() === second.getDate()
                                     );
                                 }
                              
                                 function dateKey(date)
                                 {
                                     return [
                                         date.getFullYear(),
                                         pad(date.getMonth() + 1),
                                         pad(date.getDate())
                                     ].join("-");
                                 }
                              
                                 function saveState()
                                 {
                                 	if (
                                 		!window[globalStoreName] ||
                                 		!window[globalStoreName][widgetId]
                                 	)
                                 	{
                                 		return;
                                 	}
                              
                                 	window[globalStoreName][widgetId].state = {
                                 		view: state.view,
                                 		currentDate: dateKey(state.currentDate)
                                 	};
                                 }
                              
                              
                                 function getFormatter(options)
                                 {
                                     const key = JSON.stringify(options);
                              
                                     if (!formatterCache[key])
                                     {
                                         formatterCache[key] = new Intl.DateTimeFormat(
                                             config.locale || "de-DE",
                                             options
                                         );
                                     }
                              
                                     return formatterCache[key];
                                 }
                              
                                 function formatDate(date, options)
                                 {
                                     return getFormatter(options).format(date);
                                 }
                              
                                 function formatTime(date)
                                 {
                                     return formatDate(date, {
                                         hour: "2-digit",
                                         minute: "2-digit"
                                     });
                                 }
                              
                                 function formatEventTime(event)
                                 {
                                     if (event.allDay)
                                     {
                                         if (sameDay(event.start, event.end))
                                         {
                                             return config.labels.allDay;
                                         }
                              
                                         return [
                                             formatDate(event.start, {
                                                 day: "2-digit",
                                                 month: "2-digit",
                                                 year: "numeric"
                                             }),
                                             "–",
                                             formatDate(event.end, {
                                                 day: "2-digit",
                                                 month: "2-digit",
                                                 year: "numeric"
                                             })
                                         ].join(" ");
                                     }
                              
                                     if (sameDay(event.start, event.end))
                                     {
                                         return formatTime(event.start) + "–" + formatTime(event.end);
                                     }
                              
                                     return [
                                         formatDate(event.start, {
                                             day: "2-digit",
                                             month: "2-digit",
                                             year: "numeric",
                                             hour: "2-digit",
                                             minute: "2-digit"
                                         }),
                                         "–",
                                         formatDate(event.end, {
                                             day: "2-digit",
                                             month: "2-digit",
                                             year: "numeric",
                                             hour: "2-digit",
                                             minute: "2-digit"
                                         })
                                     ].join(" ");
                                 }
                              
                                 function getIsoWeekNumber(date)
                                 {
                                     const target = new Date(
                                         Date.UTC(
                                             date.getFullYear(),
                                             date.getMonth(),
                                             date.getDate()
                                         )
                                     );
                              
                                     const weekday = target.getUTCDay() || 7;
                              
                                     target.setUTCDate(target.getUTCDate() + 4 - weekday);
                              
                                     const yearStart = new Date(
                                         Date.UTC(target.getUTCFullYear(), 0, 1)
                                     );
                              
                                     return Math.ceil(
                                         (((target - yearStart) / 86400000) + 1) / 7
                                     );
                                 }
                              
                                 function eventOccursOnDay(event, day)
                                 {
                                     const dayStart = startOfDay(day);
                                     const dayEnd = endOfDay(day);
                              
                                     return (
                                         event.start.getTime() <= dayEnd.getTime() &&
                                         event.end.getTime() >= dayStart.getTime()
                                     );
                                 }
                              
                                 function getEventsForDay(day)
                                 {
                                     return events
                                         .filter(function (event)
                                         {
                                             return eventOccursOnDay(event, day);
                                         })
                                         .sort(function (first, second)
                                         {
                                             if (first.allDay !== second.allDay)
                                             {
                                                 return first.allDay ? -1 : 1;
                                             }
                              
                                             const timeDifference =
                                                 first.start.getTime() - second.start.getTime();
                              
                                             if (timeDifference !== 0)
                                             {
                                                 return timeDifference;
                                             }
                              
                                             return first.ereignis.localeCompare(
                                                 second.ereignis,
                                                 config.locale || "de-DE"
                                             );
                                         });
                                 }
                              
                                 function isWeekend(date)
                                 {
                                     return date.getDay() === 0 || date.getDay() === 6;
                                 }
                              
                                 function renderSmallEvent(event, options)
                                 {
                                     const settings = options || {};
                                     const showTime = settings.showTime !== false;
                                     const imageHtml =
                                         config.showImages &&
                                         event.img
                                             ? '<img class="jscal-event-image" src="' +
                                                 escapeAttribute(event.img) +
                                                 '" alt="">'
                                             : "";
                              
                                     let timeHtml = "";
                              
                                     if (showTime)
                                     {
                                         timeHtml = '<span class="jscal-event-time">' +
                                             escapeHtml(
                                                 event.allDay
                                                     ? ""
                                                     : formatTime(event.start)
                                             ) +
                                             "</span>";
                                     }
                              
                                     return [
                                         '<button type="button"',
                                         ' class="jscal-event"',
                                         ' data-jscal-event-id="',
                                         escapeAttribute(event.id),
                                         '"',
                                         ' style="--jscal-event-color:',
                                         escapeAttribute(event.color),
                                         ';"',
                                         ' aria-label="',
                                         escapeAttribute(event.ereignis),
                                         '">',
                                         imageHtml,
                                         timeHtml,
                                         '<span class="jscal-event-title">',
                                         escapeHtml(event.ereignis || ""),
                                         "</span>",
                                         "</button>"
                                     ].join("");
                                 }
                              
                                 function renderMonth()
                                 {
                                     const monthStart = new Date(
                                         state.currentDate.getFullYear(),
                                         state.currentDate.getMonth(),
                                         1
                                     );
                              
                                     const monthEnd = new Date(
                                         state.currentDate.getFullYear(),
                                         state.currentDate.getMonth() + 1,
                                         0
                                     );
                              
                                     const gridStart = startOfWeek(monthStart);
                                     let gridEnd = addDays(startOfWeek(monthEnd), 6);
                              
                                     if (gridEnd.getTime() < monthEnd.getTime())
                                     {
                                         gridEnd = addDays(gridEnd, 7);
                                     }
                              
                                     const dayHeaders = [];
                                     const headerReference = startOfWeek(new Date(2026, 0, 5));
                              
                                     if (config.showWeekNumbers)
                                     {
                                         dayHeaders.push(
                                             '<div class="jscal-week-header">' +
                                             escapeHtml(config.labels.calendarWeekShort) +
                                             "</div>"
                                         );
                                     }
                              
                                     for (let index = 0; index < 7; index++)
                                     {
                                         const headerDate = addDays(headerReference, index);
                              
                                         dayHeaders.push(
                                             '<div class="jscal-week-header">' +
                                             escapeHtml(
                                                 formatDate(headerDate, {
                                                     weekday: "short"
                                                 })
                                             ) +
                                             "</div>"
                                         );
                                     }
                              
                                     const cells = [];
                                     let cursor = new Date(gridStart);
                                     let rowIndex = 0;
                              
                                     while (cursor.getTime() <= gridEnd.getTime())
                                     {
                                         if (config.showWeekNumbers && rowIndex % 7 === 0)
                                         {
                                             cells.push(
                                                 '<div class="jscal-week-number">' +
                                                 escapeHtml(
                                                     config.labels.calendarWeekShort +
                                                     " " +
                                                     getIsoWeekNumber(cursor)
                                                 ) +
                                                 "</div>"
                                             );
                                         }
                              
                                         const currentDay = new Date(cursor);
                                         const currentEvents = getEventsForDay(currentDay);
                                         const visibleEvents = currentEvents.slice(0, 4);
                                         const remainingCount = currentEvents.length - visibleEvents.length;
                              
                                         const classes = ["jscal-month-day"];
                              
                                         if (currentDay.getMonth() !== state.currentDate.getMonth())
                                         {
                                             classes.push("is-outside");
                                         }
                              
                                         if (sameDay(currentDay, new Date()))
                                         {
                                             classes.push("is-today");
                                         }
                              
                                         if (config.highlightWeekend && isWeekend(currentDay))
                                         {
                                             classes.push("is-weekend");
                                         }
                              
                                         let eventHtml = visibleEvents
                                             .map(function (event)
                                             {
                                                 return renderSmallEvent(event, {
                                                     showTime: true
                                                 });
                                             })
                                             .join("");
                              
                                         if (remainingCount > 0)
                                         {
                                             eventHtml +=
                                                 '<div class="jscal-more">+' +
                                                 escapeHtml(remainingCount) +
                                                 "</div>";
                                         }
                              
                                         cells.push(
                                             '<div class="' +
                                             classes.join(" ") +
                                             '" data-jscal-date="' +
                                             escapeAttribute(dateKey(currentDay)) +
                                             '">' +
                                             '<div class="jscal-day-number"><span>' +
                                             escapeHtml(currentDay.getDate()) +
                                             "</span></div>" +
                                             '<div class="jscal-events">' +
                                             eventHtml +
                                             "</div>" +
                                             "</div>"
                                         );
                              
                                         cursor = addDays(cursor, 1);
                                         rowIndex++;
                                     }
                              
                                     const className = config.showWeekNumbers
                                         ? "jscal-month"
                                         : "jscal-month no-week-numbers";
                              
                                     titleElement.textContent = formatDate(state.currentDate, {
                                         month: "long",
                                         year: "numeric"
                                     });
                              
                                     contentElement.innerHTML =
                                         '<div class="' +
                                         className +
                                         '">' +
                                         dayHeaders.join("") +
                                         cells.join("") +
                                         "</div>";
                                 }
                              
                                 function renderWeek()
                                 {
                                     const weekStart = startOfWeek(state.currentDate);
                                     const weekEnd = addDays(weekStart, 6);
                                     const columns = [];
                              
                                     for (let index = 0; index < 7; index++)
                                     {
                                         const day = addDays(weekStart, index);
                                         const dayEvents = getEventsForDay(day);
                                         const classes = ["jscal-week-day"];
                              
                                         if (sameDay(day, new Date()))
                                         {
                                             classes.push("is-today");
                                         }
                              
                                         if (config.highlightWeekend && isWeekend(day))
                                         {
                                             classes.push("is-weekend");
                                         }
                              
                                         let eventsHtml = "";
                              
                                         if (dayEvents.length === 0)
                                         {
                                             eventsHtml =
                                                 '<div class="jscal-empty">' +
                                                 escapeHtml(config.labels.noEvents) +
                                                 "</div>";
                                         } else
                                         {
                                             eventsHtml = dayEvents
                                                 .map(function (event)
                                                 {
                                                     return renderSmallEvent(event, {
                                                         showTime: true
                                                     });
                                                 })
                                                 .join("");
                                         }
                              
                                         columns.push(
                                             '<section class="' +
                                             classes.join(" ") +
                                             '" data-jscal-date="' +
                                             escapeAttribute(dateKey(day)) +
                                             '">' +
                                             '<div class="jscal-week-day-header">' +
                                             '<div class="jscal-week-day-name">' +
                                             escapeHtml(
                                                 formatDate(day, {
                                                     weekday: "short"
                                                 })
                                             ) +
                                             "</div>" +
                                             '<div class="jscal-week-day-date">' +
                                             escapeHtml(day.getDate()) +
                                             "</div>" +
                                             "</div>" +
                                             '<div class="jscal-week-events">' +
                                             eventsHtml +
                                             "</div>" +
                                             "</section>"
                                         );
                                     }
                              
                                     const sameMonth =
                                         weekStart.getMonth() === weekEnd.getMonth() &&
                                         weekStart.getFullYear() === weekEnd.getFullYear();
                              
                                     if (sameMonth)
                                     {
                                         titleElement.textContent =
                                             config.labels.calendarWeekShort +
                                             " " +
                                             getIsoWeekNumber(weekStart) +
                                             " · " +
                                             formatDate(weekStart, {
                                                 month: "long",
                                                 year: "numeric"
                                             });
                                     } else
                                     {
                                         titleElement.textContent =
                                             config.labels.calendarWeekShort +
                                             " " +
                                             getIsoWeekNumber(weekStart) +
                                             " · " +
                                             formatDate(weekStart, {
                                                 day: "2-digit",
                                                 month: "2-digit"
                                             }) +
                                             " – " +
                                             formatDate(weekEnd, {
                                                 day: "2-digit",
                                                 month: "2-digit",
                                                 year: "numeric"
                                             });
                                     }
                              
                                     contentElement.innerHTML =
                                         '<div class="jscal-week">' +
                                         columns.join("") +
                                         "</div>";
                                 }
                              
                                 function renderDayEvent(event)
                                 {
                                     const imageHtml =
                                         config.showImages &&
                                         event.img
                                             ? '<img class="jscal-day-event-image" src="' +
                                                 escapeAttribute(event.img) +
                                                 '" alt="">'
                                             : "";
                              
                                     const locationHtml =
                                         event.ort &&
                                         !(config.hideEmptyLocation && event.ort.length === 0)
                                             ? '<div class="jscal-day-event-location">' +
                                                 escapeHtml(event.ort) +
                                                 "</div>"
                                             : "";
                              
                                     return [
                                         '<article class="jscal-day-event"',
                                         ' style="--jscal-event-color:',
                                         escapeAttribute(event.color),
                                         ';">',
                                         '<div class="jscal-day-event-time">',
                                         escapeHtml(
                                             event.allDay
                                                 ? config.labels.allDay
                                                 : formatTime(event.start)
                                         ),
                                         "</div>",
                                         '<button type="button"',
                                         ' class="jscal-day-event-content"',
                                         ' data-jscal-event-id="',
                                         escapeAttribute(event.id),
                                         '"',
                                         ' aria-label="',
                                         escapeAttribute(event.ereignis),
                                         '">',
                                         imageHtml,
                                         '<div class="jscal-day-event-text">',
                                         '<div class="jscal-day-event-title">',
                                         escapeHtml(event.ereignis),
                                         "</div>",
                                         locationHtml,
                                         "</div>",
                                         "</button>",
                                         "</article>"
                                     ].join("");
                                 }
                              
                                 function renderDay()
                                 {
                                     const dayEvents = getEventsForDay(state.currentDate);
                                     const allDayEvents = dayEvents.filter(function (event)
                                     {
                                         return event.allDay;
                                     });
                                     const timedEvents = dayEvents.filter(function (event)
                                     {
                                         return !event.allDay;
                                     });
                              
                                     const sections = [];
                              
                                     if (allDayEvents.length > 0)
                                     {
                                         sections.push(
                                             '<section class="jscal-day-section">' +
                                             '<h3 class="jscal-day-section-title">' +
                                             escapeHtml(config.labels.allDay) +
                                             "</h3>" +
                                             allDayEvents.map(renderDayEvent).join("") +
                                             "</section>"
                                         );
                                     }
                              
                                     if (timedEvents.length > 0)
                                     {
                                         sections.push(
                                             '<section class="jscal-day-section">' +
                                             timedEvents.map(renderDayEvent).join("") +
                                             "</section>"
                                         );
                                     }
                              
                                     if (sections.length === 0)
                                     {
                                         sections.push(
                                             '<div class="jscal-empty">' +
                                             escapeHtml(config.labels.noEvents) +
                                             "</div>"
                                         );
                                     }
                              
                                     titleElement.textContent = formatDate(state.currentDate, {
                                         weekday: "long",
                                         day: "2-digit",
                                         month: "long",
                                         year: "numeric"
                                     });
                              
                                     contentElement.innerHTML =
                                         '<div class="jscal-day-view">' +
                                         sections.join("") +
                                         "</div>";
                                 }
                              
                                 function renderInvalidItems()
                                 {
                                     if (invalidItems.length === 0)
                                     {
                                         return;
                                     }
                              
                                     const invalidHtml = invalidItems
                                         .map(function (entry)
                                         {
                                             const timeText =
                                                 entry.item &&
                                                 typeof entry.item.zeit === "string"
                                                     ? entry.item.zeit
                                                     : "";
                              
                                             return (
                                                 '<div class="jscal-invalid">' +
                                                 escapeHtml(config.labels.invalidDate) +
                                                 ": " +
                                                 escapeHtml(timeText) +
                                                 "</div>"
                                             );
                                         })
                                         .join("");
                              
                                     contentElement.insertAdjacentHTML(
                                         "beforeend",
                                         invalidHtml
                                     );
                                 }
                              
                                 function updateViewButtons()
                                 {
                                     const buttons = root.querySelectorAll("[data-jscal-view]");
                              
                                     buttons.forEach(function (button)
                                     {
                                         const isActive =
                                             button.getAttribute("data-jscal-view") === state.view;
                              
                                         button.classList.toggle("is-active", isActive);
                                         button.setAttribute(
                                             "aria-pressed",
                                             isActive ? "true" : "false"
                                         );
                                     });
                                 }
                              
                                 function render()
                                 {
                                     saveState();
                                 	hideTooltip();
                              
                                     if (state.view === "week")
                                     {
                                         renderWeek();
                                     } else if (state.view === "day")
                                     {
                                         renderDay();
                                     } else
                                     {
                                         renderMonth();
                                     }
                              
                                     renderInvalidItems();
                                     updateViewButtons();
                                 }
                              
                                 function findEventById(eventId)
                                 {
                                     return events.find(function (event)
                                     {
                                         return event.id === String(eventId);
                                     }) || null;
                                 }
                              
                                 function buildTooltipHtml(event)
                                 {
                                     const imageHtml =
                                         config.showImages &&
                                         event.img
                                             ? '<img class="jscal-tooltip-image" src="' +
                                                 escapeAttribute(event.img) +
                                                 '" alt="">'
                                             : "";
                              
                                     const locationRow =
                                         event.ort &&
                                         !(config.hideEmptyLocation && event.ort.length === 0)
                                             ? '<div class="jscal-tooltip-row">' +
                                                 '<div class="jscal-tooltip-label">' +
                                                 escapeHtml(config.labels.location) +
                                                 "</div>" +
                                                 '<div class="jscal-tooltip-value">' +
                                                 escapeHtml(event.ort) +
                                                 "</div>" +
                                                 "</div>"
                                             : "";
                              
                                     return (
                                         '<div class="jscal-tooltip-header">' +
                                         imageHtml +
                                         '<div class="jscal-tooltip-title">' +
                                         escapeHtml(event.ereignis) +
                                         "</div>" +
                                         "</div>" +
                                         '<div class="jscal-tooltip-row">' +
                                         '<div class="jscal-tooltip-label">Zeit</div>' +
                                         '<div class="jscal-tooltip-value">' +
                                         escapeHtml(formatEventTime(event)) +
                                         "</div>" +
                                         "</div>" +
                                         locationRow
                                     );
                                 }
                              
                                 function positionTooltip(targetElement)
                                 {
                                     const targetRect = targetElement.getBoundingClientRect();
                                     const tooltipRect = tooltipElement.getBoundingClientRect();
                                     const margin = 8;
                              
                                     let left = targetRect.left;
                                     let top = targetRect.bottom + margin;
                              
                                     if (left + tooltipRect.width > window.innerWidth - margin)
                                     {
                                         left = window.innerWidth - tooltipRect.width - margin;
                                     }
                              
                                     if (left < margin)
                                     {
                                         left = margin;
                                     }
                              
                                     if (top + tooltipRect.height > window.innerHeight - margin)
                                     {
                                         top = targetRect.top - tooltipRect.height - margin;
                                     }
                              
                                     if (top < margin)
                                     {
                                         top = margin;
                                     }
                              
                                     tooltipElement.style.left = Math.round(left) + "px";
                                     tooltipElement.style.top = Math.round(top) + "px";
                                 }
                              
                                 function showTooltip(targetElement)
                                 {
                                     const eventId = targetElement.getAttribute("data-jscal-event-id");
                                     const event = findEventById(eventId);
                              
                                     if (!event)
                                     {
                                         return;
                                     }
                              
                                     if (
                                         state.selectedEventElement &&
                                         state.selectedEventElement !== targetElement
                                     )
                                     {
                                         state.selectedEventElement.classList.remove("is-selected");
                                     }
                              
                                     state.selectedEventElement = targetElement;
                                     state.tooltipEventId = event.id;
                              
                                     targetElement.classList.add("is-selected");
                                     tooltipElement.innerHTML = buildTooltipHtml(event);
                                     tooltipElement.classList.add("is-visible");
                                     tooltipElement.setAttribute("aria-hidden", "false");
                              
                                     positionTooltip(targetElement);
                                 }
                              
                                 function hideTooltip()
                                 {
                                     if (state.selectedEventElement)
                                     {
                                         state.selectedEventElement.classList.remove("is-selected");
                                     }
                              
                                     state.selectedEventElement = null;
                                     state.tooltipEventId = null;
                              
                                     tooltipElement.classList.remove("is-visible");
                                     tooltipElement.setAttribute("aria-hidden", "true");
                                     tooltipElement.innerHTML = "";
                                 }
                              
                                 function navigate(direction)
                                 {
                                     if (state.view === "month")
                                     {
                                         state.currentDate = addMonths(
                                             state.currentDate,
                                             direction
                                         );
                                     } else if (state.view === "week")
                                     {
                                         state.currentDate = addDays(
                                             state.currentDate,
                                             direction * 7
                                         );
                                     } else
                                     {
                                         state.currentDate = addDays(
                                             state.currentDate,
                                             direction
                                         );
                                     }
                              
                                     render();
                                 }
                              
                                 function handleClick(event)
                                 {
                                     const actionButton = event.target.closest("[data-jscal-action]");
                              
                                     if (actionButton && root.contains(actionButton))
                                     {
                                         const action = actionButton.getAttribute("data-jscal-action");
                              
                                         if (action === "previous")
                                         {
                                             navigate(-1);
                                         } else if (action === "next")
                                         {
                                             navigate(1);
                                         } else if (action === "today")
                                         {
                                             state.currentDate = startOfDay(new Date());
                                             render();
                                         }
                              
                                         return;
                                     }
                              
                                     const viewButton = event.target.closest("[data-jscal-view]");
                              
                                     if (viewButton && root.contains(viewButton))
                                     {
                                         const nextView = viewButton.getAttribute("data-jscal-view");
                              
                                         if (["month", "week", "day"].includes(nextView))
                                         {
                                             state.view = nextView;
                                             render();
                                         }
                              
                                         return;
                                     }
                              
                                     const eventElement = event.target.closest("[data-jscal-event-id]");
                              
                                     if (eventElement && root.contains(eventElement))
                                     {
                                         const eventId =
                                             eventElement.getAttribute("data-jscal-event-id");
                              
                                         if (
                                             state.tooltipEventId === eventId &&
                                             tooltipElement.classList.contains("is-visible")
                                         )
                                         {
                                             hideTooltip();
                                         } else
                                         {
                                             showTooltip(eventElement);
                                         }
                              
                                         event.preventDefault();
                                         event.stopPropagation();
                                         return;
                                     }
                              
                                     const dayElement = event.target.closest("[data-jscal-date]");
                              
                                     if (dayElement && root.contains(dayElement))
                                     {
                                         const dateText = dayElement.getAttribute("data-jscal-date");
                                         const match = dateText
                                             ? dateText.match(/^(\d{4})-(\d{2})-(\d{2})$/)
                                             : null;
                              
                                         if (match)
                                         {
                                             const selectedDate = createLocalDate(
                                                 Number(match[1]),
                                                 Number(match[2]),
                                                 Number(match[3]),
                                                 0,
                                                 0
                                             );
                              
                                             if (selectedDate)
                                             {
                                                 state.currentDate = selectedDate;
                                                 state.view = "day";
                                                 render();
                                             }
                                         }
                                     }
                                 }
                              
                                 function handlePointerOver(event)
                                 {
                                     if (
                                         event.pointerType &&
                                         event.pointerType !== "mouse"
                                     )
                                     {
                                         return;
                                     }
                              
                                     const eventElement = event.target.closest("[data-jscal-event-id]");
                              
                                     if (!eventElement || !root.contains(eventElement))
                                     {
                                         return;
                                     }
                              
                                     const relatedTarget = event.relatedTarget;
                              
                                     if (
                                         relatedTarget &&
                                         eventElement.contains(relatedTarget)
                                     )
                                     {
                                         return;
                                     }
                              
                                     showTooltip(eventElement);
                                 }
                              
                                 function handlePointerOut(event)
                                 {
                                     if (
                                         event.pointerType &&
                                         event.pointerType !== "mouse"
                                     )
                                     {
                                         return;
                                     }
                              
                                     const eventElement = event.target.closest("[data-jscal-event-id]");
                              
                                     if (!eventElement || !root.contains(eventElement))
                                     {
                                         return;
                                     }
                              
                                     const relatedTarget = event.relatedTarget;
                              
                                     if (
                                         relatedTarget &&
                                         eventElement.contains(relatedTarget)
                                     )
                                     {
                                         return;
                                     }
                              
                                     hideTooltip();
                                 }
                              
                                 function handleDocumentPointerDown(event)
                                 {
                                     if (!root.contains(event.target))
                                     {
                                         hideTooltip();
                                     }
                                 }
                              
                                 function handleResize()
                                 {
                                     if (
                                         state.selectedEventElement &&
                                         tooltipElement.classList.contains("is-visible")
                                     )
                                     {
                                         positionTooltip(state.selectedEventElement);
                                     }
                                 }
                              
                                 root.addEventListener("click", handleClick);
                                 root.addEventListener("pointerover", handlePointerOver);
                                 root.addEventListener("pointerout", handlePointerOut);
                                 document.addEventListener("pointerdown", handleDocumentPointerDown);
                                 window.addEventListener("resize", handleResize);
                              
                                 root.__jsonCalendarCleanup = function ()
                                 {
                                     root.removeEventListener("click", handleClick);
                                     root.removeEventListener("pointerover", handlePointerOver);
                                     root.removeEventListener("pointerout", handlePointerOut);
                                     document.removeEventListener(
                                         "pointerdown",
                                         handleDocumentPointerDown
                                     );
                                     window.removeEventListener("resize", handleResize);
                                 };
                              
                                 render();
                              })();
                              </script>
                              

                              Das widget hat eine Monats,Wochen,Tagessicht. Details über MouseOver, verschiedene Farben für Ereignisse, was im Template konfiguriert werden kann. Hell/Dunkel/Auto (also nach browser-Einstellung) Farben und ein wenig Navigation

                              Monatssicht
                              6ca992eb-2a64-4c41-a76a-de5ad89f44ca-image.jpeg

                              Wochensicht
                              da7a0855-76a0-4597-9385-ead7e19858d2-image.jpeg

                              Tagessicht
                              04901f5e-2f32-4610-b979-ca30f279c9be-image.jpeg

                              Theoretisch werden auch Bilder angezeigt. habe die aber nicht, daher wird das leer angezeigt. Habe das dann auch nicht weiters getestet.

                              Als Testdaten habe ich die hier schon mal geposteten verwendet.

                              Ich habe noch einen kleinen Effekt mit dem jsontemplate entdeckt.
                              Bitte mal testen ob euch auch etwas auffällt.
                              Auch werde ich dem KI-Prompt noch was hinzufügen müssen, da der erste Wurf nicht funktioniert hat, wie es die KI gemacht hat.
                              Sie hat versucht die Daten per serialisierung und einfügen in das javascript für die browser-Ebene zu übergeben. Das hat nicht funktioniert. Daher wird nun eine globale window variable verwendet.

                              Meine Adapter und Widgets
                              TVProgram, SqueezeboxRPC, OpenLiga, RSSFeed, MyTime,, pi-hole2, vis-json-template, skiinfo, vis-mapwidgets, vis-2-widgets-rssfeed
                              Links im Profil

                              Eduard77E 1 Antwort Letzte Antwort
                              2
                              • Ro75R Ro75

                                Version 1.0.2 - 24.07.2026

                                • Ganztagstermine, über mehrere Tage, werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt.
                                • "Spezialtermine" wie "In Memory" oder auch Feiertage, etc. werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt, inklusive Emoji

                                Script in Post #1 angepasst.

                                Ro75.

                                sigi234S Online
                                sigi234S Online
                                sigi234
                                Forum Testing Most Active
                                schrieb zuletzt editiert von
                                #62

                                @Ro75 sagte:

                                Version 1.0.2 - 24.07.2026

                                • Ganztagstermine, über mehrere Tage, werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt.

                                Ja, aber mehrfach!

                                Screenshot (3024).png

                                • "Spezialtermine" wie "In Memory" oder auch Feiertage, etc. werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt, inklusive Emoji

                                Ja

                                Im Grossen Kalender ist der Termin immer noch nur ein Tag.

                                Screenshot (3025).png

                                Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                                Immer Daten sichern!

                                Ro75R 1 Antwort Letzte Antwort
                                0
                                • sigi234S sigi234

                                  @Ro75 sagte:

                                  Version 1.0.2 - 24.07.2026

                                  • Ganztagstermine, über mehrere Tage, werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt.

                                  Ja, aber mehrfach!

                                  Screenshot (3024).png

                                  • "Spezialtermine" wie "In Memory" oder auch Feiertage, etc. werden jetzt auch im kleinen Kalender (m1) korrekt dargestellt, inklusive Emoji

                                  Ja

                                  Im Grossen Kalender ist der Termin immer noch nur ein Tag.

                                  Screenshot (3025).png

                                  Ro75R Online
                                  Ro75R Online
                                  Ro75
                                  schrieb zuletzt editiert von
                                  #63

                                  sigi234 sagte:

                                  m Grossen Kalender ist der Termin immer noch nur ein Tag.

                                  Das ist auch nicht der Kalender den ich nutze. Der Kalender von Scrounger (materialdesign) stellt es korrekt dar. Eventuell kann @oliverio da was "zaubern". Was den kleinen Kalender betrifft. Für mich völlig korrekt, da dieser für die zeilenweise Darstellung für Termine gedacht ist, von daher kommt für jeden Tag in diesem Zeitraum ein Eintrag.

                                  Ro75.

                                  SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                                  1 Antwort Letzte Antwort
                                  0
                                  • Ro75R Online
                                    Ro75R Online
                                    Ro75
                                    schrieb zuletzt editiert von
                                    #64

                                    Vielleicht können sich ALLE Widget-Designer ja an dieser Stelle dem großen Kalender mal annehmen. Bisher gibt es für VIS-2 noch nichts vergleichbares wie der Kalender von Scrounger (materialdesign 0.5.94) unter VIS-1. Das ist für VIS-2 ein echtes Manko und hat mich bisher auch von einer Migration abgehalten.

                                    C1 ist das spezielle JSON für den Kalender von Scrounger. Das wäre ein echter Mehrwert für VIS-2 und ein Pluspunkt im Bereich Widget.

                                    Ro75.

                                    SERVER = Beelink U59 16GB DDR4 RAM 512GB SSD, FB 7490, FritzDect 200+301+440, ConBee II, Zigbee Aqara Sensoren + NOUS A1Z, NOUS A1T, Philips Hue ** ioBroker, REDIS, influxdb2, Grafana, PiHole, Plex-Mediaserver, paperless-ngx (Docker), MariaDB + phpmyadmin *** VIS-Runtime = Intel NUC 8GB RAM 128GB SSD + 24" Touchscreen

                                    1 Antwort Letzte Antwort
                                    2
                                    • skvarelS Offline
                                      skvarelS Offline
                                      skvarel
                                      Developer
                                      schrieb zuletzt editiert von
                                      #65

                                      @jkvarel ... kannst du was machen?

                                      #TeamInventwo
                                      Unsere Adapter:
                                      Autodarts, FoxESS, Enpal, Life360ng, Tidy, vis-inventwo, vis-2-widgets-inventwo, vis-icontwo, vis-2-widgets-icontwo

                                      Wer uns mit einem Kaffee unterstützen möchte: PayPal

                                      1 Antwort Letzte Antwort
                                      0
                                      • OliverIOO OliverIO

                                        Ich habe mal was für den großen Kalender probiert, welcher nur auf dem jsonTemplate und dem m1 Datenpunkt basiert

                                        <%
                                        /* ==========================================================================
                                          KONFIGURATION
                                          ========================================================================== */
                                        
                                        const calendarConfig = {
                                           /* Startansicht: "month", "week" oder "day" */
                                           defaultView: "month",
                                        
                                           /* Sprache für Datumsausgaben */
                                           locale: "de-DE",
                                        
                                           /* Erster Wochentag: 1 = Montag, 0 = Sonntag */
                                           firstDayOfWeek: 1,
                                        
                                           /* Wochenende hervorheben */
                                           highlightWeekend: true,
                                        
                                           /* Kalenderwoche anzeigen */
                                           showWeekNumbers: true,
                                        
                                           /* Bilder der Termine anzeigen */
                                           showImages: true,
                                        
                                           /* Leere Orte ausblenden */
                                           hideEmptyLocation: true,
                                        
                                           /*
                                            * Farbmodus:
                                            * "light" = helle Darstellung
                                            * "dark"  = dunkle Darstellung
                                            * "auto"  = Systemeinstellung des Browsers verwenden
                                            */
                                           theme: "auto",
                                        
                                           /*
                                            * Methode zur automatischen Gruppierung und Farbzuordnung.
                                            *
                                            * "img":
                                            * Termine mit derselben Bild-URL erhalten dieselbe Farbe.
                                            *
                                            * "eventPrefix":
                                            * Der Teil des Ereignisnamens vor dem ersten Doppelpunkt wird verwendet.
                                            */
                                           colorGrouping: "img",
                                        
                                           /*
                                            * Optional feste Farben vorgeben.
                                            *
                                            * matchField darf nur "ereignis" oder "img" sein.
                                            * match enthält einen Text, der im entsprechenden JSON-Feld gesucht wird.
                                            *
                                            * Regeln werden von oben nach unten geprüft.
                                            * Nicht passende Termine erhalten automatisch eine Farbe.
                                            */
                                           colorRules: [
                                               {
                                                   matchField: "ereignis",
                                                   match: "Altpapier",
                                                   color: "#d32f2f"
                                               },
                                               {
                                                   matchField: "ereignis",
                                                   match: "Restmüll",
                                                   color: "#546e7a"
                                               },
                                               {
                                                   matchField: "ereignis",
                                                   match: "Biotonne",
                                                   color: "#2e7d32"
                                               },
                                               {
                                                   matchField: "ereignis",
                                                   match: "gelber Sack",
                                                   color: "#f9a825"
                                               },
                                               {
                                                   matchField: "ereignis",
                                                   match: "Geburtstag",
                                                   color: "#8e24aa"
                                               }
                                           ],
                                        
                                           /*
                                            * Automatisch verwendete Farben.
                                            * Die Farben sollten einen ausreichenden Kontrast zu weißer Schrift haben.
                                            */
                                           autoColors: [
                                               "#1565c0",
                                               "#2e7d32",
                                               "#c62828",
                                               "#6a1b9a",
                                               "#00838f",
                                               "#ef6c00",
                                               "#4527a0",
                                               "#ad1457",
                                               "#00695c",
                                               "#283593",
                                               "#558b2f",
                                               "#5d4037"
                                           ],
                                        
                                           /*
                                            * Optionale Oberflächenfarben.
                                            * Ein leerer Wert verwendet die Farbe des jeweiligen Themes.
                                            */
                                           colors: {
                                               light: {
                                                   background: "",
                                                   surface: "",
                                                   surfaceSecondary: "",
                                                   text: "",
                                                   textSecondary: "",
                                                   border: "",
                                                   today: "",
                                                   weekend: ""
                                               },
                                               dark: {
                                                   background: "",
                                                   surface: "",
                                                   surfaceSecondary: "",
                                                   text: "",
                                                   textSecondary: "",
                                                   border: "",
                                                   today: "",
                                                   weekend: ""
                                               }
                                           },
                                        
                                           /* Beschriftungen */
                                           labels: {
                                               today: "Heute",
                                               month: "Monat",
                                               week: "Woche",
                                               day: "Tag",
                                               previous: "Zurück",
                                               next: "Weiter",
                                               allDay: "Ganztägig",
                                               noEvents: "Keine Termine",
                                               location: "Ort",
                                               calendarWeekShort: "KW",
                                               invalidDate: "Ungültiges Datum"
                                           }
                                        };
                                        
                                        
                                        /* ==========================================================================
                                          DATEN GLOBAL FÜR DIESES WIDGET BEREITSTELLEN
                                          ========================================================================== */
                                        
                                        const calendarGlobalStoreName = "__visJsontemplateCalendarWidgetStore";
                                        
                                        if (typeof window !== "undefined") {
                                           if (
                                               !window[calendarGlobalStoreName] ||
                                               typeof window[calendarGlobalStoreName] !== "object"
                                           ) {
                                               window[calendarGlobalStoreName] = Object.create(null);
                                           }
                                        
                                           /*
                                            * Einen bereits vorhandenen Zustand nicht überschreiben.
                                            * Bei einer Datenaktualisierung wird das Template komplett neu erzeugt,
                                            * die globale window-Variable bleibt jedoch erhalten.
                                            */
                                           const existingWidgetStore =
                                               window[calendarGlobalStoreName][widgetid] &&
                                               typeof window[calendarGlobalStoreName][widgetid] === "object"
                                                   ? window[calendarGlobalStoreName][widgetid]
                                                   : {};
                                        
                                           window[calendarGlobalStoreName][widgetid] = {
                                               data: data,
                                               config: calendarConfig,
                                        
                                               state:
                                                   existingWidgetStore.state &&
                                                   typeof existingWidgetStore.state === "object"
                                                       ? existingWidgetStore.state
                                                       : {
                                                           view: calendarConfig.defaultView,
                                                           currentDate: null
                                                       }
                                           };
                                        }
                                        %>
                                        
                                        <style>
                                        #<%- widgetid %>
                                        {
                                           --jscal-background-light: <%- calendarConfig.colors.light.background || "#f4f6f8" %>;
                                           --jscal-surface-light: <%- calendarConfig.colors.light.surface || "#ffffff" %>;
                                           --jscal-surface-secondary-light: <%- calendarConfig.colors.light.surfaceSecondary || "#f7f8fa" %>;
                                           --jscal-text-light: <%- calendarConfig.colors.light.text || "#17212b" %>;
                                           --jscal-text-secondary-light: <%- calendarConfig.colors.light.textSecondary || "#66717d" %>;
                                           --jscal-border-light: <%- calendarConfig.colors.light.border || "#d7dce2" %>;
                                           --jscal-today-light: <%- calendarConfig.colors.light.today || "#e3f2fd" %>;
                                           --jscal-weekend-light: <%- calendarConfig.colors.light.weekend || "#faf4f4" %>;
                                        
                                           --jscal-background-dark: <%- calendarConfig.colors.dark.background || "#12171d" %>;
                                           --jscal-surface-dark: <%- calendarConfig.colors.dark.surface || "#1c232b" %>;
                                           --jscal-surface-secondary-dark: <%- calendarConfig.colors.dark.surfaceSecondary || "#252e38" %>;
                                           --jscal-text-dark: <%- calendarConfig.colors.dark.text || "#f1f4f7" %>;
                                           --jscal-text-secondary-dark: <%- calendarConfig.colors.dark.textSecondary || "#aeb8c2" %>;
                                           --jscal-border-dark: <%- calendarConfig.colors.dark.border || "#3b4652" %>;
                                           --jscal-today-dark: <%- calendarConfig.colors.dark.today || "#173b55" %>;
                                           --jscal-weekend-dark: <%- calendarConfig.colors.dark.weekend || "#271f24" %>;
                                        
                                           --jscal-background: var(--jscal-background-light);
                                           --jscal-surface: var(--jscal-surface-light);
                                           --jscal-surface-secondary: var(--jscal-surface-secondary-light);
                                           --jscal-text: var(--jscal-text-light);
                                           --jscal-text-secondary: var(--jscal-text-secondary-light);
                                           --jscal-border: var(--jscal-border-light);
                                           --jscal-today: var(--jscal-today-light);
                                           --jscal-weekend: var(--jscal-weekend-light);
                                        
                                           box-sizing: border-box;
                                           width: 100%;
                                           height: 100%;
                                           min-height: 320px;
                                           overflow: hidden;
                                           color: var(--jscal-text);
                                           background: var(--jscal-background);
                                           font-family: Arial, Helvetica, sans-serif;
                                        }
                                        
                                        #<%- widgetid %> *,
                                        #<%- widgetid %> *::before,
                                        #<%- widgetid %> *::after
                                        {
                                           box-sizing: border-box;
                                        }
                                        
                                        #<%- widgetid %>[data-jscal-theme="dark"]
                                        {
                                           --jscal-background: var(--jscal-background-dark);
                                           --jscal-surface: var(--jscal-surface-dark);
                                           --jscal-surface-secondary: var(--jscal-surface-secondary-dark);
                                           --jscal-text: var(--jscal-text-dark);
                                           --jscal-text-secondary: var(--jscal-text-secondary-dark);
                                           --jscal-border: var(--jscal-border-dark);
                                           --jscal-today: var(--jscal-today-dark);
                                           --jscal-weekend: var(--jscal-weekend-dark);
                                        }
                                        
                                        @media (prefers-color-scheme: dark)
                                        {
                                           #<%- widgetid %>[data-jscal-theme="auto"]
                                           {
                                               --jscal-background: var(--jscal-background-dark);
                                               --jscal-surface: var(--jscal-surface-dark);
                                               --jscal-surface-secondary: var(--jscal-surface-secondary-dark);
                                               --jscal-text: var(--jscal-text-dark);
                                               --jscal-text-secondary: var(--jscal-text-secondary-dark);
                                               --jscal-border: var(--jscal-border-dark);
                                               --jscal-today: var(--jscal-today-dark);
                                               --jscal-weekend: var(--jscal-weekend-dark);
                                           }
                                        }
                                        
                                        #<%- widgetid %> .jscal
                                        {
                                           display: flex;
                                           flex-direction: column;
                                           width: 100%;
                                           height: 100%;
                                           min-height: 320px;
                                           background: var(--jscal-background);
                                        }
                                        
                                        #<%- widgetid %> .jscal-toolbar
                                        {
                                           display: flex;
                                           flex: 0 0 auto;
                                           align-items: center;
                                           gap: 8px;
                                           min-height: 58px;
                                           padding: 8px 10px;
                                           border-bottom: 1px solid var(--jscal-border);
                                           background: var(--jscal-surface);
                                        }
                                        
                                        #<%- widgetid %> .jscal-navigation,
                                        #<%- widgetid %> .jscal-view-buttons
                                        {
                                           display: flex;
                                           align-items: center;
                                           gap: 4px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-view-buttons
                                        {
                                           margin-left: auto;
                                        }
                                        
                                        #<%- widgetid %> .jscal-title
                                        {
                                           min-width: 0;
                                           margin: 0 8px;
                                           overflow: hidden;
                                           color: var(--jscal-text);
                                           font-size: 18px;
                                           font-weight: 700;
                                           line-height: 1.2;
                                           text-align: center;
                                           text-overflow: ellipsis;
                                           white-space: nowrap;
                                        }
                                        
                                        #<%- widgetid %> .jscal-button
                                        {
                                           display: inline-flex;
                                           align-items: center;
                                           justify-content: center;
                                           min-width: 36px;
                                           height: 36px;
                                           padding: 0 10px;
                                           border: 1px solid var(--jscal-border);
                                           border-radius: 7px;
                                           color: var(--jscal-text);
                                           background: var(--jscal-surface-secondary);
                                           font: inherit;
                                           font-size: 13px;
                                           font-weight: 600;
                                           line-height: 1;
                                           cursor: pointer;
                                           user-select: none;
                                           -webkit-tap-highlight-color: transparent;
                                        }
                                        
                                        #<%- widgetid %> .jscal-button:hover
                                        {
                                           filter: brightness(0.96);
                                        }
                                        
                                        #<%- widgetid %> .jscal-button:focus-visible
                                        {
                                           outline: 2px solid #1976d2;
                                           outline-offset: 2px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-button.is-active
                                        {
                                           border-color: #1976d2;
                                           color: #ffffff;
                                           background: #1976d2;
                                        }
                                        
                                        #<%- widgetid %> .jscal-button-icon
                                        {
                                           width: 36px;
                                           padding: 0;
                                        }
                                        
                                        #<%- widgetid %> .jscal-button svg
                                        {
                                           width: 20px;
                                           height: 20px;
                                           fill: currentColor;
                                        }
                                        
                                        #<%- widgetid %> .jscal-content
                                        {
                                           position: relative;
                                           flex: 1 1 auto;
                                           min-height: 0;
                                           overflow: auto;
                                           overscroll-behavior: contain;
                                           background: var(--jscal-background);
                                        }
                                        
                                        #<%- widgetid %> .jscal-month
                                        {
                                           display: grid;
                                           min-width: 640px;
                                           grid-template-columns: auto repeat(7, minmax(80px, 1fr));
                                           background: var(--jscal-border);
                                           gap: 1px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-month.no-week-numbers
                                        {
                                           grid-template-columns: repeat(7, minmax(80px, 1fr));
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-header
                                        {
                                           position: sticky;
                                           top: 0;
                                           z-index: 4;
                                           display: flex;
                                           align-items: center;
                                           justify-content: center;
                                           min-height: 36px;
                                           padding: 5px;
                                           color: var(--jscal-text-secondary);
                                           background: var(--jscal-surface-secondary);
                                           font-size: 12px;
                                           font-weight: 700;
                                           text-align: center;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-number
                                        {
                                           display: flex;
                                           align-items: center;
                                           justify-content: center;
                                           min-height: 112px;
                                           padding: 4px;
                                           color: var(--jscal-text-secondary);
                                           background: var(--jscal-surface-secondary);
                                           font-size: 11px;
                                           font-weight: 700;
                                        }
                                        
                                        #<%- widgetid %> .jscal-month-day
                                        {
                                           position: relative;
                                           min-height: 112px;
                                           padding: 5px;
                                           overflow: hidden;
                                           background: var(--jscal-surface);
                                        }
                                        
                                        #<%- widgetid %> .jscal-month-day.is-outside
                                        {
                                           opacity: 0.52;
                                        }
                                        
                                        #<%- widgetid %> .jscal-month-day.is-today
                                        {
                                           background: var(--jscal-today);
                                        }
                                        
                                        #<%- widgetid %> .jscal-month-day.is-weekend
                                        {
                                           background: var(--jscal-weekend);
                                        }
                                        
                                        #<%- widgetid %> .jscal-month-day.is-weekend.is-today
                                        {
                                           background: var(--jscal-today);
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-number
                                        {
                                           display: flex;
                                           align-items: center;
                                           justify-content: flex-end;
                                           height: 22px;
                                           margin-bottom: 3px;
                                           color: var(--jscal-text);
                                           font-size: 12px;
                                           font-weight: 700;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-number span
                                        {
                                           display: inline-flex;
                                           align-items: center;
                                           justify-content: center;
                                           min-width: 22px;
                                           height: 22px;
                                           padding: 0 4px;
                                           border-radius: 11px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-month-day.is-today .jscal-day-number span
                                        {
                                           color: #ffffff;
                                           background: #1976d2;
                                        }
                                        
                                        #<%- widgetid %> .jscal-events
                                        {
                                           display: flex;
                                           flex-direction: column;
                                           gap: 3px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-event
                                        {
                                           position: relative;
                                           display: flex;
                                           align-items: center;
                                           gap: 5px;
                                           min-width: 0;
                                           min-height: 24px;
                                           padding: 3px 6px;
                                           border: 0;
                                           border-left: 4px solid var(--jscal-event-color);
                                           border-radius: 4px;
                                           color: #ffffff;
                                           background: var(--jscal-event-color);
                                           box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.07);
                                           font: inherit;
                                           font-size: 11px;
                                           line-height: 1.25;
                                           text-align: left;
                                           cursor: pointer;
                                           -webkit-tap-highlight-color: transparent;
                                        }
                                        
                                        #<%- widgetid %> .jscal-event:hover,
                                        #<%- widgetid %> .jscal-event:focus-visible,
                                        #<%- widgetid %> .jscal-event.is-selected
                                        {
                                           filter: brightness(1.12);
                                           outline: none;
                                        }
                                        
                                        #<%- widgetid %> .jscal-event-image
                                        {
                                           flex: 0 0 auto;
                                           width: 17px;
                                           height: 17px;
                                           border-radius: 3px;
                                           object-fit: contain;
                                           background: rgba(255, 255, 255, 0.82);
                                        }
                                        
                                        #<%- widgetid %> .jscal-event-time
                                        {
                                           flex: 0 0 auto;
                                           font-weight: 700;
                                           white-space: nowrap;
                                        }
                                        
                                        #<%- widgetid %> .jscal-event-title
                                        {
                                           min-width: 0;
                                           overflow: hidden;
                                           text-overflow: ellipsis;
                                           white-space: nowrap;
                                        }
                                        
                                        #<%- widgetid %> .jscal-more
                                        {
                                           padding: 2px 5px;
                                           color: var(--jscal-text-secondary);
                                           font-size: 11px;
                                           font-weight: 700;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week
                                        {
                                           display: grid;
                                           min-width: 720px;
                                           min-height: 100%;
                                           grid-template-columns: repeat(7, minmax(100px, 1fr));
                                           gap: 1px;
                                           background: var(--jscal-border);
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-day
                                        {
                                           display: flex;
                                           flex-direction: column;
                                        
                                           min-height: 100%;
                                           overflow: hidden;
                                        
                                           background: var(--jscal-surface);
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-day.is-weekend
                                        {
                                           background: var(--jscal-weekend);
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-day.is-today
                                        {
                                           background: var(--jscal-today);
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-day-header
                                        {
                                           position: sticky;
                                           top: 0;
                                           z-index: 3;
                                           display: flex;
                                           flex-direction: column;
                                           align-items: center;
                                           gap: 2px;
                                           min-height: 55px;
                                           padding: 7px 4px;
                                           border-bottom: 1px solid var(--jscal-border);
                                           background: inherit;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-day-name
                                        {
                                           color: var(--jscal-text-secondary);
                                           font-size: 11px;
                                           font-weight: 700;
                                           text-transform: uppercase;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-day-date
                                        {
                                           display: inline-flex;
                                           align-items: center;
                                           justify-content: center;
                                           min-width: 30px;
                                           height: 30px;
                                           padding: 0 5px;
                                           border-radius: 15px;
                                           color: var(--jscal-text);
                                           font-size: 16px;
                                           font-weight: 700;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-day.is-today .jscal-week-day-date
                                        {
                                           color: #ffffff;
                                           background: #1976d2;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week-events
                                        {
                                           flex: 1;
                                           overflow: hidden;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week .jscal-event
                                        {
                                           display: flex;
                                           align-items: flex-start;
                                           gap: 6px;
                                        
                                           min-height: 36px;
                                           max-height: 54px;
                                        
                                           padding: 6px;
                                        
                                           overflow: hidden;
                                        }
                                        
                                        #<%- widgetid %> .jscal-week .jscal-event-title
                                        {
                                           flex: 1;
                                           min-width: 0;
                                        
                                           overflow: hidden;
                                           text-overflow: ellipsis;
                                        
                                           display: -webkit-box;
                                           -webkit-box-orient: vertical;
                                           -webkit-line-clamp: 2;
                                        
                                           line-height: 1.2;
                                           white-space: normal;
                                        
                                           overflow-wrap: anywhere;
                                           word-break: break-word;
                                        }
                                        
                                        #<%- widgetid %> .jscal-empty
                                        {
                                           padding: 14px 6px;
                                           color: var(--jscal-text-secondary);
                                           font-size: 12px;
                                           text-align: center;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-view
                                        {
                                           min-height: 100%;
                                           padding: 12px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-section
                                        {
                                           margin-bottom: 16px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-section-title
                                        {
                                           margin: 0 0 7px;
                                           padding: 0 2px 6px;
                                           border-bottom: 1px solid var(--jscal-border);
                                           color: var(--jscal-text-secondary);
                                           font-size: 12px;
                                           font-weight: 700;
                                           text-transform: uppercase;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event
                                        {
                                           display: grid;
                                           grid-template-columns: 70px minmax(0, 1fr);
                                           margin-bottom: 8px;
                                           border: 1px solid var(--jscal-border);
                                           border-left: 5px solid var(--jscal-event-color);
                                           border-radius: 7px;
                                           overflow: hidden;
                                           background: var(--jscal-surface);
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event-time
                                        {
                                           display: flex;
                                           align-items: flex-start;
                                           justify-content: center;
                                           padding: 11px 6px;
                                           color: var(--jscal-text-secondary);
                                           background: var(--jscal-surface-secondary);
                                           font-size: 12px;
                                           font-weight: 700;
                                           text-align: center;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event-content
                                        {
                                           display: flex;
                                           align-items: flex-start;
                                           gap: 9px;
                                           min-width: 0;
                                           padding: 9px;
                                           border: 0;
                                           color: var(--jscal-text);
                                           background: transparent;
                                           font: inherit;
                                           text-align: left;
                                           cursor: pointer;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event-content:hover,
                                        #<%- widgetid %> .jscal-day-event-content:focus-visible
                                        {
                                           background: var(--jscal-surface-secondary);
                                           outline: none;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event-image
                                        {
                                           flex: 0 0 auto;
                                           width: 34px;
                                           height: 34px;
                                           border-radius: 5px;
                                           object-fit: contain;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event-text
                                        {
                                           min-width: 0;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event-title
                                        {
                                           color: var(--jscal-text);
                                           font-size: 14px;
                                           font-weight: 700;
                                           line-height: 1.35;
                                        }
                                        
                                        #<%- widgetid %> .jscal-day-event-location
                                        {
                                           margin-top: 4px;
                                           color: var(--jscal-text-secondary);
                                           font-size: 12px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip
                                        {
                                           position: fixed;
                                           z-index: 2147483000;
                                           display: none;
                                           width: min(320px, calc(100vw - 20px));
                                           max-height: min(360px, calc(100vh - 20px));
                                           padding: 12px;
                                           overflow: auto;
                                           border: 1px solid var(--jscal-border);
                                           border-radius: 9px;
                                           color: var(--jscal-text);
                                           background: var(--jscal-surface);
                                           box-shadow: 0 8px 28px rgba(0, 0, 0, 0.28);
                                           pointer-events: none;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip.is-visible
                                        {
                                           display: block;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip-header
                                        {
                                           display: flex;
                                           align-items: flex-start;
                                           gap: 9px;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip-image
                                        {
                                           flex: 0 0 auto;
                                           width: 42px;
                                           height: 42px;
                                           border-radius: 6px;
                                           object-fit: contain;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip-title
                                        {
                                           min-width: 0;
                                           color: var(--jscal-text);
                                           font-size: 14px;
                                           font-weight: 700;
                                           line-height: 1.35;
                                           overflow-wrap: anywhere;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip-row
                                        {
                                           display: grid;
                                           grid-template-columns: 62px minmax(0, 1fr);
                                           gap: 8px;
                                           margin-top: 10px;
                                           color: var(--jscal-text);
                                           font-size: 12px;
                                           line-height: 1.4;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip-label
                                        {
                                           color: var(--jscal-text-secondary);
                                           font-weight: 700;
                                        }
                                        
                                        #<%- widgetid %> .jscal-tooltip-value
                                        {
                                           overflow-wrap: anywhere;
                                        }
                                        
                                        #<%- widgetid %> .jscal-invalid
                                        {
                                           margin: 6px;
                                           padding: 7px;
                                           border: 1px solid var(--jscal-border);
                                           border-left: 4px solid #c62828;
                                           border-radius: 5px;
                                           color: var(--jscal-text);
                                           background: var(--jscal-surface);
                                           font-size: 12px;
                                        }
                                        
                                        @media (max-width: 700px)
                                        {
                                           #<%- widgetid %> .jscal-toolbar
                                           {
                                               flex-wrap: wrap;
                                               gap: 6px;
                                           }
                                        
                                           #<%- widgetid %> .jscal-title
                                           {
                                               order: -1;
                                               flex: 1 0 100%;
                                               width: 100%;
                                               margin: 0;
                                               padding: 3px;
                                               font-size: 16px;
                                           }
                                        
                                           #<%- widgetid %> .jscal-view-buttons
                                           {
                                               margin-left: auto;
                                           }
                                        
                                           #<%- widgetid %> .jscal-button
                                           {
                                               min-width: 34px;
                                               height: 34px;
                                               padding: 0 8px;
                                               font-size: 12px;
                                           }
                                        
                                           #<%- widgetid %> .jscal-month-day
                                           {
                                               min-height: 96px;
                                           }
                                        }
                                        </style>
                                        
                                        <div class="jscal" data-jscal-container>
                                           <div class="jscal-toolbar">
                                               <div class="jscal-navigation">
                                                   <button
                                                       type="button"
                                                       class="jscal-button jscal-button-icon"
                                                       data-jscal-action="previous"
                                                       aria-label="<%- calendarConfig.labels.previous %>"
                                                       title="<%- calendarConfig.labels.previous %>"
                                                   >
                                                       <svg viewBox="0 0 24 24" aria-hidden="true">
                                                           <path d="M15.4 5.4 8.8 12l6.6 6.6-1.4 1.4L6 12l8-8z"></path>
                                                       </svg>
                                                   </button>
                                        
                                                   <button
                                                       type="button"
                                                       class="jscal-button"
                                                       data-jscal-action="today"
                                                   >
                                                       <%- calendarConfig.labels.today %>
                                                   </button>
                                        
                                                   <button
                                                       type="button"
                                                       class="jscal-button jscal-button-icon"
                                                       data-jscal-action="next"
                                                       aria-label="<%- calendarConfig.labels.next %>"
                                                       title="<%- calendarConfig.labels.next %>"
                                                   >
                                                       <svg viewBox="0 0 24 24" aria-hidden="true">
                                                           <path d="M8.6 18.6 15.2 12 8.6 5.4 10 4l8 8-8 8z"></path>
                                                       </svg>
                                                   </button>
                                               </div>
                                        
                                               <div class="jscal-title" data-jscal-title></div>
                                        
                                               <div class="jscal-view-buttons">
                                                   <button
                                                       type="button"
                                                       class="jscal-button"
                                                       data-jscal-view="month"
                                                   >
                                                       <%- calendarConfig.labels.month %>
                                                   </button>
                                        
                                                   <button
                                                       type="button"
                                                       class="jscal-button"
                                                       data-jscal-view="week"
                                                   >
                                                       <%- calendarConfig.labels.week %>
                                                   </button>
                                        
                                                   <button
                                                       type="button"
                                                       class="jscal-button"
                                                       data-jscal-view="day"
                                                   >
                                                       <%- calendarConfig.labels.day %>
                                                   </button>
                                               </div>
                                           </div>
                                        
                                           <div class="jscal-content" data-jscal-content></div>
                                        
                                           <div
                                               class="jscal-tooltip"
                                               data-jscal-tooltip
                                               role="tooltip"
                                           ></div>
                                        </div>
                                        
                                        <script>
                                        (function ()
                                        {
                                           "use strict";
                                           const widgetId = "<%- widgetid %>";
                                           const globalStoreName = "__visJsontemplateCalendarWidgetStore";
                                        
                                           const widgetStore =
                                           	window[globalStoreName] &&
                                           	window[globalStoreName][widgetId]
                                           		? window[globalStoreName][widgetId]
                                           		: null;
                                        
                                           if (!widgetStore)
                                           {
                                           	console.warn(
                                           		"JSON-Template-Kalender: Keine globalen Daten für Widget gefunden:",
                                           		widgetId
                                           	);
                                        
                                           	return;
                                           }
                                        
                                           const rawData = widgetStore.data;
                                           const config = widgetStore.config;
                                        
                                           const root = document.getElementById(widgetId);
                                        
                                           if (!root)
                                           {
                                               return;
                                           }
                                        
                                           if (typeof root.__jsonCalendarCleanup === "function")
                                           {
                                               root.__jsonCalendarCleanup();
                                           }
                                        
                                           root.setAttribute("data-jscal-theme", config.theme || "auto");
                                        
                                           const container = root.querySelector("[data-jscal-container]");
                                           const contentElement = root.querySelector("[data-jscal-content]");
                                           const titleElement = root.querySelector("[data-jscal-title]");
                                           const tooltipElement = root.querySelector("[data-jscal-tooltip]");
                                        
                                           if (!container || !contentElement || !titleElement || !tooltipElement)
                                           {
                                               return;
                                           }
                                        
                                           const storedState =
                                           widgetStore.state &&
                                           typeof widgetStore.state === "object"
                                               ? widgetStore.state
                                               : {};
                                        
                                           const storedView = ["month", "week", "day"].includes(storedState.view)
                                           	? storedState.view
                                           	: null;
                                        
                                           const storedCurrentDate =
                                           	typeof storedState.currentDate === "string"
                                           		? parseStoredDate(storedState.currentDate)
                                           		: null;
                                        
                                           const state = {
                                           	view:
                                           		storedView ||
                                           		(
                                           			["month", "week", "day"].includes(config.defaultView)
                                           				? config.defaultView
                                           				: "month"
                                           		),
                                        
                                           	currentDate:
                                           		storedCurrentDate ||
                                           		startOfDay(new Date()),
                                        
                                           	selectedEventElement: null,
                                           	tooltipEventId: null
                                           };
                                        
                                           const formatterCache = {};
                                           const groupColorMap = new Map();
                                        
                                           const events = Array.isArray(rawData)
                                               ? rawData
                                                   .map(function (item, index)
                                                   {
                                                       return normalizeEvent(item, index);
                                                   })
                                                   .filter(function (item)
                                                   {
                                                       return item !== null;
                                                   })
                                               : [];
                                        
                                           const invalidItems = Array.isArray(rawData)
                                               ? rawData
                                                   .map(function (item, index)
                                                   {
                                                       return {
                                                           item: item,
                                                           index: index,
                                                           parsed: normalizeEvent(item, index)
                                                       };
                                                   })
                                                   .filter(function (entry)
                                                   {
                                                       return entry.parsed === null;
                                                   })
                                               : [];
                                        
                                           function pad(value)
                                           {
                                               return String(value).padStart(2, "0");
                                           }
                                        
                                           function escapeHtml(value)
                                           {
                                               return String(value === undefined || value === null ? "" : value)
                                                   .replace(/&/g, "&amp;")
                                                   .replace(/</g, "&lt;")
                                                   .replace(/>/g, "&gt;")
                                                   .replace(/"/g, "&quot;")
                                                   .replace(/'/g, "&#039;");
                                           }
                                        
                                           function escapeAttribute(value)
                                           {
                                               return escapeHtml(value);
                                           }
                                        
                                           function createLocalDate(year, month, day, hour, minute)
                                           {
                                               const date = new Date(
                                                   year,
                                                   month - 1,
                                                   day,
                                                   hour || 0,
                                                   minute || 0,
                                                   0,
                                                   0
                                               );
                                        
                                               if (
                                                   date.getFullYear() !== year ||
                                                   date.getMonth() !== month - 1 ||
                                                   date.getDate() !== day
                                               )
                                               {
                                                   return null;
                                               }
                                        
                                               return date;
                                           }
                                        
                                           function parseStoredDate(value)
                                           {
                                           	if (typeof value !== "string")
                                           	{
                                           		return null;
                                           	}
                                        
                                           	const match = value.match(/^(\d{4})-(\d{2})-(\d{2})$/);
                                        
                                           	if (!match)
                                           	{
                                           		return null;
                                           	}
                                        
                                           	return createLocalDate(
                                           		Number(match[1]),
                                           		Number(match[2]),
                                           		Number(match[3]),
                                           		0,
                                           		0
                                           	);
                                           }
                                        
                                        
                                           function parseDateTimeText(value)
                                           {
                                               if (typeof value !== "string")
                                               {
                                                   return null;
                                               }
                                        
                                               const normalized = value
                                                   .trim()
                                                   .replace(/\s+/g, " ");
                                        
                                               const separatorMatch = normalized.match(/\s+-\s+/);
                                        
                                               if (!separatorMatch)
                                               {
                                                   return null;
                                               }
                                        
                                               const separatorIndex = separatorMatch.index;
                                               const separatorLength = separatorMatch[0].length;
                                               const startText = normalized.slice(0, separatorIndex).trim();
                                               const endText = normalized
                                                   .slice(separatorIndex + separatorLength)
                                                   .trim();
                                        
                                               const fullDatePattern = /^(\d{1,2})\.(\d{1,2})\.(\d{4})(?:\s+(\d{1,2}):(\d{2}))?$/;
                                               const timeOnlyPattern = /^(\d{1,2}):(\d{2})$/;
                                        
                                               const startMatch = startText.match(fullDatePattern);
                                        
                                               if (!startMatch)
                                               {
                                                   return null;
                                               }
                                        
                                               const startDay = Number(startMatch[1]);
                                               const startMonth = Number(startMatch[2]);
                                               const startYear = Number(startMatch[3]);
                                               const startHasTime = startMatch[4] !== undefined;
                                               const startHour = startHasTime ? Number(startMatch[4]) : 0;
                                               const startMinute = startHasTime ? Number(startMatch[5]) : 0;
                                        
                                               if (
                                                   startHour < 0 ||
                                                   startHour > 23 ||
                                                   startMinute < 0 ||
                                                   startMinute > 59
                                               )
                                               {
                                                   return null;
                                               }
                                        
                                               const start = createLocalDate(
                                                   startYear,
                                                   startMonth,
                                                   startDay,
                                                   startHour,
                                                   startMinute
                                               );
                                        
                                               if (!start)
                                               {
                                                   return null;
                                               }
                                        
                                               let end = null;
                                               let endHasTime = false;
                                        
                                               const endFullDateMatch = endText.match(fullDatePattern);
                                               const endTimeOnlyMatch = endText.match(timeOnlyPattern);
                                        
                                               if (endFullDateMatch)
                                               {
                                                   const endDay = Number(endFullDateMatch[1]);
                                                   const endMonth = Number(endFullDateMatch[2]);
                                                   const endYear = Number(endFullDateMatch[3]);
                                        
                                                   endHasTime = endFullDateMatch[4] !== undefined;
                                        
                                                   const endHour = endHasTime ? Number(endFullDateMatch[4]) : 0;
                                                   const endMinute = endHasTime ? Number(endFullDateMatch[5]) : 0;
                                        
                                                   if (
                                                       endHour < 0 ||
                                                       endHour > 23 ||
                                                       endMinute < 0 ||
                                                       endMinute > 59
                                                   )
                                                   {
                                                       return null;
                                                   }
                                        
                                                   end = createLocalDate(
                                                       endYear,
                                                       endMonth,
                                                       endDay,
                                                       endHour,
                                                       endMinute
                                                   );
                                               } else if (endTimeOnlyMatch)
                                               {
                                                   endHasTime = true;
                                        
                                                   const endHour = Number(endTimeOnlyMatch[1]);
                                                   const endMinute = Number(endTimeOnlyMatch[2]);
                                        
                                                   if (
                                                       endHour < 0 ||
                                                       endHour > 23 ||
                                                       endMinute < 0 ||
                                                       endMinute > 59
                                                   )
                                                   {
                                                       return null;
                                                   }
                                        
                                                   end = createLocalDate(
                                                       startYear,
                                                       startMonth,
                                                       startDay,
                                                       endHour,
                                                       endMinute
                                                   );
                                               }
                                        
                                               if (!end)
                                               {
                                                   return null;
                                               }
                                        
                                               const allDay = !startHasTime && !endHasTime;
                                        
                                               if (allDay)
                                               {
                                                   end.setHours(23, 59, 59, 999);
                                               }
                                        
                                               if (end.getTime() < start.getTime())
                                               {
                                                   return null;
                                               }
                                        
                                               return {
                                                   start: start,
                                                   end: end,
                                                   allDay: allDay,
                                                   sourceText: normalized
                                               };
                                           }
                                        
                                           function normalizeEvent(item, index)
                                           {
                                               if (!item || typeof item !== "object")
                                               {
                                                   return null;
                                               }
                                        
                                               const parsedDate = parseDateTimeText(item.zeit);
                                        
                                               if (!parsedDate)
                                               {
                                                   return null;
                                               }
                                        
                                               const event = {
                                                   id: String(index),
                                                   img: typeof item.img === "string"
                                                       ? item.img.trim()
                                                       : "",
                                                   zeit: typeof item.zeit === "string"
                                                       ? item.zeit.trim()
                                                       : "",
                                                   ereignis: typeof item.ereignis === "string"
                                                       ? item.ereignis.trim()
                                                       : "",
                                                   ort: typeof item.ort === "string"
                                                       ? item.ort.trim()
                                                       : "",
                                                   start: parsedDate.start,
                                                   end: parsedDate.end,
                                                   allDay: parsedDate.allDay
                                               };
                                        
                                               event.color = getEventColor(event);
                                        
                                               return event;
                                           }
                                        
                                           function getGroupingKey(event)
                                           {
                                               if (config.colorGrouping === "eventPrefix")
                                               {
                                                   const eventText = event.ereignis || "";
                                                   const separatorPosition = eventText.indexOf(":");
                                        
                                                   if (separatorPosition >= 0)
                                                   {
                                                       return eventText.slice(0, separatorPosition).trim().toLowerCase();
                                                   }
                                        
                                                   return eventText.trim().toLowerCase();
                                               }
                                        
                                               return (event.img || event.ereignis || event.id).toLowerCase();
                                           }
                                        
                                           function getEventColor(event)
                                           {
                                               if (Array.isArray(config.colorRules))
                                               {
                                                   for (const rule of config.colorRules)
                                                   {
                                                       if (
                                                           !rule ||
                                                           !["ereignis", "img"].includes(rule.matchField) ||
                                                           typeof rule.match !== "string" ||
                                                           typeof rule.color !== "string"
                                                       )
                                                       {
                                                           continue;
                                                       }
                                        
                                                       const fieldValue = String(event[rule.matchField] || "");
                                        
                                                       if (
                                                           fieldValue
                                                               .toLocaleLowerCase(config.locale)
                                                               .includes(rule.match.toLocaleLowerCase(config.locale))
                                                       )
                                                       {
                                                           return rule.color;
                                                       }
                                                   }
                                               }
                                        
                                               const groupKey = getGroupingKey(event);
                                        
                                               if (groupColorMap.has(groupKey))
                                               {
                                                   return groupColorMap.get(groupKey);
                                               }
                                        
                                               const palette = Array.isArray(config.autoColors) && config.autoColors.length > 0
                                                   ? config.autoColors
                                                   : ["#1565c0"];
                                        
                                               const color = palette[groupColorMap.size % palette.length];
                                        
                                               groupColorMap.set(groupKey, color);
                                        
                                               return color;
                                           }
                                        
                                           function startOfDay(date)
                                           {
                                               return new Date(
                                                   date.getFullYear(),
                                                   date.getMonth(),
                                                   date.getDate(),
                                                   0,
                                                   0,
                                                   0,
                                                   0
                                               );
                                           }
                                        
                                           function endOfDay(date)
                                           {
                                               return new Date(
                                                   date.getFullYear(),
                                                   date.getMonth(),
                                                   date.getDate(),
                                                   23,
                                                   59,
                                                   59,
                                                   999
                                               );
                                           }
                                        
                                           function addDays(date, amount)
                                           {
                                               const result = new Date(date);
                                               result.setDate(result.getDate() + amount);
                                               return result;
                                           }
                                        
                                           function addMonths(date, amount)
                                           {
                                               const targetDay = date.getDate();
                                               const result = new Date(
                                                   date.getFullYear(),
                                                   date.getMonth() + amount,
                                                   1
                                               );
                                        
                                               const lastDay = new Date(
                                                   result.getFullYear(),
                                                   result.getMonth() + 1,
                                                   0
                                               ).getDate();
                                        
                                               result.setDate(Math.min(targetDay, lastDay));
                                        
                                               return result;
                                           }
                                        
                                           function startOfWeek(date)
                                           {
                                               const result = startOfDay(date);
                                               const currentWeekday = result.getDay();
                                               const firstDay = Number(config.firstDayOfWeek) === 0 ? 0 : 1;
                                               const difference = (currentWeekday - firstDay + 7) % 7;
                                        
                                               result.setDate(result.getDate() - difference);
                                        
                                               return result;
                                           }
                                        
                                           function sameDay(first, second)
                                           {
                                               return (
                                                   first.getFullYear() === second.getFullYear() &&
                                                   first.getMonth() === second.getMonth() &&
                                                   first.getDate() === second.getDate()
                                               );
                                           }
                                        
                                           function dateKey(date)
                                           {
                                               return [
                                                   date.getFullYear(),
                                                   pad(date.getMonth() + 1),
                                                   pad(date.getDate())
                                               ].join("-");
                                           }
                                        
                                           function saveState()
                                           {
                                           	if (
                                           		!window[globalStoreName] ||
                                           		!window[globalStoreName][widgetId]
                                           	)
                                           	{
                                           		return;
                                           	}
                                        
                                           	window[globalStoreName][widgetId].state = {
                                           		view: state.view,
                                           		currentDate: dateKey(state.currentDate)
                                           	};
                                           }
                                        
                                        
                                           function getFormatter(options)
                                           {
                                               const key = JSON.stringify(options);
                                        
                                               if (!formatterCache[key])
                                               {
                                                   formatterCache[key] = new Intl.DateTimeFormat(
                                                       config.locale || "de-DE",
                                                       options
                                                   );
                                               }
                                        
                                               return formatterCache[key];
                                           }
                                        
                                           function formatDate(date, options)
                                           {
                                               return getFormatter(options).format(date);
                                           }
                                        
                                           function formatTime(date)
                                           {
                                               return formatDate(date, {
                                                   hour: "2-digit",
                                                   minute: "2-digit"
                                               });
                                           }
                                        
                                           function formatEventTime(event)
                                           {
                                               if (event.allDay)
                                               {
                                                   if (sameDay(event.start, event.end))
                                                   {
                                                       return config.labels.allDay;
                                                   }
                                        
                                                   return [
                                                       formatDate(event.start, {
                                                           day: "2-digit",
                                                           month: "2-digit",
                                                           year: "numeric"
                                                       }),
                                                       "–",
                                                       formatDate(event.end, {
                                                           day: "2-digit",
                                                           month: "2-digit",
                                                           year: "numeric"
                                                       })
                                                   ].join(" ");
                                               }
                                        
                                               if (sameDay(event.start, event.end))
                                               {
                                                   return formatTime(event.start) + "–" + formatTime(event.end);
                                               }
                                        
                                               return [
                                                   formatDate(event.start, {
                                                       day: "2-digit",
                                                       month: "2-digit",
                                                       year: "numeric",
                                                       hour: "2-digit",
                                                       minute: "2-digit"
                                                   }),
                                                   "–",
                                                   formatDate(event.end, {
                                                       day: "2-digit",
                                                       month: "2-digit",
                                                       year: "numeric",
                                                       hour: "2-digit",
                                                       minute: "2-digit"
                                                   })
                                               ].join(" ");
                                           }
                                        
                                           function getIsoWeekNumber(date)
                                           {
                                               const target = new Date(
                                                   Date.UTC(
                                                       date.getFullYear(),
                                                       date.getMonth(),
                                                       date.getDate()
                                                   )
                                               );
                                        
                                               const weekday = target.getUTCDay() || 7;
                                        
                                               target.setUTCDate(target.getUTCDate() + 4 - weekday);
                                        
                                               const yearStart = new Date(
                                                   Date.UTC(target.getUTCFullYear(), 0, 1)
                                               );
                                        
                                               return Math.ceil(
                                                   (((target - yearStart) / 86400000) + 1) / 7
                                               );
                                           }
                                        
                                           function eventOccursOnDay(event, day)
                                           {
                                               const dayStart = startOfDay(day);
                                               const dayEnd = endOfDay(day);
                                        
                                               return (
                                                   event.start.getTime() <= dayEnd.getTime() &&
                                                   event.end.getTime() >= dayStart.getTime()
                                               );
                                           }
                                        
                                           function getEventsForDay(day)
                                           {
                                               return events
                                                   .filter(function (event)
                                                   {
                                                       return eventOccursOnDay(event, day);
                                                   })
                                                   .sort(function (first, second)
                                                   {
                                                       if (first.allDay !== second.allDay)
                                                       {
                                                           return first.allDay ? -1 : 1;
                                                       }
                                        
                                                       const timeDifference =
                                                           first.start.getTime() - second.start.getTime();
                                        
                                                       if (timeDifference !== 0)
                                                       {
                                                           return timeDifference;
                                                       }
                                        
                                                       return first.ereignis.localeCompare(
                                                           second.ereignis,
                                                           config.locale || "de-DE"
                                                       );
                                                   });
                                           }
                                        
                                           function isWeekend(date)
                                           {
                                               return date.getDay() === 0 || date.getDay() === 6;
                                           }
                                        
                                           function renderSmallEvent(event, options)
                                           {
                                               const settings = options || {};
                                               const showTime = settings.showTime !== false;
                                               const imageHtml =
                                                   config.showImages &&
                                                   event.img
                                                       ? '<img class="jscal-event-image" src="' +
                                                           escapeAttribute(event.img) +
                                                           '" alt="">'
                                                       : "";
                                        
                                               let timeHtml = "";
                                        
                                               if (showTime)
                                               {
                                                   timeHtml = '<span class="jscal-event-time">' +
                                                       escapeHtml(
                                                           event.allDay
                                                               ? ""
                                                               : formatTime(event.start)
                                                       ) +
                                                       "</span>";
                                               }
                                        
                                               return [
                                                   '<button type="button"',
                                                   ' class="jscal-event"',
                                                   ' data-jscal-event-id="',
                                                   escapeAttribute(event.id),
                                                   '"',
                                                   ' style="--jscal-event-color:',
                                                   escapeAttribute(event.color),
                                                   ';"',
                                                   ' aria-label="',
                                                   escapeAttribute(event.ereignis),
                                                   '">',
                                                   imageHtml,
                                                   timeHtml,
                                                   '<span class="jscal-event-title">',
                                                   escapeHtml(event.ereignis || ""),
                                                   "</span>",
                                                   "</button>"
                                               ].join("");
                                           }
                                        
                                           function renderMonth()
                                           {
                                               const monthStart = new Date(
                                                   state.currentDate.getFullYear(),
                                                   state.currentDate.getMonth(),
                                                   1
                                               );
                                        
                                               const monthEnd = new Date(
                                                   state.currentDate.getFullYear(),
                                                   state.currentDate.getMonth() + 1,
                                                   0
                                               );
                                        
                                               const gridStart = startOfWeek(monthStart);
                                               let gridEnd = addDays(startOfWeek(monthEnd), 6);
                                        
                                               if (gridEnd.getTime() < monthEnd.getTime())
                                               {
                                                   gridEnd = addDays(gridEnd, 7);
                                               }
                                        
                                               const dayHeaders = [];
                                               const headerReference = startOfWeek(new Date(2026, 0, 5));
                                        
                                               if (config.showWeekNumbers)
                                               {
                                                   dayHeaders.push(
                                                       '<div class="jscal-week-header">' +
                                                       escapeHtml(config.labels.calendarWeekShort) +
                                                       "</div>"
                                                   );
                                               }
                                        
                                               for (let index = 0; index < 7; index++)
                                               {
                                                   const headerDate = addDays(headerReference, index);
                                        
                                                   dayHeaders.push(
                                                       '<div class="jscal-week-header">' +
                                                       escapeHtml(
                                                           formatDate(headerDate, {
                                                               weekday: "short"
                                                           })
                                                       ) +
                                                       "</div>"
                                                   );
                                               }
                                        
                                               const cells = [];
                                               let cursor = new Date(gridStart);
                                               let rowIndex = 0;
                                        
                                               while (cursor.getTime() <= gridEnd.getTime())
                                               {
                                                   if (config.showWeekNumbers && rowIndex % 7 === 0)
                                                   {
                                                       cells.push(
                                                           '<div class="jscal-week-number">' +
                                                           escapeHtml(
                                                               config.labels.calendarWeekShort +
                                                               " " +
                                                               getIsoWeekNumber(cursor)
                                                           ) +
                                                           "</div>"
                                                       );
                                                   }
                                        
                                                   const currentDay = new Date(cursor);
                                                   const currentEvents = getEventsForDay(currentDay);
                                                   const visibleEvents = currentEvents.slice(0, 4);
                                                   const remainingCount = currentEvents.length - visibleEvents.length;
                                        
                                                   const classes = ["jscal-month-day"];
                                        
                                                   if (currentDay.getMonth() !== state.currentDate.getMonth())
                                                   {
                                                       classes.push("is-outside");
                                                   }
                                        
                                                   if (sameDay(currentDay, new Date()))
                                                   {
                                                       classes.push("is-today");
                                                   }
                                        
                                                   if (config.highlightWeekend && isWeekend(currentDay))
                                                   {
                                                       classes.push("is-weekend");
                                                   }
                                        
                                                   let eventHtml = visibleEvents
                                                       .map(function (event)
                                                       {
                                                           return renderSmallEvent(event, {
                                                               showTime: true
                                                           });
                                                       })
                                                       .join("");
                                        
                                                   if (remainingCount > 0)
                                                   {
                                                       eventHtml +=
                                                           '<div class="jscal-more">+' +
                                                           escapeHtml(remainingCount) +
                                                           "</div>";
                                                   }
                                        
                                                   cells.push(
                                                       '<div class="' +
                                                       classes.join(" ") +
                                                       '" data-jscal-date="' +
                                                       escapeAttribute(dateKey(currentDay)) +
                                                       '">' +
                                                       '<div class="jscal-day-number"><span>' +
                                                       escapeHtml(currentDay.getDate()) +
                                                       "</span></div>" +
                                                       '<div class="jscal-events">' +
                                                       eventHtml +
                                                       "</div>" +
                                                       "</div>"
                                                   );
                                        
                                                   cursor = addDays(cursor, 1);
                                                   rowIndex++;
                                               }
                                        
                                               const className = config.showWeekNumbers
                                                   ? "jscal-month"
                                                   : "jscal-month no-week-numbers";
                                        
                                               titleElement.textContent = formatDate(state.currentDate, {
                                                   month: "long",
                                                   year: "numeric"
                                               });
                                        
                                               contentElement.innerHTML =
                                                   '<div class="' +
                                                   className +
                                                   '">' +
                                                   dayHeaders.join("") +
                                                   cells.join("") +
                                                   "</div>";
                                           }
                                        
                                           function renderWeek()
                                           {
                                               const weekStart = startOfWeek(state.currentDate);
                                               const weekEnd = addDays(weekStart, 6);
                                               const columns = [];
                                        
                                               for (let index = 0; index < 7; index++)
                                               {
                                                   const day = addDays(weekStart, index);
                                                   const dayEvents = getEventsForDay(day);
                                                   const classes = ["jscal-week-day"];
                                        
                                                   if (sameDay(day, new Date()))
                                                   {
                                                       classes.push("is-today");
                                                   }
                                        
                                                   if (config.highlightWeekend && isWeekend(day))
                                                   {
                                                       classes.push("is-weekend");
                                                   }
                                        
                                                   let eventsHtml = "";
                                        
                                                   if (dayEvents.length === 0)
                                                   {
                                                       eventsHtml =
                                                           '<div class="jscal-empty">' +
                                                           escapeHtml(config.labels.noEvents) +
                                                           "</div>";
                                                   } else
                                                   {
                                                       eventsHtml = dayEvents
                                                           .map(function (event)
                                                           {
                                                               return renderSmallEvent(event, {
                                                                   showTime: true
                                                               });
                                                           })
                                                           .join("");
                                                   }
                                        
                                                   columns.push(
                                                       '<section class="' +
                                                       classes.join(" ") +
                                                       '" data-jscal-date="' +
                                                       escapeAttribute(dateKey(day)) +
                                                       '">' +
                                                       '<div class="jscal-week-day-header">' +
                                                       '<div class="jscal-week-day-name">' +
                                                       escapeHtml(
                                                           formatDate(day, {
                                                               weekday: "short"
                                                           })
                                                       ) +
                                                       "</div>" +
                                                       '<div class="jscal-week-day-date">' +
                                                       escapeHtml(day.getDate()) +
                                                       "</div>" +
                                                       "</div>" +
                                                       '<div class="jscal-week-events">' +
                                                       eventsHtml +
                                                       "</div>" +
                                                       "</section>"
                                                   );
                                               }
                                        
                                               const sameMonth =
                                                   weekStart.getMonth() === weekEnd.getMonth() &&
                                                   weekStart.getFullYear() === weekEnd.getFullYear();
                                        
                                               if (sameMonth)
                                               {
                                                   titleElement.textContent =
                                                       config.labels.calendarWeekShort +
                                                       " " +
                                                       getIsoWeekNumber(weekStart) +
                                                       " · " +
                                                       formatDate(weekStart, {
                                                           month: "long",
                                                           year: "numeric"
                                                       });
                                               } else
                                               {
                                                   titleElement.textContent =
                                                       config.labels.calendarWeekShort +
                                                       " " +
                                                       getIsoWeekNumber(weekStart) +
                                                       " · " +
                                                       formatDate(weekStart, {
                                                           day: "2-digit",
                                                           month: "2-digit"
                                                       }) +
                                                       " – " +
                                                       formatDate(weekEnd, {
                                                           day: "2-digit",
                                                           month: "2-digit",
                                                           year: "numeric"
                                                       });
                                               }
                                        
                                               contentElement.innerHTML =
                                                   '<div class="jscal-week">' +
                                                   columns.join("") +
                                                   "</div>";
                                           }
                                        
                                           function renderDayEvent(event)
                                           {
                                               const imageHtml =
                                                   config.showImages &&
                                                   event.img
                                                       ? '<img class="jscal-day-event-image" src="' +
                                                           escapeAttribute(event.img) +
                                                           '" alt="">'
                                                       : "";
                                        
                                               const locationHtml =
                                                   event.ort &&
                                                   !(config.hideEmptyLocation && event.ort.length === 0)
                                                       ? '<div class="jscal-day-event-location">' +
                                                           escapeHtml(event.ort) +
                                                           "</div>"
                                                       : "";
                                        
                                               return [
                                                   '<article class="jscal-day-event"',
                                                   ' style="--jscal-event-color:',
                                                   escapeAttribute(event.color),
                                                   ';">',
                                                   '<div class="jscal-day-event-time">',
                                                   escapeHtml(
                                                       event.allDay
                                                           ? config.labels.allDay
                                                           : formatTime(event.start)
                                                   ),
                                                   "</div>",
                                                   '<button type="button"',
                                                   ' class="jscal-day-event-content"',
                                                   ' data-jscal-event-id="',
                                                   escapeAttribute(event.id),
                                                   '"',
                                                   ' aria-label="',
                                                   escapeAttribute(event.ereignis),
                                                   '">',
                                                   imageHtml,
                                                   '<div class="jscal-day-event-text">',
                                                   '<div class="jscal-day-event-title">',
                                                   escapeHtml(event.ereignis),
                                                   "</div>",
                                                   locationHtml,
                                                   "</div>",
                                                   "</button>",
                                                   "</article>"
                                               ].join("");
                                           }
                                        
                                           function renderDay()
                                           {
                                               const dayEvents = getEventsForDay(state.currentDate);
                                               const allDayEvents = dayEvents.filter(function (event)
                                               {
                                                   return event.allDay;
                                               });
                                               const timedEvents = dayEvents.filter(function (event)
                                               {
                                                   return !event.allDay;
                                               });
                                        
                                               const sections = [];
                                        
                                               if (allDayEvents.length > 0)
                                               {
                                                   sections.push(
                                                       '<section class="jscal-day-section">' +
                                                       '<h3 class="jscal-day-section-title">' +
                                                       escapeHtml(config.labels.allDay) +
                                                       "</h3>" +
                                                       allDayEvents.map(renderDayEvent).join("") +
                                                       "</section>"
                                                   );
                                               }
                                        
                                               if (timedEvents.length > 0)
                                               {
                                                   sections.push(
                                                       '<section class="jscal-day-section">' +
                                                       timedEvents.map(renderDayEvent).join("") +
                                                       "</section>"
                                                   );
                                               }
                                        
                                               if (sections.length === 0)
                                               {
                                                   sections.push(
                                                       '<div class="jscal-empty">' +
                                                       escapeHtml(config.labels.noEvents) +
                                                       "</div>"
                                                   );
                                               }
                                        
                                               titleElement.textContent = formatDate(state.currentDate, {
                                                   weekday: "long",
                                                   day: "2-digit",
                                                   month: "long",
                                                   year: "numeric"
                                               });
                                        
                                               contentElement.innerHTML =
                                                   '<div class="jscal-day-view">' +
                                                   sections.join("") +
                                                   "</div>";
                                           }
                                        
                                           function renderInvalidItems()
                                           {
                                               if (invalidItems.length === 0)
                                               {
                                                   return;
                                               }
                                        
                                               const invalidHtml = invalidItems
                                                   .map(function (entry)
                                                   {
                                                       const timeText =
                                                           entry.item &&
                                                           typeof entry.item.zeit === "string"
                                                               ? entry.item.zeit
                                                               : "";
                                        
                                                       return (
                                                           '<div class="jscal-invalid">' +
                                                           escapeHtml(config.labels.invalidDate) +
                                                           ": " +
                                                           escapeHtml(timeText) +
                                                           "</div>"
                                                       );
                                                   })
                                                   .join("");
                                        
                                               contentElement.insertAdjacentHTML(
                                                   "beforeend",
                                                   invalidHtml
                                               );
                                           }
                                        
                                           function updateViewButtons()
                                           {
                                               const buttons = root.querySelectorAll("[data-jscal-view]");
                                        
                                               buttons.forEach(function (button)
                                               {
                                                   const isActive =
                                                       button.getAttribute("data-jscal-view") === state.view;
                                        
                                                   button.classList.toggle("is-active", isActive);
                                                   button.setAttribute(
                                                       "aria-pressed",
                                                       isActive ? "true" : "false"
                                                   );
                                               });
                                           }
                                        
                                           function render()
                                           {
                                               saveState();
                                           	hideTooltip();
                                        
                                               if (state.view === "week")
                                               {
                                                   renderWeek();
                                               } else if (state.view === "day")
                                               {
                                                   renderDay();
                                               } else
                                               {
                                                   renderMonth();
                                               }
                                        
                                               renderInvalidItems();
                                               updateViewButtons();
                                           }
                                        
                                           function findEventById(eventId)
                                           {
                                               return events.find(function (event)
                                               {
                                                   return event.id === String(eventId);
                                               }) || null;
                                           }
                                        
                                           function buildTooltipHtml(event)
                                           {
                                               const imageHtml =
                                                   config.showImages &&
                                                   event.img
                                                       ? '<img class="jscal-tooltip-image" src="' +
                                                           escapeAttribute(event.img) +
                                                           '" alt="">'
                                                       : "";
                                        
                                               const locationRow =
                                                   event.ort &&
                                                   !(config.hideEmptyLocation && event.ort.length === 0)
                                                       ? '<div class="jscal-tooltip-row">' +
                                                           '<div class="jscal-tooltip-label">' +
                                                           escapeHtml(config.labels.location) +
                                                           "</div>" +
                                                           '<div class="jscal-tooltip-value">' +
                                                           escapeHtml(event.ort) +
                                                           "</div>" +
                                                           "</div>"
                                                       : "";
                                        
                                               return (
                                                   '<div class="jscal-tooltip-header">' +
                                                   imageHtml +
                                                   '<div class="jscal-tooltip-title">' +
                                                   escapeHtml(event.ereignis) +
                                                   "</div>" +
                                                   "</div>" +
                                                   '<div class="jscal-tooltip-row">' +
                                                   '<div class="jscal-tooltip-label">Zeit</div>' +
                                                   '<div class="jscal-tooltip-value">' +
                                                   escapeHtml(formatEventTime(event)) +
                                                   "</div>" +
                                                   "</div>" +
                                                   locationRow
                                               );
                                           }
                                        
                                           function positionTooltip(targetElement)
                                           {
                                               const targetRect = targetElement.getBoundingClientRect();
                                               const tooltipRect = tooltipElement.getBoundingClientRect();
                                               const margin = 8;
                                        
                                               let left = targetRect.left;
                                               let top = targetRect.bottom + margin;
                                        
                                               if (left + tooltipRect.width > window.innerWidth - margin)
                                               {
                                                   left = window.innerWidth - tooltipRect.width - margin;
                                               }
                                        
                                               if (left < margin)
                                               {
                                                   left = margin;
                                               }
                                        
                                               if (top + tooltipRect.height > window.innerHeight - margin)
                                               {
                                                   top = targetRect.top - tooltipRect.height - margin;
                                               }
                                        
                                               if (top < margin)
                                               {
                                                   top = margin;
                                               }
                                        
                                               tooltipElement.style.left = Math.round(left) + "px";
                                               tooltipElement.style.top = Math.round(top) + "px";
                                           }
                                        
                                           function showTooltip(targetElement)
                                           {
                                               const eventId = targetElement.getAttribute("data-jscal-event-id");
                                               const event = findEventById(eventId);
                                        
                                               if (!event)
                                               {
                                                   return;
                                               }
                                        
                                               if (
                                                   state.selectedEventElement &&
                                                   state.selectedEventElement !== targetElement
                                               )
                                               {
                                                   state.selectedEventElement.classList.remove("is-selected");
                                               }
                                        
                                               state.selectedEventElement = targetElement;
                                               state.tooltipEventId = event.id;
                                        
                                               targetElement.classList.add("is-selected");
                                               tooltipElement.innerHTML = buildTooltipHtml(event);
                                               tooltipElement.classList.add("is-visible");
                                               tooltipElement.setAttribute("aria-hidden", "false");
                                        
                                               positionTooltip(targetElement);
                                           }
                                        
                                           function hideTooltip()
                                           {
                                               if (state.selectedEventElement)
                                               {
                                                   state.selectedEventElement.classList.remove("is-selected");
                                               }
                                        
                                               state.selectedEventElement = null;
                                               state.tooltipEventId = null;
                                        
                                               tooltipElement.classList.remove("is-visible");
                                               tooltipElement.setAttribute("aria-hidden", "true");
                                               tooltipElement.innerHTML = "";
                                           }
                                        
                                           function navigate(direction)
                                           {
                                               if (state.view === "month")
                                               {
                                                   state.currentDate = addMonths(
                                                       state.currentDate,
                                                       direction
                                                   );
                                               } else if (state.view === "week")
                                               {
                                                   state.currentDate = addDays(
                                                       state.currentDate,
                                                       direction * 7
                                                   );
                                               } else
                                               {
                                                   state.currentDate = addDays(
                                                       state.currentDate,
                                                       direction
                                                   );
                                               }
                                        
                                               render();
                                           }
                                        
                                           function handleClick(event)
                                           {
                                               const actionButton = event.target.closest("[data-jscal-action]");
                                        
                                               if (actionButton && root.contains(actionButton))
                                               {
                                                   const action = actionButton.getAttribute("data-jscal-action");
                                        
                                                   if (action === "previous")
                                                   {
                                                       navigate(-1);
                                                   } else if (action === "next")
                                                   {
                                                       navigate(1);
                                                   } else if (action === "today")
                                                   {
                                                       state.currentDate = startOfDay(new Date());
                                                       render();
                                                   }
                                        
                                                   return;
                                               }
                                        
                                               const viewButton = event.target.closest("[data-jscal-view]");
                                        
                                               if (viewButton && root.contains(viewButton))
                                               {
                                                   const nextView = viewButton.getAttribute("data-jscal-view");
                                        
                                                   if (["month", "week", "day"].includes(nextView))
                                                   {
                                                       state.view = nextView;
                                                       render();
                                                   }
                                        
                                                   return;
                                               }
                                        
                                               const eventElement = event.target.closest("[data-jscal-event-id]");
                                        
                                               if (eventElement && root.contains(eventElement))
                                               {
                                                   const eventId =
                                                       eventElement.getAttribute("data-jscal-event-id");
                                        
                                                   if (
                                                       state.tooltipEventId === eventId &&
                                                       tooltipElement.classList.contains("is-visible")
                                                   )
                                                   {
                                                       hideTooltip();
                                                   } else
                                                   {
                                                       showTooltip(eventElement);
                                                   }
                                        
                                                   event.preventDefault();
                                                   event.stopPropagation();
                                                   return;
                                               }
                                        
                                               const dayElement = event.target.closest("[data-jscal-date]");
                                        
                                               if (dayElement && root.contains(dayElement))
                                               {
                                                   const dateText = dayElement.getAttribute("data-jscal-date");
                                                   const match = dateText
                                                       ? dateText.match(/^(\d{4})-(\d{2})-(\d{2})$/)
                                                       : null;
                                        
                                                   if (match)
                                                   {
                                                       const selectedDate = createLocalDate(
                                                           Number(match[1]),
                                                           Number(match[2]),
                                                           Number(match[3]),
                                                           0,
                                                           0
                                                       );
                                        
                                                       if (selectedDate)
                                                       {
                                                           state.currentDate = selectedDate;
                                                           state.view = "day";
                                                           render();
                                                       }
                                                   }
                                               }
                                           }
                                        
                                           function handlePointerOver(event)
                                           {
                                               if (
                                                   event.pointerType &&
                                                   event.pointerType !== "mouse"
                                               )
                                               {
                                                   return;
                                               }
                                        
                                               const eventElement = event.target.closest("[data-jscal-event-id]");
                                        
                                               if (!eventElement || !root.contains(eventElement))
                                               {
                                                   return;
                                               }
                                        
                                               const relatedTarget = event.relatedTarget;
                                        
                                               if (
                                                   relatedTarget &&
                                                   eventElement.contains(relatedTarget)
                                               )
                                               {
                                                   return;
                                               }
                                        
                                               showTooltip(eventElement);
                                           }
                                        
                                           function handlePointerOut(event)
                                           {
                                               if (
                                                   event.pointerType &&
                                                   event.pointerType !== "mouse"
                                               )
                                               {
                                                   return;
                                               }
                                        
                                               const eventElement = event.target.closest("[data-jscal-event-id]");
                                        
                                               if (!eventElement || !root.contains(eventElement))
                                               {
                                                   return;
                                               }
                                        
                                               const relatedTarget = event.relatedTarget;
                                        
                                               if (
                                                   relatedTarget &&
                                                   eventElement.contains(relatedTarget)
                                               )
                                               {
                                                   return;
                                               }
                                        
                                               hideTooltip();
                                           }
                                        
                                           function handleDocumentPointerDown(event)
                                           {
                                               if (!root.contains(event.target))
                                               {
                                                   hideTooltip();
                                               }
                                           }
                                        
                                           function handleResize()
                                           {
                                               if (
                                                   state.selectedEventElement &&
                                                   tooltipElement.classList.contains("is-visible")
                                               )
                                               {
                                                   positionTooltip(state.selectedEventElement);
                                               }
                                           }
                                        
                                           root.addEventListener("click", handleClick);
                                           root.addEventListener("pointerover", handlePointerOver);
                                           root.addEventListener("pointerout", handlePointerOut);
                                           document.addEventListener("pointerdown", handleDocumentPointerDown);
                                           window.addEventListener("resize", handleResize);
                                        
                                           root.__jsonCalendarCleanup = function ()
                                           {
                                               root.removeEventListener("click", handleClick);
                                               root.removeEventListener("pointerover", handlePointerOver);
                                               root.removeEventListener("pointerout", handlePointerOut);
                                               document.removeEventListener(
                                                   "pointerdown",
                                                   handleDocumentPointerDown
                                               );
                                               window.removeEventListener("resize", handleResize);
                                           };
                                        
                                           render();
                                        })();
                                        </script>
                                        

                                        Das widget hat eine Monats,Wochen,Tagessicht. Details über MouseOver, verschiedene Farben für Ereignisse, was im Template konfiguriert werden kann. Hell/Dunkel/Auto (also nach browser-Einstellung) Farben und ein wenig Navigation

                                        Monatssicht
                                        6ca992eb-2a64-4c41-a76a-de5ad89f44ca-image.jpeg

                                        Wochensicht
                                        da7a0855-76a0-4597-9385-ead7e19858d2-image.jpeg

                                        Tagessicht
                                        04901f5e-2f32-4610-b979-ca30f279c9be-image.jpeg

                                        Theoretisch werden auch Bilder angezeigt. habe die aber nicht, daher wird das leer angezeigt. Habe das dann auch nicht weiters getestet.

                                        Als Testdaten habe ich die hier schon mal geposteten verwendet.

                                        Ich habe noch einen kleinen Effekt mit dem jsontemplate entdeckt.
                                        Bitte mal testen ob euch auch etwas auffällt.
                                        Auch werde ich dem KI-Prompt noch was hinzufügen müssen, da der erste Wurf nicht funktioniert hat, wie es die KI gemacht hat.
                                        Sie hat versucht die Daten per serialisierung und einfügen in das javascript für die browser-Ebene zu übergeben. Das hat nicht funktioniert. Daher wird nun eine globale window variable verwendet.

                                        Eduard77E Online
                                        Eduard77E Online
                                        Eduard77
                                        schrieb zuletzt editiert von Eduard77
                                        #66

                                        @OliverIO
                                        bei mir zeigt dein Kalender keine Termine.

                                        007096d3-d2a3-459a-ae3a-dec58bd5004a-image.jpeg

                                        0232057e-fa4b-4f76-abb1-33ed645665fa-image.jpeg

                                        Und im ioBroker Protokol kommen Fehler von Web Adapter.

                                        web.0 2026-07-24 21:27:44.157	error	Invalid pattern on subscribe: The pattern "1,2" is not a valid ID pattern
                                        web.0 2026-07-24 21:26:11.809	error	Invalid pattern on subscribe: The pattern "1,2" is not a valid ID pattern
                                        
                                        sigi234S 1 Antwort Letzte Antwort
                                        0
                                        • Eduard77E Eduard77

                                          @OliverIO
                                          bei mir zeigt dein Kalender keine Termine.

                                          007096d3-d2a3-459a-ae3a-dec58bd5004a-image.jpeg

                                          0232057e-fa4b-4f76-abb1-33ed645665fa-image.jpeg

                                          Und im ioBroker Protokol kommen Fehler von Web Adapter.

                                          web.0 2026-07-24 21:27:44.157	error	Invalid pattern on subscribe: The pattern "1,2" is not a valid ID pattern
                                          web.0 2026-07-24 21:26:11.809	error	Invalid pattern on subscribe: The pattern "1,2" is not a valid ID pattern
                                          
                                          sigi234S Online
                                          sigi234S Online
                                          sigi234
                                          Forum Testing Most Active
                                          schrieb zuletzt editiert von sigi234
                                          #67

                                          @Eduard77 sagte:

                                          bei mir zeigt dein Kalender keine Termine.

                                          Skriptversion?

                                          Aha, jetzt bei mir auch. Hatte vorher aber einwandfrei funktioniert.

                                          web.0
                                          2026-07-24 21:54:06.041	error	Invalid pattern on subscribe: The pattern "1,2" is not a valid ID pattern
                                          

                                          Edit:

                                          Jetzt sind sie wieder da. Sehr komisch. Ich denke mal es hat was mit dem Skript auf sich. Da er auch vorher Zeitweise einfach keine Daten bekommt oder zu lange dauert oder was den DP blockiert oder es einfach nicht auslesbar ist? Wie gesagt nach einer gewissen Zeit kommen die Daten aber mal.

                                          Edit2:

                                          Termine werden im Editor angezeigt, in der Runtime nicht!

                                          Bitte benutzt das Voting rechts unten im Beitrag wenn er euch geholfen hat.
                                          Immer Daten sichern!

                                          OliverIOO 1 Antwort Letzte Antwort
                                          0

                                          Hey! Du scheinst an dieser Unterhaltung interessiert zu sein, hast aber noch kein Konto.

                                          Hast du es satt, bei jedem Besuch durch die gleichen Beiträge zu scrollen? Wenn du dich für ein Konto anmeldest, kommst du immer genau dorthin zurück, wo du zuvor warst, und kannst dich über neue Antworten benachrichtigen lassen (entweder per E-Mail oder Push-Benachrichtigung). Du kannst auch Lesezeichen speichern und Beiträge positiv bewerten, um anderen Community-Mitgliedern deine Wertschätzung zu zeigen.

                                          Mit deinem Input könnte dieser Beitrag noch besser werden 💗

                                          Registrieren Anmelden
                                          Antworten
                                          • In einem neuen Thema antworten
                                          Anmelden zum Antworten
                                          • Älteste zuerst
                                          • Neuste zuerst
                                          • Meiste Stimmen


                                          Support us

                                          ioBroker
                                          Community Adapters
                                          Donate

                                          204

                                          Online

                                          33.0k

                                          Benutzer

                                          83.5k

                                          Themen

                                          1.3m

                                          Beiträge
                                          Community
                                          Impressum | Datenschutz-Bestimmungen | Nutzungsbedingungen | Einwilligungseinstellungen
                                          ioBroker Community 2014-2026
                                          logo
                                          • Anmelden

                                          • Du hast noch kein Konto? Registrieren

                                          • Anmelden oder registrieren, um zu suchen
                                          • Erster Beitrag
                                            Letzter Beitrag
                                          0
                                          • Home
                                          • Aktuell
                                          • Tags
                                          • Ungelesen 0
                                          • Kategorien
                                          • Unreplied
                                          • Beliebt
                                          • GitHub
                                          • Docu
                                          • Hilfe