(function () { "use strict"; let config = {}; let monthStripResizeFrame = null; let initialized = false; function getCalendar() { return config.calendar || null; } function getMonthStripEl() { return config.monthStripEl || null; } function getCurrentGeo() { return config.getCurrentGeo?.() || null; } function getFormattingUi() { return window.TarotCalendarFormatting || {}; } function normalizeCalendarDateLike(value) { const formattingUi = getFormattingUi(); if (typeof formattingUi.normalizeDateLike === "function") { return formattingUi.normalizeDateLike(value); } if (value instanceof Date) { return value; } if (value && typeof value.getTime === "function") { return new Date(value.getTime()); } return new Date(value); } function clamp(value, min, max) { return Math.min(max, Math.max(min, value)); } function lerp(start, end, t) { return start + (end - start) * t; } function lerpRgb(from, to, t) { return [ Math.round(lerp(from[0], to[0], t)), Math.round(lerp(from[1], to[1], t)), Math.round(lerp(from[2], to[2], t)) ]; } function rgbString(rgb) { return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`; } function getActiveGeoForRuler() { const currentGeo = getCurrentGeo(); if (currentGeo) { return currentGeo; } try { return config.parseGeoInput?.() || null; } catch { return null; } } function buildSunRulerGradient(geo, date) { if (!window.SunCalc || !geo || !date) { return null; } const dayStart = new Date(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0, 0); const sampleCount = 48; const samples = []; for (let index = 0; index <= sampleCount; index += 1) { const sampleDate = new Date(dayStart.getTime() + index * 30 * 60 * 1000); const position = window.SunCalc.getPosition(sampleDate, geo.latitude, geo.longitude); const altitudeDeg = (position.altitude * 180) / Math.PI; samples.push(altitudeDeg); } const maxAltitude = Math.max(...samples); const NIGHT = [6, 7, 10]; const PRE_DAWN = [22, 26, 38]; const SUN_RED = [176, 45, 36]; const SUN_ORANGE = [246, 133, 54]; const SKY_BLUE = [58, 134, 255]; const nightFloor = -8; const twilightEdge = -2; const redToOrangeEdge = 2; const orangeToBlueEdge = 8; const daylightRange = Math.max(1, maxAltitude - orangeToBlueEdge); const stops = samples.map((altitudeDeg, index) => { let color; if (altitudeDeg <= nightFloor) { color = NIGHT; } else if (altitudeDeg <= twilightEdge) { const t = clamp((altitudeDeg - nightFloor) / (twilightEdge - nightFloor), 0, 1); color = lerpRgb(NIGHT, PRE_DAWN, t); } else if (altitudeDeg <= redToOrangeEdge) { const t = clamp((altitudeDeg - twilightEdge) / (redToOrangeEdge - twilightEdge), 0, 1); color = lerpRgb(PRE_DAWN, SUN_RED, t); } else if (altitudeDeg <= orangeToBlueEdge) { const t = clamp((altitudeDeg - redToOrangeEdge) / (orangeToBlueEdge - redToOrangeEdge), 0, 1); color = lerpRgb(SUN_RED, SUN_ORANGE, t); } else { const t = clamp((altitudeDeg - orangeToBlueEdge) / daylightRange, 0, 1); color = lerpRgb(SUN_ORANGE, SKY_BLUE, t); } const pct = ((index / sampleCount) * 100).toFixed(2); return `${rgbString(color)} ${pct}%`; }); return `linear-gradient(to bottom, ${stops.join(", ")})`; } function applySunRulerGradient(referenceDate = new Date()) { const geo = getActiveGeoForRuler(); if (!geo) { return; } const gradient = buildSunRulerGradient(geo, referenceDate); if (!gradient) { return; } const rulerColumns = document.querySelectorAll(".toastui-calendar-timegrid-time-column"); rulerColumns.forEach((column) => { column.style.backgroundImage = gradient; column.style.backgroundRepeat = "no-repeat"; column.style.backgroundSize = "100% 100%"; }); } function getMoonPhaseGlyph(phaseName) { if (phaseName === "New Moon") return "🌑"; if (phaseName === "Waxing Crescent") return "🌒"; if (phaseName === "First Quarter") return "🌓"; if (phaseName === "Waxing Gibbous") return "🌔"; if (phaseName === "Full Moon") return "🌕"; if (phaseName === "Waning Gibbous") return "🌖"; if (phaseName === "Last Quarter") return "🌗"; return "🌘"; } function applyDynamicNowIndicatorVisual(referenceDate = new Date()) { const currentGeo = getCurrentGeo(); if (!currentGeo || !window.SunCalc) { return; } const labelEl = document.querySelector( ".toastui-calendar-timegrid-time-column .toastui-calendar-timegrid-current-time" ); const markerEl = document.querySelector( ".toastui-calendar-timegrid .toastui-calendar-timegrid-now-indicator .toastui-calendar-timegrid-now-indicator-marker" ); if (!labelEl || !markerEl) { return; } const sunPosition = window.SunCalc.getPosition(referenceDate, currentGeo.latitude, currentGeo.longitude); const sunAltitudeDeg = (sunPosition.altitude * 180) / Math.PI; const isSunMode = sunAltitudeDeg >= -4; let icon = "☀️"; let visualKey = "sun-0"; labelEl.classList.remove("is-sun", "is-moon"); markerEl.classList.remove("is-sun", "is-moon"); if (isSunMode) { const intensity = clamp((sunAltitudeDeg + 4) / 70, 0, 1); const intensityPercent = Math.round(intensity * 100); icon = "☀️"; visualKey = `sun-${intensityPercent}`; labelEl.classList.add("is-sun"); markerEl.classList.add("is-sun"); labelEl.style.setProperty("--sun-glow-size", `${Math.round(8 + intensity * 16)}px`); labelEl.style.setProperty("--sun-glow-alpha", (0.35 + intensity * 0.55).toFixed(2)); markerEl.style.setProperty("--sun-marker-glow-size", `${Math.round(10 + intensity * 24)}px`); markerEl.style.setProperty("--sun-marker-ray-opacity", (0.45 + intensity * 0.5).toFixed(2)); labelEl.title = `Sun altitude ${sunAltitudeDeg.toFixed(1)}°`; } else { const moonIllum = window.SunCalc.getMoonIllumination(referenceDate); const moonPct = Math.round(moonIllum.fraction * 100); const moonPhaseName = config.getMoonPhaseName?.(moonIllum.phase) || "Waning Crescent"; icon = getMoonPhaseGlyph(moonPhaseName); visualKey = `moon-${moonPct}-${moonPhaseName}`; labelEl.classList.add("is-moon"); markerEl.classList.add("is-moon"); labelEl.style.setProperty("--moon-glow-alpha", (0.2 + moonIllum.fraction * 0.45).toFixed(2)); markerEl.style.setProperty("--moon-glow-alpha", (0.2 + moonIllum.fraction * 0.45).toFixed(2)); labelEl.title = `${moonPhaseName} (${moonPct}%)`; } if (labelEl.dataset.celestialKey !== visualKey) { labelEl.innerHTML = [ '', `${icon}`, "" ].join(""); labelEl.dataset.celestialKey = visualKey; } } function getVisibleWeekDates() { const calendar = getCalendar(); if (!calendar || typeof calendar.getDateRangeStart !== "function") { return []; } const rangeStart = calendar.getDateRangeStart(); if (!rangeStart) { return []; } const startDateLike = normalizeCalendarDateLike(rangeStart); const startDate = new Date( startDateLike.getFullYear(), startDateLike.getMonth(), startDateLike.getDate(), 0, 0, 0, 0 ); return Array.from({ length: 7 }, (_, dayOffset) => { const day = new Date(startDate); day.setDate(startDate.getDate() + dayOffset); return day; }); } function buildMonthSpans(days) { if (!Array.isArray(days) || days.length === 0) { return []; } const monthFormatter = new Intl.DateTimeFormat(undefined, { month: "long", year: "numeric" }); const spans = []; let currentStart = 1; let currentMonth = days[0].getMonth(); let currentYear = days[0].getFullYear(); for (let index = 1; index <= days.length; index += 1) { const day = days[index]; const monthChanged = !day || day.getMonth() !== currentMonth || day.getFullYear() !== currentYear; if (!monthChanged) { continue; } const spanEnd = index; spans.push({ start: currentStart, end: spanEnd, label: monthFormatter.format(new Date(currentYear, currentMonth, 1)) }); if (day) { currentStart = index + 1; currentMonth = day.getMonth(); currentYear = day.getFullYear(); } } return spans; } function syncMonthStripGeometry() { const monthStripEl = getMonthStripEl(); if (!monthStripEl) { return; } const calendarEl = document.getElementById("calendar"); if (!calendarEl) { return; } const dayNameItems = calendarEl.querySelectorAll( ".toastui-calendar-week-view-day-names .toastui-calendar-day-name-item.toastui-calendar-week" ); if (dayNameItems.length < 7) { monthStripEl.style.paddingLeft = "0"; monthStripEl.style.paddingRight = "0"; return; } const calendarRect = calendarEl.getBoundingClientRect(); const firstRect = dayNameItems[0].getBoundingClientRect(); const lastRect = dayNameItems[6].getBoundingClientRect(); const leftPad = Math.max(0, firstRect.left - calendarRect.left); const rightPad = Math.max(0, calendarRect.right - lastRect.right); monthStripEl.style.paddingLeft = `${leftPad}px`; monthStripEl.style.paddingRight = `${rightPad}px`; } function updateMonthStrip() { const monthStripEl = getMonthStripEl(); if (!monthStripEl) { return; } const days = getVisibleWeekDates(); const spans = buildMonthSpans(days); monthStripEl.replaceChildren(); if (!spans.length) { return; } const trackEl = document.createElement("div"); trackEl.className = "month-strip-track"; spans.forEach((span) => { const segmentEl = document.createElement("div"); segmentEl.className = "month-strip-segment"; segmentEl.style.gridColumn = `${span.start} / ${span.end + 1}`; segmentEl.textContent = span.label; trackEl.appendChild(segmentEl); }); monthStripEl.appendChild(trackEl); syncMonthStripGeometry(); } function applyTimeFormatTemplates() { const calendar = getCalendar(); const formattingUi = getFormattingUi(); if (!calendar) { return; } calendar.setOptions({ template: formattingUi.createCalendarTemplates?.() || {} }); calendar.render(); requestAnimationFrame(() => { formattingUi.forceAxisLabelFormat?.(); applySunRulerGradient(); applyDynamicNowIndicatorVisual(); updateMonthStrip(); requestAnimationFrame(() => { formattingUi.forceAxisLabelFormat?.(); applySunRulerGradient(); applyDynamicNowIndicatorVisual(); updateMonthStrip(); }); }); } function bindWindowResize() { window.addEventListener("resize", () => { if (monthStripResizeFrame) { cancelAnimationFrame(monthStripResizeFrame); } monthStripResizeFrame = requestAnimationFrame(() => { monthStripResizeFrame = null; updateMonthStrip(); }); }); } function init(nextConfig = {}) { config = { ...config, ...nextConfig }; if (initialized) { return; } initialized = true; bindWindowResize(); } window.TarotCalendarVisuals = { ...(window.TarotCalendarVisuals || {}), init, applySunRulerGradient, applyDynamicNowIndicatorVisual, updateMonthStrip, applyTimeFormatTemplates }; })();