simple-calendar-js 2.0.1 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,12 @@ A pure JavaScript calendar component with no dependencies. Features responsive d
13
13
  - **Smart Event Fetching** - Callback-based event loading with intelligent caching
14
14
  - **Event Callbacks** - Day click, event click, and state change handlers
15
15
  - **Grid Border Control** - Customizable grid borders (both, vertical, horizontal, none)
16
+ - **Internationalization** - Full localization support for weekdays, months, and UI labels
17
+ - **Month/Year Dropdowns** - Quick navigation with clickable month and year selectors
18
+ - **Color Customization** - Comprehensive theming system with light/dark mode support
19
+ - **Theme Integration** - Built-in dark theme with automatic color adaptation
20
+ - **Enhanced Navigation** - Transparent navigation buttons with theme-aware hover effects
21
+ - **Always-Visible Time Lines** - Hour and half-hour lines remain visible regardless of grid settings
16
22
  - **CSS Prefix** - All classes prefixed with 'sc-' to avoid conflicts
17
23
  - **Template Strings** - Clean HTML generation using template literals
18
24
  - **Event Support** - Add, remove, and display events
@@ -68,6 +74,54 @@ const calendar = new SimpleCalendarJs('#calendar', {
68
74
  showDayButton: true, // true/false - show day view button
69
75
  showNavigation: true, // true/false - show navigation buttons (prev/next)
70
76
 
77
+ // Localization Options
78
+ weekdays: ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
79
+ months: ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
80
+ showWeekdayChars: null, // null = full weekday names, number = show first N characters
81
+ labels: { // UI labels for buttons and text
82
+ month: 'Month',
83
+ week: 'Week',
84
+ day: 'Day',
85
+ events: 'events', // Badge text (plural)
86
+ event: 'event', // Badge text (singular)
87
+ before: 'Before', // Time slot label for early times
88
+ after: 'After' // Time slot label for late times
89
+ },
90
+
91
+ // Color Customization
92
+ colors: {
93
+ // Light theme colors (defaults)
94
+ background: '#ffffff',
95
+ backgroundSecondary: '#f8f9fa',
96
+ backgroundTertiary: '#ecf0f1',
97
+ text: '#212529',
98
+ textSecondary: '#6c757d',
99
+ textMuted: '#adb5bd',
100
+ border: '#e9ecef',
101
+ borderLight: '#dee2e6',
102
+ accent: '#4c6f94',
103
+ accentHover: '#0056b3',
104
+ todayBg: '#e3f2fd',
105
+ todayText: '#dc3545',
106
+ hoverBg: '#f8f9fa',
107
+ // Dark theme colors (auto-applied when data-theme="dark")
108
+ dark: {
109
+ background: '#2d2d2d',
110
+ backgroundSecondary: '#3a3a3a',
111
+ backgroundTertiary: '#4a4a4a',
112
+ text: '#ffffff',
113
+ textSecondary: '#cccccc',
114
+ textMuted: '#888888',
115
+ border: '#444444',
116
+ borderLight: '#555555',
117
+ accent: '#4a90e2',
118
+ accentHover: '#357abd',
119
+ todayBg: '#1a2f4a',
120
+ todayText: '#ff6b6b',
121
+ hoverBg: '#2d2d2d'
122
+ }
123
+ },
124
+
71
125
  // Data Configuration
72
126
  events: [], // Array of event objects OR callback function
73
127
 
@@ -78,6 +132,163 @@ const calendar = new SimpleCalendarJs('#calendar', {
78
132
  });
79
133
  ```
80
134
 
135
+ ## Localization
136
+
137
+ SimpleCalendarJs supports full internationalization with customizable text and date formats:
138
+
139
+ ```javascript
140
+ const calendar = new SimpleCalendarJs('#calendar', {
141
+ // Portuguese example
142
+ weekdays: ['Domingo', 'Segunda', 'Terça', 'Quarta', 'Quinta', 'Sexta', 'Sábado'],
143
+ months: ['Janeiro', 'Fevereiro', 'Março', 'Abril', 'Maio', 'Junho', 'Julho', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Dezembro'],
144
+ showWeekdayChars: 3, // Show only first 3 characters: "Dom", "Seg", etc.
145
+ labels: {
146
+ month: 'Mês',
147
+ week: 'Semana',
148
+ day: 'Dia',
149
+ events: 'eventos',
150
+ event: 'evento',
151
+ before: 'Antes das',
152
+ after: 'Depois das'
153
+ }
154
+ });
155
+
156
+ // French example
157
+ const frenchCalendar = new SimpleCalendarJs('#calendar', {
158
+ weekdays: ['Dimanche', 'Lundi', 'Mardi', 'Mercredi', 'Jeudi', 'Vendredi', 'Samedi'],
159
+ months: ['Janvier', 'Février', 'Mars', 'Avril', 'Mai', 'Juin', 'Juillet', 'Août', 'Septembre', 'Octobre', 'Novembre', 'Décembre'],
160
+ showWeekdayChars: 2, // Show first 2 characters: "Di", "Lu", etc.
161
+ labels: {
162
+ month: 'Mois',
163
+ week: 'Semaine',
164
+ day: 'Jour',
165
+ events: 'événements',
166
+ event: 'événement',
167
+ before: 'Avant',
168
+ after: 'Après'
169
+ }
170
+ });
171
+ ```
172
+
173
+ ### Weekday Display Options
174
+ - **`showWeekdayChars: null`** - Show full weekday names (default)
175
+ - **`showWeekdayChars: 3`** - Show first 3 characters ("Sun", "Mon", etc.)
176
+ - **`showWeekdayChars: 1`** - Show single character ("S", "M", etc.)
177
+
178
+ ## Month/Year Navigation
179
+
180
+ In month view, clicking on the month or year title opens dropdown menus for quick navigation:
181
+
182
+ ### Features
183
+ - **Month Dropdown** - Click month title to select any month
184
+ - **Year Dropdown** - Click year title to select from a range of years
185
+ - **Keyboard Support** - Navigate with arrow keys and Enter
186
+ - **Auto-scroll** - Current month/year automatically scrolls into view
187
+ - **Compact Design** - Minimal dropdown styling that adapts to themes
188
+
189
+ ### Styling
190
+ The dropdowns automatically inherit the calendar's theme and can be customized via CSS:
191
+
192
+ ```css
193
+ .sc-month-dropdown, .sc-year-dropdown {
194
+ /* Customize dropdown appearance */
195
+ max-height: 200px;
196
+ background: var(--sc-bg-secondary);
197
+ border: 1px solid var(--sc-border-color);
198
+ }
199
+
200
+ .sc-month-option:hover, .sc-year-option:hover {
201
+ background: var(--sc-hover-bg);
202
+ color: var(--sc-accent-color);
203
+ }
204
+ ```
205
+
206
+ ## Color Customization
207
+
208
+ SimpleCalendarJs provides a comprehensive color system that supports partial customization and automatic theme switching:
209
+
210
+ ### Basic Color Override
211
+ ```javascript
212
+ const calendar = new SimpleCalendarJs('#calendar', {
213
+ colors: {
214
+ background: '#f0f8ff', // Only override background
215
+ accent: '#ff6b6b' // Only override accent color
216
+ // All other colors use defaults
217
+ }
218
+ });
219
+ ```
220
+
221
+ ### Full Theme Customization
222
+ ```javascript
223
+ const calendar = new SimpleCalendarJs('#calendar', {
224
+ colors: {
225
+ // Light theme colors
226
+ background: '#ffffff',
227
+ backgroundSecondary: '#f8f9fa',
228
+ backgroundTertiary: '#ecf0f1',
229
+ text: '#212529',
230
+ textSecondary: '#6c757d',
231
+ textMuted: '#adb5bd',
232
+ border: '#e9ecef',
233
+ borderLight: '#dee2e6',
234
+ accent: '#4c6f94',
235
+ accentHover: '#0056b3',
236
+ todayBg: '#e3f2fd',
237
+ todayText: '#dc3545',
238
+ hoverBg: '#f8f9fa',
239
+
240
+ // Dark theme colors (optional)
241
+ dark: {
242
+ background: '#1a1a1a',
243
+ backgroundSecondary: '#2d2d2d',
244
+ backgroundTertiary: '#3a3a3a',
245
+ text: '#ffffff',
246
+ textSecondary: '#cccccc',
247
+ textMuted: '#888888',
248
+ border: '#444444',
249
+ borderLight: '#555555',
250
+ accent: '#4a90e2',
251
+ accentHover: '#357abd',
252
+ todayBg: '#1a2f4a',
253
+ todayText: '#ff6b6b',
254
+ hoverBg: '#2d2d2d'
255
+ }
256
+ }
257
+ });
258
+ ```
259
+
260
+ ### Transparent Values
261
+ Any color property can be set to `'transparent'` for seamless integration:
262
+
263
+ ```javascript
264
+ colors: {
265
+ background: 'transparent', // Calendar blends with page background
266
+ border: 'transparent', // Remove all borders
267
+ hoverBg: 'transparent' // Disable hover backgrounds
268
+ }
269
+ ```
270
+
271
+ ### Color Properties Reference
272
+
273
+ | Property | Description | Default (Light) | Default (Dark) |
274
+ |----------|-------------|-----------------|----------------|
275
+ | `background` | Main calendar background | `#ffffff` | `#2d2d2d` |
276
+ | `backgroundSecondary` | Secondary backgrounds | `#f8f9fa` | `#3a3a3a` |
277
+ | `backgroundTertiary` | Controls and headers | `#ecf0f1` | `#4a4a4a` |
278
+ | `text` | Primary text color | `#212529` | `#ffffff` |
279
+ | `textSecondary` | Secondary text | `#6c757d` | `#cccccc` |
280
+ | `textMuted` | Muted/disabled text | `#adb5bd` | `#888888` |
281
+ | `border` | Grid and element borders | `#e9ecef` | `#444444` |
282
+ | `borderLight` | Light accent borders | `#dee2e6` | `#555555` |
283
+ | `accent` | Active states and highlights | `#4c6f94` | `#4a90e2` |
284
+ | `accentHover` | Hover states | `#0056b3` | `#357abd` |
285
+ | `todayBg` | Today's date background | `#e3f2fd` | `#1a2f4a` |
286
+ | `todayText` | Today's date text | `#dc3545` | `#ff6b6b` |
287
+ | `hoverBg` | Hover backgrounds | `#f8f9fa` | `#2d2d2d` |
288
+
289
+ ### Theme Integration
290
+ The calendar automatically detects `data-theme="dark"` on parent elements and applies dark colors accordingly. Perfect for integration with existing website theme systems.
291
+
81
292
  ## Event Format
82
293
 
83
294
  ```javascript
@@ -250,9 +461,17 @@ All CSS classes are prefixed with `sc-` to prevent conflicts:
250
461
  ### Headers & Navigation
251
462
  - `.sc-nav` - Navigation buttons container
252
463
  - `.sc-title` - Month/date title
464
+ - `.sc-title-month` - Clickable month title (in month view)
465
+ - `.sc-title-year` - Clickable year title (in month view)
253
466
  - `.sc-view-switcher` - View mode buttons
254
467
  - `.sc-btn` - Button styling
255
468
 
469
+ ### Dropdown Navigation
470
+ - `.sc-month-dropdown` - Month selection dropdown
471
+ - `.sc-year-dropdown` - Year selection dropdown
472
+ - `.sc-month-option` - Individual month option
473
+ - `.sc-year-option` - Individual year option
474
+
256
475
  ### Border Modifiers
257
476
  - `.sc-borders-both` - Show all grid borders
258
477
  - `.sc-borders-vertical` - Vertical grid borders only
@@ -263,8 +482,9 @@ All CSS classes are prefixed with `sc-` to prevent conflicts:
263
482
 
264
483
  ## CSS Custom Properties (Variables)
265
484
 
266
- The calendar uses CSS custom properties for easy theming:
485
+ The calendar uses CSS custom properties for comprehensive theming. These are automatically managed by the color customization system, but can also be overridden directly:
267
486
 
487
+ ### Event Colors
268
488
  ```css
269
489
  .sc-calendar {
270
490
  --sc-event-color: #4c6f94; /* Default event background color */
@@ -272,33 +492,109 @@ The calendar uses CSS custom properties for easy theming:
272
492
  }
273
493
  ```
274
494
 
275
- You can customize these by overriding them in your CSS:
495
+ ### Theme Colors (Auto-managed)
496
+ ```css
497
+ .sc-calendar {
498
+ /* Main backgrounds */
499
+ --sc-bg-primary: #ffffff;
500
+ --sc-bg-secondary: #f8f9fa;
501
+ --sc-bg-tertiary: #ecf0f1;
502
+
503
+ /* Text colors */
504
+ --sc-text-primary: #212529;
505
+ --sc-text-secondary: #6c757d;
506
+ --sc-text-muted: #adb5bd;
507
+
508
+ /* Borders */
509
+ --sc-border-color: #e9ecef;
510
+ --sc-border-light: #dee2e6;
511
+
512
+ /* Interactive elements */
513
+ --sc-accent-color: #4c6f94;
514
+ --sc-accent-hover: #0056b3;
515
+ --sc-today-bg: #e3f2fd;
516
+ --sc-today-text: #dc3545;
517
+ --sc-hover-bg: #f8f9fa;
518
+ }
519
+
520
+ /* Dark theme variables (auto-applied with data-theme="dark") */
521
+ [data-theme="dark"] .sc-calendar {
522
+ --sc-bg-primary: #2d2d2d;
523
+ --sc-bg-secondary: #3a3a3a;
524
+ --sc-bg-tertiary: #4a4a4a;
525
+ --sc-text-primary: #ffffff;
526
+ --sc-text-secondary: #cccccc;
527
+ --sc-text-muted: #888888;
528
+ --sc-border-color: #444444;
529
+ --sc-border-light: #555555;
530
+ --sc-accent-color: #4a90e2;
531
+ --sc-accent-hover: #357abd;
532
+ --sc-today-bg: #1a2f4a;
533
+ --sc-today-text: #ff6b6b;
534
+ --sc-hover-bg: #2d2d2d;
535
+ }
536
+ ```
276
537
 
538
+ ### Manual Override Example
277
539
  ```css
278
- /* Custom theme example */
540
+ /* Custom theme using CSS variables */
279
541
  .sc-calendar {
280
- --sc-event-color: #e74c3c;
281
- --sc-event-border-color: #c0392b;
542
+ --sc-bg-primary: #f0f8ff;
543
+ --sc-accent-color: #ff6b6b;
544
+ --sc-today-bg: #ffe4e1;
282
545
  }
283
546
 
284
- /* Dark theme example */
285
- .sc-calendar.dark-theme {
286
- --sc-event-color: #3498db;
287
- background: #2c3e50;
288
- color: #ecf0f1;
547
+ /* Dark theme integration */
548
+ [data-theme="dark"] .sc-calendar {
549
+ --sc-bg-primary: #1e1e1e;
550
+ --sc-accent-color: #ff8a80;
289
551
  }
290
552
  ```
291
553
 
292
554
  ## Event Count Badges
293
555
 
294
- In month view with `fulldayMode: false`, event counts are displayed as styled badges:
556
+ In month view with `fulldayMode: false`, event counts are displayed as styled badges with theme-aware coloring:
295
557
 
296
- - **Regular days**: Light gray background with dark text
297
- - **Today**: Blue background with white text
298
- - **Other month days**: Very light gray background with muted text
558
+ - **Regular days**: Uses default event color with reduced opacity for background
559
+ - **Today**: Full default event color background with white text
560
+ - **Other month days**: Muted background with secondary text color
561
+ - **Theme adaptive**: Automatically adjusts colors for light/dark themes
299
562
  - **Responsive**: Automatically adjusts size on mobile devices
300
563
  - **Rounded corners**: Modern badge appearance with `border-radius: 10px`
301
564
 
565
+ ### Badge Color Logic
566
+ - Background uses `color-mix()` function to blend event color with theme background
567
+ - Text color adapts to theme (darker in light mode, lighter in dark mode)
568
+ - Today's badge uses full event color saturation for emphasis
569
+ - Seamlessly integrates with custom color schemes
570
+
571
+ ## Enhanced Styling Features
572
+
573
+ ### Navigation Button Styling
574
+ - **Transparent Background**: Navigation arrows have transparent backgrounds in both themes
575
+ - **Theme-Aware Hover**: Hover effects use the default event color for consistency
576
+ - **Proper Contrast**: Arrow colors automatically adjust for light/dark themes
577
+
578
+ ### View Button Styling
579
+ - **Active State**: Uses default event color when view button is active
580
+ - **Inactive State**: Transparent background with theme-appropriate text color
581
+ - **Consistent Theming**: Seamlessly adapts to color customization
582
+
583
+ ### Time Lines Enhancement
584
+ - **Always Visible**: Hour and half-hour lines remain visible regardless of grid border settings
585
+ - **Proper Hierarchy**: Uses `!important` declarations to ensure visibility
586
+ - **Theme Integration**: Line colors adapt to current theme automatically
587
+
588
+ ### Today Highlighting
589
+ - **Enhanced Contrast**: Today's background is darker in dark mode for better visibility
590
+ - **Cross-View Consistency**: Today highlighting works consistently across month, week, and day views
591
+ - **Customizable Colors**: Today colors can be overridden via color customization system
592
+
593
+ ### Page Integration
594
+ - **Scrollable Layout**: Calendar no longer uses fixed positioning, allowing page scrolling
595
+ - **Flexible Container**: Adapts to parent container constraints
596
+ - **Better Integration**: Easier to embed in existing page layouts
597
+
302
598
  ## Keyboard Shortcuts
303
599
 
304
600
  - **Arrow Left/Right** - Navigate to previous/next period
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SimpleCalendarJs v2.0.1
2
+ * SimpleCalendarJs v2.0.2
3
3
  * A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
4
4
  *
5
5
  * @author Pedro Lopes <simplecalendarjs@gmail.com>
@@ -7,4 +7,4 @@
7
7
  * @license SEE LICENSE IN LICENSE
8
8
  * @repository https://github.com/pclslopes/SimpleCalendarJs
9
9
  */
10
- .sc-calendar{font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;background:#fff;border-radius:8px;box-shadow:0 2px 10px rgba(0,0,0,.1);overflow:hidden;width:100%;max-width:100%;display:flex;flex-direction:column}.sc-header{background:#f8f9fa;border-bottom:1px solid #e9ecef;padding:16px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px}.sc-nav{display:flex;align-items:center;gap:8px;justify-self:start}.sc-title{margin:0;font-size:1.25rem;font-weight:600;color:#212529;white-space:nowrap;text-align:center;justify-self:center}.sc-btn{background:#fff;border:1px solid #dee2e6;border-radius:4px;padding:8px 12px;cursor:pointer;font-size:.875rem;font-weight:500;transition:all .2s ease;min-width:36px;display:flex;align-items:center;justify-content:center}.sc-nav .sc-btn{background:0 0;border:none;font-size:1.2rem;padding:8px;min-width:32px;color:#212529;font-weight:700}.sc-btn:hover{background:#e9ecef;border-color:#adb5bd}.sc-nav .sc-btn:hover{background:rgba(108,117,125,.1);color:#495057}.sc-btn:active{background:#dee2e6}.sc-nav .sc-btn:active{background:rgba(108,117,125,.2)}.sc-view-switcher{display:flex;gap:4px;justify-self:end}.sc-view-btn.sc-active{background:#4c6f94;color:#fff;border-color:#4c6f94}.sc-view-btn.sc-active:hover{background:#0056b3;border-color:#0056b3}.sc-content{flex:1;min-height:520px;display:flex;flex-direction:column}.sc-view-container{flex:1;overflow:hidden}.sc-month-view{height:100%;display:flex;flex-direction:column}.sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:#f8f9fa;border-bottom:1px solid #e9ecef}.sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:#6c757d;border-right:1px solid #e9ecef}.sc-weekday:last-child{border-right:none}.sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(6,minmax(80px,1fr));flex:1;overflow:visible}.sc-month-view.sc-fullday .sc-days-grid{grid-template-rows:repeat(6,auto);min-height:auto}.sc-month-view.sc-fullday .sc-day{display:flex;flex-direction:column;min-height:60px;position:relative}.sc-day{display:flex;flex-direction:column;min-height:80px;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-day:hover{background:#f8f9fa}.sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2}.sc-day.sc-other-month .sc-day-number{color:#adb5bd}.sc-day.sc-today{background:#e3f2fd}.sc-day.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-day-count-mode{display:flex;align-items:center;justify-content:center}.sc-day-content{text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center}.sc-day-count-mode .sc-day-number{font-size:1.125rem;font-weight:600;margin-bottom:4px}.sc-event-count{font-size:.7rem;font-weight:600;line-height:1.2;background:#e9ecef;color:#495057;padding:2px 6px;border-radius:10px;display:inline-block;margin-top:2px;min-width:16px;text-align:center;box-sizing:border-box}.sc-day-count-mode.sc-today .sc-event-count{background:#007bff;color:#fff}.sc-day-count-mode.sc-other-month .sc-event-count{background:#f8f9fa;color:#adb5bd}.sc-day-events{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden;padding:4px}.sc-month-view.sc-fullday .sc-day-number{flex-shrink:0;margin-bottom:4px;padding:4px 6px 0 6px}.sc-event-day,.sc-event-month,.sc-event-time,.sc-event-week{background:var(--sc-event-color,#4c6f94);color:#fff;border-radius:3px;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;box-sizing:border-box}.sc-month-view .sc-event-month{padding:2px 6px;font-size:.75rem;height:18px;margin:1px 4px;min-width:0;display:block}.sc-month-view.sc-fullday .sc-event-month{position:relative;padding:2px 6px;border-radius:3px;min-height:18px;display:block;box-sizing:border-box;overflow:visible}.sc-month-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-month-view .sc-event-single{border-radius:3px}.sc-month-view .sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-middle{border-radius:0}.sc-month-view .sc-event-end{border-radius:0 3px 3px 0}.sc-month-view .sc-event-month.sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-month.sc-event-middle{border-radius:0}.sc-month-view .sc-event-month.sc-event-end{border-radius:0 3px 3px 0}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after,.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{left:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after{right:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-event-placeholder{opacity:0!important}.sc-month-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box}.sc-week-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box;line-height:.9em}.sc-week-view{height:100%;overflow-y:auto;overflow-x:hidden;max-height:calc(100vh - 200px);display:grid;grid-template-columns:80px repeat(7,1fr);align-content:start}.sc-day-header,.sc-time-column-header{position:sticky;top:0;z-index:20;display:flex;flex-direction:column;justify-content:center;text-align:center;padding:12px 8px;background:#f8f9fa;height:60px;box-sizing:border-box}.sc-day-header.sc-today{background:#e3f2fd}.sc-day-name{font-size:.75rem;color:#6c757d;font-weight:500;margin-bottom:2px}.sc-day-header .sc-day-number{font-size:1.25rem;font-weight:600;color:#212529}.sc-day-header.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-day-column,.sc-time-slot{height:60px;position:relative;box-sizing:border-box}.sc-time-slot{background:#f8f9fa;padding:4px 8px 8px 8px;font-size:.75rem;color:#6c757d;text-align:center;display:flex;align-items:flex-start;justify-content:center}.sc-time-slot:first-child,.sc-time-slot:last-child{background:#e9ecef;color:#495057;font-weight:500;font-style:italic}.sc-day-column{cursor:pointer;background:#fff}.sc-day-column.sc-today{background:#f8fcff}.sc-day-column:hover{background:#f8f9fa}.sc-half-hour-line{position:absolute;top:50%;left:0;right:0;height:0;pointer-events:none;z-index:1}.sc-week-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-week-view .sc-event-single{border-radius:3px}.sc-week-view .sc-event-start{border-radius:3px 0 0 3px}.sc-week-view .sc-event-middle{border-radius:0}.sc-week-view .sc-event-end{border-radius:0 3px 3px 0}.sc-week-view.sc-fullday .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-week-view.sc-fullday .sc-event-start{margin-right:0}.sc-week-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-week-view.sc-fullday .sc-event-end{margin-left:0}.sc-week-view.sc-fullday{height:100%;display:flex;flex-direction:column}.sc-week-view.sc-fullday .sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:#f8f9fa;border-bottom:1px solid #e9ecef}.sc-week-view.sc-fullday .sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:#6c757d;border-right:1px solid #e9ecef}.sc-week-view.sc-fullday .sc-weekday:last-child{border-right:none}.sc-week-view.sc-fullday .sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:auto;flex:1;min-height:auto;overflow:visible}.sc-week-view.sc-fullday .sc-day{display:flex;flex-direction:column;min-height:120px;position:relative;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-week-view.sc-fullday .sc-day:hover{background:#f8f9fa}.sc-week-view.sc-fullday .sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2;flex-shrink:0;padding:4px 6px 0 6px}.sc-week-view.sc-fullday .sc-day.sc-today{background:#e3f2fd}.sc-week-view.sc-fullday .sc-day.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-week-view.sc-fullday .sc-event-week{position:relative;border-radius:3px;min-height:18px;padding:2px 6px;display:block;box-sizing:border-box;overflow:visible}.sc-week-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-week-view.sc-fullday .sc-event-week.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after,.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{left:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after{right:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-day-view{height:100%;overflow-y:auto;overflow-x:hidden;max-height:calc(100vh - 200px);display:grid;grid-template-columns:80px 1fr;align-content:start}.sc-day-view.sc-fullday{display:flex;flex-direction:column;padding:0;grid-template-columns:none}.sc-day-view.sc-fullday .sc-day-header{position:sticky;top:0;z-index:10;background:#f8f9fa;border-bottom:1px solid #e9ecef;padding:16px;text-align:center;flex-shrink:0}.sc-day-view.sc-fullday .sc-day-name{font-size:.875rem;color:#6c757d;font-weight:500;margin-bottom:4px}.sc-day-view.sc-fullday .sc-day-number{font-size:1.5rem;font-weight:600;color:#212529}.sc-day-view.sc-fullday .sc-day-header.sc-today .sc-day-number{color:#dc3545}.sc-day-view.sc-fullday .sc-day-events{flex:1;padding:16px;display:flex;flex-direction:column;gap:8px;overflow-y:auto;cursor:pointer}.sc-day-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-day-view .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-day-view .sc-event-single{border-radius:3px}.sc-day-view .sc-event-start{border-radius:3px 0 0 3px}.sc-day-view .sc-event-middle{border-radius:0}.sc-day-view .sc-event-end{border-radius:0 3px 3px 0}.sc-day-view.sc-fullday .sc-event-start{margin-right:0}.sc-day-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-day-view.sc-fullday .sc-event-end{margin-left:0}.sc-day-view.sc-fullday .sc-event-day{position:relative;padding:2px 6px;border-radius:3px;margin:1px 4px;min-height:18px;box-sizing:border-box;display:block}.sc-day-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}@media (max-width:768px){.sc-header{grid-template-columns:1fr;grid-template-rows:auto auto auto;gap:8px;justify-items:center}.sc-view-switcher{grid-row:1;justify-self:center}.sc-title{grid-row:2;font-size:1.125rem;justify-self:center}.sc-nav{grid-row:3;justify-self:center}.sc-days-grid{min-height:360px;grid-template-rows:repeat(6,minmax(60px,1fr))}.sc-day-number{font-size:.75rem}.sc-day-count-mode .sc-day-number{font-size:1rem}.sc-event-count{font-size:.625rem;padding:1px 4px;min-width:14px}.sc-event-month{font-size:.6rem;padding:1px 3px;height:14px}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:3px;left:3px;right:3px;padding:1px 4px;padding-top:2px;padding-bottom:1px;font-size:.65rem;min-height:16px}.sc-weekday{padding:8px 4px;font-size:.75rem}.sc-week-header{min-height:50px}.sc-week-view{grid-template-columns:60px repeat(7,1fr)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-day-header{padding:8px 4px}.sc-day-name{font-size:.625rem}.sc-day-header .sc-day-number{font-size:1rem}.sc-day-header.sc-today .sc-day-number{color:#dc3545;font-weight:600}.sc-time-slot{height:40px;padding:4px;font-size:.625rem}.sc-hour-slot{height:40px}.sc-day-view{grid-template-columns:60px 1fr;max-height:calc(100vh - 160px)}.sc-week-view{max-height:calc(100vh - 160px)}}@media (max-width:480px){.sc-calendar{border-radius:0;box-shadow:none;border:1px solid #e9ecef}.sc-header{padding:12px}.sc-btn{padding:6px 8px;font-size:.75rem;min-width:30px}.sc-title{font-size:1rem}.sc-day{min-height:50px;padding:2px}.sc-days-grid{min-height:300px;grid-template-rows:repeat(6,minmax(50px,1fr))}.sc-day-count-mode .sc-day-number{font-size:.875rem}.sc-event-count{font-size:.5rem;padding:1px 3px;min-width:12px}.sc-weekday{padding:6px 2px;font-size:.625rem}.sc-week-header{min-height:40px}.sc-week-view{grid-template-columns:50px repeat(7,1fr);max-height:calc(100vh - 140px)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-time-slot{height:30px;padding:2px;font-size:.5rem}.sc-hour-slot{height:30px}.sc-day-view{grid-template-columns:50px 1fr;max-height:calc(100vh - 140px)}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:2px;left:2px;right:2px;padding:1px 3px;padding-top:2px;padding-bottom:0;font-size:.5rem;min-height:14px}}.sc-borders-both .sc-day{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-day{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-day{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-day{border-right:none;border-bottom:none}.sc-borders-both .sc-day-column,.sc-borders-both .sc-time-slot{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-day-column,.sc-borders-vertical .sc-time-slot{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-day-column,.sc-borders-horizontal .sc-time-slot{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-day-column,.sc-borders-none .sc-time-slot{border-right:none;border-bottom:none}.sc-borders-both .sc-week-view.sc-fullday .sc-day{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-week-view.sc-fullday .sc-day{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:none}.sc-borders-both .sc-day-header,.sc-borders-both .sc-time-column-header{border-right:1px solid #e9ecef;border-bottom:1px solid #e9ecef}.sc-borders-vertical .sc-day-header,.sc-borders-vertical .sc-time-column-header{border-right:1px solid #e9ecef;border-bottom:none}.sc-borders-horizontal .sc-day-header,.sc-borders-horizontal .sc-time-column-header{border-right:none;border-bottom:1px solid #e9ecef}.sc-borders-none .sc-day-header,.sc-borders-none .sc-time-column-header{border-right:none;border-bottom:none}.sc-borders-both .sc-half-hour-line,.sc-borders-horizontal .sc-half-hour-line{border-top:1px dotted #e9ecef}.sc-borders-none .sc-half-hour-line,.sc-borders-vertical .sc-half-hour-line{border-top:none}.sc-borders-both .sc-day-column:nth-child(8n),.sc-borders-both .sc-day-header:nth-child(8n),.sc-borders-both .sc-day:last-child,.sc-borders-both .sc-week-view.sc-fullday .sc-day:last-child,.sc-borders-vertical .sc-day-column:nth-child(8n),.sc-borders-vertical .sc-day-header:nth-child(8n),.sc-borders-vertical .sc-day:last-child,.sc-borders-vertical .sc-week-view.sc-fullday .sc-day:last-child{border-right:none}:root{--sc-event-border-color:#6c757d}.sc-event-borders .sc-event-day:not(.sc-event-placeholder),.sc-event-borders .sc-event-month:not(.sc-event-placeholder),.sc-event-borders .sc-event-time:not(.sc-event-placeholder),.sc-event-borders .sc-event-week:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-single:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-start:not(.sc-event-placeholder){border-left:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-middle:not(.sc-event-placeholder){border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-end:not(.sc-event-placeholder){border-right:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-day{border-right:none}.sc-week-view .sc-event-week{border-right:none}.sc-event-no-borders .sc-event-day,.sc-event-no-borders .sc-event-month,.sc-event-no-borders .sc-event-time,.sc-event-no-borders .sc-event-week{border:none!important}@media print{.sc-calendar{box-shadow:none;border:1px solid #000}.sc-header{background:#fff!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.sc-btn{display:none}.sc-view-switcher{display:none}}
10
+ .sc-calendar{--sc-bg-primary:#ffffff;--sc-bg-secondary:#f8f9fa;--sc-bg-tertiary:#e9ecef;--sc-text-primary:#212529;--sc-text-secondary:#6c757d;--sc-text-muted:#adb5bd;--sc-border-color:#e9ecef;--sc-border-light:#dee2e6;--sc-accent-color:#4c6f94;--sc-accent-hover:#0056b3;--sc-today-bg:#e3f2fd;--sc-today-text:#dc3545;--sc-hover-bg:#f8f9fa;--sc-shadow:none;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.1);font-family:inherit;background:var(--sc-bg-primary);color:var(--sc-text-primary);box-shadow:var(--sc-shadow);overflow:hidden;width:100%;max-width:100%;display:flex;flex-direction:column}.sc-calendar.sc-shadow{--sc-shadow:var(--sc-shadow-enabled)}@media (prefers-color-scheme:dark){.sc-calendar{--sc-bg-primary:#1a1a1a;--sc-bg-secondary:#2d2d2d;--sc-bg-tertiary:#3a3a3a;--sc-text-primary:#ffffff;--sc-text-secondary:#b0b0b0;--sc-text-muted:#888888;--sc-border-color:#444444;--sc-border-light:#555555;--sc-accent-color:#4a90e2;--sc-accent-hover:#357abd;--sc-today-bg:#1a2f4a;--sc-today-text:#ff6b6b;--sc-hover-bg:#2d2d2d;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.5)}}[data-theme=dark] .sc-calendar{--sc-bg-primary:#1a1a1a;--sc-bg-secondary:#2d2d2d;--sc-bg-tertiary:#3a3a3a;--sc-text-primary:#ffffff;--sc-text-secondary:#b0b0b0;--sc-text-muted:#888888;--sc-border-color:#444444;--sc-border-light:#555555;--sc-accent-color:#4a90e2;--sc-accent-hover:#357abd;--sc-today-bg:#1a2f4a;--sc-today-text:#ff6b6b;--sc-hover-bg:#2d2d2d;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.5)}[data-theme=light] .sc-calendar{--sc-bg-primary:#ffffff;--sc-bg-secondary:#f8f9fa;--sc-bg-tertiary:#e9ecef;--sc-text-primary:#212529;--sc-text-secondary:#6c757d;--sc-text-muted:#adb5bd;--sc-border-color:#e9ecef;--sc-border-light:#dee2e6;--sc-accent-color:#4c6f94;--sc-accent-hover:#0056b3;--sc-today-bg:#e3f2fd;--sc-today-text:#dc3545;--sc-hover-bg:#f8f9fa;--sc-shadow-enabled:0 2px 10px rgba(0, 0, 0, 0.1)}.sc-calendar{background:var(--calendar-bg,var(--sc-bg-primary));color:var(--calendar-text,var(--sc-text-primary));height:100%}.sc-header{background:0 0;padding:16px;display:grid;grid-template-columns:1fr auto 1fr;align-items:center;gap:12px}.sc-borders-both .sc-header,.sc-borders-horizontal .sc-header{border-bottom:1px solid var(--sc-border-color)}.sc-nav{display:flex;align-items:center;gap:8px;justify-self:start}.sc-title-container{position:relative;justify-self:center}.sc-title{margin:0;font-size:1.25rem;font-weight:600;color:var(--sc-text-primary);white-space:nowrap;text-align:center;user-select:none}.sc-title-month,.sc-title-year{cursor:pointer;padding:2px 4px;border-radius:3px;transition:background-color .2s ease}.sc-title-month:hover,.sc-title-year:hover{color:var(--sc-event-color,#4c6f94)}.sc-month-dropdown{position:absolute;top:100%;left:50%;transform:translateX(-50%);background:var(--sc-bg-primary);border:1px solid var(--sc-border-color);border-radius:4px;z-index:1000;min-width:120px;box-shadow:var(--sc-shadow-enabled);max-height:240px;overflow-y:auto}.sc-month-option{padding:4px 8px;font-size:.75rem;color:var(--sc-text-primary);cursor:pointer;transition:background-color .2s ease}.sc-month-option:hover{background:var(--sc-bg-tertiary)}.sc-month-option.sc-current{background:var(--sc-event-color,#4c6f94);color:#fff}.sc-month-option.sc-current:hover{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000)}.sc-year-dropdown{position:absolute;top:100%;right:-20px;background:var(--sc-bg-primary);border:1px solid var(--sc-border-color);border-radius:4px;z-index:1000;min-width:100px;box-shadow:var(--sc-shadow-enabled);max-height:240px;overflow-y:auto}.sc-year-option{padding:4px 8px;font-size:.75rem;color:var(--sc-text-primary);cursor:pointer;transition:background-color .2s ease;text-align:center}.sc-year-option:hover{background:var(--sc-bg-tertiary)}.sc-year-option.sc-current{background:var(--sc-event-color,#4c6f94);color:#fff}.sc-year-option.sc-current:hover{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000)}.sc-btn{background:0 0;border:1px solid var(--sc-border-light);border-radius:4px;padding:8px 12px;cursor:pointer;color:var(--sc-text-primary);font-size:.875rem;font-weight:500;transition:all .2s ease;min-width:36px;display:flex;align-items:center;justify-content:center}.sc-nav .sc-btn{background:0 0;border:none;font-size:1.2rem;padding:8px;min-width:32px;color:var(--sc-text-primary);font-weight:700}.sc-btn:hover{background:var(--sc-bg-tertiary);border-color:#adb5bd}.sc-nav .sc-btn:hover{background:var(--sc-bg-tertiary);color:var(--sc-text-primary)}.sc-btn:active{background:#dee2e6}.sc-nav .sc-btn:active{background:var(--sc-bg-tertiary)}.sc-view-switcher{display:flex;gap:4px;justify-self:end}.sc-view-btn.sc-active{background:var(--sc-event-color,#4c6f94);color:#fff;border-color:var(--sc-event-color,#4c6f94)}.sc-view-btn.sc-active:hover{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000);border-color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 85%,#000)}.sc-content{flex:1;height:100%;display:flex;flex-direction:column}.sc-view-container{flex:1;overflow:hidden;height:100%}.sc-month-view{height:100%;display:flex;flex-direction:column}.sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:var(--sc-bg-secondary)}.sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:var(--sc-text-secondary)}.sc-borders-both .sc-month-view .sc-weekdays,.sc-borders-vertical .sc-month-view .sc-weekdays{border-left:1px solid var(--sc-border-color);border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-month-view .sc-weekday,.sc-borders-vertical .sc-month-view .sc-weekday{border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-month-view .sc-weekday:nth-child(7n),.sc-borders-vertical .sc-month-view .sc-weekday:nth-child(7n){border-right:none}.sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:repeat(6,1fr);flex:1;overflow:visible}.sc-month-view.sc-fullday .sc-days-grid{grid-template-rows:repeat(6,auto);min-height:auto}.sc-month-view.sc-fullday .sc-day{display:flex;flex-direction:column;position:relative}.sc-day{display:flex;flex-direction:column;min-height:80px;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-day:hover{background:var(--sc-hover-bg)}.sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2}.sc-day.sc-other-month .sc-day-number{color:var(--sc-text-muted)}.sc-day.sc-today{background:var(--sc-today-bg)}.sc-day.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-day-count-mode{display:flex;align-items:center;justify-content:center}.sc-day-content{text-align:center;display:flex;flex-direction:column;align-items:center;justify-content:center}.sc-day-count-mode .sc-day-number{font-size:1.125rem;font-weight:600;margin-bottom:4px}.sc-event-count{font-size:.7rem;font-weight:600;line-height:1.2;background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 15%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 80%,#000);padding:2px 6px;border-radius:10px;display:inline-block;margin-top:2px;text-align:center;box-sizing:border-box}@media (prefers-color-scheme:light){.sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 15%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 80%,#000)}}[data-theme=light] .sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 15%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 80%,#000)}@media (prefers-color-scheme:dark){.sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 20%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 30%,#fff)}}[data-theme=dark] .sc-event-count{background:color-mix(in srgb,var(--sc-event-color,#4c6f94) 20%,var(--sc-bg-tertiary));color:color-mix(in srgb,var(--sc-event-color,#4c6f94) 30%,#fff)}.sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}@media (prefers-color-scheme:light){.sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}}[data-theme=light] .sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}@media (prefers-color-scheme:dark){.sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}}[data-theme=dark] .sc-day-count-mode.sc-today .sc-event-count{background:var(--sc-event-color,#4c6f94);color:#fff}.sc-day-count-mode.sc-other-month .sc-event-count{background:var(--sc-bg-secondary);color:var(--sc-text-muted)}.sc-day-events{flex:1;display:flex;flex-direction:column;gap:2px;overflow:hidden;padding:4px}.sc-month-view.sc-fullday .sc-day-number{flex-shrink:0;margin-bottom:4px;padding:4px 6px 0 6px}.sc-event-day,.sc-event-month,.sc-event-time,.sc-event-week{background:var(--sc-event-color,#4c6f94);color:#fff;border-radius:3px;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;box-sizing:border-box}.sc-month-view .sc-event-month{padding:2px 6px;font-size:.75rem;height:18px;margin:1px 4px;min-width:0;display:block}.sc-month-view.sc-fullday .sc-event-month{position:relative;padding:2px 6px;border-radius:3px;min-height:18px;display:block;box-sizing:border-box;overflow:visible}.sc-month-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-month-view .sc-event-single{border-radius:3px}.sc-month-view .sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-middle{border-radius:0}.sc-month-view .sc-event-end{border-radius:0 3px 3px 0}.sc-month-view .sc-event-month.sc-event-start{border-radius:3px 0 0 3px}.sc-month-view .sc-event-month.sc-event-middle{border-radius:0}.sc-month-view .sc-event-month.sc-event-end{border-radius:0 3px 3px 0}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after,.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::before{left:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle::after{right:-2px}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-month-view.sc-fullday .sc-event-month.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-month-view.sc-fullday .sc-event-month.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-event-placeholder{opacity:0!important}.sc-month-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box}.sc-week-view.sc-fullday .sc-event-placeholder{min-height:18px;padding:2px 6px;margin:1px 4px;display:block;box-sizing:border-box;line-height:.9em}.sc-week-view{height:100%;overflow-y:auto;overflow-x:hidden;display:grid;grid-template-columns:80px repeat(7,1fr);align-content:start}.sc-day-header,.sc-time-column-header{position:sticky;top:0;z-index:20;display:flex;flex-direction:column;justify-content:center;text-align:center;padding:12px 8px;background:var(--sc-bg-secondary);height:60px;box-sizing:border-box}.sc-week-view .sc-day-header.sc-today{background:var(--sc-today-bg)}.sc-day-name{font-size:.75rem;color:var(--sc-text-secondary);font-weight:500;margin-bottom:2px}.sc-day-header .sc-day-number{font-size:1.25rem;font-weight:600;color:var(--sc-text-primary)}.sc-week-view .sc-day-header.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-day-column,.sc-time-slot{height:60px;position:relative;box-sizing:border-box}.sc-time-slot{background:var(--sc-bg-secondary);padding:4px 8px 8px 8px;font-size:.75rem;color:var(--sc-text-secondary);text-align:center;display:flex;align-items:flex-start;justify-content:center}.sc-time-slot:first-child,.sc-time-slot:last-child{background:var(--sc-bg-tertiary);color:#495057;font-weight:500;font-style:italic}.sc-day-column{cursor:pointer;background:var(--sc-bg-primary)}.sc-week-view .sc-day-column.sc-today{background:var(--sc-today-bg)}.sc-day-column:hover{background:var(--sc-hover-bg)}.sc-half-hour-line{position:absolute;top:50%;left:0;right:0;height:0;pointer-events:none;z-index:1}.sc-week-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-week-view .sc-event-single{border-radius:3px}.sc-week-view .sc-event-start{border-radius:3px 0 0 3px}.sc-week-view .sc-event-middle{border-radius:0}.sc-week-view .sc-event-end{border-radius:0 3px 3px 0}.sc-week-view.sc-fullday .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-week-view.sc-fullday .sc-event-start{margin-right:0}.sc-week-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-week-view.sc-fullday .sc-event-end{margin-left:0}.sc-week-view.sc-fullday{height:auto;display:flex;flex-direction:column}.sc-week-view.sc-fullday .sc-weekdays{display:grid;grid-template-columns:repeat(7,1fr);background:var(--sc-bg-secondary)}.sc-week-view.sc-fullday .sc-weekday{padding:12px 8px;text-align:center;font-weight:600;font-size:.875rem;color:var(--sc-text-secondary)}.sc-borders-horizontal .sc-week-view.sc-fullday .sc-weekdays{border-bottom:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-weekday,.sc-borders-vertical .sc-week-view.sc-fullday .sc-weekday{border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-weekday:nth-child(7n),.sc-borders-vertical .sc-week-view.sc-fullday .sc-weekday:nth-child(7n){border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-weekday:first-child,.sc-borders-vertical .sc-week-view.sc-fullday .sc-weekday:first-child{border-left:1px solid var(--sc-border-color)}.sc-week-view.sc-fullday .sc-days-grid{display:grid;grid-template-columns:repeat(7,1fr);grid-template-rows:1fr;flex:1;min-height:auto;overflow:visible}.sc-week-view.sc-fullday .sc-day{display:flex;flex-direction:column;min-height:80px;position:relative;cursor:pointer;transition:background-color .2s ease;overflow:visible}.sc-week-view.sc-fullday .sc-day:hover{background:var(--sc-hover-bg)}.sc-week-view.sc-fullday .sc-day-number{text-align:center;font-weight:500;margin-bottom:4px;font-size:.875rem;line-height:1.2;flex-shrink:0;padding:4px 6px 0 6px}.sc-week-view.sc-fullday .sc-day.sc-today{background:var(--sc-today-bg)}.sc-week-view.sc-fullday .sc-day.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-week-view.sc-fullday .sc-event-week{position:relative;border-radius:3px;min-height:18px;padding:2px 6px;display:block;box-sizing:border-box;overflow:visible}.sc-week-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}.sc-week-view.sc-fullday .sc-event-week.sc-event-single{margin:1px 4px!important;border-radius:3px!important;z-index:1;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start{margin:1px 0 1px 4px!important;border-radius:3px 0 0 3px!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-start::after{content:'';position:absolute;right:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle{margin:1px 0!important;border-radius:0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after,.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{content:'';position:absolute;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::before{left:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-middle::after{right:-2px}.sc-week-view.sc-fullday .sc-event-week.sc-event-end{margin:1px 4px 1px 0!important;border-radius:0 3px 3px 0!important;z-index:2;position:relative}.sc-week-view.sc-fullday .sc-event-week.sc-event-end::before{content:'';position:absolute;left:-2px;top:0;bottom:0;width:2px;background:inherit;z-index:10}.sc-day-view{height:100%;overflow-y:auto;overflow-x:hidden;max-height:calc(100vh - 200px);display:grid;grid-template-columns:80px 1fr;align-content:start}.sc-day-view.sc-fullday{display:flex;flex-direction:column;padding:0;grid-template-columns:none}.sc-day-view.sc-fullday .sc-day-header{position:sticky;top:0;z-index:10;background:var(--sc-bg-secondary);border-bottom:1px solid var(--sc-border-color);padding:16px;text-align:center;flex-shrink:0}.sc-day-view.sc-fullday .sc-day-name{font-size:.875rem;color:var(--sc-text-secondary);font-weight:500;margin-bottom:4px}.sc-day-view.sc-fullday .sc-day-number{font-size:1.5rem;font-weight:600;color:var(--sc-text-primary)}.sc-day-view.sc-fullday .sc-day-header.sc-today .sc-day-number{color:var(--sc-today-text)}.sc-day-view.sc-fullday .sc-day-events{flex:1;padding:16px;display:flex;flex-direction:column;gap:8px;overflow-y:auto;cursor:pointer}.sc-day-view .sc-event-time{position:absolute;top:4px;left:4px;right:4px;padding:2px 6px;font-size:.75rem;line-height:1.2;border-radius:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;z-index:5;min-height:18px;display:block;box-sizing:border-box;padding-top:3px;padding-bottom:1px}.sc-day-view .sc-event{width:100%;margin:2px 4px;padding:2px 6px;font-size:.75rem;height:18px;min-width:0}.sc-day-view .sc-event-single{border-radius:3px}.sc-day-view .sc-event-start{border-radius:3px 0 0 3px}.sc-day-view .sc-event-middle{border-radius:0}.sc-day-view .sc-event-end{border-radius:0 3px 3px 0}.sc-day-view.sc-fullday .sc-event-start{margin-right:0}.sc-day-view.sc-fullday .sc-event-middle{margin-left:0;margin-right:0}.sc-day-view.sc-fullday .sc-event-end{margin-left:0}.sc-day-view.sc-fullday .sc-event-day{position:relative;padding:2px 6px;border-radius:3px;margin:1px 4px;min-height:18px;box-sizing:border-box;display:block}.sc-day-view.sc-fullday .sc-event-text{font-size:.75rem;line-height:1.2;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;position:absolute;display:block;width:100%}@media (max-width:768px){.sc-header{grid-template-columns:1fr;grid-template-rows:auto auto auto;gap:8px;justify-items:center}.sc-view-switcher{grid-row:1;justify-self:center}.sc-title-container{grid-row:2;justify-self:center}.sc-title{font-size:1.125rem}.sc-nav{grid-row:3;justify-self:center}.sc-days-grid{min-height:360px;grid-template-rows:repeat(6,minmax(60px,1fr))}.sc-day-number{font-size:.75rem}.sc-day-count-mode .sc-day-number{font-size:1rem}.sc-event-count{font-size:.625rem;padding:1px 4px;min-width:14px}.sc-event-month{font-size:.6rem;padding:1px 3px;height:14px}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:3px;left:3px;right:3px;padding:1px 4px;padding-top:2px;padding-bottom:1px;font-size:.65rem;min-height:16px}.sc-weekday{padding:8px 4px;font-size:.75rem}.sc-week-header{min-height:50px}.sc-week-view{grid-template-columns:60px repeat(7,1fr)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-day-header{padding:8px 4px}.sc-day-name{font-size:.625rem}.sc-day-header .sc-day-number{font-size:1rem}.sc-day-header.sc-today .sc-day-number{color:var(--sc-today-text);font-weight:600}.sc-time-slot{height:40px;padding:4px;font-size:.625rem}.sc-hour-slot{height:40px}.sc-day-view{grid-template-columns:60px 1fr;max-height:calc(100vh - 160px)}.sc-week-view{max-height:calc(100vh - 160px)}}@media (max-width:480px){.sc-calendar{border-radius:0;box-shadow:none;border:1px solid #e9ecef}.sc-header{padding:12px}.sc-btn{padding:6px 8px;font-size:.75rem;min-width:30px}.sc-title{font-size:1rem}.sc-day{min-height:50px;padding:2px}.sc-days-grid{min-height:300px;grid-template-rows:repeat(6,minmax(50px,1fr))}.sc-day-count-mode .sc-day-number{font-size:.875rem}.sc-event-count{font-size:.5rem;padding:1px 3px;min-width:12px}.sc-weekday{padding:6px 2px;font-size:.625rem}.sc-week-header{min-height:40px}.sc-week-view{grid-template-columns:50px repeat(7,1fr);max-height:calc(100vh - 140px)}.sc-week-view.sc-fullday{grid-template-columns:repeat(7,1fr)}.sc-time-slot{height:30px;padding:2px;font-size:.5rem}.sc-hour-slot{height:30px}.sc-day-view{grid-template-columns:50px 1fr;max-height:calc(100vh - 140px)}.sc-day-view .sc-event-time,.sc-week-view .sc-event-time{top:2px;left:2px;right:2px;padding:1px 3px;padding-top:2px;padding-bottom:0;font-size:.5rem;min-height:14px}}.sc-borders-both .sc-month-view .sc-days-grid{border-left:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-month-view .sc-days-grid{border-left:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-month-view .sc-days-grid{border-left:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-month-view .sc-days-grid{border-left:none;border-bottom:none}.sc-borders-both .sc-day{border-right:1px solid var(--sc-border-color);border-top:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-day{border-right:1px solid var(--sc-border-color);border-top:none}.sc-borders-horizontal .sc-day{border-right:none;border-top:1px solid var(--sc-border-color)}.sc-borders-none .sc-day{border-right:none;border-top:none}.sc-borders-both .sc-day-column,.sc-borders-both .sc-time-slot{border-right:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-day-column,.sc-borders-vertical .sc-time-slot{border-right:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-day-column,.sc-borders-horizontal .sc-time-slot{border-right:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-day-column,.sc-borders-none .sc-time-slot{border-right:none;border-bottom:none}.sc-week-view:not(.sc-fullday) .sc-time-slot{border-bottom:1px solid var(--sc-border-color)!important}.sc-week-view:not(.sc-fullday) .sc-day-column{border-bottom:1px solid var(--sc-border-color)!important}.sc-borders-both .sc-week-view.sc-fullday .sc-day{border-right:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-both .sc-week-view.sc-fullday .sc-day:first-child{border-left:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-week-view.sc-fullday .sc-day{border-right:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-week-view.sc-fullday .sc-day{border-right:none;border-bottom:none}.sc-borders-both .sc-day-header,.sc-borders-both .sc-time-column-header{border-right:1px solid var(--sc-border-color);border-bottom:1px solid var(--sc-border-color)}.sc-borders-both .sc-day-header:first-child,.sc-borders-both .sc-time-column-header:first-child,.sc-borders-vertical .sc-day-header:first-child,.sc-borders-vertical .sc-time-column-header:first-child{border-left:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-day-header,.sc-borders-vertical .sc-time-column-header{border-right:1px solid var(--sc-border-color);border-bottom:none}.sc-borders-horizontal .sc-day-header,.sc-borders-horizontal .sc-time-column-header{border-right:none;border-bottom:1px solid var(--sc-border-color)}.sc-borders-none .sc-day-header,.sc-borders-none .sc-time-column-header{border-right:none;border-bottom:none}.sc-borders-both .sc-week-view:not(.sc-fullday){border-left:1px solid var(--sc-border-color);border-right:1px solid var(--sc-border-color)}.sc-borders-vertical .sc-week-view:not(.sc-fullday){border-left:1px solid var(--sc-border-color);border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-day-header:nth-child(8n),.sc-borders-vertical .sc-day-header:nth-child(8n){border-right:none}.sc-borders-both .sc-half-hour-line,.sc-borders-horizontal .sc-half-hour-line{border-top:1px dotted var(--sc-border-color)}.sc-borders-none .sc-half-hour-line,.sc-borders-vertical .sc-half-hour-line{border-top:none}.sc-week-view:not(.sc-fullday) .sc-half-hour-line{border-top:1px dotted var(--sc-border-color)!important}.sc-day-view:not(.sc-fullday) .sc-time-slot{border-bottom:1px solid var(--sc-border-color)!important}.sc-borders-both .sc-day-view:not(.sc-fullday) .sc-time-slot,.sc-borders-vertical .sc-day-view:not(.sc-fullday) .sc-time-slot{border-left:1px solid var(--sc-border-color)!important}.sc-day-view:not(.sc-fullday) .sc-day-column{border-bottom:1px solid var(--sc-border-color)!important}.sc-day-view:not(.sc-fullday) .sc-half-hour-line{border-top:1px dotted var(--sc-border-color)!important}.sc-borders-both .sc-month-view .sc-days-grid,.sc-borders-vertical .sc-month-view .sc-days-grid{border-right:1px solid var(--sc-border-color)}.sc-borders-both .sc-day:nth-child(7n),.sc-borders-vertical .sc-day:nth-child(7n){border-right:none}:root{--sc-event-border-color:#6c757d}.sc-event-borders .sc-event-day:not(.sc-event-placeholder),.sc-event-borders .sc-event-month:not(.sc-event-placeholder),.sc-event-borders .sc-event-time:not(.sc-event-placeholder),.sc-event-borders .sc-event-week:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-single:not(.sc-event-placeholder){border:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-start:not(.sc-event-placeholder){border-left:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-middle:not(.sc-event-placeholder){border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-event-borders .sc-event-end:not(.sc-event-placeholder){border-right:1px solid var(--sc-event-border-color);border-top:1px solid var(--sc-event-border-color);border-bottom:1px solid var(--sc-event-border-color)}.sc-week-view .sc-event-week{border-right:none}.sc-event-no-borders .sc-event-day,.sc-event-no-borders .sc-event-month,.sc-event-no-borders .sc-event-time,.sc-event-no-borders .sc-event-week{border:none!important}@media print{.sc-calendar{box-shadow:none;border:1px solid #000}.sc-header{background:#fff!important;-webkit-print-color-adjust:exact;print-color-adjust:exact}.sc-btn{display:none}.sc-view-switcher{display:none}}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * SimpleCalendarJs v2.0.1
2
+ * SimpleCalendarJs v2.0.2
3
3
  * A clean, modern, and feature-rich JavaScript calendar component with zero dependencies
4
4
  *
5
5
  * @author Pedro Lopes <simplecalendarjs@gmail.com>
@@ -7,4 +7,4 @@
7
7
  * @license SEE LICENSE IN LICENSE
8
8
  * @repository https://github.com/pclslopes/SimpleCalendarJs
9
9
  */
10
- class t{constructor(t,e={}){this.container="string"==typeof t?document.querySelector(t):t,this.options={view:"month",date:new Date,events:[],fulldayMode:!1,startHour:6,endHour:22,timeSlotMinutes:30,dayClick:null,eventClick:null,changeState:null,gridBorders:"both",eventBorders:!1,eventBorderColor:"#6c757d",defaultEventColor:"#4c6f94",showMonthButton:!0,showWeekButton:!0,showDayButton:!0,showNavigation:!0,...e},this.currentDate=new Date(this.options.date),this.view=this.options.view,this.events=this.options.events,this.eventsCallback="function"==typeof this.options.events?this.options.events:null,this.fulldayMode=this.options.fulldayMode,this.fetchedRanges=[],this.allCachedEvents=[],this.weekdays=["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],this.weekdaysShort=["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],this.months=["January","February","March","April","May","June","July","August","September","October","November","December"],this.init()}init(){this.container.innerHTML=this.getCalendarTemplate(),this.applyGridBorders(),this.applyEventBorders(),this.applyEventColor(),this.applyViewButtonVisibility(),this.applyNavigationVisibility(),this.bindEvents(),this.render()}triggerChangeState(){if(this.options.changeState){const t={view:this.view,date:new Date(this.currentDate),fulldayMode:this.fulldayMode,startHour:this.options.startHour,endHour:this.options.endHour,timeSlotMinutes:this.options.timeSlotMinutes};this.options.changeState(t)}}applyGridBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-borders-both","sc-borders-vertical","sc-borders-horizontal","sc-borders-none"),t.classList.add(`sc-borders-${this.options.gridBorders}`))}applyEventBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-event-borders","sc-event-no-borders"),this.options.eventBorders?(t.classList.add("sc-event-borders"),t.style.setProperty("--sc-event-border-color",this.options.eventBorderColor)):t.classList.add("sc-event-no-borders"))}applyEventColor(){const t=this.container.querySelector(".sc-calendar");t&&t.style.setProperty("--sc-event-color",this.options.defaultEventColor)}applyViewButtonVisibility(){const t=this.container.querySelector('[data-view="month"]'),e=this.container.querySelector('[data-view="week"]'),s=this.container.querySelector('[data-view="day"]');t&&(t.style.display=this.options.showMonthButton?"flex":"none"),e&&(e.style.display=this.options.showWeekButton?"flex":"none"),s&&(s.style.display=this.options.showDayButton?"flex":"none");const n=this.container.querySelector(".sc-view-switcher");if(n){const t=this.options.showMonthButton||this.options.showWeekButton||this.options.showDayButton;n.style.display=t?"flex":"none"}}applyNavigationVisibility(){const t=this.container.querySelector(".sc-nav");t&&(t.style.visibility=this.options.showNavigation?"visible":"hidden")}getCalendarTemplate(){return`\n <div class="sc-calendar">\n <div class="sc-header">\n <div class="sc-nav">\n <button class="sc-btn sc-prev">←</button>\n <button class="sc-btn sc-next">→</button>\n </div>\n <h2 class="sc-title"></h2>\n <div class="sc-view-switcher">\n <button class="sc-btn sc-view-btn ${"month"===this.view?"sc-active":""}" data-view="month">Month</button>\n <button class="sc-btn sc-view-btn ${"week"===this.view?"sc-active":""}" data-view="week">Week</button>\n <button class="sc-btn sc-view-btn ${"day"===this.view?"sc-active":""}" data-view="day">Day</button>\n </div>\n </div>\n <div class="sc-content">\n <div class="sc-view-container"></div>\n </div>\n </div>\n `}bindEvents(){const t=this.container.querySelector(".sc-prev"),e=this.container.querySelector(".sc-next"),s=this.container.querySelectorAll(".sc-view-btn");t.addEventListener("click",()=>this.navigate("prev")),e.addEventListener("click",()=>this.navigate("next")),s.forEach(t=>{t.addEventListener("click",t=>{const e=t.target.dataset.view;this.setView(e)})}),this.container.addEventListener("click",t=>{this.handleCalendarClick(t)})}calculatePreciseTime(t,e){const s=t.getAttribute("data-time");if(!s)return null;const n=t.getBoundingClientRect(),i=e.clientY-n.top>n.height/2,a=this.parseTimeString(s);return a?(i&&a.setMinutes(a.getMinutes()+30),this.formatTime(a)):s}handleCalendarClick(t){const e=t.target.closest("[data-event-id]");if(e)return t.stopPropagation(),void this.handleEventClick(e,t);const s=t.target.closest("[data-date]");s&&this.handleDayClick(s,t)}handleEventClick(t,e){if(!this.options.eventClick)return;const s=t.getAttribute("data-event-id"),n=t.closest("[data-date]"),i=n?n.getAttribute("data-date"):null;if(s&&i){const t=new Date(i),a=this.getAllEventsForRange().find(t=>t.id==s);if(a){const s={date:t,time:this.calculatePreciseTime(n,e)};this.options.eventClick(a,s,e)}}}handleDayClick(t,e){if(!this.options.dayClick)return;const s=t.getAttribute("data-date");if(s){const n={date:new Date(s),time:this.calculatePreciseTime(t,e)};this.options.dayClick(n,e)}}navigate(t){const e="next"===t?1:-1;switch(this.view){case"month":this.currentDate.setMonth(this.currentDate.getMonth()+e);break;case"week":this.currentDate.setDate(this.currentDate.getDate()+7*e);break;case"day":this.currentDate.setDate(this.currentDate.getDate()+e)}this.render(),this.triggerChangeState()}setView(t){this.view=t,this.container.querySelectorAll(".sc-view-btn").forEach(t=>{t.classList.remove("sc-active")}),this.container.querySelector(`[data-view="${t}"]`).classList.add("sc-active"),this.render(),this.triggerChangeState()}render(){this.updateTitle(),this.renderView()}updateTitle(){const t=this.container.querySelector(".sc-title");switch(this.view){case"month":t.textContent=`${this.months[this.currentDate.getMonth()]} ${this.currentDate.getFullYear()}`;break;case"week":const e=this.getWeekStart(this.currentDate),s=new Date(e);s.setDate(s.getDate()+6),t.textContent=`${this.months[e.getMonth()]} ${e.getDate()} - ${this.months[s.getMonth()]} ${s.getDate()}, ${e.getFullYear()}`;break;case"day":t.textContent=`${this.weekdays[this.currentDate.getDay()]}, ${this.months[this.currentDate.getMonth()]} ${this.currentDate.getDate()}, ${this.currentDate.getFullYear()}`}}renderView(){const t=this.container.querySelector(".sc-view-container");switch(this.view){case"month":t.innerHTML=this.renderMonthView();break;case"week":t.innerHTML=this.renderWeekView();break;case"day":t.innerHTML=this.renderDayView()}}renderMonthView(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth(),s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());let i=`\n <div class="sc-month-view ${this.fulldayMode?"sc-fullday":""}">\n <div class="sc-weekdays">\n ${this.weekdaysShort.map(t=>`<div class="sc-weekday">${t}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n `;for(let t=0;t<6;t++)for(let s=0;s<7;s++){const a=new Date(n);a.setDate(n.getDate()+7*t+s);const r=a.getMonth()===e,o=this.isToday(a),c=this.getEventsForDate(a);if(this.fulldayMode){const t=this.addEventPlaceholders(c,a);i+=`\n <div class="sc-day ${r?"sc-current-month":"sc-other-month"} ${o?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-number">${a.getDate()}</div>\n ${t.map(t=>{if(t.isPlaceholder)return'<div class="sc-event-month sc-event-single sc-event-placeholder">&nbsp;</div>';const e=this.getEventPosition(t.event,a),s=e.isSingle?"sc-event-single":e.isStart?"sc-event-start":e.isEnd?"sc-event-end":"sc-event-middle",n=e.isSingle||e.isStart?t.event.title:"",i=this.applyEventStyles(t.event);return`<div class="sc-event-month ${s}" data-event-id="${t.event.id}" ${i}><span class="sc-event-text">${n}</span></div>`}).join("")}\n </div>\n `}else{const t=c.length,e=0===t?"":1===t?"1 event":`${t} events`;i+=`\n <div class="sc-day sc-day-count-mode ${r?"sc-current-month":"sc-other-month"} ${o?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-content">\n <div class="sc-day-number">${a.getDate()}</div>\n ${e?`<div class="sc-event-count">${e}</div>`:""}\n </div>\n </div>\n `}}return i+="\n </div>\n </div>\n ",i}renderWeekView(){const t=this.getWeekStart(this.currentDate),e=[];for(let s=0;s<7;s++){const n=new Date(t);n.setDate(n.getDate()+s),e.push(n)}if(this.fulldayMode)return this.renderWeekFullday(e);const s=this.generateTimeSlots();return`\n <div class="sc-week-view">\n <div class="sc-time-column-header"></div>\n ${e.map(t=>`\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.weekdaysShort[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n `).join("")}\n ${s.map(t=>`\n <div class="sc-time-slot">${t}</div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(e,t);return`\n <div class="sc-day-column ${this.isToday(e)?"sc-today":""}" data-date="${e.toISOString()}" data-time="${t}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n `).join("")}\n </div>\n `}renderWeekFullday(t){return`\n <div class="sc-week-view sc-fullday">\n <div class="sc-weekdays">\n ${t.map(t=>`<div class="sc-weekday">${this.weekdaysShort[t.getDay()]}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n ${t.map(t=>{const e=this.isToday(t),s=this.getEventsForDate(t),n=this.addEventPlaceholders(s,t);return`\n <div class="sc-day ${e?"sc-today":""}" data-date="${t.toISOString()}">\n <div class="sc-day-number">${t.getDate()}</div>\n ${n.map(e=>{if(e.isPlaceholder)return'<div class="sc-event-week sc-event-single sc-event-placeholder">&nbsp;</div>';const s=this.getEventPosition(e.event,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=s.isSingle||s.isStart?e.event.title:"",a=this.applyEventStyles(e.event);return`<div class="sc-event-week ${n}" data-event-id="${e.event.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n </div>\n `}renderDayView(){const t=new Date(this.currentDate);if(this.fulldayMode){const e=this.getEventsForDate(t);return`\n <div class="sc-day-view sc-fullday">\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n <div class="sc-day-events" data-date="${t.toISOString()}">\n ${e.map(e=>{const s=this.getEventPosition(e,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=e.title,a=this.applyEventStyles(e);return`<div class="sc-event-day ${n}" data-event-id="${e.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n </div>\n `}const e=this.generateTimeSlots();return`\n <div class="sc-day-view">\n <div class="sc-time-column-header"></div>\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(t,e);return`\n <div class="sc-time-slot">${e}</div>\n <div class="sc-day-column ${this.isToday(t)?"sc-today":""}" data-date="${t.toISOString()}" data-time="${e}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n `}generateTimeSlots(){const t=[],e=this.options.startHour,s=this.options.endHour,n=new Date;n.setHours(e,0,0,0),t.push(`Before ${this.formatTime(n)}`);for(let n=e;n<=s;n++){const e=new Date;e.setHours(n,0,0,0),t.push(this.formatTime(e))}const i=new Date;return i.setHours(s,0,0,0),t.push(`After ${this.formatTime(i)}`),t}formatTime(t){return t.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}getWeekStart(t){const e=new Date(t);return e.setDate(e.getDate()-e.getDay()),e}getVisibleDateRange(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth();switch(this.view){case"month":const s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());const i=new Date(n);return i.setDate(i.getDate()+41),{startDate:n,endDate:i};case"week":const a=this.getWeekStart(this.currentDate),r=new Date(a);return r.setDate(r.getDate()+6),{startDate:a,endDate:r};case"day":const o=new Date(this.currentDate);o.setHours(0,0,0,0);const c=new Date(o);return c.setHours(23,59,59,999),{startDate:o,endDate:c}}}isToday(t){const e=new Date;return t.toDateString()===e.toDateString()}isRangeCovered(t,e){for(const s of this.fetchedRanges)if(s.startDate<=t&&s.endDate>=e)return!0;return!1}getMissingRanges(t,e){const s=[];let n=new Date(t);const i=[...this.fetchedRanges].sort((t,e)=>t.startDate-e.startDate);for(const t of i){if(n<t.startDate&&n<=e){const i=new Date(Math.min(t.startDate.getTime()-864e5,e.getTime()));i>=n&&s.push({startDate:new Date(n),endDate:i})}t.endDate>=n&&(n=new Date(t.endDate.getTime()+864e5))}return n<=e&&s.push({startDate:new Date(n),endDate:new Date(e)}),s}addFetchedRange(t,e,s){const n={startDate:new Date(t),endDate:new Date(e),events:s};this.allCachedEvents=this.allCachedEvents.concat(s),this.fetchedRanges.push(n),this.mergeOverlappingRanges()}mergeOverlappingRanges(){this.fetchedRanges.sort((t,e)=>t.startDate-e.startDate);const t=[];for(const e of this.fetchedRanges)if(0===t.length||t[t.length-1].endDate<e.startDate)t.push(e);else{const s=t[t.length-1];s.endDate=new Date(Math.max(s.endDate.getTime(),e.endDate.getTime())),s.events=s.events.concat(e.events)}this.fetchedRanges=t,this.allCachedEvents=[];for(const t of this.fetchedRanges)this.allCachedEvents=this.allCachedEvents.concat(t.events)}getAllEventsForRange(){if(this.eventsCallback){const t=this.getVisibleDateRange();if(this.isRangeCovered(t.startDate,t.endDate))return this.allCachedEvents;const e=this.getMissingRanges(t.startDate,t.endDate);for(const t of e){const e=this.eventsCallback(t);this.addFetchedRange(t.startDate,t.endDate,e)}return this.allCachedEvents}return this.events||[]}getEventsForDate(t){return this.getAllEventsForRange().filter(e=>{const s=new Date(e.startDate||e.date),n=new Date(e.endDate||e.date),i=new Date(t);return i.setHours(0,0,0,0),s.setHours(0,0,0,0),n.setHours(0,0,0,0),i>=s&&i<=n}).sort((t,e)=>{const s=this.isMultiDayEvent(t),n=this.isMultiDayEvent(e);return s&&!n?-1:!s&&n?1:0})}getEventsForTimeSlot(t,e){if(this.fulldayMode)return this.getEventsForDate(t);return this.getEventsForDate(t).filter(t=>{if(!t.time)return!1;const s=this.parseTimeString(t.time);if(!s)return!1;const n=s.getHours(),i=this.options.startHour,a=this.options.endHour;if(e.startsWith("Before"))return n<i;if(e.startsWith("After"))return n>a;const r=this.parseTimeString(e);if(!r)return!1;return n===r.getHours()})}parseTimeString(t){try{const e=new Date,[s,n]=t.split(" "),[i,a]=s.split(":").map(Number);let r=i;return"PM"===n&&12!==i&&(r+=12),"AM"===n&&12===i&&(r=0),e.setHours(r,a||0,0,0),e}catch(t){return null}}isMultiDayEvent(t){const e=new Date(t.startDate||t.date),s=new Date(t.endDate||t.date);return e.setHours(0,0,0,0),s.setHours(0,0,0,0),e.getTime()!==s.getTime()}addEventPlaceholders(t,e){if(!this.fulldayMode)return t.map(t=>({event:t,isPlaceholder:!1}));const s=this.getWeekStart(e),n=[];for(let t=0;t<7;t++){const e=new Date(s);e.setDate(e.getDate()+t),n.push(e)}const i=new Map;n.forEach(t=>{this.getEventsForDate(t).filter(t=>this.isMultiDayEvent(t)).forEach(t=>{i.has(t.id)||i.set(t.id,t)})});const a=Array.from(i.values()).sort((t,e)=>new Date(t.startDate||t.date)-new Date(e.startDate||e.date)),r=[],o=t.filter(t=>this.isMultiDayEvent(t)),c=t.filter(t=>!this.isMultiDayEvent(t));return a.forEach(t=>{const n=o.find(e=>e.id===t.id);if(n)r.push({event:n,isPlaceholder:!1});else{const n=new Date(t.startDate||t.date),i=new Date(t.endDate||t.date);n.setHours(0,0,0,0),i.setHours(0,0,0,0);const a=new Date(e);a.setHours(0,0,0,0);const o=new Date(s);o.setHours(0,0,0,0);const c=new Date(s);c.setDate(c.getDate()+6),c.setHours(0,0,0,0);n<=c&&(i>=o&&i<=c)&&a>i&&r.push({isPlaceholder:!0,eventId:t.id})}}),c.forEach(t=>{r.push({event:t,isPlaceholder:!1})}),r}getEventPosition(t,e){const s=new Date(t.startDate||t.date),n=new Date(t.endDate||t.date),i=new Date(e);s.setHours(0,0,0,0),n.setHours(0,0,0,0),i.setHours(0,0,0,0);return{isStart:i.getTime()===s.getTime(),isEnd:i.getTime()===n.getTime(),isMiddle:i>s&&i<n,isSingle:s.getTime()===n.getTime()}}addEvent(t){this.eventsCallback||(this.events.push(t),this.render())}removeEvent(t){this.eventsCallback||(this.events=this.events.filter(e=>e.id!==t),this.render())}setEvents(t){"function"==typeof t?(this.eventsCallback=t,this.events=[]):(this.events=t||[],this.eventsCallback=null),this.fetchedRanges=[],this.allCachedEvents=[],this.render()}setFulldayMode(t){this.fulldayMode=t,this.render(),this.triggerChangeState()}goToDate(t){this.currentDate=new Date(t),this.render(),this.triggerChangeState()}goToToday(){this.currentDate=new Date,this.render(),this.triggerChangeState()}setGridBorders(t){this.options.gridBorders=t,this.applyGridBorders()}setEventBorders(t){this.options.eventBorders=t,this.applyEventBorders(),this.render()}setEventBorderColor(t){this.options.eventBorderColor=t,this.applyEventBorders(),this.render()}setShowMonthButton(t){this.options.showMonthButton=t,this.applyViewButtonVisibility()}setShowWeekButton(t){this.options.showWeekButton=t,this.applyViewButtonVisibility()}setShowDayButton(t){this.options.showDayButton=t,this.applyViewButtonVisibility()}setViewButtonsVisibility(t,e,s){this.options.showMonthButton=t,this.options.showWeekButton=e,this.options.showDayButton=s,this.applyViewButtonVisibility()}setShowNavigation(t){this.options.showNavigation=t,this.applyNavigationVisibility()}setDefaultEventColor(t){this.options.defaultEventColor=t,this.applyEventColor(),this.render()}getEventBaseCssStyle(t){const e=t.color,s=!0===t.colorIsGradient,n=e?t.color:this.options.defaultEventColor;if(!n)return{};const i=(" "+n).slice(1),a=this.hexToRgb(i);if(!a)return{};const r=this.getOptimalTextColor(a);if(s){const t={r:Math.min(255,a.r+40),g:Math.min(255,a.g+40),b:Math.min(255,a.b+40)};return{"background-image":`linear-gradient(to bottom, ${this.rgbToHex(t)} 0px, ${n} 100%)`,"background-repeat":"repeat-x",color:r,"background-color":n}}return{"background-color":n,color:r}}getOptimalTextColor(t){return this.getLuminance(t)>.5?"#000000":"#ffffff"}getLuminance(t){return.2126*this.getRelativeLuminanceComponent(t.r/255)+.7152*this.getRelativeLuminanceComponent(t.g/255)+.0722*this.getRelativeLuminanceComponent(t.b/255)}getRelativeLuminanceComponent(t){return t<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4)}getContrastRatio(t,e){const s=this.getLuminance(t),n=this.getLuminance(e);return(Math.max(s,n)+.05)/(Math.min(s,n)+.05)}hexToRgb(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}rgbToHex(t){return"#"+(1<<24|t.r<<16|t.g<<8|t.b).toString(16).slice(1)}applyEventStyles(t){const e=this.getEventBaseCssStyle(t),s=Object.entries(e).map(([t,e])=>`${t}: ${e}`).join("; ");return s?`style="${s}"`:""}}
10
+ class t{constructor(t,e={}){this.container="string"==typeof t?document.querySelector(t):t;const s=this.mergeColors({background:"#ffffff",backgroundSecondary:"#f8f9fa",backgroundTertiary:"#ecf0f1",text:"#212529",textSecondary:"#6c757d",textMuted:"#adb5bd",border:"#e9ecef",borderLight:"#dee2e6",accent:"#4c6f94",accentHover:"#0056b3",todayBg:"#e3f2fd",todayText:"#dc3545",hoverBg:"#f8f9fa",dark:{background:"#2d2d2d",backgroundSecondary:"#3a3a3a",backgroundTertiary:"#4a4a4a",text:"#ffffff",textSecondary:"#cccccc",textMuted:"#888888",border:"#444444",borderLight:"#555555",accent:"#4a90e2",accentHover:"#357abd",todayBg:"#1a2f4a",todayText:"#ff6b6b",hoverBg:"#2d2d2d"}},e.colors||{});this.options={view:"month",date:new Date,events:[],fulldayMode:!1,startHour:6,endHour:22,timeSlotMinutes:30,dayClick:null,eventClick:null,changeState:null,gridBorders:"both",eventBorders:!1,eventBorderColor:"#6c757d",defaultEventColor:"#4c6f94",showMonthButton:!0,showWeekButton:!0,showDayButton:!0,showNavigation:!0,weekdays:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],showWeekdayChars:null,labels:{month:"Month",week:"Week",day:"Day",events:"events",event:"event",before:"Before",after:"After"},colors:s,...e,colors:s},this.currentDate=new Date(this.options.date),this.view=this.options.view,this.events=this.options.events,this.eventsCallback="function"==typeof this.options.events?this.options.events:null,this.fulldayMode=this.options.fulldayMode,this.fetchedRanges=[],this.allCachedEvents=[],this.init()}mergeColors(t,e){const s={...t};return Object.keys(e).forEach(t=>{"dark"!==t&&void 0!==e[t]&&(s[t]=e[t])}),e.dark&&(s.dark={...t.dark},Object.keys(e.dark).forEach(t=>{void 0!==e.dark[t]&&(s.dark[t]=e.dark[t])})),s}init(){this.container.innerHTML=this.getCalendarTemplate(),this.applyGridBorders(),this.applyEventBorders(),this.applyEventColor(),this.applyViewButtonVisibility(),this.applyNavigationVisibility(),this.applyCustomColors(),this.bindEvents(),this.render()}getWeekdayDisplay(t){const e=this.options.weekdays[t];return null===this.options.showWeekdayChars?e:e.substring(0,this.options.showWeekdayChars)}triggerChangeState(){if(this.options.changeState){const t={view:this.view,date:new Date(this.currentDate),fulldayMode:this.fulldayMode,startHour:this.options.startHour,endHour:this.options.endHour,timeSlotMinutes:this.options.timeSlotMinutes};this.options.changeState(t)}}applyGridBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-borders-both","sc-borders-vertical","sc-borders-horizontal","sc-borders-none"),t.classList.add(`sc-borders-${this.options.gridBorders}`))}applyEventBorders(){const t=this.container.querySelector(".sc-calendar");t&&(t.classList.remove("sc-event-borders","sc-event-no-borders"),this.options.eventBorders?(t.classList.add("sc-event-borders"),t.style.setProperty("--sc-event-border-color",this.options.eventBorderColor)):t.classList.add("sc-event-no-borders"))}applyEventColor(){const t=this.container.querySelector(".sc-calendar");t&&t.style.setProperty("--sc-event-color",this.options.defaultEventColor)}applyViewButtonVisibility(){const t=this.container.querySelector('[data-view="month"]'),e=this.container.querySelector('[data-view="week"]'),s=this.container.querySelector('[data-view="day"]');t&&(t.style.display=this.options.showMonthButton?"flex":"none"),e&&(e.style.display=this.options.showWeekButton?"flex":"none"),s&&(s.style.display=this.options.showDayButton?"flex":"none");const n=this.container.querySelector(".sc-view-switcher");if(n){const t=this.options.showMonthButton||this.options.showWeekButton||this.options.showDayButton;n.style.display=t?"flex":"none"}}applyNavigationVisibility(){const t=this.container.querySelector(".sc-nav");t&&(t.style.visibility=this.options.showNavigation?"visible":"hidden")}applyCustomColors(){if(!this.container.querySelector(".sc-calendar"))return;const t=this.options.colors,e=(t,e)=>void 0!==e?`--${t}: ${e} !important;`:"",s=`\n .sc-calendar {\n ${e("sc-bg-primary",t.background)}\n ${e("sc-bg-secondary",t.backgroundSecondary)}\n ${e("sc-bg-tertiary",t.backgroundTertiary)}\n ${e("sc-text-primary",t.text)}\n ${e("sc-text-secondary",t.textSecondary)}\n ${e("sc-text-muted",t.textMuted)}\n ${e("sc-border-color",t.border)}\n ${e("sc-border-light",t.borderLight)}\n ${e("sc-accent-color",t.accent)}\n ${e("sc-accent-hover",t.accentHover)}\n ${e("sc-today-bg",t.todayBg)}\n ${e("sc-today-text",t.todayText)}\n ${e("sc-hover-bg",t.hoverBg)}\n }\n\n ${t.dark?`\n [data-theme="dark"] .sc-calendar {\n ${e("sc-bg-primary",t.dark.background)}\n ${e("sc-bg-secondary",t.dark.backgroundSecondary)}\n ${e("sc-bg-tertiary",t.dark.backgroundTertiary)}\n ${e("sc-text-primary",t.dark.text)}\n ${e("sc-text-secondary",t.dark.textSecondary)}\n ${e("sc-text-muted",t.dark.textMuted)}\n ${e("sc-border-color",t.dark.border)}\n ${e("sc-border-light",t.dark.borderLight)}\n ${e("sc-accent-color",t.dark.accent)}\n ${e("sc-accent-hover",t.dark.accentHover)}\n ${e("sc-today-bg",t.dark.todayBg)}\n ${e("sc-today-text",t.dark.todayText)}\n ${e("sc-hover-bg",t.dark.hoverBg)}\n }\n `:""}\n `;let n=document.getElementById("sc-custom-colors");n||(n=document.createElement("style"),n.id="sc-custom-colors",document.head.appendChild(n)),n.textContent=s}getCalendarTemplate(){return`\n <div class="sc-calendar">\n <div class="sc-header">\n <div class="sc-nav">\n <button class="sc-btn sc-prev">←</button>\n <button class="sc-btn sc-next">→</button>\n </div>\n <div class="sc-title-container">\n <h2 class="sc-title">\n <span class="sc-title-month"></span>\n <span class="sc-title-year"></span>\n </h2>\n <div class="sc-month-dropdown" style="display: none;">\n ${this.options.months.map((t,e)=>`<div class="sc-month-option" data-month="${e}">${t}</div>`).join("")}\n </div>\n <div class="sc-year-dropdown" style="display: none;">\n </div>\n </div>\n <div class="sc-view-switcher">\n <button class="sc-btn sc-view-btn ${"month"===this.view?"sc-active":""}" data-view="month">${this.options.labels.month}</button>\n <button class="sc-btn sc-view-btn ${"week"===this.view?"sc-active":""}" data-view="week">${this.options.labels.week}</button>\n <button class="sc-btn sc-view-btn ${"day"===this.view?"sc-active":""}" data-view="day">${this.options.labels.day}</button>\n </div>\n </div>\n <div class="sc-content">\n <div class="sc-view-container"></div>\n </div>\n </div>\n `}bindEvents(){const t=this.container.querySelector(".sc-prev"),e=this.container.querySelector(".sc-next"),s=this.container.querySelectorAll(".sc-view-btn");t.addEventListener("click",()=>this.navigate("prev")),e.addEventListener("click",()=>this.navigate("next")),s.forEach(t=>{t.addEventListener("click",t=>{const e=t.target.dataset.view;this.setView(e)})}),this.container.addEventListener("click",t=>{this.handleCalendarClick(t)}),this.outsideClickBound||(document.addEventListener("click",t=>{t.target.closest(".sc-title-container")||(this.hideMonthDropdown(),this.hideYearDropdown())}),this.outsideClickBound=!0)}bindMonthViewDropdowns(){if("month"!==this.view)return;const t=this.container.querySelector(".sc-title-month"),e=this.container.querySelector(".sc-title-year");if(t){const e=t.cloneNode(!0);t.parentNode.replaceChild(e,t),e.addEventListener("click",t=>{t.stopPropagation(),this.hideYearDropdown(),this.toggleMonthDropdown()})}if(e){const t=e.cloneNode(!0);e.parentNode.replaceChild(t,e),t.addEventListener("click",t=>{t.stopPropagation(),this.hideMonthDropdown(),this.toggleYearDropdown()})}this.generateYearOptions(),this.dropdownOptionsHandler&&this.container.removeEventListener("click",this.dropdownOptionsHandler),this.dropdownOptionsHandler=t=>{if(t.target.classList.contains("sc-month-option")){const e=parseInt(t.target.getAttribute("data-month"));this.setMonth(e),this.hideMonthDropdown()}else if(t.target.classList.contains("sc-year-option")){const e=parseInt(t.target.getAttribute("data-year"));this.setYear(e),this.hideYearDropdown()}},this.container.addEventListener("click",this.dropdownOptionsHandler);this.container.querySelectorAll(".sc-month-option"),this.container.querySelectorAll(".sc-year-option")}calculatePreciseTime(t,e){const s=t.getAttribute("data-time");if(!s)return null;const n=t.getBoundingClientRect(),i=e.clientY-n.top>n.height/2,a=this.parseTimeString(s);return a?(i&&a.setMinutes(a.getMinutes()+30),this.formatTime(a)):s}handleCalendarClick(t){if(t.target.classList.contains("sc-title-month")||t.target.classList.contains("sc-title-year")||t.target.classList.contains("sc-month-option")||t.target.classList.contains("sc-year-option")||t.target.closest(".sc-month-dropdown")||t.target.closest(".sc-year-dropdown"))return;const e=t.target.closest("[data-event-id]");if(e)return t.stopPropagation(),void this.handleEventClick(e,t);const s=t.target.closest("[data-date]");s&&this.handleDayClick(s,t)}handleEventClick(t,e){if(!this.options.eventClick)return;const s=t.getAttribute("data-event-id"),n=t.closest("[data-date]"),i=n?n.getAttribute("data-date"):null;if(s&&i){const t=new Date(i),a=this.getAllEventsForRange().find(t=>t.id==s);if(a){const s={date:t,time:this.calculatePreciseTime(n,e)};this.options.eventClick(a,s,e)}}}handleDayClick(t,e){if(!this.options.dayClick)return;const s=t.getAttribute("data-date");if(s){const n={date:new Date(s),time:this.calculatePreciseTime(t,e)};this.options.dayClick(n,e)}}navigate(t){const e="next"===t?1:-1;switch(this.view){case"month":this.currentDate.setMonth(this.currentDate.getMonth()+e);break;case"week":this.currentDate.setDate(this.currentDate.getDate()+7*e);break;case"day":this.currentDate.setDate(this.currentDate.getDate()+e)}this.render(),this.triggerChangeState()}toggleMonthDropdown(){if("month"!==this.view)return;const t=this.container.querySelector(".sc-month-dropdown");if(t){"none"!==t.style.display?this.hideMonthDropdown():(this.updateMonthDropdownSelection(),t.style.display="block",this.scrollToActiveMonth())}}hideMonthDropdown(){const t=this.container.querySelector(".sc-month-dropdown");t&&(t.style.display="none")}updateMonthDropdownSelection(){const t=this.container.querySelectorAll(".sc-month-option"),e=this.currentDate.getMonth();t.forEach((t,s)=>{t.classList.remove("sc-current"),s===e&&t.classList.add("sc-current")})}toggleYearDropdown(){if("month"!==this.view)return;const t=this.container.querySelector(".sc-year-dropdown");if(t){"none"!==t.style.display?this.hideYearDropdown():(this.generateYearOptions(),this.updateYearDropdownSelection(),t.style.display="block",this.scrollToActiveYear())}}hideYearDropdown(){const t=this.container.querySelector(".sc-year-dropdown");t&&(t.style.display="none")}generateYearOptions(){const t=this.container.querySelector(".sc-year-dropdown"),e=this.currentDate.getFullYear(),s=e+10;if(t){let n="";for(let t=e-10;t<=s;t++)n+=`<div class="sc-year-option" data-year="${t}">${t}</div>`;t.innerHTML=n}}updateYearDropdownSelection(){const t=this.container.querySelectorAll(".sc-year-option"),e=this.currentDate.getFullYear();t.forEach(t=>{t.classList.remove("sc-current"),parseInt(t.getAttribute("data-year"))===e&&t.classList.add("sc-current")})}setYear(t){this.currentDate.setFullYear(t),this.render(),this.triggerChangeState()}scrollToActiveMonth(){const t=this.container.querySelector(".sc-month-dropdown"),e=t?.querySelector(".sc-month-option.sc-current");t&&e&&setTimeout(()=>{t.getBoundingClientRect(),e.getBoundingClientRect(),t.scrollTop;const s=e.offsetTop-t.clientHeight/2+e.offsetHeight/2;t.scrollTop=s},0)}scrollToActiveYear(){const t=this.container.querySelector(".sc-year-dropdown"),e=t?.querySelector(".sc-year-option.sc-current");t&&e&&setTimeout(()=>{t.getBoundingClientRect(),e.getBoundingClientRect(),t.scrollTop;const s=e.offsetTop-t.clientHeight/2+e.offsetHeight/2;t.scrollTop=s},0)}setMonth(t){this.currentDate.setMonth(t),this.render(),this.triggerChangeState()}setView(t){this.view=t,this.container.querySelectorAll(".sc-view-btn").forEach(t=>{t.classList.remove("sc-active")}),this.container.querySelector(`[data-view="${t}"]`).classList.add("sc-active"),this.hideMonthDropdown(),this.hideYearDropdown(),this.render(),this.triggerChangeState()}render(){this.updateTitle(),this.renderView(),"month"===this.view&&this.bindMonthViewDropdowns()}updateTitle(){const t=this.container.querySelector(".sc-title"),e=this.container.querySelector(".sc-title-month"),s=this.container.querySelector(".sc-title-year");switch(this.view){case"month":e&&s?(e.textContent=this.options.months[this.currentDate.getMonth()],s.textContent=this.currentDate.getFullYear()):t.innerHTML=`<span class="sc-title-month">${this.options.months[this.currentDate.getMonth()]}</span> <span class="sc-title-year">${this.currentDate.getFullYear()}</span>`;break;case"week":const n=this.getWeekStart(this.currentDate),i=new Date(n);i.setDate(i.getDate()+6),t.textContent=`${this.options.months[n.getMonth()]} ${n.getDate()} - ${this.options.months[i.getMonth()]} ${i.getDate()}, ${n.getFullYear()}`;break;case"day":t.textContent=`${this.options.weekdays[this.currentDate.getDay()]}, ${this.options.months[this.currentDate.getMonth()]} ${this.currentDate.getDate()}, ${this.currentDate.getFullYear()}`}}renderView(){const t=this.container.querySelector(".sc-view-container");switch(this.view){case"month":t.innerHTML=this.renderMonthView();break;case"week":t.innerHTML=this.renderWeekView();break;case"day":t.innerHTML=this.renderDayView()}}renderMonthView(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth(),s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());let i=`\n <div class="sc-month-view ${this.fulldayMode?"sc-fullday":""}">\n <div class="sc-weekdays">\n ${this.options.weekdays.map((t,e)=>`<div class="sc-weekday">${this.getWeekdayDisplay(e)}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n `;for(let t=0;t<6;t++)for(let s=0;s<7;s++){const a=new Date(n);a.setDate(n.getDate()+7*t+s);const o=a.getMonth()===e,r=this.isToday(a),c=this.getEventsForDate(a);if(this.fulldayMode){const t=this.addEventPlaceholders(c,a);i+=`\n <div class="sc-day ${o?"sc-current-month":"sc-other-month"} ${r?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-number">${a.getDate()}</div>\n ${t.map(t=>{if(t.isPlaceholder)return'<div class="sc-event-month sc-event-single sc-event-placeholder">&nbsp;</div>';const e=this.getEventPosition(t.event,a),s=e.isSingle?"sc-event-single":e.isStart?"sc-event-start":e.isEnd?"sc-event-end":"sc-event-middle",n=e.isSingle||e.isStart?t.event.title:"",i=this.applyEventStyles(t.event);return`<div class="sc-event-month ${s}" data-event-id="${t.event.id}" ${i}><span class="sc-event-text">${n}</span></div>`}).join("")}\n </div>\n `}else{const t=c.length,e=0===t?"":1===t?`1 ${this.options.labels.event}`:`${t} ${this.options.labels.events}`;i+=`\n <div class="sc-day sc-day-count-mode ${o?"sc-current-month":"sc-other-month"} ${r?"sc-today":""}" data-date="${a.toISOString()}">\n <div class="sc-day-content">\n <div class="sc-day-number">${a.getDate()}</div>\n ${e?`<div class="sc-event-count">${e}</div>`:""}\n </div>\n </div>\n `}}return i+="\n </div>\n </div>\n ",i}renderWeekView(){const t=this.getWeekStart(this.currentDate),e=[];for(let s=0;s<7;s++){const n=new Date(t);n.setDate(n.getDate()+s),e.push(n)}if(this.fulldayMode)return this.renderWeekFullday(e);const s=this.generateTimeSlots();return`\n <div class="sc-week-view">\n <div class="sc-time-column-header"></div>\n ${e.map(t=>`\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.getWeekdayDisplay(t.getDay())}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n `).join("")}\n ${s.map(t=>`\n <div class="sc-time-slot">${t}</div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(e,t);return`\n <div class="sc-day-column ${this.isToday(e)?"sc-today":""}" data-date="${e.toISOString()}" data-time="${t}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n `).join("")}\n </div>\n `}renderWeekFullday(t){return`\n <div class="sc-week-view sc-fullday">\n <div class="sc-weekdays">\n ${t.map(t=>`<div class="sc-weekday">${this.getWeekdayDisplay(t.getDay())}</div>`).join("")}\n </div>\n <div class="sc-days-grid">\n ${t.map(t=>{const e=this.isToday(t),s=this.getEventsForDate(t),n=this.addEventPlaceholders(s,t);return`\n <div class="sc-day ${e?"sc-today":""}" data-date="${t.toISOString()}">\n <div class="sc-day-number">${t.getDate()}</div>\n ${n.map(e=>{if(e.isPlaceholder)return'<div class="sc-event-week sc-event-single sc-event-placeholder">&nbsp;</div>';const s=this.getEventPosition(e.event,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=s.isSingle||s.isStart?e.event.title:"",a=this.applyEventStyles(e.event);return`<div class="sc-event-week ${n}" data-event-id="${e.event.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n </div>\n `}renderDayView(){const t=new Date(this.currentDate);if(this.fulldayMode){const e=this.getEventsForDate(t);return`\n <div class="sc-day-view sc-fullday">\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.options.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n <div class="sc-day-events" data-date="${t.toISOString()}">\n ${e.map(e=>{const s=this.getEventPosition(e,t),n=s.isSingle?"sc-event-single":s.isStart?"sc-event-start":s.isEnd?"sc-event-end":"sc-event-middle",i=e.title,a=this.applyEventStyles(e);return`<div class="sc-event-day ${n}" data-event-id="${e.id}" ${a}><span class="sc-event-text">${i}</span></div>`}).join("")}\n </div>\n </div>\n `}const e=this.generateTimeSlots();return`\n <div class="sc-day-view">\n <div class="sc-time-column-header"></div>\n <div class="sc-day-header ${this.isToday(t)?"sc-today":""}">\n <div class="sc-day-name">${this.options.weekdays[t.getDay()]}</div>\n <div class="sc-day-number">${t.getDate()}</div>\n </div>\n ${e.map(e=>{const s=this.getEventsForTimeSlot(t,e);return`\n <div class="sc-time-slot">${e}</div>\n <div class="sc-day-column ${this.isToday(t)?"sc-today":""}" data-date="${t.toISOString()}" data-time="${e}">\n <div class="sc-half-hour-line"></div>\n ${s.map(t=>{const e=this.applyEventStyles(t);return`<div class="sc-event-time" data-event-id="${t.id}" ${e}>${t.title}</div>`}).join("")}\n </div>\n `}).join("")}\n </div>\n `}generateTimeSlots(){const t=[],e=this.options.startHour,s=this.options.endHour,n=new Date;n.setHours(e,0,0,0),t.push(`${this.options.labels.before} ${this.formatTime(n)}`);for(let n=e;n<=s;n++){const e=new Date;e.setHours(n,0,0,0),t.push(this.formatTime(e))}const i=new Date;return i.setHours(s,0,0,0),t.push(`${this.options.labels.after} ${this.formatTime(i)}`),t}formatTime(t){return t.toLocaleTimeString("en-US",{hour:"numeric",minute:"2-digit",hour12:!0})}getWeekStart(t){const e=new Date(t);return e.setDate(e.getDate()-e.getDay()),e}getVisibleDateRange(){const t=this.currentDate.getFullYear(),e=this.currentDate.getMonth();switch(this.view){case"month":const s=new Date(t,e,1),n=new Date(s);n.setDate(n.getDate()-s.getDay());const i=new Date(n);return i.setDate(i.getDate()+41),{startDate:n,endDate:i};case"week":const a=this.getWeekStart(this.currentDate),o=new Date(a);return o.setDate(o.getDate()+6),{startDate:a,endDate:o};case"day":const r=new Date(this.currentDate);r.setHours(0,0,0,0);const c=new Date(r);return c.setHours(23,59,59,999),{startDate:r,endDate:c}}}isToday(t){const e=new Date;return t.toDateString()===e.toDateString()}isRangeCovered(t,e){for(const s of this.fetchedRanges)if(s.startDate<=t&&s.endDate>=e)return!0;return!1}getMissingRanges(t,e){const s=[];let n=new Date(t);const i=[...this.fetchedRanges].sort((t,e)=>t.startDate-e.startDate);for(const t of i){if(n<t.startDate&&n<=e){const i=new Date(Math.min(t.startDate.getTime()-864e5,e.getTime()));i>=n&&s.push({startDate:new Date(n),endDate:i})}t.endDate>=n&&(n=new Date(t.endDate.getTime()+864e5))}return n<=e&&s.push({startDate:new Date(n),endDate:new Date(e)}),s}addFetchedRange(t,e,s){const n={startDate:new Date(t),endDate:new Date(e),events:s};this.allCachedEvents=this.allCachedEvents.concat(s),this.fetchedRanges.push(n),this.mergeOverlappingRanges()}mergeOverlappingRanges(){this.fetchedRanges.sort((t,e)=>t.startDate-e.startDate);const t=[];for(const e of this.fetchedRanges)if(0===t.length||t[t.length-1].endDate<e.startDate)t.push(e);else{const s=t[t.length-1];s.endDate=new Date(Math.max(s.endDate.getTime(),e.endDate.getTime())),s.events=s.events.concat(e.events)}this.fetchedRanges=t,this.allCachedEvents=[];for(const t of this.fetchedRanges)this.allCachedEvents=this.allCachedEvents.concat(t.events)}getAllEventsForRange(){if(this.eventsCallback){const t=this.getVisibleDateRange();if(this.isRangeCovered(t.startDate,t.endDate))return this.allCachedEvents;const e=this.getMissingRanges(t.startDate,t.endDate);for(const t of e){const e=this.eventsCallback(t);this.addFetchedRange(t.startDate,t.endDate,e)}return this.allCachedEvents}return this.events||[]}getEventsForDate(t){return this.getAllEventsForRange().filter(e=>{const s=new Date(e.startDate||e.date),n=new Date(e.endDate||e.date),i=new Date(t);return i.setHours(0,0,0,0),s.setHours(0,0,0,0),n.setHours(0,0,0,0),i>=s&&i<=n}).sort((t,e)=>{const s=this.isMultiDayEvent(t),n=this.isMultiDayEvent(e);return s&&!n?-1:!s&&n?1:0})}getEventsForTimeSlot(t,e){if(this.fulldayMode)return this.getEventsForDate(t);return this.getEventsForDate(t).filter(t=>{if(!t.time)return!1;const s=this.parseTimeString(t.time);if(!s)return!1;const n=s.getHours(),i=this.options.startHour,a=this.options.endHour;if(e.startsWith(this.options.labels.before))return n<i;if(e.startsWith(this.options.labels.after))return n>a;const o=this.parseTimeString(e);if(!o)return!1;return n===o.getHours()})}parseTimeString(t){try{const e=new Date,[s,n]=t.split(" "),[i,a]=s.split(":").map(Number);let o=i;return"PM"===n&&12!==i&&(o+=12),"AM"===n&&12===i&&(o=0),e.setHours(o,a||0,0,0),e}catch(t){return null}}isMultiDayEvent(t){const e=new Date(t.startDate||t.date),s=new Date(t.endDate||t.date);return e.setHours(0,0,0,0),s.setHours(0,0,0,0),e.getTime()!==s.getTime()}addEventPlaceholders(t,e){if(!this.fulldayMode)return t.map(t=>({event:t,isPlaceholder:!1}));const s=this.getWeekStart(e),n=[];for(let t=0;t<7;t++){const e=new Date(s);e.setDate(e.getDate()+t),n.push(e)}const i=new Map;n.forEach(t=>{this.getEventsForDate(t).filter(t=>this.isMultiDayEvent(t)).forEach(t=>{i.has(t.id)||i.set(t.id,t)})});const a=Array.from(i.values()).sort((t,e)=>new Date(t.startDate||t.date)-new Date(e.startDate||e.date)),o=[],r=t.filter(t=>this.isMultiDayEvent(t)),c=t.filter(t=>!this.isMultiDayEvent(t));return a.forEach(t=>{const n=r.find(e=>e.id===t.id);if(n)o.push({event:n,isPlaceholder:!1});else{const n=new Date(t.startDate||t.date),i=new Date(t.endDate||t.date);n.setHours(0,0,0,0),i.setHours(0,0,0,0);const a=new Date(e);a.setHours(0,0,0,0);const r=new Date(s);r.setHours(0,0,0,0);const c=new Date(s);c.setDate(c.getDate()+6),c.setHours(0,0,0,0);n<=c&&(i>=r&&i<=c)&&a>i&&o.push({isPlaceholder:!0,eventId:t.id})}}),c.forEach(t=>{o.push({event:t,isPlaceholder:!1})}),o}getEventPosition(t,e){const s=new Date(t.startDate||t.date),n=new Date(t.endDate||t.date),i=new Date(e);s.setHours(0,0,0,0),n.setHours(0,0,0,0),i.setHours(0,0,0,0);return{isStart:i.getTime()===s.getTime(),isEnd:i.getTime()===n.getTime(),isMiddle:i>s&&i<n,isSingle:s.getTime()===n.getTime()}}addEvent(t){this.eventsCallback||(this.events.push(t),this.render())}removeEvent(t){this.eventsCallback||(this.events=this.events.filter(e=>e.id!==t),this.render())}setEvents(t){"function"==typeof t?(this.eventsCallback=t,this.events=[]):(this.events=t||[],this.eventsCallback=null),this.fetchedRanges=[],this.allCachedEvents=[],this.render()}setFulldayMode(t){this.fulldayMode=t,this.render(),this.triggerChangeState()}goToDate(t){this.currentDate=new Date(t),this.render(),this.triggerChangeState()}goToToday(){this.currentDate=new Date,this.render(),this.triggerChangeState()}setGridBorders(t){this.options.gridBorders=t,this.applyGridBorders()}setEventBorders(t){this.options.eventBorders=t,this.applyEventBorders(),this.render()}setEventBorderColor(t){this.options.eventBorderColor=t,this.applyEventBorders(),this.render()}setShowMonthButton(t){this.options.showMonthButton=t,this.applyViewButtonVisibility()}setShowWeekButton(t){this.options.showWeekButton=t,this.applyViewButtonVisibility()}setShowDayButton(t){this.options.showDayButton=t,this.applyViewButtonVisibility()}setViewButtonsVisibility(t,e,s){this.options.showMonthButton=t,this.options.showWeekButton=e,this.options.showDayButton=s,this.applyViewButtonVisibility()}setShowNavigation(t){this.options.showNavigation=t,this.applyNavigationVisibility()}setDefaultEventColor(t){this.options.defaultEventColor=t,this.applyEventColor(),this.render()}getEventBaseCssStyle(t){const e=t.color,s=!0===t.colorIsGradient,n=e?t.color:this.options.defaultEventColor;if(!n)return{};const i=(" "+n).slice(1),a=this.hexToRgb(i);if(!a)return{};const o=this.getOptimalTextColor(a);if(s){const t={r:Math.min(255,a.r+40),g:Math.min(255,a.g+40),b:Math.min(255,a.b+40)};return{"background-image":`linear-gradient(to bottom, ${this.rgbToHex(t)} 0px, ${n} 100%)`,"background-repeat":"repeat-x",color:o,"background-color":n}}return{"background-color":n,color:o}}getOptimalTextColor(t){return this.getLuminance(t)>.5?"#000000":"#ffffff"}getLuminance(t){return.2126*this.getRelativeLuminanceComponent(t.r/255)+.7152*this.getRelativeLuminanceComponent(t.g/255)+.0722*this.getRelativeLuminanceComponent(t.b/255)}getRelativeLuminanceComponent(t){return t<=.03928?t/12.92:Math.pow((t+.055)/1.055,2.4)}getContrastRatio(t,e){const s=this.getLuminance(t),n=this.getLuminance(e);return(Math.max(s,n)+.05)/(Math.min(s,n)+.05)}hexToRgb(t){const e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return e?{r:parseInt(e[1],16),g:parseInt(e[2],16),b:parseInt(e[3],16)}:null}rgbToHex(t){return"#"+(1<<24|t.r<<16|t.g<<8|t.b).toString(16).slice(1)}applyEventStyles(t){const e=this.getEventBaseCssStyle(t),s=Object.entries(e).map(([t,e])=>`${t}: ${e}`).join("; ");return s?`style="${s}"`:""}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "simple-calendar-js",
3
- "version": "2.0.1",
3
+ "version": "2.0.2",
4
4
  "description": "A clean, modern, and feature-rich JavaScript calendar component with zero dependencies. Responsive design and intuitive navigation.",
5
5
  "main": "dist/SimpleCalendarJs.min.js",
6
6
  "style": "dist/SimpleCalendarJs.min.css",