Lines Matching +full:star +full:- +full:light
6 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html
15 * Get the meta directory path (farm-safe)
38 * Get sync config file path (farm-safe)
39 * Checks per-animal metadir first, falls back to shared plugin dir
42 $perAnimal = $this->metaDir() . 'calendar/sync_config.php';
60 $this->Lexer->addSpecialPattern('\{\{calendar(?:[^\}]*)\}\}', $mode, 'plugin_calendar');
61 $this->Lexer->addSpecialPattern('\{\{eventlist(?:[^\}]*)\}\}', $mode, 'plugin_calendar');
62 $this->Lexer->addSpecialPattern('\{\{eventpanel(?:[^\}]*)\}\}', $mode, 'plugin_calendar');
70 $match = substr($match, 12, -2);
72 $match = substr($match, 13, -2);
74 $match = substr($match, 10, -2);
134 // Disable caching - theme can change via admin without page edit
135 $renderer->nocache();
138 $html = $this->renderStandaloneEventList($data);
140 $html = $this->renderEventPanelOnly($data);
142 $html = $this->renderStaticCalendar($data);
144 $html = $this->renderCompactCalendar($data);
147 $renderer->doc .= $html;
156 $excludeList = $this->parseExcludeList($exclude);
158 // Get theme - prefer inline theme= parameter, fall back to admin default
159 $theme = !empty($data['theme']) ? $data['theme'] : $this->getSidebarTheme();
160 $themeStyles = $this->getSidebarThemeStyles($theme);
161 $themeClass = 'calendar-theme-' . $theme;
170 $events = $this->loadEventsMultiNamespace($namespace, $year, $month, $excludeList);
172 $events = $this->loadEvents($namespace, $year, $month);
178 $prevMonth = $month - 1;
182 $prevYear--;
193 $configFile = $this->syncConfigPath();
202 // Container - all styling via CSS variables
203 …-compact-container ' . $themeClass . '" id="' . $calId . '" data-namespace="' . htmlspecialchars($…
205 // Inject CSS variables for this calendar instance - all theming flows from here
208 --background-site: ' . $themeStyles['bg'] . ';
209 --background-alt: ' . $themeStyles['cell_bg'] . ';
210 --background-header: ' . $themeStyles['header_bg'] . ';
211 --text-primary: ' . $themeStyles['text_primary'] . ';
212 --text-dim: ' . $themeStyles['text_dim'] . ';
213 --text-bright: ' . $themeStyles['text_bright'] . ';
214 --border-color: ' . $themeStyles['grid_border'] . ';
215 --border-main: ' . $themeStyles['border'] . ';
216 --cell-bg: ' . $themeStyles['cell_bg'] . ';
217 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
218 --shadow-color: ' . $themeStyles['shadow'] . ';
219 --header-border: ' . $themeStyles['header_border'] . ';
220 --header-shadow: ' . $themeStyles['header_shadow'] . ';
221 --grid-bg: ' . $themeStyles['grid_bg'] . ';
222 --btn-text: ' . $btnTextColor . ';
223 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
224 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
225 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
226 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
227 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
228 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
229 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
231 … #event-search-' . $calId . '::placeholder { color: ' . $themeStyles['text_dim'] . '; opacity: 1; }
232 …#event-search-' . $calId . '::-webkit-input-placeholder { color: ' . $themeStyles['text_dim'] . ';…
233 …#event-search-' . $calId . '::-moz-placeholder { color: ' . $themeStyles['text_dim'] . '; opacity:…
234 …#event-search-' . $calId . ':-ms-input-placeholder { color: ' . $themeStyles['text_dim'] . '; opac…
238 $html .= '<script src="' . DOKU_BASE . 'lib/plugins/calendar/calendar-main.js"></script>';
244 …$html .= '<script type="application/json" id="events-data-' . $calId . '">' . json_encode($events)…
247 $html .= '<div class="calendar-compact-left">';
250 $html .= '<div class="calendar-compact-header">';
251 …$html .= '<button class="cal-nav-btn" onclick="navCalendar(\'' . $calId . '\', ' . $prevYear . ', …
252 …$html .= '<h3 class="calendar-month-picker" onclick="openMonthPicker(\'' . $calId . '\', ' . $year…
253 …$html .= '<button class="cal-nav-btn" onclick="navCalendar(\'' . $calId . '\', ' . $nextYear . ', …
254 …$html .= '<button class="cal-today-btn" onclick="jumpToToday(\'' . $calId . '\', \'' . $namespace …
257 // Calendar grid - day name headers as a separate div (avoids Firefox th height issues)
258 $html .= '<div class="calendar-day-headers">';
261 $html .= '<table class="calendar-compact-grid">';
279 $monthStart = new DateTime(sprintf('%04d-%02d-01', $year, $month));
280 $monthEnd = new DateTime(sprintf('%04d-%02d-%02d', $year, $month, $daysInMonth));
290 $currentKey = $current->format('Y-m-d');
293 $currentDate = DateTime::createFromFormat('Y-m-d', $currentKey);
294 … if ($currentDate && $currentDate->format('Y-m') === sprintf('%04d-%02d', $year, $month)) {
313 $current->modify('+1 day');
325 $html .= '<td class="cal-empty"></td>';
327 $dateKey = sprintf('%04d-%02d-%02d', $year, $month, $currentDay);
328 $isToday = ($dateKey === date('Y-m-d'));
331 $classes = 'cal-day';
332 if ($isToday) $classes .= ' cal-today';
333 if ($hasEvents) $classes .= ' cal-has-events';
335 …$html .= '<td class="' . $classes . '" data-date="' . $dateKey . '" tabindex="0" role="gridcell" a…
337 $dayNumClass = $isToday ? 'day-num day-num-today' : 'day-num';
348 if (empty($timeA) && !empty($timeB)) return -1;
357 $html .= '<div class="event-indicators">';
380 $barClass = empty($eventTime) ? 'event-bar-no-time' : 'event-bar-timed';
382 // Add classes for multi-day spanning
383 if (!$isFirstDay) $barClass .= ' event-bar-continues';
384 if (!$isLastDay) $barClass .= ' event-bar-continuing';
386 $barClass .= ' event-bar-important';
388 $barClass .= ' event-bar-has-star';
394 $html .= '<span class="event-bar ' . $barClass . '" ';
411 $html .= '</div>'; // End calendar-left
414 $html .= '<div class="calendar-compact-right">';
415 $html .= '<div class="event-list-header">';
416 $html .= '<div class="event-list-header-content">';
417 $html .= '<h4 id="eventlist-title-' . $calId . '">Events</h4>';
419 $html .= '<span class="namespace-badge">' . htmlspecialchars($namespace) . '</span>';
424 $searchDefault = $this->getSearchDefault();
425 $searchAllClass = $searchDefault === 'all' ? ' all-dates' : '';
429 $html .= '<div class="event-search-container-inline">';
430 …$html .= '<input type="text" class="event-search-input-inline" id="event-search-' . $calId . '" pl…
431 …$html .= '<button class="event-search-clear-inline" id="search-clear-' . $calId . '" onclick="clea…
432 …$html .= '<button class="event-search-mode-inline' . $searchAllClass . '" id="search-mode-' . $cal…
435 …$html .= '<button class="add-event-compact" onclick="openAddEvent(\'' . $calId . '\', \'' . $names…
438 $html .= '<div class="event-list-compact" id="eventlist-' . $calId . '">';
439 $html .= $this->renderEventListContent($events, $calId, $namespace, $themeStyles);
442 $html .= '</div>'; // End calendar-right
445 $html .= $this->renderEventDialog($calId, $namespace, $theme);
448 $html .= $this->renderMonthPicker($calId, $year, $month, $namespace, $theme, $themeStyles);
456 * Render a static/read-only calendar for presentation and printing
457 * No edit buttons, clean layout, print-friendly itinerary
464 $excludeList = $this->parseExcludeList($exclude);
471 $calId = 'static-cal-' . substr(md5($namespace . $year . $month . uniqid()), 0, 8);
474 … in_array($themeOverride, ['matrix', 'pink', 'purple', 'professional', 'wiki', 'dark', 'light'])) {
477 $theme = $this->getSidebarTheme();
479 $themeStyles = $this->getSidebarThemeStyles($theme);
482 $importantNsList = $this->getImportantNamespaces();
484 // Load events - check for multi-namespace or wildcard
487 $events = $this->loadEventsMultiNamespace($namespace, $year, $month, $excludeList);
489 $events = $this->loadEvents($namespace, $year, $month);
498 // Display title - custom or default month/year
502 $themeClass = 'static-theme-' . $theme;
505 …-static ' . $themeClass . '" id="' . $calId . '" data-year="' . $year . '" data-month="' . $month …
508 $html .= '<div class="static-screen-view">';
511 $html .= '<div class="static-header">';
513 …$html .= '<button class="static-nav-btn" onclick="navStaticCalendar(\'' . $calId . '\', -1)" title…
515 $html .= '<h2 class="static-month-title">' . hsc($displayTitle) . '</h2>';
517 …$html .= '<button class="static-nav-btn" onclick="navStaticCalendar(\'' . $calId . '\', 1)" title=…
520 …$html .= '<button class="static-print-btn" onclick="printStaticCalendar(\'' . $calId . '\')" title…
525 $html .= '<table class="static-calendar-grid">';
544 $html .= '<td class="static-day-empty"></td>';
546 $dateKey = sprintf('%04d-%02d-%02d', $year, $month, $dayCount);
548 $isToday = ($dateKey === date('Y-m-d'));
551 $cellClass = 'static-day';
552 if ($isToday) $cellClass .= ' static-day-today';
553 if ($isWeekend) $cellClass .= ' static-day-weekend';
554 if (!empty($dayEvents)) $cellClass .= ' static-day-has-events';
557 $html .= '<div class="static-day-number">' . $dayCount . '</div>';
560 $html .= '<div class="static-day-events">';
577 // Build tooltip - plain text with basic formatting indicators
580 $tooltipText .= "\n " . $this->formatTime12Hour($time);
582 … $tooltipText .= ' - ' . $this->formatTime12Hour($event['endTime']);
596 $eventClass = 'static-event';
597 if ($isImportant) $eventClass .= ' static-event-important';
599 …$html .= '<div class="' . $eventClass . '" style="border-left-color: ' . $color . ';" title="' . h…
601 $html .= '<span class="static-event-star">⭐</span>';
604 … $html .= '<span class="static-event-time">' . $this->formatTime12Hour($time) . '</span> ';
606 $html .= '<span class="static-event-title">' . $title . '</span>';
624 $html .= '<div class="static-print-view">';
625 $html .= '<h2 class="static-print-title">' . hsc($displayTitle) . '</h2>';
628 …$html .= '<p class="static-print-namespace">' . $this->getLang('calendar_label') . ': ' . hsc($nam…
650 … $html .= '<p class="static-print-empty">' . $this->getLang('no_events_scheduled') . '</p>';
652 $html .= '<table class="static-itinerary">';
660 $dateDisplay = $dateObj->format('D, M j');
672 $rowClass = $isImportant ? 'static-itinerary-important' : '';
678 $html .= '<td class="static-itinerary-date">' . $dateDisplay . '</td>';
685 …$time = isset($event['time']) && $event['time'] ? $this->formatTime12Hour($event['time']) : $this-…
687 $time .= ' - ' . $this->formatTime12Hour($event['endTime']);
689 $html .= '<td class="static-itinerary-time">' . $time . '</td>';
691 // Title with star for important
692 $html .= '<td class="static-itinerary-title">';
699 // Description - with formatting
700 … $desc = isset($event['description']) ? $this->renderDescription($event['description']) : '';
701 $html .= '<td class="static-itinerary-desc">' . $desc . '</td>';
718 * Format time to 12-hour format
726 $hour12 = $hour == 0 ? 12 : ($hour > 12 ? $hour - 12 : $hour);
734 $configFile = $this->syncConfigPath();
747 return '<p class="no-events-msg">No events this month</p>';
752 $theme = $this->getSidebarTheme();
753 $themeStyles = $this->getSidebarThemeStyles($theme);
755 $theme = $this->getSidebarTheme();
759 $configFile = $this->syncConfigPath();
769 $events = $this->checkTimeConflicts($events);
771 // Sort by date ascending (chronological order - oldest first)
780 // All-day events (no time) go to the TOP
781 if ($timeA === null && $timeB !== null) return -1; // A before B
783 if ($timeA === null && $timeB === null) return 0; // Both all-day, equal
792 $today = date('Y-m-d');
795 // Helper function to check if event is past (with 15-minute grace period for timed events)
807 // Event is today - check time with grace period
813 // Add 15-minute grace period
814 $eventDateTime->modify('+15 minutes');
819 // If time parsing fails, fall back to date-only comparison
828 // Build HTML for each event - separate past/completed from future
836 // Track first future/today event for auto-scroll
866 $renderedDescription = $this->renderDescription($description, $themeStyles);
868 // Convert to 12-hour format and handle time ranges
873 $displayTime = $timeObj->format('g:i A');
879 $displayTime .= ' - ' . $endTimeObj->format('g:i A');
888 // Use originalStartDate if this is a multi-month event continuation
891 $displayDate = $dateObj->format('D, M j'); // e.g., "Mon, Jan 24"
893 // Multi-day indicator
897 $multiDay = ' → ' . $endObj->format('D, M j');
900 $completedClass = $completed ? ' event-completed' : '';
901 // Don't grey out past due tasks - they need attention!
902 $pastClass = ($isPast && !$isPastDue) ? ' event-past' : '';
903 $pastDueClass = $isPastDue ? ' event-pastdue' : '';
904 … $firstFutureAttr = ($firstFutureEventId === $eventId) ? ' data-first-future="true"' : '';
918 $importantClass = $isImportantNs ? ' event-important' : '';
920 // For all themes: use CSS variables, only keep border-left-color as inline
922 …-compact-item' . $completedClass . $pastClass . $pastDueClass . $importantClass . '" data-event-id…
923 $eventHtml .= '<div class="event-info">';
925 $eventHtml .= '<div class="event-title-row">';
926 // Add star for important namespace events
928 $eventHtml .= '<span class="event-important-star" title="Important">⭐</span> ';
930 $eventHtml .= '<span class="event-title-compact">' . $title . '</span>';
936 $eventHtml .= '<div class="event-meta-compact">';
937 $eventHtml .= '<span class="event-date-time">' . $displayDate . $multiDay;
943 … class="event-pastdue-badge" style="background:' . $themeStyles['pastdue_color'] . ' !important; c…
945 …="event-today-badge" style="background:' . $themeStyles['border'] . ' !important; color:' . $theme…
947 // Add namespace badge - ALWAYS show if event has a namespace
954 …-namespace-badge" onclick="filterCalendarByNamespace(\'' . $calId . '\', \'' . htmlspecialchars($e…
965 … $startTimeFormatted = $startTimeObj ? $startTimeObj->format('g:i A') : $conflict['time'];
969 … $endTimeFormatted = $endTimeObj ? $endTimeObj->format('g:i A') : $conflict['endTime'];
970 … $conflictText .= ' (' . $startTimeFormatted . ' - ' . $endTimeFormatted . ')';
979 …$eventHtml .= ' <span class="event-conflict-badge" data-conflicts="' . $conflictJson . '" onmousee…
986 … $eventHtml .= '<div class="event-desc-compact">' . $renderedDescription . '</div>';
989 // Past events: render with display:none for click-to-expand
990 $eventHtml .= '<div class="event-meta-compact" style="display:none;">';
991 $eventHtml .= '<span class="event-date-time">' . $displayDate . $multiDay;
1000 …-namespace-badge" onclick="filterCalendarByNamespace(\'' . $calId . '\', \'' . htmlspecialchars($e…
1010 … $startTimeFormatted = $startTimeObj ? $startTimeObj->format('g:i A') : $conflict['time'];
1014 … $endTimeFormatted = $endTimeObj ? $endTimeObj->format('g:i A') : $conflict['endTime'];
1015 … $conflictText .= ' (' . $startTimeFormatted . ' - ' . $endTimeFormatted . ')';
1024 …$eventHtml .= ' <span class="event-conflict-badge" data-conflicts="' . $conflictJson . '" onmousee…
1031 …$eventHtml .= '<div class="event-desc-compact" style="display:none;">' . $renderedDescription . '<…
1035 $eventHtml .= '</div>'; // event-info
1040 $eventHtml .= '<div class="event-actions-compact">';
1041 …$eventHtml .= '<button class="event-action-btn" onclick="deleteEvent(\'' . $calId . '\', \'' . $ev…
1042 …$eventHtml .= '<button class="event-action-btn" onclick="editEvent(\'' . $calId . '\', \'' . $even…
1045 // Checkbox for tasks - ON THE FAR RIGHT
1048 …$eventHtml .= '<input type="checkbox" class="task-checkbox" ' . $checked . ' onclick="toggleTaskCo…
1067 $html .= '<div class="past-events-section">';
1068 … $html .= '<div class="past-events-toggle" onclick="togglePastEvents(\'' . $calId . '\')">';
1069 $html .= '<span class="past-events-arrow" id="past-arrow-' . $calId . '">▶</span> ';
1070 $html .= '<span class="past-events-label">Past Events (' . $pastCount . ')</span>';
1072 …$html .= '<div class="past-events-content" id="past-events-' . $calId . '" style="display:none;">';
1094 if (empty($evt['time'])) continue; // Skip all-day events
1107 if ($this->eventsOverlap($dateEvents[$i], $dateEvents[$j])) {
1161 return false; // All-day events don't conflict
1171 $start1Mins = $this->timeToMinutes($start1);
1172 $end1Mins = $this->timeToMinutes($end1);
1173 $start2Mins = $this->timeToMinutes($start2);
1174 $end2Mins = $this->timeToMinutes($end2);
1195 $excludeList = $this->parseExcludeList($exclude);
1203 // Get theme - prefer inline theme= parameter, fall back to admin default
1204 …me = !empty($data['theme']) ? $data['theme'] : $this->getSidebarTheme(); $themeStyles = $th…
1210 $events = $this->loadEventsMultiNamespace($namespace, $year, $month, $excludeList);
1212 $events = $this->loadEvents($namespace, $year, $month);
1218 $prevMonth = $month - 1;
1222 $prevYear--;
1236 $configFile = $this->syncConfigPath();
1245 …-panel-standalone" id="' . $calId . '" data-height="' . htmlspecialchars($height) . '" data-namesp…
1247 // Inject CSS variables for this panel instance - same as main calendar
1250 --background-site: ' . $themeStyles['bg'] . ';
1251 --background-alt: ' . $themeStyles['cell_bg'] . ';
1252 --background-header: ' . $themeStyles['header_bg'] . ';
1253 --text-primary: ' . $themeStyles['text_primary'] . ';
1254 --text-dim: ' . $themeStyles['text_dim'] . ';
1255 --text-bright: ' . $themeStyles['text_bright'] . ';
1256 --border-color: ' . $themeStyles['grid_border'] . ';
1257 --border-main: ' . $themeStyles['border'] . ';
1258 --cell-bg: ' . $themeStyles['cell_bg'] . ';
1259 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
1260 --shadow-color: ' . $themeStyles['shadow'] . ';
1261 --header-border: ' . $themeStyles['header_border'] . ';
1262 --header-shadow: ' . $themeStyles['header_shadow'] . ';
1263 --grid-bg: ' . $themeStyles['grid_bg'] . ';
1264 --btn-text: ' . $btnTextColor . ';
1265 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
1266 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
1267 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
1268 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
1269 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
1270 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
1271 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
1273 … #event-search-' . $calId . '::placeholder { color: ' . $themeStyles['text_dim'] . '; opacity: 1; }
1277 $html .= '<script src="' . DOKU_BASE . 'lib/plugins/calendar/calendar-main.js"></script>';
1282 // Compact two-row header designed for ~500px width
1283 $html .= '<div class="panel-header-compact">';
1286 $html .= '<div class="panel-header-row-1">';
1287 …$html .= '<button class="panel-nav-btn" onclick="navEventPanel(\'' . $calId . '\', ' . $prevYear .…
1291 …$html .= '<h3 class="panel-month-title" onclick="openMonthPickerPanel(\'' . $calId . '\', ' . $yea…
1293 …$html .= '<button class="panel-nav-btn" onclick="navEventPanel(\'' . $calId . '\', ' . $nextYear .…
1299 …ss="panel-ns-badge" style="background:var(--cell-today-bg) !important; color:var(--text-bright) !i…
1303 …ss="panel-ns-badge" style="background:var(--cell-today-bg) !important; color:var(--text-bright) !i…
1308 …-ns-badge filter-on" style="background:var(--text-bright) !important; color:var(--background-site)…
1310 …ss="panel-ns-badge" style="background:var(--cell-today-bg) !important; color:var(--text-bright) !i…
1315 …$html .= '<button class="panel-today-btn" onclick="jumpTodayPanel(\'' . $calId . '\', \'' . $names…
1319 $searchDefault = $this->getSearchDefault();
1320 $searchAllClass = $searchDefault === 'all' ? ' all-dates' : '';
1324 $html .= '<div class="panel-header-row-2">';
1325 $html .= '<div class="panel-search-box">';
1326 …$html .= '<input type="text" class="panel-search-input" id="event-search-' . $calId . '" placehold…
1327 …$html .= '<button class="panel-search-clear" id="search-clear-' . $calId . '" onclick="clearEventS…
1328 …$html .= '<button class="panel-search-mode' . $searchAllClass . '" id="search-mode-' . $calId . '"…
1330 …$html .= '<button class="panel-add-btn" onclick="openAddEventPanel(\'' . $calId . '\', \'' . $name…
1335 …$html .= '<div class="event-list-compact" id="eventlist-' . $calId . '" style="max-height: ' . htm…
1336 $html .= $this->renderEventListContent($events, $calId, $namespace);
1339 $html .= $this->renderEventDialog($calId, $namespace, $theme);
1342 $html .= $this->renderMonthPicker($calId, $year, $month, $namespace, $theme, $themeStyles);
1356 $excludeList = $this->parseExcludeList($exclude);
1365 // Handle "range" parameter - day, week, or month
1367 … $startDate = date('Y-m-d', strtotime('-30 days')); // Include past 30 days for past due tasks
1368 $endDate = date('Y-m-d');
1371 … $startDate = date('Y-m-d', strtotime('-30 days')); // Include past 30 days for past due tasks
1373 $endDateTime->modify('+7 days');
1374 $endDate = $endDateTime->format('Y-m-d');
1377 … $startDate = date('Y-m-01', strtotime('-1 month')); // Include previous month for past due tasks
1378 $endDate = date('Y-m-t'); // Last of current month
1380 $headerText = $dt->format('F Y');
1382 // NEW: Sidebar widget - load current week's events
1383 $weekStartDay = $this->getWeekStartDay(); // Get saved preference
1387 $weekStart = date('Y-m-d', strtotime('monday this week'));
1388 $weekEnd = date('Y-m-d', strtotime('sunday this week'));
1390 // Sunday start (default - US/Canada standard)
1394 $weekStart = date('Y-m-d');
1396 // Monday-Saturday: go back to last Sunday
1397 $weekStart = date('Y-m-d', strtotime('-' . $today . ' days'));
1399 $weekEnd = date('Y-m-d', strtotime($weekStart . ' +6 days'));
1408 $tomorrowDate = date('Y-m-d', strtotime('+1 day'));
1415 $twoWeeksOut = date('Y-m-d', strtotime($weekEnd . ' +14 days'));
1418 $end->modify('+1 day'); // DatePeriod excludes end date
1427 $year = (int)$dt->format('Y');
1428 $month = (int)$dt->format('n');
1429 $dateKey = $dt->format('Y-m-d');
1431 $monthKey = $year . '-' . $month . '-' . $namespace;
1435 …$loadedMonths[$monthKey] = $this->loadEventsMultiNamespace($namespace, $year, $month, $excludeList…
1437 $loadedMonths[$monthKey] = $this->loadEvents($namespace, $year, $month);
1449 $allEvents = $this->checkTimeConflicts($allEvents);
1451 $calId = 'sidebar-' . substr(md5($namespace . $weekStart), 0, 8);
1455 return $this->renderSidebarWidget($allEvents, $namespace, $calId, $themeOverride);
1457 $startDate = date('Y-m-d');
1458 $endDate = date('Y-m-d');
1464 $headerText = $start->format('M j') . ' - ' . $end->format('M j, Y');
1469 $headerText = $dt->format('l, F j, Y');
1471 $startDate = date('Y-m-01');
1472 $endDate = date('Y-m-t');
1474 $headerText = $dt->format('F Y');
1481 $end->modify('+1 day');
1492 $year = (int)$dt->format('Y');
1493 $month = (int)$dt->format('n');
1494 $dateKey = $dt->format('Y-m-d');
1496 $monthKey = $year . '-' . $month . '-' . $namespace;
1500 …$loadedMonths[$monthKey] = $this->loadEventsMultiNamespace($namespace, $year, $month, $excludeList…
1502 $loadedMonths[$monthKey] = $this->loadEvents($namespace, $year, $month);
1519 // All-day events (no time) go to the TOP
1520 if ($timeA === null && $timeB !== null) return -1; // A before B
1522 if ($timeA === null && $timeB === null) return 0; // Both all-day, equal
1530 // Simple 2-line display widget
1532 $theme = !empty($data['theme']) ? $data['theme'] : $this->getSidebarTheme();
1533 $themeStyles = $this->getSidebarThemeStyles($theme);
1538 $themeClass = 'eventlist-theme-' . $theme;
1540 // Container styling - dark themes get border + glow, light themes get subtle border
1544 $containerStyle .= ' border-radius:4px;';
1545 $containerStyle .= ' box-shadow:0 0 10px ' . $themeStyles['shadow'] . ';';
1548 $containerStyle .= ' border-radius:4px;';
1551 …$html = '<div class="eventlist-simple ' . $themeClass . '" id="' . $calId . '" style="' . $contain…
1556 --background-site: ' . $themeStyles['bg'] . ';
1557 --background-alt: ' . $themeStyles['cell_bg'] . ';
1558 --text-primary: ' . $themeStyles['text_primary'] . ';
1559 --text-dim: ' . $themeStyles['text_dim'] . ';
1560 --text-bright: ' . $themeStyles['text_bright'] . ';
1561 --border-color: ' . $themeStyles['grid_border'] . ';
1562 --border-main: ' . $themeStyles['border'] . ';
1563 --cell-bg: ' . $themeStyles['cell_bg'] . ';
1564 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
1565 --shadow-color: ' . $themeStyles['shadow'] . ';
1566 --grid-bg: ' . $themeStyles['grid_bg'] . ';
1567 --btn-text: ' . $btnTextColor . ';
1568 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
1569 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
1570 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
1571 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
1572 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
1573 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
1574 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
1579 $html .= '<script src="' . DOKU_BASE . 'lib/plugins/calendar/calendar-main.js"></script>';
1587 $displayDate = $todayDate->format('D, M j, Y'); // "Fri, Jan 30, 2026"
1588 $currentTime = $todayDate->format('g:i:s A'); // "2:45:30 PM"
1590 $html .= '<div class="eventlist-today-header">';
1591 …$html .= '<span class="eventlist-today-clock" id="clock-' . $calId . '">' . $currentTime . '</span…
1592 $html .= '<div class="eventlist-bottom-info">';
1593 ….= '<span class="eventlist-weather"><span id="weather-icon-' . $calId . '">️</span> <span id="we…
1594 $html .= '<span class="eventlist-today-date">' . $displayDate . '</span>';
1611 const clockEl = document.getElementById("clock-' . $calId . '");
1616 // Fetch weather - uses default location, click weather to get local
1619 var userLon = -121.4944;
1622 …fetch("https://api.open-meteo.com/v1/forecast?latitude=" + lat + "&longitude=" + lon + "¤t_w…
1629 const iconEl = document.getElementById("weather-icon-' . $calId . '");
1630 const tempEl = document.getElementById("weather-temp-' . $calId . '");
1662 var weatherEl = document.querySelector("#weather-icon-' . $calId . '");
1679 51: "️", // Light drizzle
1709 $html .= '<div class="eventlist-simple-empty">';
1710 $html .= '<div class="eventlist-simple-header">' . htmlspecialchars($headerText);
1712 … $html .= ' <span class="eventlist-simple-namespace">' . htmlspecialchars($namespace) . '</span>';
1715 $html .= '<div class="eventlist-simple-body">No events</div>';
1719 $todayStr = date('Y-m-d');
1720 $tomorrow = date('Y-m-d', strtotime('+1 day'));
1724 $displayDate = $dateObj->format('D, M j');
1752 $todayClass = $isToday ? ' eventlist-simple-today' : '';
1753 $tomorrowClass = $isTomorrow ? ' eventlist-simple-tomorrow' : '';
1754 $pastDueClass = $isPastDue ? ' eventlist-simple-pastdue' : '';
1755 …$html .= '<div class="eventlist-simple-item' . $todayClass . $tomorrowClass . $pastDueClass . '">';
1756 $html .= '<div class="eventlist-simple-header">';
1759 … $html .= '<span class="eventlist-simple-title">' . htmlspecialchars($event['title']) . '</span>';
1761 // Time (12-hour format)
1770 … $html .= ' <span class="eventlist-simple-time">' . $displayTime . '</span>';
1775 $html .= ' <span class="eventlist-simple-date">' . $displayDate . '</span>';
1779 …"eventlist-simple-pastdue-badge" style="background:' . $themeStyles['pastdue_color'] . ' !importan…
1781 …list-simple-today-badge" style="background:' . $themeStyles['border'] . ' !important; color:' . $t…
1787 … $eventNamespace = $event['_namespace']; // Fallback to _namespace for multi-namespace loading
1790 …$html .= ' <span class="eventlist-simple-namespace">' . htmlspecialchars($eventNamespace) . '</spa…
1795 // Line 2: Body (Description only) - only show if description exists
1797 …$html .= '<div class="eventlist-simple-body">' . $this->renderDescription($event['description']) .…
1805 $html .= '</div>'; // eventlist-simple
1813 $theme = $this->getSidebarTheme();
1815 $themeStyles = $this->getSidebarThemeStyles($theme);
1817 … $html = '<div class="event-dialog-compact" id="dialog-' . $calId . '" style="display:none;">';
1818 … $html .= '<div class="dialog-overlay" onclick="closeEventDialog(\'' . $calId . '\')"></div>';
1821 $html .= '<div class="dialog-content-sleek" id="dialog-content-' . $calId . '">';
1824 … $html .= '<div class="dialog-header-sleek dialog-drag-handle" id="drag-handle-' . $calId . '">';
1825 $html .= '<h3 id="dialog-title-' . $calId . '">Add Event</h3>';
1826 …$html .= '<button type="button" class="dialog-close-btn" onclick="closeEventDialog(\'' . $calId . …
1830 …form id="eventform-' . $calId . '" onsubmit="saveEventCompact(\'' . $calId . '\', \'' . $namespace…
1833 $html .= '<input type="hidden" id="event-id-' . $calId . '" name="eventId" value="">';
1836 $html .= '<div class="form-field">';
1837 $html .= '<label class="field-label"> Title</label>';
1838 …$html .= '<input type="text" id="event-title-' . $calId . '" name="title" required class="input-sl…
1842 $html .= '<div class="form-field">';
1843 $html .= '<label class="field-label"> Namespace</label>';
1846 … $html .= '<input type="hidden" id="event-namespace-' . $calId . '" name="namespace" value="">';
1849 $html .= '<div class="namespace-search-wrapper">';
1850 …l .= '<input type="text" id="event-namespace-search-' . $calId . '" class="input-sleek input-compa…
1851 …$html .= '<div class="namespace-dropdown" id="event-namespace-dropdown-' . $calId . '" style="disp…
1855 $allNamespaces = $this->getAllNamespaces();
1856 …$html .= '<script type="application/json" id="namespaces-data-' . $calId . '">' . json_encode($all…
1861 $html .= '<div class="form-field">';
1862 $html .= '<label class="field-label"> Description</label>';
1863 …$html .= '<textarea id="event-desc-' . $calId . '" name="description" rows="2" class="input-sleek …
1866 // 3. START DATE - END DATE (inline)
1867 $html .= '<div class="form-row-group">';
1869 $html .= '<div class="form-field form-field-half">';
1870 $html .= '<label class="field-label-compact"> Start Date</label>';
1871 $html .= '<div class="date-picker-wrapper">';
1872 … $html .= '<input type="hidden" id="event-date-' . $calId . '" name="date" required value="">';
1873 …n" class="custom-date-picker input-sleek input-compact" id="date-picker-btn-' . $calId . '" data-t…
1874 $html .= '<span class="date-display">Select date</span>';
1875 $html .= '<span class="date-arrow">▼</span>';
1877 $html .= '<div class="date-dropdown" id="date-dropdown-' . $calId . '"></div>';
1881 $html .= '<div class="form-field form-field-half">';
1882 $html .= '<label class="field-label-compact"> End Date</label>';
1883 $html .= '<div class="date-picker-wrapper">';
1884 $html .= '<input type="hidden" id="event-end-date-' . $calId . '" name="endDate" value="">';
1885 …lass="custom-date-picker input-sleek input-compact" id="end-date-picker-btn-' . $calId . '" data-t…
1886 $html .= '<span class="date-display">Optional</span>';
1887 $html .= '<span class="date-arrow">▼</span>';
1889 $html .= '<div class="date-dropdown" id="end-date-dropdown-' . $calId . '"></div>';
1896 $html .= '<div class="form-field form-field-checkbox form-field-checkbox-compact">';
1897 $html .= '<label class="checkbox-label checkbox-label-compact">';
1898 …$html .= '<input type="checkbox" id="event-recurring-' . $calId . '" name="isRecurring" class="rec…
1904 …-options-' . $calId . '" class="recurring-options" style="display:none; border:1px solid var(--bor…
1907 $html .= '<div class="form-row-group" style="margin-bottom:6px;">';
1909 $html .= '<div class="form-field" style="flex:0 0 auto; min-width:0;">';
1910 $html .= '<label class="field-label-compact">Repeat every</label>';
1911 … '<input type="number" id="event-recurrence-interval-' . $calId . '" name="recurrenceInterval" cla…
1914 $html .= '<div class="form-field" style="flex:1; min-width:0;">';
1915 $html .= '<label class="field-label-compact"> </label>';
1916 …$html .= '<select id="event-recurrence-type-' . $calId . '" name="recurrenceType" class="input-sle…
1926 // Row 2: Weekly options - day of week checkboxes
1927 …$html .= '<div id="weekly-options-' . $calId . '" class="weekly-options" style="display:none; marg…
1928 …$html .= '<label class="field-label-compact" style="display:block; margin-bottom:4px;">On these da…
1929 $html .= '<div style="display:flex; flex-wrap:wrap; gap:2px;">';
1932 …-flex; align-items:center; padding:2px 6px; background:var(--cell-bg, #1a1a1a); border:1px solid v…
1933 …$html .= '<input type="checkbox" name="weekDays[]" value="' . $idx . '" style="margin-right:3px; w…
1940 // Row 3: Monthly options - day of month OR ordinal weekday
1941 …$html .= '<div id="monthly-options-' . $calId . '" class="monthly-options" style="display:none; ma…
1942 …$html .= '<label class="field-label-compact" style="display:block; margin-bottom:4px;">Repeat on:<…
1945 $html .= '<div style="margin-bottom:6px;">';
1946 …$html .= '<label style="display:inline-flex; align-items:center; margin-right:12px; cursor:pointer…
1947 …="dayOfMonth" checked onchange="updateMonthlyType(\'' . $calId . '\')" style="margin-right:4px;">';
1950 …$html .= '<label style="display:inline-flex; align-items:center; cursor:pointer; font-size:11px;">…
1951 …alue="ordinalWeekday" onchange="updateMonthlyType(\'' . $calId . '\')" style="margin-right:4px;">';
1957 …$html .= '<div id="monthly-day-' . $calId . '" style="display:flex; align-items:center; gap:6px;">…
1958 $html .= '<span style="font-size:11px;">Day</span>';
1959 …$html .= '<input type="number" id="event-month-day-' . $calId . '" name="monthDay" class="input-sl…
1960 $html .= '<span style="font-size:10px; color:var(--text-dim, #666);">of each month</span>';
1964 $html .= '<div id="monthly-ordinal-' . $calId . '" style="display:none;">';
1965 $html .= '<div style="display:flex; align-items:center; gap:4px; flex-wrap:wrap;">';
1966 …$html .= '<select id="event-ordinal-' . $calId . '" name="ordinalWeek" class="input-sleek input-co…
1972 $html .= '<option value="-1">Last</option>';
1974 …$html .= '<select id="event-ordinal-day-' . $calId . '" name="ordinalDay" class="input-sleek input…
1983 $html .= '<span style="font-size:10px; color:var(--text-dim, #666);">of each month</span>';
1990 $html .= '<div class="form-row-group">';
1991 $html .= '<div class="form-field">';
1992 $html .= '<label class="field-label-compact">Repeat Until (optional)</label>';
1993 ….= '<input type="date" id="event-recurrence-end-' . $calId . '" name="recurrenceEnd" class="input-…
1994 …$html .= '<div style="font-size:9px; color:var(--text-dim, #666); margin-top:2px;">Leave empty for…
2000 // 5. TIME (Start & End) - COLOR (inline)
2001 $html .= '<div class="form-row-group">';
2003 $html .= '<div class="form-field form-field-half">';
2004 $html .= '<label class="field-label-compact"> Start Time</label>';
2005 $html .= '<div class="time-picker-wrapper">';
2007 $html .= '<input type="hidden" id="event-time-' . $calId . '" name="time" value="">';
2008 …n" class="custom-time-picker input-sleek input-compact" id="time-picker-btn-' . $calId . '" data-t…
2009 $html .= '<span class="time-display">All day</span>';
2010 $html .= '<span class="time-arrow">▼</span>';
2012 $html .= '<div class="time-dropdown" id="time-dropdown-' . $calId . '"></div>';
2016 $html .= '<div class="form-field form-field-half">';
2017 $html .= '<label class="field-label-compact"> End Time</label>';
2018 $html .= '<div class="time-picker-wrapper">';
2020 $html .= '<input type="hidden" id="event-end-time-' . $calId . '" name="endTime" value="">';
2021 …lass="custom-time-picker input-sleek input-compact" id="end-time-picker-btn-' . $calId . '" data-t…
2022 $html .= '<span class="time-display">Same as start</span>';
2023 $html .= '<span class="time-arrow">▼</span>';
2025 $html .= '<div class="time-dropdown" id="end-time-dropdown-' . $calId . '"></div>';
2032 $html .= '<div class="form-row-group">';
2034 $html .= '<div class="form-field form-field-full">';
2035 $html .= '<label class="field-label-compact"> Color</label>';
2036 $html .= '<div class="color-picker-wrapper">';
2037 …$html .= '<select id="event-color-' . $calId . '" name="color" class="input-sleek input-compact co…
2047 …$html .= '<input type="color" id="event-color-custom-' . $calId . '" class="color-picker-input col…
2054 $html .= '<div class="form-field form-field-checkbox form-field-checkbox-compact">';
2055 $html .= '<label class="checkbox-label checkbox-label-compact">';
2056 …$html .= '<input type="checkbox" id="event-is-task-' . $calId . '" name="isTask" class="task-toggl…
2062 $html .= '<div class="dialog-actions-sleek">';
2063 …$html .= '<button type="button" class="btn-sleek btn-cancel-sleek" onclick="closeEventDialog(\'' .…
2064 $html .= '<button type="submit" class="btn-sleek btn-save-sleek"> Save</button>';
2077 $themeStyles = $this->getSidebarThemeStyles($theme);
2080 $themeClass = 'calendar-theme-' . $theme;
2082 …$html = '<div class="month-picker-overlay ' . $themeClass . '" id="month-picker-overlay-' . $calId…
2083 $html .= '<div class="month-picker-dialog" onclick="event.stopPropagation();">';
2086 $html .= '<div class="month-picker-selects">';
2087 $html .= '<select id="month-picker-month-' . $calId . '" class="month-picker-select">';
2091 … $html .= '<option value="' . $m . '"' . $selected . '>' . $monthNames[$m - 1] . '</option>';
2095 $html .= '<select id="month-picker-year-' . $calId . '" class="month-picker-select">';
2097 for ($y = $currentYear - 5; $y <= $currentYear + 5; $y++) {
2104 $html .= '<div class="month-picker-actions">';
2105 …$html .= '<button class="btn-sleek btn-cancel-sleek" onclick="closeMonthPicker(\'' . $calId . '\')…
2106 …$html .= '<button class="btn-sleek btn-save-sleek" onclick="jumpToSelectedMonth(\'' . $calId . '\'…
2122 $theme = $this->getSidebarTheme();
2123 $themeStyles = $this->getSidebarThemeStyles($theme);
2127 $linkStyle = ' class="cal-link"';
2129 // Token-based parsing to avoid escaping issues
2143 …' . htmlspecialchars($imagePath) . '" alt="' . htmlspecialchars($alt) . '" class="event-image" />';
2147 …eHtml = '<img src="' . $imageUrl . '" alt="' . htmlspecialchars($alt) . '" class="event-image" />';
2182 // Convert markdown-style links [text](url) to tokens
2250 $dataDir = $this->metaDir();
2256 $eventFile = $dataDir . sprintf('%04d-%02d.json', $year, $month);
2270 return $this->loadEventsWildcard($baseNamespace, $year, $month, $excludeList);
2275 return $this->loadEventsWildcard('', $year, $month, $excludeList);
2289 if ($this->isNamespaceExcluded($ns, $excludeList)) continue;
2292 if (!$this->checkNamespaceRead($ns)) continue;
2294 $events = $this->loadEvents($ns, $year, $month);
2312 $metaDir = $this->metaDir();
2322 $events = $this->loadEvents('', $year, $month);
2333 …if (!$this->isNamespaceExcluded($baseNamespace, $excludeList) && $this->checkNamespaceRead($baseNa…
2334 $events = $this->loadEvents($baseNamespace, $year, $month);
2349 $this->findCalendarNamespaces($dataDir, $metaDir, $year, $month, $allEvents, $excludeList);
2372 // No calendar dirs at this depth or deeper - stop early
2388 if ($this->isNamespaceExcluded($namespace, $excludeList)) continue;
2391 if (!$this->checkNamespaceRead($namespace)) continue;
2393 $events = $this->loadEvents($namespace, $year, $month);
2408 $dataDir = $this->metaDir();
2412 $this->scanForCalendarNamespaces($dataDir, '', $namespaces);
2443 $this->scanForCalendarNamespaces($path . '/', $namespace, $namespaces);
2449 * Render new sidebar widget - Week at a glance itinerary (200px wide)
2453 …return '<div style="width:200px; padding:12px; text-align:center; color:#999; font-size:11px;">No …
2457 $configFile = $this->syncConfigPath();
2467 $todayStr = date('Y-m-d');
2468 $tomorrowStr = date('Y-m-d', strtotime('+1 day'));
2471 $weekStartDay = $this->getWeekStartDay();
2475 $weekStart = date('Y-m-d', strtotime('monday this week'));
2476 $weekEnd = date('Y-m-d', strtotime('sunday this week'));
2478 // Sunday start (default - US/Canada standard)
2482 $weekStart = date('Y-m-d');
2484 // Monday-Saturday: go back to last Sunday
2485 $weekStart = date('Y-m-d', strtotime('-' . $today . ' days'));
2487 $weekEnd = date('Y-m-d', strtotime($weekStart . ' +6 days'));
2499 $eventsWithConflicts = $this->detectTimeConflicts($dayEvents);
2517 // Pre-render DokuWiki syntax to HTML for JavaScript display
2520 $eventWithHtml['title_html'] = $this->renderDokuWikiToHtml($event['title']);
2523 … $eventWithHtml['description_html'] = $this->renderDokuWikiToHtml($event['description']);
2552 // Same date - sort by time
2556 if (empty($timeA) && !empty($timeB)) return 1; // All-day events last
2557 if (!empty($timeA) && empty($timeB)) return -1;
2561 $aMinutes = $this->timeToMinutes($timeA);
2562 $bMinutes = $this->timeToMinutes($timeB);
2563 return $aMinutes - $bMinutes;
2569 // Get theme - prefer override from syntax parameter, fall back to admin default
2570 $theme = !empty($themeOverride) ? $themeOverride : $this->getSidebarTheme();
2571 $themeStyles = $this->getSidebarThemeStyles($theme);
2572 $themeClass = 'sidebar-' . $theme;
2574 // Start building HTML - Dynamic width with default font (overflow:visible for tooltips)
2575 …-widget ' . $themeClass . '" id="sidebar-widget-' . $calId . '" style="width:100%; max-width:100%;…
2580 #sidebar-widget-' . $calId . ' {
2581 --background-site: ' . $themeStyles['bg'] . ';
2582 --background-alt: ' . $themeStyles['cell_bg'] . ';
2583 --background-header: ' . $themeStyles['header_bg'] . ';
2584 --text-primary: ' . $themeStyles['text_primary'] . ';
2585 --text-dim: ' . $themeStyles['text_dim'] . ';
2586 --text-bright: ' . $themeStyles['text_bright'] . ';
2587 --border-color: ' . $themeStyles['grid_border'] . ';
2588 --border-main: ' . $themeStyles['border'] . ';
2589 --cell-bg: ' . $themeStyles['cell_bg'] . ';
2590 --cell-today-bg: ' . $themeStyles['cell_today_bg'] . ';
2591 --shadow-color: ' . $themeStyles['shadow'] . ';
2592 --header-border: ' . $themeStyles['header_border'] . ';
2593 --header-shadow: ' . $themeStyles['header_shadow'] . ';
2594 --grid-bg: ' . $themeStyles['grid_bg'] . ';
2595 --btn-text: ' . $btnTextColor . ';
2596 --pastdue-color: ' . $themeStyles['pastdue_color'] . ';
2597 --pastdue-bg: ' . $themeStyles['pastdue_bg'] . ';
2598 --pastdue-bg-strong: ' . $themeStyles['pastdue_bg_strong'] . ';
2599 --pastdue-bg-light: ' . $themeStyles['pastdue_bg_light'] . ';
2600 --tomorrow-bg: ' . $themeStyles['tomorrow_bg'] . ';
2601 --tomorrow-bg-strong: ' . $themeStyles['tomorrow_bg_strong'] . ';
2602 --tomorrow-bg-light: ' . $themeStyles['tomorrow_bg_light'] . ';
2609 @keyframes sparkle-' . $calId . ' {
2616 transform: translate(var(--tx), var(--ty)) scale(1) rotate(180deg);
2620 … transform: translate(calc(var(--tx) * 2), calc(var(--ty) * 2)) scale(0) rotate(360deg);
2624 @keyframes pulse-glow-' . $calId . ' {
2625 0%, 100% { box-shadow: 0 0 10px rgba(255, 20, 147, 0.4); }
2626 … 50% { box-shadow: 0 0 25px rgba(255, 20, 147, 0.8), 0 0 40px rgba(255, 20, 147, 0.4); }
2629 @keyframes shimmer-' . $calId . ' {
2630 0% { background-position: -200% center; }
2631 100% { background-position: 200% center; }
2634 .sidebar-pink {
2635 animation: pulse-glow-' . $calId . ' 3s ease-in-out infinite;
2638 .sidebar-pink:hover {
2639 … box-shadow: 0 0 30px rgba(255, 20, 147, 0.9), 0 0 50px rgba(255, 20, 147, 0.5) !important;
2642 .sparkle-' . $calId . ' {
2644 pointer-events: none;
2645 font-size: 20px;
2646 z-index: 1000;
2647 animation: sparkle-' . $calId . ' 1s ease-out forwards;
2648 filter: drop-shadow(0 0 3px rgba(255, 20, 147, 0.8));
2654 const container = document.getElementById("sidebar-widget-' . $calId . '");
2659 sparkle.className = "sparkle-' . $calId . '";
2667 sparkle.style.setProperty("--tx", Math.cos(angle) * distance + "px");
2668 sparkle.style.setProperty("--ty", Math.sin(angle) * distance + "px");
2678 const x = e.clientX - rect.left;
2679 const y = e.clientY - rect.top;
2684 const offsetX = x + (Math.random() - 0.5) * 30;
2685 const offsetY = y + (Math.random() - 0.5) * 30;
2691 // Random auto-sparkles for extra glamour
2702 $jsCalId = str_replace('-', '_', $calId);
2716 const clockEl = document.getElementById("clock-' . $calId . '");
2721 // Weather - uses default location, click weather to get local
2724 var userLon = -121.4944;
2727 …fetch("https://api.open-meteo.com/v1/forecast?latitude=" + lat + "&longitude=" + lon + "¤t_w…
2734 const iconEl = document.getElementById("weather-icon-' . $calId . '");
2735 const tempEl = document.getElementById("weather-temp-' . $calId . '");
2763 var weatherEl = document.querySelector("#weather-icon-' . $calId . '");
2790 $displayDate = $todayDate->format('D, M j, Y');
2791 $currentTime = $todayDate->format('g:i:s A');
2793 …s="eventlist-today-header" style="background:' . $themeStyles['header_bg'] . '; border:2px solid '…
2794 …$html .= '<span class="eventlist-today-clock" id="clock-' . $calId . '" style="color:' . $themeSty…
2795 $html .= '<div class="eventlist-bottom-info">';
2796 …-weather"><span id="weather-icon-' . $calId . '">️</span> <span id="weather-temp-' . $calId . '"…
2797 …$html .= '<span class="eventlist-today-date" style="color:' . $themeStyles['text_dim'] . ';">' . $…
2802 $todayStr = date('Y-m-d');
2804 // Thin "Add Event" bar between header and week grid - theme-aware colors
2814 …x; line-height:10px; text-align:center; cursor:pointer; border-top:1px solid rgba(0, 0, 0, 0.1); b…
2816 …nt-size:8px; font-weight:700; letter-spacing:0.4px; font-family:system-ui, sans-serif; text-shadow…
2820 $html .= $this->renderWeekGrid($weekEvents, $weekStart, $themeStyles, $theme);
2822 // Section colors - derived from theme palette
2835 $importantColor = '#ff85c1'; // Light pink
2841 // Wiki - section header backgrounds from template colors
2850 // Itinerary bar (collapsible toggle) - styled like +Add bar
2862 $jsCalId = str_replace('-', '_', $calId);
2865 $itineraryDefaultCollapsed = $this->getItineraryCollapsed();
2866 $arrowDefaultStyle = $itineraryDefaultCollapsed ? 'transform:rotate(-90deg);' : '';
2867 $contentDefaultStyle = $itineraryDefaultCollapsed ? 'max-height:0px; opacity:0;' : '';
2869 …-bar-' . $calId . '" style="background:' . $itineraryBg . '; padding:0; margin:0; height:12px; lin…
2870 …-arrow-' . $calId . '" style="color:' . $itineraryTextColor . '; font-size:6px; font-weight:700; f…
2871 …t-size:8px; font-weight:700; letter-spacing:0.4px; font-family:system-ui, sans-serif; text-shadow:…
2875 …$html .= '<div id="itinerary-content-' . $calId . '" style="transition:max-height 0.3s ease-out, o…
2879 …$html .= $this->renderSidebarSection('Today', $todayEvents, $todayColor, $calId, $themeStyles, $th…
2884 …$html .= $this->renderSidebarSection('Tomorrow', $tomorrowEvents, $tomorrowColor, $calId, $themeSt…
2889 …$html .= $this->renderSidebarSection('Important Events', $importantEvents, $importantColor, $calId…
2894 …iv style="padding:8px; text-align:center; color:' . $themeStyles['text_dim'] . '; font-size:10px; …
2897 $html .= '</div>'; // Close itinerary-content
2900 $itineraryDefaultCollapsed = $this->getItineraryCollapsed();
2902 $itineraryArrowDefault = $itineraryDefaultCollapsed ? 'rotate(-90deg)' : 'rotate(0deg)';
2903 …$itineraryContentDefault = $itineraryDefaultCollapsed ? 'max-height:0px; opacity:0;' : 'max-height…
2911 const content = document.getElementById("itinerary-content-' . $calId . '");
2912 const arrow = document.getElementById("itinerary-arrow-' . $calId . '");
2918 arrow.style.transform = "rotate(-90deg)";
2937 const content = document.getElementById("itinerary-content-' . $calId . '");
2938 const arrow = document.getElementById("itinerary-arrow-' . $calId . '");
2946 arrow.style.transform = "rotate(-90deg)";
2955 $html .= $this->renderEventDialog($calId, $namespace, $theme);
2957 // Add JavaScript for positioning data-tooltip elements
2959 // Position data-tooltip elements to prevent cutoff (up and to the LEFT)
2961 const tooltipElements = document.querySelectorAll("[data-tooltip]");
2962 const isPinkTheme = document.querySelector(".sidebar-pink") !== null;
2970 element.style.setProperty("--tooltip-left", (rect.left - 150) + "px");
2971 element.style.setProperty("--tooltip-top", (rect.top - 30) + "px");
2975 element.style.setProperty("--heart-left", (rect.left - 150 + 210) + "px");
2976 element.style.setProperty("--heart-top", (rect.top - 30) + "px");
2985 [data-tooltip]:hover:before {
2986 left: var(--tooltip-left, 0) !important;
2987 top: var(--tooltip-top, 0) !important;
2989 .sidebar-pink [data-tooltip]:hover:after {
2990 left: var(--heart-left, 0) !important;
2991 top: var(--heart-top, 0) !important;
3001 * Render compact week grid (7 cells with event bars) - Theme-aware
3004 // Generate unique ID for this calendar instance - sanitize for JavaScript
3006 $jsCalId = str_replace('-', '_', $calId); // Sanitize for JS variable names
3008 …div style="display:grid; grid-template-columns:repeat(7, 1fr); gap:1px; background:' . $themeStyle…
3011 $weekStartDay = $this->getWeekStartDay();
3017 $today = date('Y-m-d');
3020 $date = date('Y-m-d', strtotime($weekStart . ' +' . $i . ' days'));
3031 // Theme-aware text shadow
3034 …$textShadow = $isToday ? 'text-shadow:0 0 3px ' . $glowColor . ';' : 'text-shadow:0 0 2px ' . $glo…
3037 …$textShadow = $isToday ? 'text-shadow:0 0 2px ' . $glowColor . ';' : 'text-shadow:0 0 1px ' . $glo…
3040 …$textShadow = $isToday ? 'text-shadow:0 0 2px ' . $glowColor . ';' : 'text-shadow:0 0 1px ' . $glo…
3052 … .= '<div style="background:' . $bgColor . '; padding:4px 2px; text-align:center; min-height:45px;…
3054 // Day letter - theme color
3056 …$html .= '<div style="font-size:9px; color:' . $dayLetterColor . '; font-weight:500; font-family:s…
3059 ….= '<div style="font-size:12px; color:' . $textColor . '; font-weight:' . $fontWeight . '; margin:…
3061 // Event bars (max 4 visible) with theme-aware glow
3068 …x; background:' . htmlspecialchars($color) . '; margin:1px 0; border-radius:1px; box-shadow:' . $b…
3071 // Show "+N more" if more than 4 - theme color
3074 ….= '<div style="font-size:7px; color:' . $moreTextColor . '; margin-top:1px; font-family:system-ui…
3083 // Add container for selected day events display (with unique ID) - theme-aware
3093 // Header text color - dark bg text for dark themes, white for light theme accent headers
3097 …-day-events-' . $calId . '" style="display:none; margin:8px 4px; border-left:3px solid ' . $panelB…
3099 …-size:9px; font-weight:700; letter-spacing:0.3px; font-family:system-ui, sans-serif; box-shadow:' …
3100 $html .= '<span id="selected-day-title-' . $calId . '"></span>';
3101 …mentById(\'selected-day-events-' . $calId . '\').style.display=\'none\';" style="cursor:pointer; f…
3103 …-webkit-text-fill-color:' . $panelHeaderColor . ' !important; padding:4px 6px; font-size:9px; font…
3104 $html .= '<span id="selected-day-title-' . $calId . '"></span>';
3105 …-day-events-' . $calId . '\').style.display=\'none\';" style="cursor:pointer; font-size:12px; padd…
3108 …$html .= '<div id="selected-day-content-' . $calId . '" style="padding:4px 0; background:' . $pane…
3114 $jsCalId = str_replace('-', '_', $calId);
3122 … 'text_shadow' => ($theme === 'pink') ? 'text-shadow:0 0 2px ' . $themeStyles['text_primary'] :
3123 …((in_array($theme, ['matrix', 'purple'])) ? 'text-shadow:0 0 1px ' . $themeStyles['text_primary'] …
3137 const container = document.getElementById("selected-day-events-' . $calId . '");
3138 const title = document.getElementById("selected-day-title-' . $calId . '");
3139 const content = document.getElementById("selected-day-content-' . $calId . '");
3145 const dayName = dateObj.toLocaleDateString("en-US", { weekday: "long" });
3146 … const monthDay = dateObj.toLocaleDateString("en-US", { month: "short", day: "numeric" });
3152 // Sort events by time (all-day events first, then timed events chronologically)
3154 // All-day events (no time) go to the beginning
3156 if (!a.time) return -1; // a is all-day, comes first
3157 if (!b.time) return 1; // b is all-day, comes first
3165 return minutesA - minutesB;
3168 // Build events HTML with single color bar (event color only) - theme-aware
3174 …-bottom:1px solid " + themeColors.border_color + "; font-size:10px; display:flex; align-items:stre…
3178 // Event assigned color bar (single bar on left) - theme-aware shadow
3180 …+= "<div style=\\"width:3px; align-self:stretch; background:" + eventColor + "; border-radius:1px;…
3183 …eventHTML += "<div style=\\"flex:1; min-width:0; display:flex; justify-content:space-between; alig…
3186 eventHTML += "<div style=\\"flex:1; min-width:0;\\">";
3187 …ML += "<div style=\\"font-weight:600; color:" + themeColors.text_primary + "; word-wrap:break-word…
3196 …HTML += "<span style=\\"color:" + themeColors.text_bright + "; font-weight:500; font-size:9px;\\">…
3199 // Title - use HTML version if available
3204 // Description if present - use HTML version - theme-aware color
3207 …eventHTML += "<div style=\\"font-size:9px; color:" + themeColors.text_dim + "; margin-top:2px;\\">…
3217 … const confTime = conf.time + (conf.end_time ? " - " + conf.end_time : "");
3222 …eventHTML += "<span class=\\"event-conflict-badge\\" style=\\"font-size:10px;\\" data-conflicts=\\…
3240 * Render a sidebar section (Today/Tomorrow/Important) - Matrix themed with colored borders
3256 // Different dates - sort by date
3261 // Same date - sort by time
3265 // All-day events last within same date
3267 if (!empty($aTime) && empty($bTime)) return -1;
3271 $aMinutes = $this->timeToMinutes($aTime);
3272 $bMinutes = $this->timeToMinutes($bTime);
3273 return $aMinutes - $bMinutes;
3281 // All-day events (no time) come first
3282 if (empty($aTime) && !empty($bTime)) return -1;
3286 // Both have times - convert to minutes for proper chronological sort
3287 $aMinutes = $this->timeToMinutes($aTime);
3288 $bMinutes = $this->timeToMinutes($bTime);
3290 return $aMinutes - $bMinutes;
3294 // Theme-aware section shadow
3300 // Wiki theme: use a background div for the left bar instead of border-left
3302 …$html = '<div style="display:flex; margin:8px 4px; box-shadow:' . $sectionShadow . '; background:'…
3303 … $html .= '<div style="width:3px; flex-shrink:0; background:' . $borderColor . ';"></div>';
3304 $html .= '<div style="flex:1; min-width:0;">';
3306 …$html = '<div style="border-left:3px solid ' . $borderColor . ' !important; margin:8px 4px; box-sh…
3309 // Section header with accent color background - theme-aware
3315 …Color . '; padding:4px 6px; font-size:9px; font-weight:700; letter-spacing:0.3px; font-family:syst…
3318 …-webkit-text-fill-color:' . $headerTextColor . ' !important; padding:4px 6px; font-size:9px; font-…
3323 // Events - no background (transparent)
3327 …$html .= $this->renderSidebarEvent($event, $calId, $showDate, $accentColor, $themeStyles, $theme, …
3340 * Render individual event in sidebar - Theme-aware
3361 // Theme-aware colors
3364 $textShadow = ($theme === 'pink') ? 'text-shadow:0 0 2px ' . $titleColor . ';' :
3365 … ((in_array($theme, ['matrix', 'purple'])) ? 'text-shadow:0 0 1px ' . $titleColor . ';' : '');
3375 …$confTime = $this->formatTimeDisplay($conf['time'], isset($conf['end_time']) ? $conf['end_time'] :…
3380 // No background on individual events (transparent) - unless important namespace
3384 // Important namespace highlighting - subtle themed background
3388 // Theme-specific important highlighting
3392 $importantBorder = 'border-right:2px solid rgba(0,204,7,0.4);';
3396 $importantBorder = 'border-right:2px solid rgba(156,39,176,0.4);';
3400 $importantBorder = 'border-right:2px solid rgba(255,105,180,0.5);';
3404 $importantBorder = 'border-right:2px solid rgba(33,150,243,0.4);';
3408 $importantBorder = 'border-right:2px solid rgba(0,102,204,0.3);';
3412 $importantBorder = 'border-right:2px solid rgba(0,204,7,0.4);';
3416 …ing:4px 6px; border-bottom:1px solid ' . $borderColor . ' !important; font-size:10px; display:flex…
3420 …l .= '<div style="width:3px; align-self:stretch; background:' . $eventColor . '; border-radius:1px…
3423 $html .= '<div style="flex:1; min-width:0;">';
3426 …$html .= '<div style="font-weight:600; color:' . $titleColor . '; word-wrap:break-word; font-famil…
3429 $displayTime = $this->formatTimeDisplay($time, $endTime);
3430 …$html .= '<span style="color:' . $timeColor . '; font-weight:500; font-size:9px;">' . htmlspecialc…
3437 … $html .= '<span style="font-size:11px; color:' . $checkColor . ';">' . $checkIcon . '</span> ';
3442 $html .= '<span style="font-size:9px;" title="Important">⭐</span> ';
3445 $html .= $title; // Already HTML-escaped on line 2625
3450 …$html .= ' <span class="event-conflict-badge" style="font-size:10px;" data-conflicts="' . $conflic…
3458 $displayDate = $dateObj->format('D, M j'); // e.g., "Mon, Feb 10"
3460 $dateShadow = ($theme === 'pink') ? 'text-shadow:0 0 2px ' . $dateColor . ';' :
3461 … ((in_array($theme, ['matrix', 'purple'])) ? 'text-shadow:0 0 1px ' . $dateColor . ';' : '');
3462 …$html .= '<div style="font-size:8px; color:' . $dateColor . '; font-weight:500; margin-top:2px; ' …
3472 * Format time display (12-hour format with optional end time)
3492 $display .= '-' . $endDisplayHour . ':' . $endMinute . ' ' . $endAmpm;
3518 // Skip all-day events (no time)
3533 // If no end time, use start time (zero duration) - matches main calendar logic
3540 if (empty($otherEvent['time'])) continue; // Skip all-day events
3554 $start1Min = $this->timeToMinutes($startTime);
3555 $end1Min = $this->timeToMinutes($endTime);
3556 $start2Min = $this->timeToMinutes($otherStart);
3557 $end2Min = $this->timeToMinutes($otherEnd);
3561 // e.g., 1:00-2:00 and 2:00-3:00 are NOT in conflict
3585 $totalMinutes = $this->timeToMinutes($time) + ($hours * 60);
3622 $this->scanForNamespaces($path . '/', $namespace, $namespaces);
3631 $configFile = $this->metaDir() . 'calendar_theme.txt';
3650 // Try multiple possible locations for style.ini (farm-safe)
3654 // Add farm-specific conf override path if available
3692 // __background_site__ → --background-site → Container, panel backgrounds
3693 … // __background__ → --cell-bg → Cell/input backgrounds (typically white)
3694 // __background_alt__ → --background-alt → Hover states, header backgrounds
3695 // → --background-header
3696 // __background_neu__ → --cell-today-bg → Today cell highlight
3697 // __text__ → --text-primary → Primary text, labels, titles
3698 // __text_neu__ → --text-dim → Secondary text, dates, descriptions
3700 // __border__ → --border-color → Grid lines, input borders
3701 …// → --border-main → Accent color: buttons, badges, active element…
3702 // → --header-border
3703 // __link__ → --text-bright → Links, accent text
3719 'cell_bg' => $background, // Cells use __background__ (white/light)
3733 * Get theme-specific color styles
3738 $wikiColors = $this->getWikiTemplateColors();
3750 'header_bg' => 'linear-gradient(180deg, #2a2a2a 0%, #242424 100%)',
3773 'header_bg' => 'linear-gradient(180deg, #2f2438 0%, #2a2030 100%)',
3796 'header_bg' => 'linear-gradient(180deg, #ffffff 0%, #f5f7fa 100%)',
3819 'header_bg' => 'linear-gradient(180deg, #2d1a24 0%, #1a0d14 100%)',
3870 $configFile = $this->metaDir() . 'calendar_week_start.txt';
3884 $configFile = $this->metaDir() . 'calendar_itinerary_collapsed.txt';
3895 $configFile = $this->metaDir() . 'calendar_search_default.txt';
3907 * Supports semicolon-separated list: "journal;drafts;personal:private"