Lines Matching +full:star +full:- +full:light

6  * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
27 $this->Lexer->addSpecialPattern('\{\{calendar(?:[^\}]*)\}\}', $mode, 'plugin_calendar');
28 $this->Lexer->addSpecialPattern('\{\{eventlist(?:[^\}]*)\}\}', $mode, 'plugin_calendar');
29 $this->Lexer->addSpecialPattern('\{\{eventpanel(?:[^\}]*)\}\}', $mode, 'plugin_calendar');
37 $match = substr($match, 12, -2);
39 $match = substr($match, 13, -2);
41 $match = substr($match, 10, -2);
101 // Disable caching - theme can change via admin without page edit
102 $renderer->nocache();
105 $html = $this->renderStandaloneEventList($data);
107 $html = $this->renderEventPanelOnly($data);
109 $html = $this->renderStaticCalendar($data);
111 $html = $this->renderCompactCalendar($data);
114 $renderer->doc .= $html;
123 // Get theme - prefer inline theme= parameter, fall back to admin default
124 $theme = !empty($data['theme']) ? $data['theme'] : $this->getSidebarTheme();
125 $themeStyles = $this->getSidebarThemeStyles($theme);
126 $themeClass = 'calendar-theme-' . $theme;
135 $events = $this->loadEventsMultiNamespace($namespace, $year, $month);
137 $events = $this->loadEvents($namespace, $year, $month);
143 $prevMonth = $month - 1;
147 $prevYear--;
167 // Container - all styling via CSS variables
168-compact-container ' . $themeClass . '" id="' . $calId . '" data-namespace="' . htmlspecialchars($…
170 // Inject CSS variables for this calendar instance - all theming flows from here
173 --background-site: ' . $themeStyles['bg'] . ';
174 --background-alt: ' . $themeStyles['cell_bg'] . ';
175 --background-header: ' . $themeStyles['header_bg'] . ';
176 --text-primary: ' . $themeStyles['text_primary'] . ';
177 --text-dim: ' . $themeStyles['text_dim'] . ';
178 --text-bright: ' . $themeStyles['text_bright'] . ';
179 --border-color: ' . $themeStyles['grid_border'] . ';
180 --border-main: ' . $themeStyles['border'] . ';
181 --cell-bg: ' . $themeStyles['cell_bg'] . ';
182 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
183 --shadow-color: ' . $themeStyles['shadow'] . ';
184 --header-border: ' . $themeStyles['header_border'] . ';
185 --header-shadow: ' . $themeStyles['header_shadow'] . ';
186 --grid-bg: ' . $themeStyles['grid_bg'] . ';
187 --btn-text: ' . $btnTextColor . ';
188 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
189 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
190 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
191 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
192 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
193 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
194 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
196 … #event-search-' . $calId . '::placeholder { color: ' . $themeStyles['text_dim'] . '; opacity: 1; }
197 …#event-search-' . $calId . '::-webkit-input-placeholder { color: ' . $themeStyles['text_dim'] . ';…
198 …#event-search-' . $calId . '::-moz-placeholder { color: ' . $themeStyles['text_dim'] . '; opacity:…
199 …#event-search-' . $calId . ':-ms-input-placeholder { color: ' . $themeStyles['text_dim'] . '; opac…
203 $html .= '<script src="' . DOKU_BASE . 'lib/plugins/calendar/calendar-main.js"></script>';
209 …$html .= '<script type="application/json" id="events-data-' . $calId . '">' . json_encode($events)…
212 $html .= '<div class="calendar-compact-left">';
215 $html .= '<div class="calendar-compact-header">';
216 …$html .= '<button class="cal-nav-btn" onclick="navCalendar(\'' . $calId . '\', ' . $prevYear . ', …
217 …$html .= '<h3 class="calendar-month-picker" onclick="openMonthPicker(\'' . $calId . '\', ' . $year…
218 …$html .= '<button class="cal-nav-btn" onclick="navCalendar(\'' . $calId . '\', ' . $nextYear . ', …
219 …$html .= '<button class="cal-today-btn" onclick="jumpToToday(\'' . $calId . '\', \'' . $namespace …
222 // Calendar grid - day name headers as a separate div (avoids Firefox th height issues)
223 $html .= '<div class="calendar-day-headers">';
226 $html .= '<table class="calendar-compact-grid">';
244 $monthStart = new DateTime(sprintf('%04d-%02d-01', $year, $month));
245 $monthEnd = new DateTime(sprintf('%04d-%02d-%02d', $year, $month, $daysInMonth));
255 $currentKey = $current->format('Y-m-d');
258 $currentDate = DateTime::createFromFormat('Y-m-d', $currentKey);
259 … if ($currentDate && $currentDate->format('Y-m') === sprintf('%04d-%02d', $year, $month)) {
278 $current->modify('+1 day');
290 $html .= '<td class="cal-empty"></td>';
292 $dateKey = sprintf('%04d-%02d-%02d', $year, $month, $currentDay);
293 $isToday = ($dateKey === date('Y-m-d'));
296 $classes = 'cal-day';
297 if ($isToday) $classes .= ' cal-today';
298 if ($hasEvents) $classes .= ' cal-has-events';
300 …$html .= '<td class="' . $classes . '" data-date="' . $dateKey . '" tabindex="0" role="gridcell" a…
302 $dayNumClass = $isToday ? 'day-num day-num-today' : 'day-num';
313 if (empty($timeA) && !empty($timeB)) return -1;
322 $html .= '<div class="event-indicators">';
345 $barClass = empty($eventTime) ? 'event-bar-no-time' : 'event-bar-timed';
347 // Add classes for multi-day spanning
348 if (!$isFirstDay) $barClass .= ' event-bar-continues';
349 if (!$isLastDay) $barClass .= ' event-bar-continuing';
351 $barClass .= ' event-bar-important';
353 $barClass .= ' event-bar-has-star';
359 $html .= '<span class="event-bar ' . $barClass . '" ';
376 $html .= '</div>'; // End calendar-left
379 $html .= '<div class="calendar-compact-right">';
380 $html .= '<div class="event-list-header">';
381 $html .= '<div class="event-list-header-content">';
382 $html .= '<h4 id="eventlist-title-' . $calId . '">Events</h4>';
384 $html .= '<span class="namespace-badge">' . htmlspecialchars($namespace) . '</span>';
389 $html .= '<div class="event-search-container-inline">';
390 …$html .= '<input type="text" class="event-search-input-inline" id="event-search-' . $calId . '" pl…
391 …$html .= '<button class="event-search-clear-inline" id="search-clear-' . $calId . '" onclick="clea…
392 …$html .= '<button class="event-search-mode-inline" id="search-mode-' . $calId . '" onclick="toggle…
395 …$html .= '<button class="add-event-compact" onclick="openAddEvent(\'' . $calId . '\', \'' . $names…
398 $html .= '<div class="event-list-compact" id="eventlist-' . $calId . '">';
399 $html .= $this->renderEventListContent($events, $calId, $namespace, $themeStyles);
402 $html .= '</div>'; // End calendar-right
405 $html .= $this->renderEventDialog($calId, $namespace, $theme);
408 $html .= $this->renderMonthPicker($calId, $year, $month, $namespace, $theme, $themeStyles);
416 * Render a static/read-only calendar for presentation and printing
417 * No edit buttons, clean layout, print-friendly itinerary
429 $calId = 'static-cal-' . substr(md5($namespace . $year . $month . uniqid()), 0, 8);
432 … in_array($themeOverride, ['matrix', 'pink', 'purple', 'professional', 'wiki', 'dark', 'light'])) {
435 $theme = $this->getSidebarTheme();
437 $themeStyles = $this->getSidebarThemeStyles($theme);
440 $importantNsList = $this->getImportantNamespaces();
442 // Load events - check for multi-namespace or wildcard
445 $events = $this->loadEventsMultiNamespace($namespace, $year, $month);
447 $events = $this->loadEvents($namespace, $year, $month);
456 // Display title - custom or default month/year
460 $themeClass = 'static-theme-' . $theme;
463 …ar-static ' . $themeClass . '" id="' . $calId . '" data-year="' . $year . '" data-month="' . $mont…
466 $html .= '<div class="static-screen-view">';
469 $html .= '<div class="static-header">';
471 …$html .= '<button class="static-nav-btn" onclick="navStaticCalendar(\'' . $calId . '\', -1)" title…
473 $html .= '<h2 class="static-month-title">' . hsc($displayTitle) . '</h2>';
475 …$html .= '<button class="static-nav-btn" onclick="navStaticCalendar(\'' . $calId . '\', 1)" title=…
478 …$html .= '<button class="static-print-btn" onclick="printStaticCalendar(\'' . $calId . '\')" title…
483 $html .= '<table class="static-calendar-grid">';
502 $html .= '<td class="static-day-empty"></td>';
504 $dateKey = sprintf('%04d-%02d-%02d', $year, $month, $dayCount);
506 $isToday = ($dateKey === date('Y-m-d'));
509 $cellClass = 'static-day';
510 if ($isToday) $cellClass .= ' static-day-today';
511 if ($isWeekend) $cellClass .= ' static-day-weekend';
512 if (!empty($dayEvents)) $cellClass .= ' static-day-has-events';
515 $html .= '<div class="static-day-number">' . $dayCount . '</div>';
518 $html .= '<div class="static-day-events">';
535 // Build tooltip - plain text with basic formatting indicators
538 $tooltipText .= "\n�� " . $this->formatTime12Hour($time);
540 … $tooltipText .= ' - ' . $this->formatTime12Hour($event['endTime']);
554 $eventClass = 'static-event';
555 if ($isImportant) $eventClass .= ' static-event-important';
557 …$html .= '<div class="' . $eventClass . '" style="border-left-color: ' . $color . ';" title="' . h…
559 $html .= '<span class="static-event-star">⭐</span>';
562 … $html .= '<span class="static-event-time">' . $this->formatTime12Hour($time) . '</span> ';
564 $html .= '<span class="static-event-title">' . $title . '</span>';
582 $html .= '<div class="static-print-view">';
583 $html .= '<h2 class="static-print-title">' . hsc($displayTitle) . '</h2>';
586 …$html .= '<p class="static-print-namespace">' . $this->getLang('calendar_label') . ': ' . hsc($nam…
608 … $html .= '<p class="static-print-empty">' . $this->getLang('no_events_scheduled') . '</p>';
610 $html .= '<table class="static-itinerary">';
618 $dateDisplay = $dateObj->format('D, M j');
630 $rowClass = $isImportant ? 'static-itinerary-important' : '';
636 $html .= '<td class="static-itinerary-date">' . $dateDisplay . '</td>';
643 …$time = isset($event['time']) && $event['time'] ? $this->formatTime12Hour($event['time']) : $this-
645 $time .= ' - ' . $this->formatTime12Hour($event['endTime']);
647 $html .= '<td class="static-itinerary-time">' . $time . '</td>';
649 // Title with star for important
650 $html .= '<td class="static-itinerary-title">';
657 // Description - with formatting
658 … $desc = isset($event['description']) ? $this->renderDescription($event['description']) : '';
659 $html .= '<td class="static-itinerary-desc">' . $desc . '</td>';
676 * Format time to 12-hour format
684 $hour12 = $hour == 0 ? 12 : ($hour > 12 ? $hour - 12 : $hour);
705 return '<p class="no-events-msg">No events this month</p>';
710 $theme = $this->getSidebarTheme();
711 $themeStyles = $this->getSidebarThemeStyles($theme);
713 $theme = $this->getSidebarTheme();
727 $events = $this->checkTimeConflicts($events);
729 // Sort by date ascending (chronological order - oldest first)
738 // All-day events (no time) go to the TOP
739 if ($timeA === null && $timeB !== null) return -1; // A before B
741 if ($timeA === null && $timeB === null) return 0; // Both all-day, equal
750 $today = date('Y-m-d');
753 // Helper function to check if event is past (with 15-minute grace period for timed events)
765 // Event is today - check time with grace period
771 // Add 15-minute grace period
772 $eventDateTime->modify('+15 minutes');
777 // If time parsing fails, fall back to date-only comparison
786 // Build HTML for each event - separate past/completed from future
794 // Track first future/today event for auto-scroll
824 $renderedDescription = $this->renderDescription($description, $themeStyles);
826 // Convert to 12-hour format and handle time ranges
831 $displayTime = $timeObj->format('g:i A');
837 $displayTime .= ' - ' . $endTimeObj->format('g:i A');
846 // Use originalStartDate if this is a multi-month event continuation
849 $displayDate = $dateObj->format('D, M j'); // e.g., "Mon, Jan 24"
851 // Multi-day indicator
855 $multiDay = ' → ' . $endObj->format('D, M j');
858 $completedClass = $completed ? ' event-completed' : '';
859 // Don't grey out past due tasks - they need attention!
860 $pastClass = ($isPast && !$isPastDue) ? ' event-past' : '';
861 $pastDueClass = $isPastDue ? ' event-pastdue' : '';
862 … $firstFutureAttr = ($firstFutureEventId === $eventId) ? ' data-first-future="true"' : '';
876 $importantClass = $isImportantNs ? ' event-important' : '';
878 // For all themes: use CSS variables, only keep border-left-color as inline
880-compact-item' . $completedClass . $pastClass . $pastDueClass . $importantClass . '" data-event-id…
881 $eventHtml .= '<div class="event-info">';
883 $eventHtml .= '<div class="event-title-row">';
884 // Add star for important namespace events
886 $eventHtml .= '<span class="event-important-star" title="Important">⭐</span> ';
888 $eventHtml .= '<span class="event-title-compact">' . $title . '</span>';
894 $eventHtml .= '<div class="event-meta-compact">';
895 $eventHtml .= '<span class="event-date-time">' . $displayDate . $multiDay;
901 … class="event-pastdue-badge" style="background:' . $themeStyles['pastdue_color'] . ' !important; c…
903 …="event-today-badge" style="background:' . $themeStyles['border'] . ' !important; color:' . $theme…
905 // Add namespace badge - ALWAYS show if event has a namespace
912-namespace-badge" onclick="filterCalendarByNamespace(\'' . $calId . '\', \'' . htmlspecialchars($e…
923 … $startTimeFormatted = $startTimeObj ? $startTimeObj->format('g:i A') : $conflict['time'];
927 … $endTimeFormatted = $endTimeObj ? $endTimeObj->format('g:i A') : $conflict['endTime'];
928 … $conflictText .= ' (' . $startTimeFormatted . ' - ' . $endTimeFormatted . ')';
937 …$eventHtml .= ' <span class="event-conflict-badge" data-conflicts="' . $conflictJson . '" onmousee…
944 … $eventHtml .= '<div class="event-desc-compact">' . $renderedDescription . '</div>';
947 // Past events: render with display:none for click-to-expand
948 $eventHtml .= '<div class="event-meta-compact" style="display:none;">';
949 $eventHtml .= '<span class="event-date-time">' . $displayDate . $multiDay;
958-namespace-badge" onclick="filterCalendarByNamespace(\'' . $calId . '\', \'' . htmlspecialchars($e…
968 … $startTimeFormatted = $startTimeObj ? $startTimeObj->format('g:i A') : $conflict['time'];
972 … $endTimeFormatted = $endTimeObj ? $endTimeObj->format('g:i A') : $conflict['endTime'];
973 … $conflictText .= ' (' . $startTimeFormatted . ' - ' . $endTimeFormatted . ')';
982 …$eventHtml .= ' <span class="event-conflict-badge" data-conflicts="' . $conflictJson . '" onmousee…
989 …$eventHtml .= '<div class="event-desc-compact" style="display:none;">' . $renderedDescription . '<…
993 $eventHtml .= '</div>'; // event-info
998 $eventHtml .= '<div class="event-actions-compact">';
999 …$eventHtml .= '<button class="event-action-btn" onclick="deleteEvent(\'' . $calId . '\', \'' . $ev…
1000 …$eventHtml .= '<button class="event-action-btn" onclick="editEvent(\'' . $calId . '\', \'' . $even…
1003 // Checkbox for tasks - ON THE FAR RIGHT
1006 …$eventHtml .= '<input type="checkbox" class="task-checkbox" ' . $checked . ' onclick="toggleTaskCo…
1025 $html .= '<div class="past-events-section">';
1026 … $html .= '<div class="past-events-toggle" onclick="togglePastEvents(\'' . $calId . '\')">';
1027 $html .= '<span class="past-events-arrow" id="past-arrow-' . $calId . '">▶</span> ';
1028 $html .= '<span class="past-events-label">Past Events (' . $pastCount . ')</span>';
1030 …$html .= '<div class="past-events-content" id="past-events-' . $calId . '" style="display:none;">';
1052 if (empty($evt['time'])) continue; // Skip all-day events
1065 if ($this->eventsOverlap($dateEvents[$i], $dateEvents[$j])) {
1119 return false; // All-day events don't conflict
1129 $start1Mins = $this->timeToMinutes($start1);
1130 $end1Mins = $this->timeToMinutes($end1);
1131 $start2Mins = $this->timeToMinutes($start2);
1132 $end2Mins = $this->timeToMinutes($end2);
1159 // Get theme - prefer inline theme= parameter, fall back to admin default
1160 …me = !empty($data['theme']) ? $data['theme'] : $this->getSidebarTheme(); $themeStyles = $th…
1166 $events = $this->loadEventsMultiNamespace($namespace, $year, $month);
1168 $events = $this->loadEvents($namespace, $year, $month);
1174 $prevMonth = $month - 1;
1178 $prevYear--;
1201-panel-standalone" id="' . $calId . '" data-height="' . htmlspecialchars($height) . '" data-namesp…
1203 // Inject CSS variables for this panel instance - same as main calendar
1206 --background-site: ' . $themeStyles['bg'] . ';
1207 --background-alt: ' . $themeStyles['cell_bg'] . ';
1208 --background-header: ' . $themeStyles['header_bg'] . ';
1209 --text-primary: ' . $themeStyles['text_primary'] . ';
1210 --text-dim: ' . $themeStyles['text_dim'] . ';
1211 --text-bright: ' . $themeStyles['text_bright'] . ';
1212 --border-color: ' . $themeStyles['grid_border'] . ';
1213 --border-main: ' . $themeStyles['border'] . ';
1214 --cell-bg: ' . $themeStyles['cell_bg'] . ';
1215 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
1216 --shadow-color: ' . $themeStyles['shadow'] . ';
1217 --header-border: ' . $themeStyles['header_border'] . ';
1218 --header-shadow: ' . $themeStyles['header_shadow'] . ';
1219 --grid-bg: ' . $themeStyles['grid_bg'] . ';
1220 --btn-text: ' . $btnTextColor . ';
1221 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
1222 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
1223 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
1224 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
1225 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
1226 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
1227 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
1229 … #event-search-' . $calId . '::placeholder { color: ' . $themeStyles['text_dim'] . '; opacity: 1; }
1233 $html .= '<script src="' . DOKU_BASE . 'lib/plugins/calendar/calendar-main.js"></script>';
1238 // Compact two-row header designed for ~500px width
1239 $html .= '<div class="panel-header-compact">';
1242 $html .= '<div class="panel-header-row-1">';
1243 …$html .= '<button class="panel-nav-btn" onclick="navEventPanel(\'' . $calId . '\', ' . $prevYear .…
1247 …$html .= '<h3 class="panel-month-title" onclick="openMonthPickerPanel(\'' . $calId . '\', ' . $yea…
1249 …$html .= '<button class="panel-nav-btn" onclick="navEventPanel(\'' . $calId . '\', ' . $nextYear .…
1255 …ss="panel-ns-badge" style="background:var(--cell-today-bg) !important; color:var(--text-bright) !i…
1259 …ss="panel-ns-badge" style="background:var(--cell-today-bg) !important; color:var(--text-bright) !i…
1264-ns-badge filter-on" style="background:var(--text-bright) !important; color:var(--background-site)…
1266 …ss="panel-ns-badge" style="background:var(--cell-today-bg) !important; color:var(--text-bright) !i…
1271 …$html .= '<button class="panel-today-btn" onclick="jumpTodayPanel(\'' . $calId . '\', \'' . $names…
1275 $html .= '<div class="panel-header-row-2">';
1276 $html .= '<div class="panel-search-box">';
1277 …$html .= '<input type="text" class="panel-search-input" id="event-search-' . $calId . '" placehold…
1278 …$html .= '<button class="panel-search-clear" id="search-clear-' . $calId . '" onclick="clearEventS…
1279 …$html .= '<button class="panel-search-mode" id="search-mode-' . $calId . '" onclick="toggleSearchM…
1281 …$html .= '<button class="panel-add-btn" onclick="openAddEventPanel(\'' . $calId . '\', \'' . $name…
1286 …$html .= '<div class="event-list-compact" id="eventlist-' . $calId . '" style="max-height: ' . htm…
1287 $html .= $this->renderEventListContent($events, $calId, $namespace);
1290 $html .= $this->renderEventDialog($calId, $namespace, $theme);
1293 $html .= $this->renderMonthPicker($calId, $year, $month, $namespace, $theme, $themeStyles);
1314 // Handle "range" parameter - day, week, or month
1316 … $startDate = date('Y-m-d', strtotime('-30 days')); // Include past 30 days for past due tasks
1317 $endDate = date('Y-m-d');
1320 … $startDate = date('Y-m-d', strtotime('-30 days')); // Include past 30 days for past due tasks
1322 $endDateTime->modify('+7 days');
1323 $endDate = $endDateTime->format('Y-m-d');
1326 … $startDate = date('Y-m-01', strtotime('-1 month')); // Include previous month for past due tasks
1327 $endDate = date('Y-m-t'); // Last of current month
1329 $headerText = $dt->format('F Y');
1331 // NEW: Sidebar widget - load current week's events
1332 $weekStartDay = $this->getWeekStartDay(); // Get saved preference
1336 $weekStart = date('Y-m-d', strtotime('monday this week'));
1337 $weekEnd = date('Y-m-d', strtotime('sunday this week'));
1339 // Sunday start (default - US/Canada standard)
1343 $weekStart = date('Y-m-d');
1345 // Monday-Saturday: go back to last Sunday
1346 $weekStart = date('Y-m-d', strtotime('-' . $today . ' days'));
1348 $weekEnd = date('Y-m-d', strtotime($weekStart . ' +6 days'));
1357 $tomorrowDate = date('Y-m-d', strtotime('+1 day'));
1364 $twoWeeksOut = date('Y-m-d', strtotime($weekEnd . ' +14 days'));
1367 $end->modify('+1 day'); // DatePeriod excludes end date
1376 $year = (int)$dt->format('Y');
1377 $month = (int)$dt->format('n');
1378 $dateKey = $dt->format('Y-m-d');
1380 $monthKey = $year . '-' . $month . '-' . $namespace;
1384 … $loadedMonths[$monthKey] = $this->loadEventsMultiNamespace($namespace, $year, $month);
1386 $loadedMonths[$monthKey] = $this->loadEvents($namespace, $year, $month);
1398 $allEvents = $this->checkTimeConflicts($allEvents);
1400 $calId = 'sidebar-' . substr(md5($namespace . $weekStart), 0, 8);
1404 return $this->renderSidebarWidget($allEvents, $namespace, $calId, $themeOverride);
1406 $startDate = date('Y-m-d');
1407 $endDate = date('Y-m-d');
1413 $headerText = $start->format('M j') . ' - ' . $end->format('M j, Y');
1418 $headerText = $dt->format('l, F j, Y');
1420 $startDate = date('Y-m-01');
1421 $endDate = date('Y-m-t');
1423 $headerText = $dt->format('F Y');
1430 $end->modify('+1 day');
1441 $year = (int)$dt->format('Y');
1442 $month = (int)$dt->format('n');
1443 $dateKey = $dt->format('Y-m-d');
1445 $monthKey = $year . '-' . $month . '-' . $namespace;
1449 … $loadedMonths[$monthKey] = $this->loadEventsMultiNamespace($namespace, $year, $month);
1451 $loadedMonths[$monthKey] = $this->loadEvents($namespace, $year, $month);
1468 // All-day events (no time) go to the TOP
1469 if ($timeA === null && $timeB !== null) return -1; // A before B
1471 if ($timeA === null && $timeB === null) return 0; // Both all-day, equal
1479 // Simple 2-line display widget
1481 $theme = !empty($data['theme']) ? $data['theme'] : $this->getSidebarTheme();
1482 $themeStyles = $this->getSidebarThemeStyles($theme);
1487 $themeClass = 'eventlist-theme-' . $theme;
1489 // Container styling - dark themes get border + glow, light themes get subtle border
1493 $containerStyle .= ' border-radius:4px;';
1494 $containerStyle .= ' box-shadow:0 0 10px ' . $themeStyles['shadow'] . ';';
1497 $containerStyle .= ' border-radius:4px;';
1500 …iv class="eventlist-simple ' . $themeClass . '" id="' . $calId . '" style="' . $containerStyle . '…
1505 --background-site: ' . $themeStyles['bg'] . ';
1506 --background-alt: ' . $themeStyles['cell_bg'] . ';
1507 --text-primary: ' . $themeStyles['text_primary'] . ';
1508 --text-dim: ' . $themeStyles['text_dim'] . ';
1509 --text-bright: ' . $themeStyles['text_bright'] . ';
1510 --border-color: ' . $themeStyles['grid_border'] . ';
1511 --border-main: ' . $themeStyles['border'] . ';
1512 --cell-bg: ' . $themeStyles['cell_bg'] . ';
1513 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
1514 --shadow-color: ' . $themeStyles['shadow'] . ';
1515 --grid-bg: ' . $themeStyles['grid_bg'] . ';
1516 --btn-text: ' . $btnTextColor . ';
1517 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
1518 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
1519 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
1520 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
1521 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
1522 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
1523 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
1528 $html .= '<script src="' . DOKU_BASE . 'lib/plugins/calendar/calendar-main.js"></script>';
1536 $displayDate = $todayDate->format('D, M j, Y'); // "Fri, Jan 30, 2026"
1537 $currentTime = $todayDate->format('g:i:s A'); // "2:45:30 PM"
1539 $html .= '<div class="eventlist-today-header">';
1540 …$html .= '<span class="eventlist-today-clock" id="clock-' . $calId . '">' . $currentTime . '</span…
1541 $html .= '<div class="eventlist-bottom-info">';
1542 ….= '<span class="eventlist-weather"><span id="weather-icon-' . $calId . '">��️</span> <span id="we…
1543 $html .= '<span class="eventlist-today-date">' . $displayDate . '</span>';
1546 // Three CPU/Memory bars (all update live) - only if enabled
1547 $showSystemLoad = $this->getShowSystemLoad();
1549 $html .= '<div class="eventlist-stats-container">';
1551 // 5-minute load average (green, updates every 2 seconds)
1552 …$html .= '<div class="eventlist-cpu-bar" style="background:' . $themeStyles['cell_today_bg'] . ' !…
1553 …$html .= '<div class="eventlist-cpu-fill" id="cpu-5min-' . $calId . '" style="width: 0%; backgroun…
1554 …$html .= '<div class="system-tooltip" id="tooltip-green-' . $calId . '" style="display:none;"></di…
1557 // Real-time CPU (purple, updates with 5-sec average)
1558 …$html .= '<div class="eventlist-cpu-bar eventlist-cpu-realtime" style="background:' . $themeStyles…
1559 …$html .= '<div class="eventlist-cpu-fill eventlist-cpu-fill-purple" id="cpu-realtime-' . $calId . …
1560 …$html .= '<div class="system-tooltip" id="tooltip-purple-' . $calId . '" style="display:none;"></d…
1563 // Real-time Memory (orange, updates)
1564 …$html .= '<div class="eventlist-cpu-bar eventlist-mem-realtime" style="background:' . $themeStyles…
1565 …$html .= '<div class="eventlist-cpu-fill eventlist-cpu-fill-orange" id="mem-realtime-' . $calId . …
1566 …$html .= '<div class="system-tooltip" id="tooltip-orange-' . $calId . '" style="display:none;"></d…
1585 const clockEl = document.getElementById("clock-' . $calId . '");
1590 // Fetch weather - uses default location, click weather to get local
1593 var userLon = -121.4944;
1596 …fetch("https://api.open-meteo.com/v1/forecast?latitude=" + lat + "&longitude=" + lon + "&current_w…
1603 const iconEl = document.getElementById("weather-icon-' . $calId . '");
1604 const tempEl = document.getElementById("weather-temp-' . $calId . '");
1636 var weatherEl = document.querySelector("#weather-icon-' . $calId . '");
1653 51: "��️", // Light drizzle
1684 // CPU load history for 4-second rolling average
1698 const tooltip = document.getElementById("tooltip-" + color + "-' . $calId . '");
1709 content = "<div class=\"tooltip-title\">CPU Load Average</div>";
1714 …content += "<div style=\"margin-top:3px; padding-top:2px; border-top:1px solid ' . $themeStyles['t…
1716 … tooltip.style.setProperty("border-color", "' . $themeStyles['text_bright'] . '", "important");
1718 …tooltip.style.setProperty("-webkit-text-fill-color", "' . $themeStyles['text_bright'] . '", "impor…
1720 // Purple bar: Load averages (short-term) and top processes
1721 content = "<div class=\"tooltip-title\">CPU Load (Short-term)</div>";
1725 …ent += "<div style=\"margin-top:3px; padding-top:2px; border-top:1px solid ' . $themeStyles['borde…
1730 … tooltip.style.setProperty("border-color", "' . $themeStyles['border'] . '", "important");
1732 …tooltip.style.setProperty("-webkit-text-fill-color", "' . $themeStyles['border'] . '", "important"…
1735 content = "<div class=\"tooltip-title\">Memory Usage</div>";
1747 … += "<div style=\"margin-top:3px; padding-top:2px; border-top:1px solid ' . $themeStyles['text_pri…
1752 … tooltip.style.setProperty("border-color", "' . $themeStyles['text_primary'] . '", "important");
1754 …tooltip.style.setProperty("-webkit-text-fill-color", "' . $themeStyles['text_primary'] . '", "impo…
1767 const left = barRect.left + (barRect.width / 2) - (tooltipRect.width / 2);
1769 const top = barRect.top - tooltipRect.height - 8;
1776 const tooltip = document.getElementById("tooltip-" + color + "-' . $calId . '");
1798 // Update green bar (5-minute average) - updates live now!
1799 const greenBar = document.getElementById("cpu-5min-' . $calId . '");
1810 // Calculate 5-second average for CPU
1813 // Update CPU bar (purple) with 5-second average
1814 const cpuBar = document.getElementById("cpu-realtime-' . $calId . '");
1820 const memBar = document.getElementById("mem-realtime-' . $calId . '");
1827 // Fallback to client-side estimates on error
1835 const greenBar = document.getElementById("cpu-5min-' . $calId . '");
1838 const cpuBar = document.getElementById("cpu-realtime-' . $calId . '");
1847 const memBar = document.getElementById("mem-realtime-' . $calId . '");
1861 $html .= '<div class="eventlist-simple-empty">';
1862 $html .= '<div class="eventlist-simple-header">' . htmlspecialchars($headerText);
1864 … $html .= ' <span class="eventlist-simple-namespace">' . htmlspecialchars($namespace) . '</span>';
1867 $html .= '<div class="eventlist-simple-body">No events</div>';
1871 $todayStr = date('Y-m-d');
1872 $tomorrow = date('Y-m-d', strtotime('+1 day'));
1876 $displayDate = $dateObj->format('D, M j');
1904 $todayClass = $isToday ? ' eventlist-simple-today' : '';
1905 $tomorrowClass = $isTomorrow ? ' eventlist-simple-tomorrow' : '';
1906 $pastDueClass = $isPastDue ? ' eventlist-simple-pastdue' : '';
1907 …$html .= '<div class="eventlist-simple-item' . $todayClass . $tomorrowClass . $pastDueClass . '">';
1908 $html .= '<div class="eventlist-simple-header">';
1911 … $html .= '<span class="eventlist-simple-title">' . htmlspecialchars($event['title']) . '</span>';
1913 // Time (12-hour format)
1922 … $html .= ' <span class="eventlist-simple-time">' . $displayTime . '</span>';
1927 $html .= ' <span class="eventlist-simple-date">' . $displayDate . '</span>';
1931 …"eventlist-simple-pastdue-badge" style="background:' . $themeStyles['pastdue_color'] . ' !importan…
1933 …list-simple-today-badge" style="background:' . $themeStyles['border'] . ' !important; color:' . $t…
1939 … $eventNamespace = $event['_namespace']; // Fallback to _namespace for multi-namespace loading
1942 …$html .= ' <span class="eventlist-simple-namespace">' . htmlspecialchars($eventNamespace) . '</spa…
1947 // Line 2: Body (Description only) - only show if description exists
1949 …$html .= '<div class="eventlist-simple-body">' . $this->renderDescription($event['description']) .…
1957 $html .= '</div>'; // eventlist-simple
1965 $theme = $this->getSidebarTheme();
1967 $themeStyles = $this->getSidebarThemeStyles($theme);
1969 … $html = '<div class="event-dialog-compact" id="dialog-' . $calId . '" style="display:none;">';
1970 … $html .= '<div class="dialog-overlay" onclick="closeEventDialog(\'' . $calId . '\')"></div>';
1973 $html .= '<div class="dialog-content-sleek" id="dialog-content-' . $calId . '">';
1976 … $html .= '<div class="dialog-header-sleek dialog-drag-handle" id="drag-handle-' . $calId . '">';
1977 $html .= '<h3 id="dialog-title-' . $calId . '">Add Event</h3>';
1978 …$html .= '<button type="button" class="dialog-close-btn" onclick="closeEventDialog(\'' . $calId . …
1982 …form id="eventform-' . $calId . '" onsubmit="saveEventCompact(\'' . $calId . '\', \'' . $namespace…
1985 $html .= '<input type="hidden" id="event-id-' . $calId . '" name="eventId" value="">';
1988 $html .= '<div class="form-field">';
1989 $html .= '<label class="field-label">�� Title</label>';
1990 …$html .= '<input type="text" id="event-title-' . $calId . '" name="title" required class="input-sl…
1994 $html .= '<div class="form-field">';
1995 $html .= '<label class="field-label">�� Namespace</label>';
1998 … $html .= '<input type="hidden" id="event-namespace-' . $calId . '" name="namespace" value="">';
2001 $html .= '<div class="namespace-search-wrapper">';
2002 …l .= '<input type="text" id="event-namespace-search-' . $calId . '" class="input-sleek input-compa…
2003 …$html .= '<div class="namespace-dropdown" id="event-namespace-dropdown-' . $calId . '" style="disp…
2007 $allNamespaces = $this->getAllNamespaces();
2008 …$html .= '<script type="application/json" id="namespaces-data-' . $calId . '">' . json_encode($all…
2013 $html .= '<div class="form-field">';
2014 $html .= '<label class="field-label">�� Description</label>';
2015 …$html .= '<textarea id="event-desc-' . $calId . '" name="description" rows="2" class="input-sleek …
2018 // 3. START DATE - END DATE (inline)
2019 $html .= '<div class="form-row-group">';
2021 $html .= '<div class="form-field form-field-half">';
2022 $html .= '<label class="field-label-compact">�� Start Date</label>';
2023 $html .= '<div class="date-picker-wrapper">';
2024 … $html .= '<input type="hidden" id="event-date-' . $calId . '" name="date" required value="">';
2025 …n" class="custom-date-picker input-sleek input-compact" id="date-picker-btn-' . $calId . '" data-t…
2026 $html .= '<span class="date-display">Select date</span>';
2027 $html .= '<span class="date-arrow">▼</span>';
2029 $html .= '<div class="date-dropdown" id="date-dropdown-' . $calId . '"></div>';
2033 $html .= '<div class="form-field form-field-half">';
2034 $html .= '<label class="field-label-compact">�� End Date</label>';
2035 $html .= '<div class="date-picker-wrapper">';
2036 $html .= '<input type="hidden" id="event-end-date-' . $calId . '" name="endDate" value="">';
2037 …lass="custom-date-picker input-sleek input-compact" id="end-date-picker-btn-' . $calId . '" data-t…
2038 $html .= '<span class="date-display">Optional</span>';
2039 $html .= '<span class="date-arrow">▼</span>';
2041 $html .= '<div class="date-dropdown" id="end-date-dropdown-' . $calId . '"></div>';
2048 $html .= '<div class="form-field form-field-checkbox form-field-checkbox-compact">';
2049 $html .= '<label class="checkbox-label checkbox-label-compact">';
2050 …$html .= '<input type="checkbox" id="event-recurring-' . $calId . '" name="isRecurring" class="rec…
2056-options-' . $calId . '" class="recurring-options" style="display:none; border:1px solid var(--bor…
2059 $html .= '<div class="form-row-group" style="margin-bottom:6px;">';
2061 $html .= '<div class="form-field" style="flex:0 0 auto; min-width:0;">';
2062 $html .= '<label class="field-label-compact">Repeat every</label>';
2063 … '<input type="number" id="event-recurrence-interval-' . $calId . '" name="recurrenceInterval" cla…
2066 $html .= '<div class="form-field" style="flex:1; min-width:0;">';
2067 $html .= '<label class="field-label-compact">&nbsp;</label>';
2068 …$html .= '<select id="event-recurrence-type-' . $calId . '" name="recurrenceType" class="input-sle…
2078 // Row 2: Weekly options - day of week checkboxes
2079 …$html .= '<div id="weekly-options-' . $calId . '" class="weekly-options" style="display:none; marg…
2080 …$html .= '<label class="field-label-compact" style="display:block; margin-bottom:4px;">On these da…
2081 $html .= '<div style="display:flex; flex-wrap:wrap; gap:2px;">';
2084-flex; align-items:center; padding:2px 6px; background:var(--cell-bg, #1a1a1a); border:1px solid v…
2085 …$html .= '<input type="checkbox" name="weekDays[]" value="' . $idx . '" style="margin-right:3px; w…
2092 // Row 3: Monthly options - day of month OR ordinal weekday
2093 …$html .= '<div id="monthly-options-' . $calId . '" class="monthly-options" style="display:none; ma…
2094 …$html .= '<label class="field-label-compact" style="display:block; margin-bottom:4px;">Repeat on:<…
2097 $html .= '<div style="margin-bottom:6px;">';
2098 …$html .= '<label style="display:inline-flex; align-items:center; margin-right:12px; cursor:pointer…
2099 …="dayOfMonth" checked onchange="updateMonthlyType(\'' . $calId . '\')" style="margin-right:4px;">';
2102 …$html .= '<label style="display:inline-flex; align-items:center; cursor:pointer; font-size:11px;">…
2103 …alue="ordinalWeekday" onchange="updateMonthlyType(\'' . $calId . '\')" style="margin-right:4px;">';
2109 …$html .= '<div id="monthly-day-' . $calId . '" style="display:flex; align-items:center; gap:6px;">…
2110 $html .= '<span style="font-size:11px;">Day</span>';
2111 …$html .= '<input type="number" id="event-month-day-' . $calId . '" name="monthDay" class="input-sl…
2112 $html .= '<span style="font-size:10px; color:var(--text-dim, #666);">of each month</span>';
2116 $html .= '<div id="monthly-ordinal-' . $calId . '" style="display:none;">';
2117 $html .= '<div style="display:flex; align-items:center; gap:4px; flex-wrap:wrap;">';
2118 …$html .= '<select id="event-ordinal-' . $calId . '" name="ordinalWeek" class="input-sleek input-co…
2124 $html .= '<option value="-1">Last</option>';
2126 …$html .= '<select id="event-ordinal-day-' . $calId . '" name="ordinalDay" class="input-sleek input
2135 $html .= '<span style="font-size:10px; color:var(--text-dim, #666);">of each month</span>';
2142 $html .= '<div class="form-row-group">';
2143 $html .= '<div class="form-field">';
2144 $html .= '<label class="field-label-compact">Repeat Until (optional)</label>';
2145 ….= '<input type="date" id="event-recurrence-end-' . $calId . '" name="recurrenceEnd" class="input-
2146 …$html .= '<div style="font-size:9px; color:var(--text-dim, #666); margin-top:2px;">Leave empty for…
2152 // 5. TIME (Start & End) - COLOR (inline)
2153 $html .= '<div class="form-row-group">';
2155 $html .= '<div class="form-field form-field-half">';
2156 $html .= '<label class="field-label-compact">�� Start Time</label>';
2157 $html .= '<div class="time-picker-wrapper">';
2159 $html .= '<input type="hidden" id="event-time-' . $calId . '" name="time" value="">';
2160 …n" class="custom-time-picker input-sleek input-compact" id="time-picker-btn-' . $calId . '" data-t…
2161 $html .= '<span class="time-display">All day</span>';
2162 $html .= '<span class="time-arrow">▼</span>';
2164 $html .= '<div class="time-dropdown" id="time-dropdown-' . $calId . '"></div>';
2168 $html .= '<div class="form-field form-field-half">';
2169 $html .= '<label class="field-label-compact">�� End Time</label>';
2170 $html .= '<div class="time-picker-wrapper">';
2172 $html .= '<input type="hidden" id="event-end-time-' . $calId . '" name="endTime" value="">';
2173 …lass="custom-time-picker input-sleek input-compact" id="end-time-picker-btn-' . $calId . '" data-t…
2174 $html .= '<span class="time-display">Same as start</span>';
2175 $html .= '<span class="time-arrow">▼</span>';
2177 $html .= '<div class="time-dropdown" id="end-time-dropdown-' . $calId . '"></div>';
2184 $html .= '<div class="form-row-group">';
2186 $html .= '<div class="form-field form-field-full">';
2187 $html .= '<label class="field-label-compact">�� Color</label>';
2188 $html .= '<div class="color-picker-wrapper">';
2189 …$html .= '<select id="event-color-' . $calId . '" name="color" class="input-sleek input-compact co…
2199 …$html .= '<input type="color" id="event-color-custom-' . $calId . '" class="color-picker-input col…
2206 $html .= '<div class="form-field form-field-checkbox form-field-checkbox-compact">';
2207 $html .= '<label class="checkbox-label checkbox-label-compact">';
2208 …$html .= '<input type="checkbox" id="event-is-task-' . $calId . '" name="isTask" class="task-toggl…
2214 $html .= '<div class="dialog-actions-sleek">';
2215 …$html .= '<button type="button" class="btn-sleek btn-cancel-sleek" onclick="closeEventDialog(\'' .…
2216 $html .= '<button type="submit" class="btn-sleek btn-save-sleek">�� Save</button>';
2229 $themeStyles = $this->getSidebarThemeStyles($theme);
2232 $themeClass = 'calendar-theme-' . $theme;
2234 …$html = '<div class="month-picker-overlay ' . $themeClass . '" id="month-picker-overlay-' . $calId…
2235 $html .= '<div class="month-picker-dialog" onclick="event.stopPropagation();">';
2238 $html .= '<div class="month-picker-selects">';
2239 $html .= '<select id="month-picker-month-' . $calId . '" class="month-picker-select">';
2243 … $html .= '<option value="' . $m . '"' . $selected . '>' . $monthNames[$m - 1] . '</option>';
2247 $html .= '<select id="month-picker-year-' . $calId . '" class="month-picker-select">';
2249 for ($y = $currentYear - 5; $y <= $currentYear + 5; $y++) {
2256 $html .= '<div class="month-picker-actions">';
2257 …$html .= '<button class="btn-sleek btn-cancel-sleek" onclick="closeMonthPicker(\'' . $calId . '\')…
2258 …$html .= '<button class="btn-sleek btn-save-sleek" onclick="jumpToSelectedMonth(\'' . $calId . '\'…
2274 $theme = $this->getSidebarTheme();
2275 $themeStyles = $this->getSidebarThemeStyles($theme);
2279 $linkStyle = ' class="cal-link"';
2281 // Token-based parsing to avoid escaping issues
2295 …' . htmlspecialchars($imagePath) . '" alt="' . htmlspecialchars($alt) . '" class="event-image" />';
2299 …eHtml = '<img src="' . $imageUrl . '" alt="' . htmlspecialchars($alt) . '" class="event-image" />';
2334 // Convert markdown-style links [text](url) to tokens
2408 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
2422 return $this->loadEventsWildcard($baseNamespace, $year, $month);
2427 return $this->loadEventsWildcard('', $year, $month);
2440 $events = $this->loadEvents($ns, $year, $month);
2468 // Root wildcard - load from root calendar
2469 $events = $this->loadEvents('', $year, $month);
2480 $events = $this->loadEvents($baseNamespace, $year, $month);
2493 $this->findSubNamespaces($dataDir, $baseNamespace, $year, $month, $allEvents);
2511 $events = $this->loadEvents($namespace, $year, $month);
2523 $this->findSubNamespaces($path . '/', $namespace, $year, $month, $allEvents);
2533 $this->scanForCalendarNamespaces($dataDir, '', $namespaces);
2564 $this->scanForCalendarNamespaces($path . '/', $namespace, $namespaces);
2570 * Render new sidebar widget - Week at a glance itinerary (200px wide)
2574 …return '<div style="width:200px; padding:12px; text-align:center; color:#999; font-size:11px;">No …
2588 $todayStr = date('Y-m-d');
2589 $tomorrowStr = date('Y-m-d', strtotime('+1 day'));
2592 $weekStartDay = $this->getWeekStartDay();
2596 $weekStart = date('Y-m-d', strtotime('monday this week'));
2597 $weekEnd = date('Y-m-d', strtotime('sunday this week'));
2599 // Sunday start (default - US/Canada standard)
2603 $weekStart = date('Y-m-d');
2605 // Monday-Saturday: go back to last Sunday
2606 $weekStart = date('Y-m-d', strtotime('-' . $today . ' days'));
2608 $weekEnd = date('Y-m-d', strtotime($weekStart . ' +6 days'));
2620 $eventsWithConflicts = $this->detectTimeConflicts($dayEvents);
2638 // Pre-render DokuWiki syntax to HTML for JavaScript display
2641 $eventWithHtml['title_html'] = $this->renderDokuWikiToHtml($event['title']);
2644 … $eventWithHtml['description_html'] = $this->renderDokuWikiToHtml($event['description']);
2673 // Same date - sort by time
2677 if (empty($timeA) && !empty($timeB)) return 1; // All-day events last
2678 if (!empty($timeA) && empty($timeB)) return -1;
2682 $aMinutes = $this->timeToMinutes($timeA);
2683 $bMinutes = $this->timeToMinutes($timeB);
2684 return $aMinutes - $bMinutes;
2690 // Get theme - prefer override from syntax parameter, fall back to admin default
2691 $theme = !empty($themeOverride) ? $themeOverride : $this->getSidebarTheme();
2692 $themeStyles = $this->getSidebarThemeStyles($theme);
2693 $themeClass = 'sidebar-' . $theme;
2695 // Start building HTML - Dynamic width with default font (overflow:visible for tooltips)
2696-widget ' . $themeClass . '" id="sidebar-widget-' . $calId . '" style="width:100%; max-width:100%;…
2701 #sidebar-widget-' . $calId . ' {
2702 --background-site: ' . $themeStyles['bg'] . ';
2703 --background-alt: ' . $themeStyles['cell_bg'] . ';
2704 --background-header: ' . $themeStyles['header_bg'] . ';
2705 --text-primary: ' . $themeStyles['text_primary'] . ';
2706 --text-dim: ' . $themeStyles['text_dim'] . ';
2707 --text-bright: ' . $themeStyles['text_bright'] . ';
2708 --border-color: ' . $themeStyles['grid_border'] . ';
2709 --border-main: ' . $themeStyles['border'] . ';
2710 --cell-bg: ' . $themeStyles['cell_bg'] . ';
2711 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
2712 --shadow-color: ' . $themeStyles['shadow'] . ';
2713 --header-border: ' . $themeStyles['header_border'] . ';
2714 --header-shadow: ' . $themeStyles['header_shadow'] . ';
2715 --grid-bg: ' . $themeStyles['grid_bg'] . ';
2716 --btn-text: ' . $btnTextColor . ';
2717 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
2718 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
2719 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
2720 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
2721 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
2722 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
2723 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
2730 @keyframes sparkle-' . $calId . ' {
2737 transform: translate(var(--tx), var(--ty)) scale(1) rotate(180deg);
2741 … transform: translate(calc(var(--tx) * 2), calc(var(--ty) * 2)) scale(0) rotate(360deg);
2745 @keyframes pulse-glow-' . $calId . ' {
2746 0%, 100% { box-shadow: 0 0 10px rgba(255, 20, 147, 0.4); }
2747 … 50% { box-shadow: 0 0 25px rgba(255, 20, 147, 0.8), 0 0 40px rgba(255, 20, 147, 0.4); }
2750 @keyframes shimmer-' . $calId . ' {
2751 0% { background-position: -200% center; }
2752 100% { background-position: 200% center; }
2755 .sidebar-pink {
2756 animation: pulse-glow-' . $calId . ' 3s ease-in-out infinite;
2759 .sidebar-pink:hover {
2760 … box-shadow: 0 0 30px rgba(255, 20, 147, 0.9), 0 0 50px rgba(255, 20, 147, 0.5) !important;
2763 .sparkle-' . $calId . ' {
2765 pointer-events: none;
2766 font-size: 20px;
2767 z-index: 1000;
2768 animation: sparkle-' . $calId . ' 1s ease-out forwards;
2769 filter: drop-shadow(0 0 3px rgba(255, 20, 147, 0.8));
2775 const container = document.getElementById("sidebar-widget-' . $calId . '");
2780 sparkle.className = "sparkle-' . $calId . '";
2788 sparkle.style.setProperty("--tx", Math.cos(angle) * distance + "px");
2789 sparkle.style.setProperty("--ty", Math.sin(angle) * distance + "px");
2799 const x = e.clientX - rect.left;
2800 const y = e.clientY - rect.top;
2805 const offsetX = x + (Math.random() - 0.5) * 30;
2806 const offsetY = y + (Math.random() - 0.5) * 30;
2812 // Random auto-sparkles for extra glamour
2823 $jsCalId = str_replace('-', '_', $calId);
2840 // Tooltip functions - MUST be defined before HTML uses them
2842 const tooltip = document.getElementById("tooltip-" + color + "-' . $calId . '");
2852 content = "<div class=\\"tooltip-title\\">CPU Load Average</div>";
2857 …content += "<div style=\\"margin-top:3px; padding-top:2px; border-top:1px solid ' . $themeStyles['…
2859 … tooltip.style.setProperty("border-color", "' . $themeStyles['text_bright'] . '", "important");
2861 …tooltip.style.setProperty("-webkit-text-fill-color", "' . $themeStyles['text_bright'] . '", "impor…
2863 content = "<div class=\\"tooltip-title\\">CPU Load (Short-term)</div>";
2867 …t += "<div style=\\"margin-top:3px; padding-top:2px; border-top:1px solid ' . $themeStyles['border…
2872 … tooltip.style.setProperty("border-color", "' . $themeStyles['border'] . '", "important");
2874 …tooltip.style.setProperty("-webkit-text-fill-color", "' . $themeStyles['border'] . '", "important"…
2876 content = "<div class=\\"tooltip-title\\">Memory Usage</div>";
2888 …= "<div style=\\"margin-top:3px; padding-top:2px; border-top:1px solid ' . $themeStyles['text_prim…
2893 … tooltip.style.setProperty("border-color", "' . $themeStyles['text_primary'] . '", "important");
2895 …tooltip.style.setProperty("-webkit-text-fill-color", "' . $themeStyles['text_primary'] . '", "impo…
2906 const left = barRect.left + (barRect.width / 2) - (tooltipRect.width / 2);
2907 const top = barRect.top - tooltipRect.height - 8;
2914 const tooltip = document.getElementById("tooltip-" + color + "-' . $calId . '");
2929 const clockEl = document.getElementById("clock-' . $calId . '");
2934 // Weather - uses default location, click weather to get local
2937 var userLon = -121.4944;
2940 …fetch("https://api.open-meteo.com/v1/forecast?latitude=" + lat + "&longitude=" + lon + "&current_w…
2947 const iconEl = document.getElementById("weather-icon-' . $calId . '");
2948 const tempEl = document.getElementById("weather-temp-' . $calId . '");
2976 var weatherEl = document.querySelector("#weather-icon-' . $calId . '");
3011 const greenBar = document.getElementById("cpu-5min-' . $calId . '");
3023 const cpuBar = document.getElementById("cpu-realtime-' . $calId . '");
3028 const memBar = document.getElementById("mem-realtime-' . $calId . '");
3045 $displayDate = $todayDate->format('D, M j, Y');
3046 $currentTime = $todayDate->format('g:i:s A');
3048 …s="eventlist-today-header" style="background:' . $themeStyles['header_bg'] . '; border:2px solid '…
3049 …$html .= '<span class="eventlist-today-clock" id="clock-' . $calId . '" style="color:' . $themeSty…
3050 $html .= '<div class="eventlist-bottom-info">';
3051-weather"><span id="weather-icon-' . $calId . '">��️</span> <span id="weather-temp-' . $calId . '"…
3052 …$html .= '<span class="eventlist-today-date" style="color:' . $themeStyles['text_dim'] . ';">' . $…
3055 // Three CPU/Memory bars (all update live) - only if enabled
3056 $showSystemLoad = $this->getShowSystemLoad();
3058 $html .= '<div class="eventlist-stats-container">';
3060 // 5-minute load average (green, updates every 2 seconds)
3061 …$html .= '<div class="eventlist-cpu-bar" style="background:' . $themeStyles['cell_today_bg'] . ' !…
3062 …$html .= '<div class="eventlist-cpu-fill" id="cpu-5min-' . $calId . '" style="width: 0%; backgroun…
3063 …$html .= '<div class="system-tooltip" id="tooltip-green-' . $calId . '" style="display:none;"></di…
3066 // Real-time CPU (purple, updates with 5-sec average)
3067 …$html .= '<div class="eventlist-cpu-bar eventlist-cpu-realtime" style="background:' . $themeStyles…
3068 …$html .= '<div class="eventlist-cpu-fill eventlist-cpu-fill-purple" id="cpu-realtime-' . $calId . …
3069 …$html .= '<div class="system-tooltip" id="tooltip-purple-' . $calId . '" style="display:none;"></d…
3072 // Real-time Memory (orange, updates)
3073 …$html .= '<div class="eventlist-cpu-bar eventlist-mem-realtime" style="background:' . $themeStyles…
3074 …$html .= '<div class="eventlist-cpu-fill eventlist-cpu-fill-orange" id="mem-realtime-' . $calId . …
3075 …$html .= '<div class="system-tooltip" id="tooltip-orange-' . $calId . '" style="display:none;"></d…
3083 $todayStr = date('Y-m-d');
3085 // Thin "Add Event" bar between header and week grid - theme-aware colors
3095 …x; line-height:10px; text-align:center; cursor:pointer; border-top:1px solid rgba(0, 0, 0, 0.1); b…
3097 …nt-size:8px; font-weight:700; letter-spacing:0.4px; font-family:system-ui, sans-serif; text-shadow…
3101 $html .= $this->renderWeekGrid($weekEvents, $weekStart, $themeStyles, $theme);
3103 // Section colors - derived from theme palette
3116 $importantColor = '#ff85c1'; // Light pink
3122 // Wiki - section header backgrounds from template colors
3131 // Itinerary bar (collapsible toggle) - styled like +Add bar
3143 $jsCalId = str_replace('-', '_', $calId);
3146 $itineraryDefaultCollapsed = $this->getItineraryCollapsed();
3147 $arrowDefaultStyle = $itineraryDefaultCollapsed ? 'transform:rotate(-90deg);' : '';
3148 $contentDefaultStyle = $itineraryDefaultCollapsed ? 'max-height:0px; opacity:0;' : '';
3150-bar-' . $calId . '" style="background:' . $itineraryBg . '; padding:0; margin:0; height:12px; lin…
3151-arrow-' . $calId . '" style="color:' . $itineraryTextColor . '; font-size:6px; font-weight:700; f…
3152 …t-size:8px; font-weight:700; letter-spacing:0.4px; font-family:system-ui, sans-serif; text-shadow:…
3156 …$html .= '<div id="itinerary-content-' . $calId . '" style="transition:max-height 0.3s ease-out, o…
3160 …$html .= $this->renderSidebarSection('Today', $todayEvents, $todayColor, $calId, $themeStyles, $th…
3165 …$html .= $this->renderSidebarSection('Tomorrow', $tomorrowEvents, $tomorrowColor, $calId, $themeSt…
3170 …$html .= $this->renderSidebarSection('Important Events', $importantEvents, $importantColor, $calId…
3175 …iv style="padding:8px; text-align:center; color:' . $themeStyles['text_dim'] . '; font-size:10px; …
3178 $html .= '</div>'; // Close itinerary-content
3181 $itineraryDefaultCollapsed = $this->getItineraryCollapsed();
3183 $itineraryArrowDefault = $itineraryDefaultCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)';
3184 …$itineraryContentDefault = $itineraryDefaultCollapsed ? 'max-height:0px; opacity:0;' : 'max-height…
3192 const content = document.getElementById("itinerary-content-' . $calId . '");
3193 const arrow = document.getElementById("itinerary-arrow-' . $calId . '");
3199 arrow.style.transform = "rotate(-90deg)";
3218 const content = document.getElementById("itinerary-content-' . $calId . '");
3219 const arrow = document.getElementById("itinerary-arrow-' . $calId . '");
3227 arrow.style.transform = "rotate(-90deg)";
3236 $html .= $this->renderEventDialog($calId, $namespace, $theme);
3238 // Add JavaScript for positioning data-tooltip elements
3240 // Position data-tooltip elements to prevent cutoff (up and to the LEFT)
3242 const tooltipElements = document.querySelectorAll("[data-tooltip]");
3243 const isPinkTheme = document.querySelector(".sidebar-pink") !== null;
3251 element.style.setProperty("--tooltip-left", (rect.left - 150) + "px");
3252 element.style.setProperty("--tooltip-top", (rect.top - 30) + "px");
3256 element.style.setProperty("--heart-left", (rect.left - 150 + 210) + "px");
3257 element.style.setProperty("--heart-top", (rect.top - 30) + "px");
3266 [data-tooltip]:hover:before {
3267 left: var(--tooltip-left, 0) !important;
3268 top: var(--tooltip-top, 0) !important;
3270 .sidebar-pink [data-tooltip]:hover:after {
3271 left: var(--heart-left, 0) !important;
3272 top: var(--heart-top, 0) !important;
3282 * Render compact week grid (7 cells with event bars) - Theme-aware
3285 // Generate unique ID for this calendar instance - sanitize for JavaScript
3287 $jsCalId = str_replace('-', '_', $calId); // Sanitize for JS variable names
3289 …div style="display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; background:' . $themeStyle…
3292 $weekStartDay = $this->getWeekStartDay();
3298 $today = date('Y-m-d');
3301 $date = date('Y-m-d', strtotime($weekStart . ' +' . $i . ' days'));
3312 // Theme-aware text shadow
3315 …$textShadow = $isToday ? 'text-shadow:0 0 3px ' . $glowColor . ';' : 'text-shadow:0 0 2px ' . $glo…
3318 …$textShadow = $isToday ? 'text-shadow:0 0 2px ' . $glowColor . ';' : 'text-shadow:0 0 1px ' . $glo…
3321 …$textShadow = $isToday ? 'text-shadow:0 0 2px ' . $glowColor . ';' : 'text-shadow:0 0 1px ' . $glo…
3333 … .= '<div style="background:' . $bgColor . '; padding:4px 2px; text-align:center; min-height:45px;…
3335 // Day letter - theme color
3337 …$html .= '<div style="font-size:9px; color:' . $dayLetterColor . '; font-weight:500; font-family:s…
3340 ….= '<div style="font-size:12px; color:' . $textColor . '; font-weight:' . $fontWeight . '; margin:…
3342 // Event bars (max 4 visible) with theme-aware glow
3349 …x; background:' . htmlspecialchars($color) . '; margin:1px 0; border-radius:1px; box-shadow:' . $b…
3352 // Show "+N more" if more than 4 - theme color
3355 ….= '<div style="font-size:7px; color:' . $moreTextColor . '; margin-top:1px; font-family:system-ui…
3364 // Add container for selected day events display (with unique ID) - theme-aware
3374 // Header text color - dark bg text for dark themes, white for light theme accent headers
3378-day-events-' . $calId . '" style="display:none; margin:8px 4px; border-left:3px solid ' . $panelB…
3380-size:9px; font-weight:700; letter-spacing:0.3px; font-family:system-ui, sans-serif; box-shadow:' …
3381 $html .= '<span id="selected-day-title-' . $calId . '"></span>';
3382 …mentById(\'selected-day-events-' . $calId . '\').style.display=\'none\';" style="cursor:pointer; f…
3384-webkit-text-fill-color:' . $panelHeaderColor . ' !important; padding:4px 6px; font-size:9px; font
3385 $html .= '<span id="selected-day-title-' . $calId . '"></span>';
3386-day-events-' . $calId . '\').style.display=\'none\';" style="cursor:pointer; font-size:12px; padd…
3389 …$html .= '<div id="selected-day-content-' . $calId . '" style="padding:4px 0; background:' . $pane…
3395 $jsCalId = str_replace('-', '_', $calId);
3403 … 'text_shadow' => ($theme === 'pink') ? 'text-shadow:0 0 2px ' . $themeStyles['text_primary'] :
3404 …((in_array($theme, ['matrix', 'purple'])) ? 'text-shadow:0 0 1px ' . $themeStyles['text_primary'] …
3418 const container = document.getElementById("selected-day-events-' . $calId . '");
3419 const title = document.getElementById("selected-day-title-' . $calId . '");
3420 const content = document.getElementById("selected-day-content-' . $calId . '");
3426 const dayName = dateObj.toLocaleDateString("en-US", { weekday: "long" });
3427 … const monthDay = dateObj.toLocaleDateString("en-US", { month: "short", day: "numeric" });
3433 // Sort events by time (all-day events first, then timed events chronologically)
3435 // All-day events (no time) go to the beginning
3437 if (!a.time) return -1; // a is all-day, comes first
3438 if (!b.time) return 1; // b is all-day, comes first
3446 return minutesA - minutesB;
3449 // Build events HTML with single color bar (event color only) - theme-aware
3455-bottom:1px solid " + themeColors.border_color + "; font-size:10px; display:flex; align-items:stre…
3459 // Event assigned color bar (single bar on left) - theme-aware shadow
3461 …+= "<div style=\\"width:3px; align-self:stretch; background:" + eventColor + "; border-radius:1px;…
3464 …eventHTML += "<div style=\\"flex:1; min-width:0; display:flex; justify-content:space-between; alig…
3467 eventHTML += "<div style=\\"flex:1; min-width:0;\\">";
3468 …ML += "<div style=\\"font-weight:600; color:" + themeColors.text_primary + "; word-wrap:break-word…
3477 …HTML += "<span style=\\"color:" + themeColors.text_bright + "; font-weight:500; font-size:9px;\\">…
3480 // Title - use HTML version if available
3485 // Description if present - use HTML version - theme-aware color
3488 …eventHTML += "<div style=\\"font-size:9px; color:" + themeColors.text_dim + "; margin-top:2px;\\">…
3498 … const confTime = conf.time + (conf.end_time ? " - " + conf.end_time : "");
3503 …eventHTML += "<span class=\\"event-conflict-badge\\" style=\\"font-size:10px;\\" data-conflicts=\\…
3521 * Render a sidebar section (Today/Tomorrow/Important) - Matrix themed with colored borders
3537 // Different dates - sort by date
3542 // Same date - sort by time
3546 // All-day events last within same date
3548 if (!empty($aTime) && empty($bTime)) return -1;
3552 $aMinutes = $this->timeToMinutes($aTime);
3553 $bMinutes = $this->timeToMinutes($bTime);
3554 return $aMinutes - $bMinutes;
3562 // All-day events (no time) come first
3563 if (empty($aTime) && !empty($bTime)) return -1;
3567 // Both have times - convert to minutes for proper chronological sort
3568 $aMinutes = $this->timeToMinutes($aTime);
3569 $bMinutes = $this->timeToMinutes($bTime);
3571 return $aMinutes - $bMinutes;
3575 // Theme-aware section shadow
3581 // Wiki theme: use a background div for the left bar instead of border-left
3583 …$html = '<div style="display:flex; margin:8px 4px; box-shadow:' . $sectionShadow . '; background:'…
3584 … $html .= '<div style="width:3px; flex-shrink:0; background:' . $borderColor . ';"></div>';
3585 $html .= '<div style="flex:1; min-width:0;">';
3587 …$html = '<div style="border-left:3px solid ' . $borderColor . ' !important; margin:8px 4px; box-sh…
3590 // Section header with accent color background - theme-aware
3596 …Color . '; padding:4px 6px; font-size:9px; font-weight:700; letter-spacing:0.3px; font-family:syst…
3599-webkit-text-fill-color:' . $headerTextColor . ' !important; padding:4px 6px; font-size:9px; font-
3604 // Events - no background (transparent)
3608 …$html .= $this->renderSidebarEvent($event, $calId, $showDate, $accentColor, $themeStyles, $theme, …
3621 * Render individual event in sidebar - Theme-aware
3642 // Theme-aware colors
3645 $textShadow = ($theme === 'pink') ? 'text-shadow:0 0 2px ' . $titleColor . ';' :
3646 … ((in_array($theme, ['matrix', 'purple'])) ? 'text-shadow:0 0 1px ' . $titleColor . ';' : '');
3656 …$confTime = $this->formatTimeDisplay($conf['time'], isset($conf['end_time']) ? $conf['end_time'] :…
3661 // No background on individual events (transparent) - unless important namespace
3665 // Important namespace highlighting - subtle themed background
3669 // Theme-specific important highlighting
3673 $importantBorder = 'border-right:2px solid rgba(0,204,7,0.4);';
3677 $importantBorder = 'border-right:2px solid rgba(156,39,176,0.4);';
3681 $importantBorder = 'border-right:2px solid rgba(255,105,180,0.5);';
3685 $importantBorder = 'border-right:2px solid rgba(33,150,243,0.4);';
3689 $importantBorder = 'border-right:2px solid rgba(0,102,204,0.3);';
3693 $importantBorder = 'border-right:2px solid rgba(0,204,7,0.4);';
3697 …ing:4px 6px; border-bottom:1px solid ' . $borderColor . ' !important; font-size:10px; display:flex…
3701 …l .= '<div style="width:3px; align-self:stretch; background:' . $eventColor . '; border-radius:1px…
3704 $html .= '<div style="flex:1; min-width:0;">';
3707 …$html .= '<div style="font-weight:600; color:' . $titleColor . '; word-wrap:break-word; font-famil…
3710 $displayTime = $this->formatTimeDisplay($time, $endTime);
3711 …$html .= '<span style="color:' . $timeColor . '; font-weight:500; font-size:9px;">' . htmlspecialc…
3718 … $html .= '<span style="font-size:11px; color:' . $checkColor . ';">' . $checkIcon . '</span> ';
3723 $html .= '<span style="font-size:9px;" title="Important">⭐</span> ';
3726 $html .= $title; // Already HTML-escaped on line 2625
3731 …$html .= ' <span class="event-conflict-badge" style="font-size:10px;" data-conflicts="' . $conflic…
3739 $displayDate = $dateObj->format('D, M j'); // e.g., "Mon, Feb 10"
3741 $dateShadow = ($theme === 'pink') ? 'text-shadow:0 0 2px ' . $dateColor . ';' :
3742 … ((in_array($theme, ['matrix', 'purple'])) ? 'text-shadow:0 0 1px ' . $dateColor . ';' : '');
3743 …$html .= '<div style="font-size:8px; color:' . $dateColor . '; font-weight:500; margin-top:2px; ' …
3753 * Format time display (12-hour format with optional end time)
3773 $display .= '-' . $endDisplayHour . ':' . $endMinute . ' ' . $endAmpm;
3799 // Skip all-day events (no time)
3814 // If no end time, use start time (zero duration) - matches main calendar logic
3821 if (empty($otherEvent['time'])) continue; // Skip all-day events
3835 $start1Min = $this->timeToMinutes($startTime);
3836 $end1Min = $this->timeToMinutes($endTime);
3837 $start2Min = $this->timeToMinutes($otherStart);
3838 $end2Min = $this->timeToMinutes($otherEnd);
3842 // e.g., 1:00-2:00 and 2:00-3:00 are NOT in conflict
3866 $totalMinutes = $this->timeToMinutes($time) + ($hours * 60);
3903 $this->scanForNamespaces($path . '/', $namespace, $namespaces);
3969 // __background_site__ → --background-site → Container, panel backgrounds
3970 … // __background__ → --cell-bg → Cell/input backgrounds (typically white)
3971 // __background_alt__ → --background-alt → Hover states, header backgrounds
3972 // → --background-header
3973 // __background_neu__ → --cell-today-bg → Today cell highlight
3974 // __text__ → --text-primary → Primary text, labels, titles
3975 // __text_neu__ → --text-dim → Secondary text, dates, descriptions
3977 // __border__ → --border-color → Grid lines, input borders
3978 …// → --border-main → Accent color: buttons, badges, active element…
3979 // → --header-border
3980 // __link__ → --text-bright → Links, accent text
3996 'cell_bg' => $background, // Cells use __background__ (white/light)
4010 * Get theme-specific color styles
4015 $wikiColors = $this->getWikiTemplateColors();
4027 'header_bg' => 'linear-gradient(180deg, #2a2a2a 0%, #242424 100%)',
4050 'header_bg' => 'linear-gradient(180deg, #2f2438 0%, #2a2030 100%)',
4073 'header_bg' => 'linear-gradient(180deg, #ffffff 0%, #f5f7fa 100%)',
4096 'header_bg' => 'linear-gradient(180deg, #2d1a24 0%, #1a0d14 100%)',