@nomideusz/svelte-calendar 0.4.0 → 0.5.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.
Files changed (73) hide show
  1. package/README.md +331 -568
  2. package/dist/adapters/recurring.d.ts +45 -8
  3. package/dist/adapters/recurring.js +227 -49
  4. package/dist/adapters/rest.js +6 -1
  5. package/dist/calendar/Calendar.svelte +118 -30
  6. package/dist/calendar/Calendar.svelte.d.ts +9 -8
  7. package/dist/calendar/index.d.ts +0 -1
  8. package/dist/calendar/index.js +0 -1
  9. package/dist/core/index.d.ts +5 -4
  10. package/dist/core/index.js +2 -3
  11. package/dist/core/locale.d.ts +74 -0
  12. package/dist/core/locale.js +96 -4
  13. package/dist/core/time.d.ts +30 -0
  14. package/dist/core/time.js +42 -0
  15. package/dist/core/types.d.ts +1 -72
  16. package/dist/core/types.js +1 -8
  17. package/dist/engine/event-store.svelte.js +2 -2
  18. package/dist/engine/index.d.ts +1 -1
  19. package/dist/engine/view-state.svelte.d.ts +11 -6
  20. package/dist/engine/view-state.svelte.js +5 -4
  21. package/dist/index.d.ts +6 -7
  22. package/dist/index.js +4 -4
  23. package/dist/primitives/EmptySlot.svelte +6 -16
  24. package/dist/primitives/EventBlock.svelte +6 -19
  25. package/dist/primitives/NowIndicator.svelte +7 -4
  26. package/dist/primitives/TimeGutter.svelte +1 -1
  27. package/dist/theme/index.d.ts +1 -1
  28. package/dist/theme/index.js +1 -1
  29. package/dist/theme/presets.d.ts +11 -19
  30. package/dist/theme/presets.js +25 -111
  31. package/dist/views/agenda/Agenda.svelte +17 -1516
  32. package/dist/views/agenda/Agenda.svelte.d.ts +0 -14
  33. package/dist/views/agenda/AgendaDay.svelte +976 -0
  34. package/dist/views/agenda/AgendaDay.svelte.d.ts +14 -0
  35. package/dist/views/agenda/AgendaWeek.svelte +775 -0
  36. package/dist/views/agenda/AgendaWeek.svelte.d.ts +15 -0
  37. package/dist/views/agenda/index.d.ts +2 -0
  38. package/dist/views/agenda/index.js +2 -0
  39. package/dist/views/index.d.ts +2 -6
  40. package/dist/views/index.js +2 -6
  41. package/dist/views/planner/Planner.svelte +17 -0
  42. package/dist/views/planner/Planner.svelte.d.ts +7 -0
  43. package/dist/views/planner/PlannerDay.svelte +957 -0
  44. package/dist/views/{day/DayGrid.svelte.d.ts → planner/PlannerDay.svelte.d.ts} +6 -16
  45. package/dist/views/planner/PlannerWeek.svelte +840 -0
  46. package/dist/views/{week/WeekGrid.svelte.d.ts → planner/PlannerWeek.svelte.d.ts} +4 -9
  47. package/dist/views/planner/index.d.ts +1 -0
  48. package/dist/views/planner/index.js +1 -0
  49. package/dist/widget/CalendarWidget.svelte +54 -33
  50. package/dist/widget/CalendarWidget.svelte.d.ts +1 -1
  51. package/package.json +70 -65
  52. package/widget/svelte-calendar.css +1 -0
  53. package/widget/widget.js +122 -260
  54. package/dist/calendar/Toolbar.svelte +0 -244
  55. package/dist/calendar/Toolbar.svelte.d.ts +0 -21
  56. package/dist/views/day/DayGrid.svelte +0 -1104
  57. package/dist/views/day/DayTimeline.svelte +0 -533
  58. package/dist/views/day/DayTimeline.svelte.d.ts +0 -8
  59. package/dist/views/day/index.d.ts +0 -2
  60. package/dist/views/day/index.js +0 -2
  61. package/dist/views/schedule/WeekSchedule.svelte +0 -137
  62. package/dist/views/schedule/WeekSchedule.svelte.d.ts +0 -40
  63. package/dist/views/schedule/index.d.ts +0 -1
  64. package/dist/views/schedule/index.js +0 -2
  65. package/dist/views/settings/Settings.svelte +0 -585
  66. package/dist/views/settings/Settings.svelte.d.ts +0 -39
  67. package/dist/views/settings/index.d.ts +0 -2
  68. package/dist/views/settings/index.js +0 -1
  69. package/dist/views/week/WeekGrid.svelte +0 -632
  70. package/dist/views/week/WeekHeatmap.svelte +0 -461
  71. package/dist/views/week/WeekHeatmap.svelte.d.ts +0 -9
  72. package/dist/views/week/index.d.ts +0 -2
  73. package/dist/views/week/index.js +0 -2
