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
    765

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

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

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

Geplant Angeheftet Gesperrt Verschoben Visualisierung
vis
74 Beiträge 7 Kommentatoren 1.5k 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.
  • 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 Online
        skvarelS Online
        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
          • Eduard77E Eduard77

            @OliverIO
            bei mir zeigt dein Kalender keine Termine.

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

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

            Und im ioBroker Protokol kommen Fehler von Web Adapter.

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

            @Eduard77 sagte:

            bei mir zeigt dein Kalender keine Termine.

            Skriptversion?

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

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

            Edit:

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

            Edit2:

            Termine werden im Editor angezeigt, in der Runtime nicht!

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

            OliverIOO 1 Antwort Letzte Antwort
            0
            • sigi234S sigi234

              @Eduard77 sagte:

              bei mir zeigt dein Kalender keine Termine.

              Skriptversion?

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

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

              Edit:

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

              Edit2:

              Termine werden im Editor angezeigt, in der Runtime nicht!

              OliverIOO Offline
              OliverIOO Offline
              OliverIO
              schrieb am zuletzt editiert von
              #68

              @sigi234

              Nein
              Das hat was mit jsontemplate und der react Aktualisierung zu tun.
              Da muss ich was tun.
              Bitte mal mit v4.4.3 vis-jsontemplate
              Probieren. Da gibt es das Problem
              Das vis1 nur Widgets und vis2 nur widgetID oder umgekehrt kennt. Das muss man dann im Template entsprechend anpassen

              Habe jetzt die Woche wenig Zeit

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

              sigi234S 1 Antwort Letzte Antwort
              0
              • OliverIOO OliverIO

                @sigi234

                Nein
                Das hat was mit jsontemplate und der react Aktualisierung zu tun.
                Da muss ich was tun.
                Bitte mal mit v4.4.3 vis-jsontemplate
                Probieren. Da gibt es das Problem
                Das vis1 nur Widgets und vis2 nur widgetID oder umgekehrt kennt. Das muss man dann im Template entsprechend anpassen

                Habe jetzt die Woche wenig Zeit

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

                @OliverIO sagte:

                Bitte mal mit v4.4.3 vis-jsontemplate
                Probieren.

                Installierte Version:
                4.4.5

                Soll ich zurück?

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

                Eduard77E 1 Antwort Letzte Antwort
                0
                • sigi234S sigi234

                  @OliverIO sagte:

                  Bitte mal mit v4.4.3 vis-jsontemplate
                  Probieren.

                  Installierte Version:
                  4.4.5

                  Soll ich zurück?

                  Eduard77E Online
                  Eduard77E Online
                  Eduard77
                  schrieb am zuletzt editiert von
                  #70

                  @oliverio
                  mit Version 4.4.3 gleiche Ergebnis und gleiche Fehlermeldungen im Log.
                  61c141d9-92f8-46af-a42a-229d7b517af2-image.jpeg

                  1 Antwort Letzte Antwort
                  0
                  • OliverIOO Offline
                    OliverIOO Offline
                    OliverIO
                    schrieb zuletzt editiert von
                    #71

                    Das korrigierte jsonTemplate widget steht nun zur Verfügung.

                    https://forum.iobroker.net/topic/31521/test-widget-json-template/68?_=1785334921385

                    Für das Kalender widget von mir weiter oben korrigiere ich gleich noch das Template, da sich dort in den regex-Ausdrücken noch ein paar kleine inkompatibilitäten in vis-1 gezeigt haben (die doofen Klammerausdrücke wurde dort wieder als binding interpretiert)

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

                    1 Antwort Letzte Antwort
                    0
                    • Ro75R Ro75

                      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.

                      OliverIOO Offline
                      OliverIOO Offline
                      OliverIO
                      schrieb zuletzt editiert von OliverIO
                      #72

                      @Ro75 sagte:

                      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.

                      was wären den konkret die Anforderungen.
                      Ich selbst mach hier nix mit Kalender.
                      Wenn ich Anforderungen und Testdaten bekomme kann ich was probieren.

                      Was ist der Unterschied zwischen m1 und c1?
                      Habe bisher nur Testdaten für m1

                      Nachtrag:
                      Ich habe mal codex gebeten auf basis des scrounger widgets ein ejs template zu entwickeln. Habe mir dazu auch künstliche Testdaten entwickeln lassen (weiß nicht ob die alle Fälle abdecken).

                      Template

                      <%
                      /*
                      * Standalone calendar for ioBroker.vis-jsontemplate
                      *
                      * Change the values in calendarConfig to customize the widget.
                      */
                      var calendarConfig = {
                         initialView: "month",
                         theme: "dark",
                         locale: "en-US",
                         firstDayOfWeek: 1,
                         weekdays: [1, 2, 3, 4, 5, 6, 0],
                         showWeekNumbers: true,
                         showControlLabels: true,
                         monthClickView: "week",
                         weekClickView: "day",
                         dayClickView: "week",
                         timeAxisStartHour: 0,
                         timeAxisEndHour: 24,
                         intervalMinutes: 60,
                         intervalHeight: 48,
                         eventMinHeight: 22
                      };
                      
                      var calendarThemes = {
                         dark: {
                             background: "#151922",
                             panel: "#1d2330",
                             panelElevated: "#252c3b",
                             border: "#343d50",
                             text: "#f4f7fb",
                             muted: "#9ca8ba",
                             outside: "#11151d",
                             today: "#3b82f6",
                             todayText: "#ffffff",
                             hover: "#2d3748",
                             weekend: "rgba(255, 255, 255, 0.025)",
                             timeLine: "#ef4444",
                             scrollbar: "#536176"
                         },
                         light: {
                             background: "#f8fafc",
                             panel: "#ffffff",
                             panelElevated: "#f1f5f9",
                             border: "#d8e0ea",
                             text: "#172033",
                             muted: "#667085",
                             outside: "#eef2f7",
                             today: "#2563eb",
                             todayText: "#ffffff",
                             hover: "#e8eef7",
                             weekend: "rgba(15, 23, 42, 0.025)",
                             timeLine: "#dc2626",
                             scrollbar: "#a2adbd"
                         }
                      };
                      
                      var selectedTheme = calendarThemes[calendarConfig.theme] || calendarThemes.dark;
                      var calendarEvents = [];
                      var calendarError = "";
                      
                      try {
                         var calendarSource = data;
                      
                         if (typeof calendarSource === "string") {
                             calendarSource = JSON.parse(calendarSource);
                         }
                      
                         if (!Array.isArray(calendarSource) && calendarSource) {
                             if (Array.isArray(calendarSource.events)) {
                                 calendarSource = calendarSource.events;
                             } else if (Array.isArray(calendarSource.items)) {
                                 calendarSource = calendarSource.items;
                             } else if (Array.isArray(calendarSource.data)) {
                                 calendarSource = calendarSource.data;
                             } else if (Array.isArray(calendarSource.result)) {
                                 calendarSource = calendarSource.result;
                             }
                         }
                      
                         if (!Array.isArray(calendarSource)) {
                             calendarError = "The selected data point does not contain an event array.";
                         } else {
                             for (var sourceIndex = 0; sourceIndex < calendarSource.length; sourceIndex += 1) {
                                 var sourceEvent = calendarSource[sourceIndex];
                      
                                 if (
                                     sourceEvent &&
                                     sourceEvent.name !== undefined &&
                                     sourceEvent.start !== undefined &&
                                     sourceEvent.end !== undefined
                                 ) {
                                     calendarEvents.push(sourceEvent);
                                 }
                             }
                      
                             if (calendarEvents.length !== calendarSource.length) {
                                 calendarError = "Some events were ignored because name, start, or end is missing.";
                             }
                         }
                      } catch (calendarException) {
                         calendarError = "The calendar JSON could not be processed.";
                      }
                      
                      var calendarGlobalKey =
                         "__visJsontemplateCalendar_" +
                         String(widgetid).replace(/[^A-Za-z0-9_$]/g, "_");
                      
                      window[calendarGlobalKey] = {
                         config: calendarConfig,
                         error: calendarError,
                         events: calendarEvents
                      };
                      %>
                      
                      <style>
                         #<%- widgetid %> {
                             width: 100%;
                             height: 100%;
                             box-sizing: border-box;
                             overflow: hidden;
                             color: <%- selectedTheme.text %>;
                             font-family: Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Arial, sans-serif;
                         }
                      
                         #<%- widgetid %> * {
                             box-sizing: border-box;
                         }
                      
                         #<%- widgetid %> .jtc-calendar {
                             --jtc-background: <%- selectedTheme.background %>;
                             --jtc-panel: <%- selectedTheme.panel %>;
                             --jtc-panel-elevated: <%- selectedTheme.panelElevated %>;
                             --jtc-border: <%- selectedTheme.border %>;
                             --jtc-text: <%- selectedTheme.text %>;
                             --jtc-muted: <%- selectedTheme.muted %>;
                             --jtc-outside: <%- selectedTheme.outside %>;
                             --jtc-today: <%- selectedTheme.today %>;
                             --jtc-today-text: <%- selectedTheme.todayText %>;
                             --jtc-hover: <%- selectedTheme.hover %>;
                             --jtc-weekend: <%- selectedTheme.weekend %>;
                             --jtc-time-line: <%- selectedTheme.timeLine %>;
                             --jtc-scrollbar: <%- selectedTheme.scrollbar %>;
                      
                             position: absolute;
                             inset: 0;
                             width: auto;
                             height: auto;
                             display: flex;
                             flex-direction: column;
                             overflow: hidden;
                             background: var(--jtc-background);
                             border: 1px solid var(--jtc-border);
                             border-radius: 14px;
                             color: var(--jtc-text);
                         }
                      
                         #<%- widgetid %> .jtc-controls {
                             flex: 0 0 auto;
                             display: flex;
                             align-items: center;
                             gap: 7px;
                             padding: 10px;
                             background: var(--jtc-panel);
                             border-bottom: 1px solid var(--jtc-border);
                         }
                      
                         #<%- widgetid %> .jtc-control-group {
                             display: flex;
                             align-items: center;
                             gap: 5px;
                         }
                      
                         #<%- widgetid %> .jtc-title {
                             min-width: 0;
                             flex: 1 1 auto;
                             overflow: hidden;
                             padding: 0 10px;
                             text-align: center;
                             text-overflow: ellipsis;
                             white-space: nowrap;
                             font-size: 16px;
                             font-weight: 750;
                         }
                      
                         #<%- widgetid %> .jtc-button {
                             min-width: 36px;
                             height: 34px;
                             display: inline-flex;
                             align-items: center;
                             justify-content: center;
                             gap: 6px;
                             padding: 0 10px;
                             color: var(--jtc-text);
                             background: var(--jtc-panel-elevated);
                             border: 1px solid var(--jtc-border);
                             border-radius: 8px;
                             cursor: pointer;
                             font: inherit;
                             font-size: 12px;
                             transition: background 120ms ease, border-color 120ms ease;
                         }
                      
                         #<%- widgetid %> .jtc-button:hover,
                         #<%- widgetid %> .jtc-button:focus-visible {
                             background: var(--jtc-hover);
                             outline: none;
                         }
                      
                         #<%- widgetid %> .jtc-button.is-active {
                             color: var(--jtc-today-text);
                             background: var(--jtc-today);
                             border-color: var(--jtc-today);
                         }
                      
                         #<%- widgetid %> .jtc-status {
                             flex: 0 0 auto;
                             padding: 6px 10px;
                             color: #ffffff;
                             background: #b42318;
                             font-size: 11px;
                             line-height: 1.35;
                         }
                      
                         #<%- widgetid %> .jtc-body {
                             position: relative;
                             min-height: 0;
                             flex: 1 1 auto;
                             overflow: hidden;
                         }
                      
                         #<%- widgetid %> .jtc-month {
                             width: 100%;
                             height: 100%;
                             display: flex;
                             flex-direction: column;
                         }
                      
                         #<%- widgetid %> .jtc-month-header,
                         #<%- widgetid %> .jtc-month-grid {
                             display: grid;
                             grid-template-columns: var(--jtc-month-columns);
                         }
                      
                         #<%- widgetid %> .jtc-month-header {
                             flex: 0 0 auto;
                             background: var(--jtc-panel);
                             border-bottom: 1px solid var(--jtc-border);
                         }
                      
                         #<%- widgetid %> .jtc-weekday {
                             min-width: 0;
                             padding: 8px 5px;
                             color: var(--jtc-muted);
                             text-align: center;
                             font-size: 11px;
                             font-weight: 700;
                             text-transform: uppercase;
                         }
                      
                         #<%- widgetid %> .jtc-month-grid {
                             min-height: 0;
                             flex: 1 1 auto;
                             grid-template-rows: repeat(6, minmax(0, 1fr));
                         }
                      
                         #<%- widgetid %> .jtc-month-cell {
                             min-width: 0;
                             min-height: 0;
                             overflow: hidden;
                             padding: 5px;
                             background: var(--jtc-background);
                             border-right: 1px solid var(--jtc-border);
                             border-bottom: 1px solid var(--jtc-border);
                             cursor: pointer;
                         }
                      
                         #<%- widgetid %> .jtc-month-cell.is-outside {
                             background: var(--jtc-outside);
                             color: var(--jtc-muted);
                         }
                      
                         #<%- widgetid %> .jtc-month-cell.is-weekend {
                             box-shadow: inset 0 0 0 999px var(--jtc-weekend);
                         }
                      
                         #<%- widgetid %> .jtc-month-cell:hover {
                             background: var(--jtc-hover);
                         }
                      
                         #<%- widgetid %> .jtc-date-row {
                             height: 24px;
                             display: flex;
                             align-items: center;
                             justify-content: space-between;
                             gap: 5px;
                             margin-bottom: 3px;
                         }
                      
                         #<%- widgetid %> .jtc-date-number {
                             width: 24px;
                             height: 24px;
                             display: inline-grid;
                             place-items: center;
                             border-radius: 50%;
                             font-size: 12px;
                             font-weight: 700;
                         }
                      
                         #<%- widgetid %> .jtc-date-number.is-today {
                             color: var(--jtc-today-text);
                             background: var(--jtc-today);
                         }
                      
                         #<%- widgetid %> .jtc-week-number {
                             color: var(--jtc-muted);
                             font-size: 9px;
                         }
                      
                         #<%- widgetid %> .jtc-month-events {
                             min-height: 0;
                             overflow: hidden;
                         }
                      
                         #<%- widgetid %> .jtc-month-event {
                             overflow: hidden;
                             margin-bottom: 2px;
                             padding: 2px 5px;
                             border-radius: 4px;
                             font-size: 10px;
                             line-height: 15px;
                             text-overflow: ellipsis;
                             white-space: nowrap;
                         }
                      
                         #<%- widgetid %> .jtc-more {
                             color: var(--jtc-muted);
                             font-size: 9px;
                         }
                      
                         #<%- widgetid %> .jtc-agenda {
                             width: 100%;
                             height: 100%;
                             display: flex;
                             flex-direction: column;
                             overflow: hidden;
                         }
                      
                         #<%- widgetid %> .jtc-all-day,
                         #<%- widgetid %> .jtc-agenda-header {
                             flex: 0 0 auto;
                             display: grid;
                             grid-template-columns: 58px repeat(var(--jtc-day-columns), minmax(90px, 1fr));
                             background: var(--jtc-panel);
                         }
                      
                         #<%- widgetid %> .jtc-agenda-header {
                             border-bottom: 1px solid var(--jtc-border);
                         }
                      
                         #<%- widgetid %> .jtc-agenda-corner,
                         #<%- widgetid %> .jtc-agenda-day-title,
                         #<%- widgetid %> .jtc-all-day-label,
                         #<%- widgetid %> .jtc-all-day-cell {
                             min-width: 0;
                             padding: 7px 5px;
                             border-right: 1px solid var(--jtc-border);
                         }
                      
                         #<%- widgetid %> .jtc-agenda-day-title {
                             cursor: pointer;
                             text-align: center;
                         }
                      
                         #<%- widgetid %> .jtc-agenda-day-title:hover {
                             background: var(--jtc-hover);
                         }
                      
                         #<%- widgetid %> .jtc-agenda-day-name {
                             color: var(--jtc-muted);
                             font-size: 10px;
                             text-transform: uppercase;
                         }
                      
                         #<%- widgetid %> .jtc-agenda-day-number {
                             margin-top: 3px;
                             font-size: 14px;
                             font-weight: 750;
                         }
                      
                         #<%- widgetid %> .jtc-agenda-day-title.is-today .jtc-agenda-day-number {
                             width: 26px;
                             height: 26px;
                             display: inline-grid;
                             place-items: center;
                             color: var(--jtc-today-text);
                             background: var(--jtc-today);
                             border-radius: 50%;
                         }
                      
                         #<%- widgetid %> .jtc-all-day {
                             max-height: 95px;
                             overflow-y: auto;
                             border-bottom: 1px solid var(--jtc-border);
                         }
                      
                         #<%- widgetid %> .jtc-all-day-label {
                             color: var(--jtc-muted);
                             font-size: 9px;
                             text-align: right;
                         }
                      
                         #<%- widgetid %> .jtc-all-day-cell {
                             min-height: 31px;
                         }
                      
                         #<%- widgetid %> .jtc-all-day-event {
                             overflow: hidden;
                             margin-bottom: 2px;
                             padding: 2px 4px;
                             border-radius: 4px;
                             font-size: 9px;
                             line-height: 14px;
                             text-overflow: ellipsis;
                             white-space: nowrap;
                         }
                      
                         #<%- widgetid %> .jtc-time-scroll {
                             min-height: 0;
                             flex: 1 1 auto;
                             overflow: auto;
                             scrollbar-color: var(--jtc-scrollbar) transparent;
                             scrollbar-width: thin;
                         }
                      
                         #<%- widgetid %> .jtc-time-grid {
                             position: relative;
                             display: grid;
                             grid-template-columns: 58px repeat(var(--jtc-day-columns), minmax(90px, 1fr));
                             min-width: max-content;
                         }
                      
                         #<%- widgetid %> .jtc-time-axis,
                         #<%- widgetid %> .jtc-day-column {
                             position: relative;
                             min-width: 0;
                         }
                      
                         #<%- widgetid %> .jtc-time-axis {
                             background: var(--jtc-panel);
                             border-right: 1px solid var(--jtc-border);
                         }
                      
                         #<%- widgetid %> .jtc-time-label {
                             position: absolute;
                             right: 7px;
                             color: var(--jtc-muted);
                             font-size: 9px;
                             transform: translateY(-50%);
                         }
                      
                         #<%- widgetid %> .jtc-day-column {
                             background-color: var(--jtc-background);
                             border-right: 1px solid var(--jtc-border);
                             background-image: repeating-linear-gradient(
                                 to bottom,
                                 transparent 0,
                                 transparent calc(var(--jtc-interval-height) - 1px),
                                 var(--jtc-border) calc(var(--jtc-interval-height) - 1px),
                                 var(--jtc-border) var(--jtc-interval-height)
                             );
                         }
                      
                         #<%- widgetid %> .jtc-day-column.is-weekend {
                             box-shadow: inset 0 0 0 9999px var(--jtc-weekend);
                         }
                      
                         #<%- widgetid %> .jtc-time-event {
                             position: absolute;
                             z-index: 2;
                             overflow: hidden;
                             padding: 3px 5px;
                             border-left: 3px solid rgba(255, 255, 255, 0.5);
                             border-radius: 5px;
                             font-size: 10px;
                             line-height: 1.25;
                             box-shadow: 0 2px 7px rgba(0, 0, 0, 0.2);
                         }
                      
                         #<%- widgetid %> .jtc-time-event-time {
                             display: block;
                             margin-bottom: 2px;
                             font-size: 8px;
                             opacity: 0.85;
                         }
                      
                         #<%- widgetid %> .jtc-now-line {
                             position: absolute;
                             z-index: 3;
                             right: 0;
                             left: 0;
                             height: 2px;
                             background: var(--jtc-time-line);
                             pointer-events: none;
                         }
                      
                         #<%- widgetid %> .jtc-now-line::before {
                             position: absolute;
                             top: -4px;
                             left: -4px;
                             width: 9px;
                             height: 9px;
                             background: var(--jtc-time-line);
                             border-radius: 50%;
                             content: "";
                         }
                      
                         #<%- widgetid %> .jtc-empty {
                             height: 100%;
                             display: grid;
                             place-items: center;
                             padding: 24px;
                             color: var(--jtc-muted);
                             text-align: center;
                         }
                      
                         @media (max-width: 700px) {
                             #<%- widgetid %> .jtc-controls {
                                 flex-wrap: wrap;
                             }
                      
                             #<%- widgetid %> .jtc-title {
                                 order: -1;
                                 flex-basis: 100%;
                                 padding-bottom: 3px;
                             }
                      
                             #<%- widgetid %> .jtc-control-group {
                                 flex: 1 1 auto;
                                 justify-content: center;
                             }
                      
                             #<%- widgetid %> .jtc-button-label {
                                 display: none;
                             }
                      
                             #<%- widgetid %> .jtc-month-cell {
                                 padding: 3px;
                             }
                      
                             #<%- widgetid %> .jtc-month-event {
                                 padding: 1px 3px;
                                 font-size: 8px;
                             }
                         }
                      </style>
                      
                      <div class="jtc-calendar" data-calendar-key="<%- calendarGlobalKey %>">
                         <div class="jtc-controls">
                             <div class="jtc-control-group">
                                 <button class="jtc-button" type="button" data-action="previous" title="Previous">&#x276E;</button>
                                 <button class="jtc-button" type="button" data-action="today" title="Today">
                                     <span>&#x25C9;</span>
                                     <% if (calendarConfig.showControlLabels) { %>
                                         <span class="jtc-button-label">Today</span>
                                     <% } %>
                                 </button>
                                 <button class="jtc-button" type="button" data-action="next" title="Next">&#x276F;</button>
                             </div>
                      
                             <div class="jtc-title" data-role="title"></div>
                      
                             <div class="jtc-control-group">
                                 <button class="jtc-button" type="button" data-view="month">
                                     <span>&#x25A6;</span>
                                     <% if (calendarConfig.showControlLabels) { %>
                                         <span class="jtc-button-label">Month</span>
                                     <% } %>
                                 </button>
                                 <button class="jtc-button" type="button" data-view="week">
                                     <span>&#x2637;</span>
                                     <% if (calendarConfig.showControlLabels) { %>
                                         <span class="jtc-button-label">Week</span>
                                     <% } %>
                                 </button>
                                 <button class="jtc-button" type="button" data-view="day">
                                     <span>&#x25A1;</span>
                                     <% if (calendarConfig.showControlLabels) { %>
                                         <span class="jtc-button-label">Day</span>
                                     <% } %>
                                 </button>
                             </div>
                         </div>
                      
                         <% if (calendarError) { %>
                             <div class="jtc-status"><%- calendarError %></div>
                         <% } %>
                      
                         <div class="jtc-body" data-role="body"></div>
                      </div>
                      
                      <script>
                         (function () {
                             "use strict";
                      
                             var root = document.querySelector(
                                 "#<%- widgetid %> .jtc-calendar[data-calendar-key='<%- calendarGlobalKey %>']"
                             );
                      
                             var runtime = window["<%- calendarGlobalKey %>"];
                      
                             if (!root || !runtime) {
                                 return;
                             }
                      
                             var config = runtime.config;
                             var body = root.querySelector("[data-role='body']");
                             var title = root.querySelector("[data-role='title']");
                             var view = config.initialView;
                             var focusDate = startOfDay(new Date());
                             var listeners = [];
                             var touchStartX = null;
                             var parsedEvents = parseEvents(runtime.events);
                      
                             root.style.setProperty(
                                 "--jtc-month-columns",
                                 "repeat(" + config.weekdays.length + ", minmax(0, 1fr))"
                             );
                             root.style.setProperty("--jtc-day-columns", String(config.weekdays.length));
                             root.style.setProperty("--jtc-interval-height", config.intervalHeight + "px");
                      
                             function addListener(target, eventName, handler, options) {
                                 target.addEventListener(eventName, handler, options);
                                 listeners.push(
                                     function () {
                                         target.removeEventListener(eventName, handler, options);
                                     }
                                 );
                             }
                      
                             function startOfDay(date) {
                                 return new Date(date.getFullYear(), date.getMonth(), date.getDate());
                             }
                      
                             function copyDate(date) {
                                 return new Date(date.getTime());
                             }
                      
                             function addDays(date, count) {
                                 var result = copyDate(date);
                                 result.setDate(result.getDate() + count);
                                 return result;
                             }
                      
                             function addMonths(date, count) {
                                 var result = new Date(date.getFullYear(), date.getMonth() + count, 1);
                                 return result;
                             }
                      
                             function sameDay(left, right) {
                                 return (
                                     left.getFullYear() === right.getFullYear() &&
                                     left.getMonth() === right.getMonth() &&
                                     left.getDate() === right.getDate()
                                 );
                             }
                      
                             function isDateOnly(value) {
                                 return /^\d\d\d\d-\d\d-\d\d$/.test(String(value || ""));
                             }
                      
                             function parseDate(value) {
                                 var text = String(value || "");
                                 var dateOnlyMatch = /^(\d\d\d\d)-(\d\d)-(\d\d)$/.exec(text);
                      
                                 if (dateOnlyMatch) {
                                     return new Date(
                                         Number(dateOnlyMatch[1]),
                                         Number(dateOnlyMatch[2]) - 1,
                                         Number(dateOnlyMatch[3])
                                     );
                                 }
                      
                                 var parsed = new Date(text);
                                 return Number.isNaN(parsed.getTime()) ? null : parsed;
                             }
                      
                             function parseEvents(events) {
                                 var result = [];
                      
                                 for (var index = 0; index < events.length; index += 1) {
                                     var source = events[index];
                                     var start = parseDate(source.start);
                                     var end = parseDate(source.end);
                      
                                     if (!start || !end) {
                                         continue;
                                     }
                      
                                     if (end.getTime() < start.getTime()) {
                                         end = copyDate(start);
                                     }
                      
                                     result.push(
                                         {
                                             allDay: isDateOnly(source.start) && isDateOnly(source.end),
                                             color: source.color || "#3b82f6",
                                             colorText: source.colorText || "#ffffff",
                                             end: end,
                                             name: String(source.name),
                                             start: start
                                         }
                                     );
                                 }
                      
                                 return result;
                             }
                      
                             function isVisibleWeekday(date) {
                                 return config.weekdays.indexOf(date.getDay()) !== -1;
                             }
                      
                             function nextVisibleDate(date, direction) {
                                 var result = addDays(date, direction);
                                 var guard = 0;
                      
                                 while (!isVisibleWeekday(result) && guard < 8) {
                                     result = addDays(result, direction);
                                     guard += 1;
                                 }
                      
                                 return result;
                             }
                      
                             function startOfConfiguredWeek(date) {
                                 var result = startOfDay(date);
                                 var targetDay = config.weekdays[0];
                                 var guard = 0;
                      
                                 while (result.getDay() !== targetDay && guard < 8) {
                                     result = addDays(result, -1);
                                     guard += 1;
                                 }
                      
                                 return result;
                             }
                      
                             function getVisibleWeek(date) {
                                 var days = [];
                                 var current = startOfConfiguredWeek(date);
                      
                                 for (var index = 0; index < config.weekdays.length; index += 1) {
                                     if (index > 0) {
                                         current = nextVisibleDate(current, 1);
                                     }
                      
                                     days.push(copyDate(current));
                                 }
                      
                                 return days;
                             }
                      
                             function getWeekNumber(date) {
                                 var target = startOfDay(date);
                                 var dayNumber = (target.getDay() + 6) % 7;
                                 target.setDate(target.getDate() - dayNumber + 3);
                                 var firstThursday = new Date(target.getFullYear(), 0, 4);
                                 var firstDayNumber = (firstThursday.getDay() + 6) % 7;
                                 firstThursday.setDate(firstThursday.getDate() - firstDayNumber + 3);
                                 return 1 + Math.round((target.getTime() - firstThursday.getTime()) / 604800000);
                             }
                      
                             function formatDate(date, options) {
                                 return new Intl.DateTimeFormat(config.locale, options).format(date);
                             }
                      
                             function formatTime(date) {
                                 return formatDate(
                                     date,
                                     {
                                         hour: "2-digit",
                                         minute: "2-digit"
                                     }
                                 );
                             }
                      
                             function eventTouchesDay(event, date) {
                                 var dayStart = startOfDay(date);
                                 var dayEnd = addDays(dayStart, 1);
                      
                                 if (event.allDay) {
                                     return event.start.getTime() < dayEnd.getTime() && event.end.getTime() >= dayStart.getTime();
                                 }
                      
                                 return event.start.getTime() < dayEnd.getTime() && event.end.getTime() > dayStart.getTime();
                             }
                      
                             function createElement(tagName, className, text) {
                                 var element = document.createElement(tagName);
                      
                                 if (className) {
                                     element.className = className;
                                 }
                      
                                 if (text !== undefined) {
                                     element.textContent = text;
                                 }
                      
                                 return element;
                             }
                      
                             function applyEventColors(element, event) {
                                 element.style.backgroundColor = event.color;
                                 element.style.color = event.colorText;
                             }
                      
                             function createMonthEvent(event) {
                                 var element = createElement("div", "jtc-month-event", event.name);
                                 element.title = event.name + " | " + formatTime(event.start);
                                 applyEventColors(element, event);
                                 return element;
                             }
                      
                             function setView(nextView, selectedDate) {
                                 if (nextView !== "month" && nextView !== "week" && nextView !== "day") {
                                     return;
                                 }
                      
                                 view = nextView;
                      
                                 if (selectedDate) {
                                     focusDate = startOfDay(selectedDate);
                                 }
                      
                                 render();
                             }
                      
                             function updateViewButtons() {
                                 var buttons = root.querySelectorAll("[data-view]");
                      
                                 for (var index = 0; index < buttons.length; index += 1) {
                                     buttons[index].classList.toggle("is-active", buttons[index].dataset.view === view);
                                 }
                             }
                      
                             function updateTitle() {
                                 if (view === "month") {
                                     title.textContent = formatDate(
                                         focusDate,
                                         {
                                             month: "long",
                                             year: "numeric"
                                         }
                                     );
                                     return;
                                 }
                      
                                 if (view === "day") {
                                     title.textContent = formatDate(
                                         focusDate,
                                         {
                                             day: "numeric",
                                             month: "long",
                                             weekday: "long",
                                             year: "numeric"
                                         }
                                     );
                                     return;
                                 }
                      
                                 var week = getVisibleWeek(focusDate);
                                 title.textContent =
                                     formatDate(
                                         week[0],
                                         {
                                             day: "numeric",
                                             month: "short"
                                         }
                                     ) +
                                     " - " +
                                     formatDate(
                                         week[week.length - 1],
                                         {
                                             day: "numeric",
                                             month: "short",
                                             year: "numeric"
                                         }
                                     );
                             }
                      
                             function renderMonth() {
                                 var month = createElement("div", "jtc-month");
                                 var header = createElement("div", "jtc-month-header");
                                 var grid = createElement("div", "jtc-month-grid");
                                 var monthStart = new Date(focusDate.getFullYear(), focusDate.getMonth(), 1);
                                 var current = startOfConfiguredWeek(monthStart);
                                 var today = startOfDay(new Date());
                      
                                 for (var headerIndex = 0; headerIndex < config.weekdays.length; headerIndex += 1) {
                                     var sampleDate = new Date(2023, 0, 1 + config.weekdays[headerIndex]);
                                     var weekday = createElement(
                                         "div",
                                         "jtc-weekday",
                                         formatDate(
                                             sampleDate,
                                             {
                                                 weekday: "short"
                                             }
                                         ).replace(".", "")
                                     );
                                     header.appendChild(weekday);
                                 }
                      
                                 for (var cellIndex = 0; cellIndex < config.weekdays.length * 6; cellIndex += 1) {
                                     if (cellIndex > 0) {
                                         current = nextVisibleDate(current, 1);
                                     }
                      
                                     var cellDate = copyDate(current);
                                     var cell = createElement("div", "jtc-month-cell");
                                     var dateRow = createElement("div", "jtc-date-row");
                                     var number = createElement("span", "jtc-date-number", String(cellDate.getDate()));
                      
                                     cell.dataset.date = String(cellDate.getTime());
                                     cell.classList.toggle("is-outside", cellDate.getMonth() !== focusDate.getMonth());
                                     cell.classList.toggle("is-weekend", cellDate.getDay() === 0 || cellDate.getDay() === 6);
                                     number.classList.toggle("is-today", sameDay(cellDate, today));
                                     dateRow.appendChild(number);
                      
                                     if (config.showWeekNumbers && cellIndex % config.weekdays.length === 0) {
                                         dateRow.appendChild(
                                             createElement("span", "jtc-week-number", "W" + getWeekNumber(cellDate))
                                         );
                                     }
                      
                                     cell.appendChild(dateRow);
                      
                                     var eventContainer = createElement("div", "jtc-month-events");
                                     var dayEvents = parsedEvents.filter(
                                         function (event) {
                                             return eventTouchesDay(event, cellDate);
                                         }
                                     );
                                     var visibleCount = Math.min(dayEvents.length, 3);
                      
                                     for (var eventIndex = 0; eventIndex < visibleCount; eventIndex += 1) {
                                         eventContainer.appendChild(createMonthEvent(dayEvents[eventIndex]));
                                     }
                      
                                     if (dayEvents.length > visibleCount) {
                                         eventContainer.appendChild(
                                             createElement("div", "jtc-more", "+" + (dayEvents.length - visibleCount) + " more")
                                         );
                                     }
                      
                                     cell.appendChild(eventContainer);
                                     grid.appendChild(cell);
                                 }
                      
                                 month.appendChild(header);
                                 month.appendChild(grid);
                                 body.appendChild(month);
                             }
                      
                             function getAgendaDays() {
                                 if (view === "day") {
                                     return [copyDate(focusDate)];
                                 }
                      
                                 return getVisibleWeek(focusDate);
                             }
                      
                             function renderAgenda() {
                                 var agenda = createElement("div", "jtc-agenda");
                                 var header = createElement("div", "jtc-agenda-header");
                                 var allDay = createElement("div", "jtc-all-day");
                                 var scroll = createElement("div", "jtc-time-scroll");
                                 var grid = createElement("div", "jtc-time-grid");
                                 var axis = createElement("div", "jtc-time-axis");
                                 var days = getAgendaDays();
                                 var intervalCount =
                                     ((config.timeAxisEndHour - config.timeAxisStartHour) * 60) /
                                     config.intervalMinutes;
                                 var gridHeight = intervalCount * config.intervalHeight;
                                 var today = startOfDay(new Date());
                      
                                 agenda.style.setProperty("--jtc-day-columns", String(days.length));
                                 header.appendChild(createElement("div", "jtc-agenda-corner"));
                      
                                 for (var headerIndex = 0; headerIndex < days.length; headerIndex += 1) {
                                     var headerDay = days[headerIndex];
                                     var dayTitle = createElement("div", "jtc-agenda-day-title");
                                     dayTitle.dataset.date = String(headerDay.getTime());
                                     dayTitle.classList.toggle("is-today", sameDay(headerDay, today));
                                     dayTitle.appendChild(
                                         createElement(
                                             "div",
                                             "jtc-agenda-day-name",
                                             formatDate(
                                                 headerDay,
                                                 {
                                                     weekday: "short"
                                                 }
                                             ).replace(".", "")
                                         )
                                     );
                                     dayTitle.appendChild(
                                         createElement("div", "jtc-agenda-day-number", String(headerDay.getDate()))
                                     );
                                     header.appendChild(dayTitle);
                                 }
                      
                                 allDay.appendChild(createElement("div", "jtc-all-day-label", "All day"));
                      
                                 for (var allDayIndex = 0; allDayIndex < days.length; allDayIndex += 1) {
                                     var allDayCell = createElement("div", "jtc-all-day-cell");
                                     var allDayDate = days[allDayIndex];
                      
                                     for (var parsedIndex = 0; parsedIndex < parsedEvents.length; parsedIndex += 1) {
                                         var allDayEvent = parsedEvents[parsedIndex];
                      
                                         if (allDayEvent.allDay && eventTouchesDay(allDayEvent, allDayDate)) {
                                             var allDayElement = createElement(
                                                 "div",
                                                 "jtc-all-day-event",
                                                 allDayEvent.name
                                             );
                                             allDayElement.title = allDayEvent.name;
                                             applyEventColors(allDayElement, allDayEvent);
                                             allDayCell.appendChild(allDayElement);
                                         }
                                     }
                      
                                     allDay.appendChild(allDayCell);
                                 }
                      
                                 axis.style.height = gridHeight + "px";
                      
                                 for (var intervalIndex = 0; intervalIndex <= intervalCount; intervalIndex += 1) {
                                     var totalMinutes =
                                         config.timeAxisStartHour * 60 + intervalIndex * config.intervalMinutes;
                                     var hours = Math.floor(totalMinutes / 60);
                                     var minutes = totalMinutes % 60;
                                     var labelDate = new Date(2000, 0, 1, hours, minutes);
                                     var timeLabel = createElement("div", "jtc-time-label", formatTime(labelDate));
                                     timeLabel.style.top = intervalIndex * config.intervalHeight + "px";
                                     axis.appendChild(timeLabel);
                                 }
                      
                                 grid.appendChild(axis);
                      
                                 for (var dayIndex = 0; dayIndex < days.length; dayIndex += 1) {
                                     var day = days[dayIndex];
                                     var column = createElement("div", "jtc-day-column");
                                     column.style.height = gridHeight + "px";
                                     column.classList.toggle("is-weekend", day.getDay() === 0 || day.getDay() === 6);
                      
                                     var timedEvents = parsedEvents.filter(
                                         function (event) {
                                             return !event.allDay && eventTouchesDay(event, day);
                                         }
                                     );
                      
                                     for (var timedIndex = 0; timedIndex < timedEvents.length; timedIndex += 1) {
                                         var timedEvent = timedEvents[timedIndex];
                                         var dayStart = startOfDay(day);
                                         var visibleStart = new Date(
                                             day.getFullYear(),
                                             day.getMonth(),
                                             day.getDate(),
                                             config.timeAxisStartHour
                                         );
                                         var visibleEnd = new Date(
                                             day.getFullYear(),
                                             day.getMonth(),
                                             day.getDate(),
                                             config.timeAxisEndHour
                                         );
                                         var eventStart = new Date(
                                             Math.max(timedEvent.start.getTime(), dayStart.getTime(), visibleStart.getTime())
                                         );
                                         var eventEnd = new Date(
                                             Math.min(timedEvent.end.getTime(), addDays(dayStart, 1).getTime(), visibleEnd.getTime())
                                         );
                      
                                         if (eventEnd.getTime() <= eventStart.getTime()) {
                                             continue;
                                         }
                      
                                         var startMinutes =
                                             eventStart.getHours() * 60 +
                                             eventStart.getMinutes() -
                                             config.timeAxisStartHour * 60;
                                         var durationMinutes = (eventEnd.getTime() - eventStart.getTime()) / 60000;
                                         var top = (startMinutes / config.intervalMinutes) * config.intervalHeight;
                                         var height = Math.max(
                                             config.eventMinHeight,
                                             (durationMinutes / config.intervalMinutes) * config.intervalHeight
                                         );
                                         var eventElement = createElement("div", "jtc-time-event");
                                         eventElement.style.top = top + "px";
                                         eventElement.style.height = height + "px";
                                         eventElement.style.left = 3 + (timedIndex % 3) * 4 + "%";
                                         eventElement.style.right = 3 + ((timedIndex + 1) % 3) * 4 + "%";
                                         eventElement.title =
                                             timedEvent.name +
                                             " | " +
                                             formatTime(timedEvent.start) +
                                             " - " +
                                             formatTime(timedEvent.end);
                                         eventElement.appendChild(
                                             createElement(
                                                 "span",
                                                 "jtc-time-event-time",
                                                 formatTime(timedEvent.start) + " - " + formatTime(timedEvent.end)
                                             )
                                         );
                                         eventElement.appendChild(document.createTextNode(timedEvent.name));
                                         applyEventColors(eventElement, timedEvent);
                                         column.appendChild(eventElement);
                                     }
                      
                                     if (sameDay(day, today)) {
                                         var now = new Date();
                                         var nowMinutes =
                                             now.getHours() * 60 +
                                             now.getMinutes() -
                                             config.timeAxisStartHour * 60;
                      
                                         if (
                                             nowMinutes >= 0 &&
                                             nowMinutes <= (config.timeAxisEndHour - config.timeAxisStartHour) * 60
                                         ) {
                                             var nowLine = createElement("div", "jtc-now-line");
                                             nowLine.style.top =
                                                 (nowMinutes / config.intervalMinutes) * config.intervalHeight + "px";
                                             column.appendChild(nowLine);
                                         }
                                     }
                      
                                     grid.appendChild(column);
                                 }
                      
                                 scroll.appendChild(grid);
                                 agenda.appendChild(header);
                                 agenda.appendChild(allDay);
                                 agenda.appendChild(scroll);
                                 body.appendChild(agenda);
                      
                                 if (view === "day" || days.some(sameDay.bind(null, today))) {
                                     var currentMinutes =
                                         new Date().getHours() * 60 +
                                         new Date().getMinutes() -
                                         config.timeAxisStartHour * 60;
                                     var scrollTop =
                                         (currentMinutes / config.intervalMinutes) * config.intervalHeight -
                                         scroll.clientHeight / 3;
                                     scroll.scrollTop = Math.max(0, scrollTop);
                                 }
                             }
                      
                             function render() {
                                 body.replaceChildren();
                                 updateTitle();
                                 updateViewButtons();
                      
                                 if (view === "month") {
                                     renderMonth();
                                 } else {
                                     renderAgenda();
                                 }
                             }
                      
                             function navigate(direction) {
                                 if (view === "month") {
                                     focusDate = addMonths(focusDate, direction);
                                 } else if (view === "week") {
                                     focusDate = addDays(focusDate, direction * 7);
                                 } else {
                                     focusDate = addDays(focusDate, direction);
                                 }
                      
                                 render();
                             }
                      
                             function onClick(event) {
                                 var actionButton = event.target.closest("[data-action]");
                                 var viewButton = event.target.closest("[data-view]");
                                 var dateElement = event.target.closest("[data-date]");
                      
                                 if (actionButton) {
                                     if (actionButton.dataset.action === "previous") {
                                         navigate(-1);
                                     } else if (actionButton.dataset.action === "next") {
                                         navigate(1);
                                     } else if (actionButton.dataset.action === "today") {
                                         focusDate = startOfDay(new Date());
                                         render();
                                     }
                      
                                     return;
                                 }
                      
                                 if (viewButton) {
                                     setView(viewButton.dataset.view, focusDate);
                                     return;
                                 }
                      
                                 if (dateElement) {
                                     var selectedDate = new Date(Number(dateElement.dataset.date));
                      
                                     if (view === "month") {
                                         setView(config.monthClickView, selectedDate);
                                     } else if (view === "week") {
                                         setView(config.weekClickView, selectedDate);
                                     } else {
                                         setView(config.dayClickView, selectedDate);
                                     }
                                 }
                             }
                      
                             function onTouchStart(event) {
                                 touchStartX = event.changedTouches[0].clientX;
                             }
                      
                             function onTouchEnd(event) {
                                 if (touchStartX === null) {
                                     return;
                                 }
                      
                                 var distance = event.changedTouches[0].clientX - touchStartX;
                                 touchStartX = null;
                      
                                 if (Math.abs(distance) < 50) {
                                     return;
                                 }
                      
                                 navigate(distance < 0 ? 1 : -1);
                             }
                      
                             addListener(root, "click", onClick);
                             addListener(root, "touchstart", onTouchStart, {
                                 passive: true
                             });
                             addListener(root, "touchend", onTouchEnd, {
                                 passive: true
                             });
                      
                             render();
                      
                             if (typeof window.__visJsonTemplateRegisterCleanup === "function") {
                                 window.__visJsonTemplateRegisterCleanup(
                                     function () {
                                         for (var index = 0; index < listeners.length; index += 1) {
                                             listeners[index]();
                                         }
                      
                                     }
                                 );
                             }
                         }());
                      </script>
                      
                      

                      Ergebnis ist nun
                      b289fc33-148b-48b0-92a1-f5a23299ddd8-image.jpeg
                      58ed2571-3d3e-42b1-9e35-6b546b7a3853-image.jpeg
                      f6f9a693-1b1c-4da9-b71f-44f64fde0c72-image.jpeg

                      in der wochen und tagesansicht werden dann noch oben die ganztages events angezeigt

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

                      1 Antwort Letzte Antwort
                      0
                      • skvarelS Online
                        skvarelS Online
                        skvarel
                        Developer
                        schrieb zuletzt editiert von skvarel
                        #73

                        @jkvarel hat ein Terminkalender-Widget in unseren VIS2 Adapter gesetzt.

                        Recht einfach gehalten aber wie gewohnt, eine Menge Einstellmöglichkeiten, wie bei all unsren Widgets.

                        Das Widget greift direkt auf die json vom iCal Adapter zu.

                        a5f248ae-8686-461a-b85c-11a55cd7aa8d-image.jpeg

                        609ab126-f602-46a1-b5df-50bcbbec86dd-image.jpeg

                        Bereits verschiedene Ansichten auswählbar:
                        bc889f7b-927c-44ac-a4a1-34dcbfc0b043-image.jpeg

                        #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
                        • skvarelS Online
                          skvarelS Online
                          skvarel
                          Developer
                          schrieb zuletzt editiert von
                          #74

                          Das geht natürlich mit jedem iCal Kalender.

                          Hier mal meine Termine, allerdings noch ohne die Farbeinstellungen je Termin

                          19eea0cf-4324-401c-afad-29b320c3b46d-image.jpeg

                          #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

                          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

                          429

                          Online

                          33.0k

                          Benutzer

                          83.5k

                          Themen

                          1.3m

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

                          • Du hast noch kein Konto? Registrieren

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