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
    1.3k

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

  • Neues YouTube-Video: Visualisierung im Devices-Adapter
    BluefoxB
    Bluefox
    17
    1
    6.8k

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

Geplant Angeheftet Gesperrt Verschoben Visualisierung
vis
79 Beiträge 7 Kommentatoren 2.7k 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 Rudi-Ment

    Super.
    Dann teste ich das nachher mal.

    Ich habe festgestellt das die Geburtstage aus meinen Kalender nicht übernommen werden, wenn diese als Serie angelegt sind.

    Ro75R Offline
    Ro75R Offline
    Ro75
    schrieb am zuletzt editiert von Ro75
    #47

    @Rudi-Ment sagte:

    Ich habe festgestellt das die Geburtstage aus meinen Kalender nicht übernommen werden, wenn diese als Serie angelegt sind.

    Wie sind diese denn konkret angelegt? Also hier und uch bei anderen Usern laufen die. Lese mal ein paar Posts weiter oben. Nashra hat dazu auch was geschrieben. Offensichtlich hat Google Probleme mit "alten" Einträgen.

    Siehe Post #35 +-

    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
    • R Offline
      R Offline
      Rudi-Ment
      schrieb am zuletzt editiert von
      #48

      Ok.
      Mache ich

      1 Antwort Letzte Antwort
      0
      • Ro75R Offline
        Ro75R Offline
        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 Offline
              Ro75R Offline
              Ro75
              schrieb am 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 am 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 Offline
                  Ro75R Offline
                  Ro75
                  schrieb am 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 am 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 Offline
                      Ro75R Offline
                      Ro75
                      schrieb am 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 am 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 Offline
                          Ro75R Offline
                          Ro75
                          schrieb am 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 am 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 Offline
                              Ro75R Offline
                              Ro75
                              schrieb am 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 am 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\d?)\.(\d\d?)\.(\d\d\d\d)(?:\s+(\d\d?):(\d\d))?$/;
                                      const timeOnlyPattern = /^(\d\d?):(\d\d)$/;
                                
                                      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\d\d\d)-(\d\d)-(\d\d)$/)
                                              : 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.

                                Anmerkung: 29.7.26: Das Template wurde aktualisiert um regex-Probleme zu vermeiden

                                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 am 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 Offline
                                    Ro75R Offline
                                    Ro75
                                    schrieb am 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 Offline
                                      Ro75R Offline
                                      Ro75
                                      schrieb am 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

                                      OliverIOO 1 Antwort Letzte Antwort
                                      2
                                      • skvarelS Offline
                                        skvarelS Offline
                                        skvarel
                                        Developer
                                        schrieb am 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\d?)\.(\d\d?)\.(\d\d\d\d)(?:\s+(\d\d?):(\d\d))?$/;
                                                const timeOnlyPattern = /^(\d\d?):(\d\d)$/;
                                          
                                                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\d\d\d)-(\d\d)-(\d\d)$/)
                                                        : 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.

                                          Anmerkung: 29.7.26: Das Template wurde aktualisiert um regex-Probleme zu vermeiden

                                          Eduard77E Online
                                          Eduard77E Online
                                          Eduard77
                                          schrieb am 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

                                          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

                                          434

                                          Online

                                          33.0k

                                          Benutzer

                                          83.7k

                                          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