@@ -0,0 +1,775 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AgendaWeek — rolling 7-day agenda view.
4
+ *
5
+ * "The Week Ahead":
6
+ * Today + tomorrow expanded with time slots/countdowns.
7
+ * Future days compact (dot + time + title).
8
+ * Past days dimmed.
9
+ *
10
+ * Answers: "What's coming up and when do I need to be ready?"
11
+ */
12
+ import { getContext } from 'svelte';
13
+ import { createClock } from '../../core/clock.svelte.js';
14
+ import type { TimelineEvent } from '../../core/types.js';
15
+ import { sod, DAY_MS, startOfWeek, dayNum, isAllDay, isMultiDay } from '../../core/time.js';
16
+ import { weekdayLong, weekdayShort, monthShort, fmtTime as _fmtTime, fmtDuration, getLabels } from '../../core/locale.js';
17
+ import type { ViewState } from '../../engine/view-state.svelte.js';
18
+
19
+ const L = $derived(getLabels());
20
+
21
+ interface Props {
22
+ mondayStart?: boolean;
23
+ locale?: string;
24
+ height?: number;
25
+ events?: TimelineEvent[];
26
+ style?: string;
27
+ focusDate?: Date;
28
+ oneventclick?: (event: TimelineEvent) => void;
29
+ selectedEventId?: string | null;
30
+ [key: string]: unknown;
31
+ }
32
+
33
+ let {
34
+ mondayStart = true,
35
+ locale,
36
+ height = 520,
37
+ events = [],
38
+ style = '',
39
+ focusDate,
40
+ oneventclick,
41
+ selectedEventId = null,
42
+ }: Props = $props();
43
+
44
+ const clock = createClock();
45
+ const viewState = getContext<ViewState>('calendar:viewState') as ViewState | undefined;
46
+
47
+ // ── Format helpers ──────────────────────────────────
48
+ function fmtTime(d: Date): string {
49
+ return _fmtTime(d, locale);
50
+ }
51
+
52
+ function duration(ev: TimelineEvent): string {
53
+ return fmtDuration(ev.start, ev.end);
54
+ }
55
+
56
+ function timeUntilMs(ms: number): string {
57
+ const diff = ms - clock.tick;
58
+ if (diff <= 0) return L.now;
59
+ const tMins = Math.floor(diff / 60000);
60
+ if (tMins < 60) return `in ${tMins}m`;
61
+ const hrs = Math.floor(tMins / 60);
62
+ const rm = tMins % 60;
63
+ if (hrs < 24) return rm > 0 ? `in ${hrs}h ${rm}m` : `in ${hrs}h`;
64
+ const days = Math.floor(hrs / 24);
65
+ return `in ${days}d`;
66
+ }
67
+
68
+ function progress(ev: TimelineEvent): number {
69
+ const s = ev.start.getTime();
70
+ const e = ev.end.getTime();
71
+ return Math.min(1, Math.max(0, (clock.tick - s) / (e - s)));
72
+ }
73
+
74
+ // ── Overlap grouping ────────────────────────────────
75
+ interface TimeSlot {
76
+ startMs: number;
77
+ endMs: number;
78
+ events: TimelineEvent[];
79
+ }
80
+
81
+ function groupIntoSlots(evts: TimelineEvent[]): TimeSlot[] {
82
+ const sorted = [...evts].sort((a, b) => a.start.getTime() - b.start.getTime());
83
+ const slots: TimeSlot[] = [];
84
+ for (const ev of sorted) {
85
+ const last = slots[slots.length - 1];
86
+ if (last && ev.start.getTime() < last.endMs) {
87
+ last.events.push(ev);
88
+ last.endMs = Math.max(last.endMs, ev.end.getTime());
89
+ } else {
90
+ slots.push({
91
+ startMs: ev.start.getTime(),
92
+ endMs: ev.end.getTime(),
93
+ events: [ev],
94
+ });
95
+ }
96
+ }
97
+ return slots;
98
+ }
99
+
100
+ // ── Event handlers ──────────────────────────────────
101
+ function handleClick(ev: TimelineEvent): void {
102
+ oneventclick?.(ev);
103
+ }
104
+
105
+ function handleKeydown(e: KeyboardEvent, ev: TimelineEvent): void {
106
+ if (e.key === 'Enter' || e.key === ' ') {
107
+ e.preventDefault();
108
+ oneventclick?.(ev);
109
+ }
110
+ }
111
+
112
+ // ── Week derivations ────────────────────────────────
113
+ type DayTier = 'today' | 'tomorrow' | 'upcoming' | 'past';
114
+
115
+ interface DayGroup {
116
+ ms: number;
117
+ dayName: string;
118
+ dateLabel: string;
119
+ tier: DayTier;
120
+ events: TimelineEvent[];
121
+ allDayEvents: TimelineEvent[];
122
+ timedEvents: TimelineEvent[];
123
+ pastEvents: TimelineEvent[];
124
+ currentEvents: TimelineEvent[];
125
+ upcomingEvents: TimelineEvent[];
126
+ totalHours: number;
127
+ }
128
+
129
+ const weekStartMs = $derived(
130
+ focusDate
131
+ ? startOfWeek(sod(focusDate.getTime()), mondayStart)
132
+ : startOfWeek(clock.today, mondayStart),
133
+ );
134
+
135
+ const isThisWeek = $derived(
136
+ weekStartMs === startOfWeek(clock.today, mondayStart),
137
+ );
138
+
139
+ const weekDays = $derived.by((): DayGroup[] => {
140
+ const now = clock.tick;
141
+ const todayMs = clock.today;
142
+ const tomorrowMs = todayMs + DAY_MS;
143
+ const out: DayGroup[] = [];
144
+ for (let i = 0; i < 7; i++) {
145
+ const ms = weekStartMs + i * DAY_MS;
146
+ const dEnd = ms + DAY_MS;
147
+ const dayEvts = events
148
+ .filter((ev) => ev.start.getTime() < dEnd && ev.end.getTime() > ms)
149
+ .sort((a, b) => a.start.getTime() - b.start.getTime());
150
+ const allDayEvts = dayEvts.filter((ev) => isAllDay(ev) || isMultiDay(ev));
151
+ const timedEvts = dayEvts.filter((ev) => !isAllDay(ev) && !isMultiDay(ev));
152
+ const totalMinutes = timedEvts.reduce((sum, ev) => {
153
+ const s = Math.max(ev.start.getTime(), ms);
154
+ const e = Math.min(ev.end.getTime(), dEnd);
155
+ return sum + (e - s) / 60000;
156
+ }, 0);
157
+ const pastEvents: TimelineEvent[] = [];
158
+ const currentEvents: TimelineEvent[] = [];
159
+ const upcomingEvents: TimelineEvent[] = [];
160
+ for (const ev of timedEvts) {
161
+ if (ev.end.getTime() <= now) pastEvents.push(ev);
162
+ else if (ev.start.getTime() <= now && ev.end.getTime() > now) currentEvents.push(ev);
163
+ else upcomingEvents.push(ev);
164
+ }
165
+ let tier: DayTier;
166
+ if (ms === todayMs) tier = 'today';
167
+ else if (ms === tomorrowMs) tier = 'tomorrow';
168
+ else if (ms < todayMs) tier = 'past';
169
+ else tier = 'upcoming';
170
+
171
+ out.push({
172
+ ms,
173
+ dayName: tier === 'today' || tier === 'tomorrow' ? weekdayLong(ms, locale) : weekdayShort(ms, locale),
174
+ dateLabel: `${monthShort(ms, locale)} ${dayNum(ms)}`,
175
+ tier,
176
+ events: dayEvts,
177
+ allDayEvents: allDayEvts,
178
+ timedEvents: timedEvts,
179
+ pastEvents,
180
+ currentEvents,
181
+ upcomingEvents,
182
+ totalHours: Math.round((totalMinutes / 60) * 10) / 10,
183
+ });
184
+ }
185
+ return out;
186
+ });
187
+ </script>
188
+
189
+ <!-- ═══ Shared event card snippet ═══ -->
190
+ {#snippet eventCard(ev: TimelineEvent, isNow: boolean)}
191
+ <div
192
+ class="ag-card"
193
+ class:ag-card--selected={selectedEventId === ev.id}
194
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
195
+ role="button"
196
+ tabindex="0"
197
+ aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}, {duration(ev)}"
198
+ onclick={() => handleClick(ev)}
199
+ onkeydown={(e) => handleKeydown(e, ev)}
200
+ >
201
+ <div class="ag-card-stripe"></div>
202
+ <div class="ag-card-body">
203
+ <span class="ag-card-title">{ev.title}</span>
204
+ {#if ev.subtitle}
205
+ <span class="ag-card-sub">{ev.subtitle}</span>
206
+ {/if}
207
+ <span class="ag-card-meta">
208
+ {#if isNow}
209
+ {L.until} {fmtTime(ev.end)}
210
+ {:else}
211
+ {fmtTime(ev.start)} – {fmtTime(ev.end)}
212
+ {/if}
213
+ <span class="ag-card-dur">{duration(ev)}</span>
214
+ </span>
215
+ {#if ev.tags?.length}
216
+ <div class="ag-card-tags">
217
+ {#each ev.tags as tag}
218
+ <span class="ag-card-tag">{tag}</span>
219
+ {/each}
220
+ </div>
221
+ {/if}
222
+ {#if isNow}
223
+ <div class="ag-card-progress">
224
+ <div class="ag-card-progress-fill" style:width="{progress(ev) * 100}%"></div>
225
+ </div>
226
+ {/if}
227
+ </div>
228
+ </div>
229
+ {/snippet}
230
+
231
+ <div
232
+ class="ag ag--week"
233
+ style={style || undefined}
234
+ >
235
+ <div class="ag-body" role="list" aria-label={L.weekAhead}>
236
+ {#each weekDays as day (day.ms)}
237
+ {@const expanded = day.tier === 'today' || day.tier === 'tomorrow'}
238
+ {#if day.tier === 'past'}
239
+ <!-- Past day: single collapsed line -->
240
+ <div class="ag-wday ag-wday--past" role="listitem">
241
+ <div class="ag-wday-head">
242
+ <div class="ag-wday-head-left">
243
+ <span class="ag-wday-name">{day.dayName}</span>
244
+ <span class="ag-wday-date">{day.dateLabel}</span>
245
+ {#if day.events.length > 0}
246
+ <span class="ag-wday-done">✓ {day.events.length}</span>
247
+ {/if}
248
+ </div>
249
+ </div>
250
+ </div>
251
+ {:else}
252
+ <div
253
+ class="ag-wday"
254
+ class:ag-wday--today={day.tier === 'today'}
255
+ class:ag-wday--tomorrow={day.tier === 'tomorrow'}
256
+ role="listitem"
257
+ >
258
+ <!-- Day header -->
259
+ <div class="ag-wday-head">
260
+ <div class="ag-wday-head-left">
261
+ {#if day.tier === 'today'}
262
+ <span class="ag-wday-badge">{L.today}</span>
263
+ {:else if day.tier === 'tomorrow'}
264
+ <span class="ag-wday-badge ag-wday-badge--muted">{L.tomorrow}</span>
265
+ {/if}
266
+ <span class="ag-wday-name">{day.dayName}</span>
267
+ <span class="ag-wday-date">{day.dateLabel}</span>
268
+ </div>
269
+ </div>
270
+
271
+ {#if day.allDayEvents.length > 0}
272
+ <div class="ag-allday">
273
+ {#each day.allDayEvents as ev (ev.id)}
274
+ <div
275
+ class="ag-allday-chip"
276
+ class:ag-allday-chip--selected={selectedEventId === ev.id}
277
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
278
+ role="button"
279
+ tabindex="0"
280
+ aria-label="{ev.title}, {L.allDay}"
281
+ onclick={() => handleClick(ev)}
282
+ onkeydown={(e) => handleKeydown(e, ev)}
283
+ >
284
+ <span class="ag-allday-dot"></span>
285
+ <span class="ag-allday-title">{ev.title}</span>
286
+ </div>
287
+ {/each}
288
+ </div>
289
+ {/if}
290
+
291
+ {#if day.events.length === 0}
292
+ <div class="ag-wday-empty">{L.noEvents}</div>
293
+ {:else if expanded}
294
+ <!-- Expanded: today/tomorrow get full slot treatment -->
295
+ <div class="ag-wday-expanded">
296
+ {#if day.currentEvents.length > 0}
297
+ {#each day.currentEvents as ev (ev.id)}
298
+ <div class="ag-wslot">
299
+ <div class="ag-wslot-header">
300
+ <span class="ag-wslot-now">{L.now}</span>
301
+ </div>
302
+ {@render eventCard(ev, true)}
303
+ </div>
304
+ {/each}
305
+ {/if}
306
+ {#each groupIntoSlots(day.upcomingEvents) as slot (slot.startMs)}
307
+ <div class="ag-wslot">
308
+ <div class="ag-wslot-header">
309
+ <span class="ag-wslot-time">{fmtTime(new Date(slot.startMs))}</span>
310
+ {#if day.tier === 'today'}
311
+ <span class="ag-wslot-eta">{timeUntilMs(slot.startMs)}</span>
312
+ {/if}
313
+ </div>
314
+ <div class="ag-wslot-cards" class:ag-wslot-cards--multi={slot.events.length > 1}>
315
+ {#each slot.events as ev (ev.id)}
316
+ {@render eventCard(ev, false)}
317
+ {/each}
318
+ </div>
319
+ </div>
320
+ {/each}
321
+ {#if day.pastEvents.length > 0}
322
+ <div class="ag-wday-past-line">✓ {L.nCompleted(day.pastEvents.length)}</div>
323
+ {/if}
324
+ </div>
325
+ {:else}
326
+ <!-- Compact: future days get minimal rows -->
327
+ <div class="ag-wday-compact">
328
+ {#each day.events.slice(0, 3) as ev (ev.id)}
329
+ <div
330
+ class="ag-compact"
331
+ class:ag-compact--selected={selectedEventId === ev.id}
332
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
333
+ role="button"
334
+ tabindex="0"
335
+ aria-label="{ev.title}, {fmtTime(ev.start)}, {duration(ev)}"
336
+ onclick={() => handleClick(ev)}
337
+ onkeydown={(e) => handleKeydown(e, ev)}
338
+ >
339
+ <span class="ag-compact-dot"></span>
340
+ <span class="ag-compact-time">{fmtTime(ev.start)}</span>
341
+ <span class="ag-compact-title">{ev.title}</span>
342
+ {#if ev.subtitle}
343
+ <span class="ag-compact-sub">{ev.subtitle}</span>
344
+ {/if}
345
+ {#if ev.tags?.length}
346
+ {#each ev.tags as tag}
347
+ <span class="ag-compact-tag">{tag}</span>
348
+ {/each}
349
+ {/if}
350
+ <span class="ag-compact-dur">{duration(ev)}</span>
351
+ </div>
352
+ {/each}
353
+ {#if day.events.length > 3}
354
+ <div class="ag-compact-more">{L.nMore(day.events.length - 3)}</div>
355
+ {/if}
356
+ </div>
357
+ {/if}
358
+ </div>
359
+ {/if}
360
+ {/each}
361
+ </div>
362
+
363
+ <!-- ── Floating nav pills ── -->
364
+ <nav class="ag-nav" aria-label={L.weekNavigation}>
365
+ <button
366
+ class="ag-nav-pill ag-nav-today"
367
+ class:ag-nav-today--hidden={isThisWeek}
368
+ onclick={() => viewState?.goToday()}
369
+ aria-label={L.goToToday}
370
+ tabindex={isThisWeek ? -1 : 0}
371
+ >
372
+ {L.today}
373
+ </button>
374
+ <button
375
+ class="ag-nav-pill"
376
+ onclick={() => viewState?.prev()}
377
+ aria-label={L.previousWeek}
378
+ >
379
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><path d="M10 3 5 8l5 5"/></svg>
380
+ </button>
381
+ <button
382
+ class="ag-nav-pill"
383
+ onclick={() => viewState?.next()}
384
+ aria-label={L.nextWeek}
385
+ >
386
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
387
+ </button>
388
+ </nav>
389
+ </div>
390
+
391
+ <style>
392
+ /* ═══ Floating nav pills ═══ */
393
+ .ag-nav {
394
+ position: absolute;
395
+ top: 10px;
396
+ right: 14px;
397
+ z-index: 20;
398
+ display: flex;
399
+ gap: 2px;
400
+ background: color-mix(in srgb, var(--dt-surface, #10141c) 85%, transparent);
401
+ backdrop-filter: blur(6px);
402
+ -webkit-backdrop-filter: blur(6px);
403
+ border-radius: 8px;
404
+ padding: 2px;
405
+ border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
406
+ }
407
+ .ag-nav-pill {
408
+ border: none;
409
+ background: transparent;
410
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
411
+ cursor: pointer;
412
+ font: 600 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
413
+ padding: 6px 12px;
414
+ border-radius: 6px;
415
+ letter-spacing: 0.04em;
416
+ text-transform: uppercase;
417
+ transition: background 100ms, color 100ms;
418
+ display: inline-flex;
419
+ align-items: center;
420
+ justify-content: center;
421
+ }
422
+ .ag-nav-pill:hover {
423
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
424
+ }
425
+ .ag-nav-today {
426
+ max-width: 60px;
427
+ overflow: hidden;
428
+ white-space: nowrap;
429
+ transition: max-width 250ms ease, padding 250ms ease, opacity 200ms ease;
430
+ }
431
+ .ag-nav-today--hidden {
432
+ max-width: 0;
433
+ padding-left: 0;
434
+ padding-right: 0;
435
+ opacity: 0;
436
+ pointer-events: none;
437
+ }
438
+ .ag-nav-pill:focus-visible {
439
+ outline: 2px solid color-mix(in srgb, var(--dt-accent, #ef4444) 55%, transparent);
440
+ outline-offset: 2px;
441
+ }
442
+
443
+ /* ═══ Container ═══ */
444
+ .ag {
445
+ position: relative;
446
+ overflow: hidden;
447
+ user-select: none;
448
+ display: flex;
449
+ flex-direction: column;
450
+ height: 100%;
451
+ color: var(--dt-text, rgba(255, 255, 255, 0.92));
452
+ font-family: var(--dt-sans, system-ui, sans-serif);
453
+ }
454
+
455
+ /* ═══ Body ═══ */
456
+ .ag-body {
457
+ flex: 1;
458
+ overflow-y: auto;
459
+ scrollbar-width: thin;
460
+ scrollbar-color: var(--dt-border) transparent;
461
+ }
462
+ .ag-body::-webkit-scrollbar {
463
+ width: 4px;
464
+ }
465
+ .ag-body::-webkit-scrollbar-thumb {
466
+ background: var(--dt-border);
467
+ border-radius: 2px;
468
+ }
469
+
470
+ /* ═══ All-day chips ═══ */
471
+ .ag-allday {
472
+ display: flex;
473
+ flex-wrap: wrap;
474
+ gap: 4px;
475
+ padding: 4px 14px 6px;
476
+ }
477
+ .ag-allday-chip {
478
+ display: flex;
479
+ align-items: center;
480
+ gap: 4px;
481
+ padding: 2px 8px;
482
+ border-radius: 5px;
483
+ background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, #10141c));
484
+ border: 1px solid color-mix(in srgb, var(--ev-color) 18%, transparent);
485
+ cursor: pointer;
486
+ transition: background 0.15s, border-color 0.15s;
487
+ }
488
+ .ag-allday-chip:hover {
489
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #10141c));
490
+ border-color: color-mix(in srgb, var(--ev-color) 30%, transparent);
491
+ }
492
+ .ag-allday-chip:focus-visible {
493
+ outline: 2px solid var(--dt-accent, #ff6b4a);
494
+ outline-offset: 2px;
495
+ }
496
+ .ag-allday-chip--selected {
497
+ border-color: var(--ev-color);
498
+ background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, #10141c));
499
+ }
500
+ .ag-allday-dot {
501
+ width: 5px;
502
+ height: 5px;
503
+ border-radius: 50%;
504
+ background: var(--ev-color);
505
+ flex-shrink: 0;
506
+ }
507
+ .ag-allday-title {
508
+ font: 500 0.7rem/1.2 var(--dt-sans, system-ui, sans-serif);
509
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
510
+ white-space: nowrap;
511
+ }
512
+
513
+ /* ═══ Shared: event card ═══ */
514
+ .ag-card {
515
+ display: flex;
516
+ align-items: stretch;
517
+ border-radius: 10px;
518
+ background: var(--dt-surface, #191919);
519
+ border: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
520
+ overflow: hidden;
521
+ cursor: pointer;
522
+ transition: border-color 150ms;
523
+ }
524
+ .ag-card:hover {
525
+ border-color: color-mix(in srgb, var(--ev-color) 40%, transparent);
526
+ }
527
+ .ag-card:focus-visible {
528
+ outline: 2px solid var(--dt-accent, #ff6b4a);
529
+ outline-offset: 2px;
530
+ }
531
+ .ag-card--selected {
532
+ border-color: var(--ev-color);
533
+ background: color-mix(in srgb, var(--ev-color) 6%, var(--dt-surface, #191919));
534
+ }
535
+ .ag-card-stripe {
536
+ width: 3px;
537
+ background: var(--ev-color, var(--dt-accent));
538
+ flex-shrink: 0;
539
+ }
540
+ .ag-card-body {
541
+ padding: 10px 12px;
542
+ display: flex;
543
+ flex-direction: column;
544
+ gap: 4px;
545
+ min-width: 0;
546
+ flex: 1;
547
+ }
548
+ .ag-card-title {
549
+ font-size: 13px;
550
+ font-weight: 600;
551
+ line-height: 1.2;
552
+ white-space: nowrap;
553
+ overflow: hidden;
554
+ text-overflow: ellipsis;
555
+ flex: 1;
556
+ }
557
+ .ag-card-meta {
558
+ font-size: 11px;
559
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
560
+ font-family: var(--dt-mono, monospace);
561
+ line-height: 1;
562
+ }
563
+ .ag-card-dur {
564
+ margin-left: 6px;
565
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
566
+ }
567
+ .ag-card-sub {
568
+ font-size: 11px;
569
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
570
+ line-height: 1;
571
+ }
572
+ .ag-card-tags {
573
+ display: flex;
574
+ gap: 4px;
575
+ flex-wrap: wrap;
576
+ }
577
+ .ag-card-tag {
578
+ font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
579
+ color: var(--ev-color, var(--dt-accent));
580
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
581
+ padding: 2px 5px;
582
+ border-radius: 3px;
583
+ white-space: nowrap;
584
+ }
585
+ .ag-card-progress {
586
+ height: 3px;
587
+ background: var(--dt-border, rgba(255, 255, 255, 0.06));
588
+ border-radius: 2px;
589
+ overflow: hidden;
590
+ margin-top: 2px;
591
+ }
592
+ .ag-card-progress-fill {
593
+ height: 100%;
594
+ background: var(--ev-color, var(--dt-accent));
595
+ border-radius: 2px;
596
+ transition: width 1s linear;
597
+ }
598
+
599
+ /* ═══ Week day groups ═══ */
600
+ .ag-wday {
601
+ border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
602
+ }
603
+ .ag-wday--today {
604
+ background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 4%, transparent);
605
+ }
606
+ .ag-wday--past {
607
+ opacity: 0.35;
608
+ }
609
+ .ag-wday--past .ag-wday-head {
610
+ padding: 6px 20px;
611
+ }
612
+ .ag-wday-done {
613
+ font-size: 10px;
614
+ font-family: var(--dt-mono, monospace);
615
+ color: var(--dt-success, rgba(120, 200, 140, 0.6));
616
+ }
617
+ .ag-wday-head {
618
+ display: flex;
619
+ justify-content: space-between;
620
+ align-items: center;
621
+ padding: 10px 20px;
622
+ }
623
+ .ag-wday-head-left {
624
+ display: flex;
625
+ align-items: center;
626
+ gap: 8px;
627
+ }
628
+ .ag-wday-badge {
629
+ font-size: 10px;
630
+ font-weight: 600;
631
+ letter-spacing: 0.08em;
632
+ text-transform: uppercase;
633
+ color: var(--dt-accent, #ff6b4a);
634
+ background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 12%, transparent);
635
+ padding: 2px 7px;
636
+ border-radius: 3px;
637
+ }
638
+ .ag-wday-badge--muted {
639
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
640
+ background: color-mix(
641
+ in srgb,
642
+ var(--dt-text-2, rgba(255, 255, 255, 0.5)) 10%,
643
+ transparent
644
+ );
645
+ }
646
+ .ag-wday-name {
647
+ font-size: 13px;
648
+ font-weight: 600;
649
+ }
650
+ .ag-wday-date {
651
+ font-size: 11px;
652
+ font-family: var(--dt-mono, monospace);
653
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
654
+ }
655
+ .ag-wday-empty {
656
+ padding: 4px 20px 10px;
657
+ font-size: 12px;
658
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
659
+ }
660
+
661
+ /* Expanded day */
662
+ .ag-wday-expanded {
663
+ padding: 0 20px 12px;
664
+ }
665
+ .ag-wslot {
666
+ margin-bottom: 6px;
667
+ }
668
+ .ag-wslot-header {
669
+ display: flex;
670
+ align-items: baseline;
671
+ gap: 8px;
672
+ padding: 4px 0;
673
+ }
674
+ .ag-wslot-time {
675
+ font-size: 12px;
676
+ font-weight: 600;
677
+ font-family: var(--dt-mono, monospace);
678
+ }
679
+ .ag-wslot-now {
680
+ font-size: 9px;
681
+ font-weight: 700;
682
+ letter-spacing: 0.08em;
683
+ text-transform: uppercase;
684
+ color: var(--dt-accent, #ff6b4a);
685
+ }
686
+ .ag-wslot-eta {
687
+ font-size: 11px;
688
+ font-weight: 500;
689
+ color: var(--dt-accent, #ff6b4a);
690
+ }
691
+ .ag-wslot-cards {
692
+ display: flex;
693
+ flex-direction: column;
694
+ gap: 4px;
695
+ }
696
+ .ag-wslot-cards--multi {
697
+ display: grid;
698
+ grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
699
+ gap: 4px;
700
+ }
701
+ .ag-wday-past-line {
702
+ font-size: 10px;
703
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
704
+ padding: 6px 0 0;
705
+ opacity: 0.5;
706
+ }
707
+
708
+ /* Compact day events */
709
+ .ag-wday-compact {
710
+ padding: 0 20px 10px;
711
+ }
712
+ .ag-compact {
713
+ display: flex;
714
+ align-items: center;
715
+ gap: 8px;
716
+ padding: 4px 0;
717
+ cursor: pointer;
718
+ }
719
+ .ag-compact:hover .ag-compact-title {
720
+ color: var(--dt-text);
721
+ }
722
+ .ag-compact:focus-visible {
723
+ outline: 2px solid var(--dt-accent, #ff6b4a);
724
+ outline-offset: 2px;
725
+ }
726
+ .ag-compact-dot {
727
+ width: 5px;
728
+ height: 5px;
729
+ border-radius: 50%;
730
+ background: var(--ev-color, var(--dt-accent));
731
+ flex-shrink: 0;
732
+ }
733
+ .ag-compact-time {
734
+ font-size: 11px;
735
+ font-family: var(--dt-mono, monospace);
736
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
737
+ min-width: 64px;
738
+ flex-shrink: 0;
739
+ }
740
+ .ag-compact-title {
741
+ font-size: 12px;
742
+ font-weight: 500;
743
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
744
+ flex: 1;
745
+ white-space: nowrap;
746
+ overflow: hidden;
747
+ text-overflow: ellipsis;
748
+ transition: color 150ms;
749
+ }
750
+ .ag-compact-dur {
751
+ font-size: 10px;
752
+ font-family: var(--dt-mono, monospace);
753
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
754
+ flex-shrink: 0;
755
+ }
756
+ .ag-compact-sub {
757
+ font-size: 10px;
758
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
759
+ flex-shrink: 0;
760
+ }
761
+ .ag-compact-tag {
762
+ font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
763
+ color: var(--ev-color, var(--dt-accent));
764
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 12%, transparent);
765
+ padding: 1px 4px;
766
+ border-radius: 3px;
767
+ white-space: nowrap;
768
+ flex-shrink: 0;
769
+ }
770
+ .ag-compact-more {
771
+ font-size: 11px;
772
+ color: var(--dt-text-3);
773
+ padding: 2px 0 0 13px;
774
+ }
775
+ </style>