@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
@@ -1,1516 +1,17 @@
1
- <script lang="ts">
2
- /**
3
- * Agenda — unified view for day and week granularity.
4
- *
5
- * Day mode ("The Queue"):
6
- * Upcoming events are THE hero — big, center, detailed.
7
- * NOW is just a slim status strip on the left (context, not focus).
8
- * Past is a faded gutter on the right.
9
- * Answers: "What's coming up next?"
10
- *
11
- * Week mode ("The Week Ahead"):
12
- * Rolling 7-day view with proximity-based detail.
13
- * Today + tomorrow expanded with slots/countdowns.
14
- * Future days compact (dot + time + title).
15
- * Past days dimmed.
16
- *
17
- * Answers: "What's coming up and when do I need to be ready?"
18
- */
19
- import { createClock } from '../../core/clock.svelte.js';
20
- import type { TimelineEvent } from '../../core/types.js';
21
- import { sod, DAY_MS, startOfWeek, dayNum } from '../../core/time.js';
22
- import { fmtDay, weekdayLong, weekdayShort, monthShort, fmtWeekRange } from '../../core/locale.js';
23
-
24
- interface Props {
25
- /** 'day' = single-day timeline rail, 'week' = rolling 7-day view */
26
- mode?: 'day' | 'week';
27
- mondayStart?: boolean;
28
- locale?: string;
29
- height?: number;
30
- events?: TimelineEvent[];
31
- style?: string;
32
- focusDate?: Date;
33
- oneventclick?: (event: TimelineEvent) => void;
34
- oneventcreate?: (range: { start: Date; end: Date }) => void;
35
- selectedEventId?: string | null;
36
- [key: string]: unknown;
37
- }
38
-
39
- let {
40
- mode = 'day',
41
- mondayStart = true,
42
- locale,
43
- height = 520,
44
- events = [],
45
- style = '',
46
- focusDate,
47
- oneventclick,
48
- selectedEventId = null,
49
- }: Props = $props();
50
-
51
- const clock = createClock();
52
-
53
- // ── Format helpers ──────────────────────────────────
54
- function fmtTime(d: Date): string {
55
- return d
56
- .toLocaleTimeString(locale ?? 'en-US', { hour: 'numeric', minute: '2-digit', hour12: true })
57
- .toLowerCase();
58
- }
59
-
60
- function duration(ev: TimelineEvent): string {
61
- const mins = Math.round((ev.end.getTime() - ev.start.getTime()) / 60000);
62
- if (mins < 60) return `${mins}min`;
63
- const h = Math.floor(mins / 60);
64
- const m = mins % 60;
65
- return m > 0 ? `${h}h ${m}m` : `${h}h`;
66
- }
67
-
68
- function timeUntilMs(ms: number): string {
69
- const diff = ms - clock.tick;
70
- if (diff <= 0) return 'now';
71
- const tMins = Math.floor(diff / 60000);
72
- if (tMins < 60) return `in ${tMins}m`;
73
- const hrs = Math.floor(tMins / 60);
74
- const rm = tMins % 60;
75
- if (hrs < 24) return rm > 0 ? `in ${hrs}h ${rm}m` : `in ${hrs}h`;
76
- const days = Math.floor(hrs / 24);
77
- return `in ${days}d`;
78
- }
79
-
80
- function progress(ev: TimelineEvent): number {
81
- const s = ev.start.getTime();
82
- const e = ev.end.getTime();
83
- return Math.min(1, Math.max(0, (clock.tick - s) / (e - s)));
84
- }
85
-
86
- // ── Overlap grouping ────────────────────────────────
87
- interface TimeSlot {
88
- startMs: number;
89
- endMs: number;
90
- events: TimelineEvent[];
91
- }
92
-
93
- function groupIntoSlots(evts: TimelineEvent[]): TimeSlot[] {
94
- const sorted = [...evts].sort((a, b) => a.start.getTime() - b.start.getTime());
95
- const slots: TimeSlot[] = [];
96
- for (const ev of sorted) {
97
- const last = slots[slots.length - 1];
98
- if (last && ev.start.getTime() < last.endMs) {
99
- last.events.push(ev);
100
- last.endMs = Math.max(last.endMs, ev.end.getTime());
101
- } else {
102
- slots.push({
103
- startMs: ev.start.getTime(),
104
- endMs: ev.end.getTime(),
105
- events: [ev],
106
- });
107
- }
108
- }
109
- return slots;
110
- }
111
-
112
- // ── Event handlers ──────────────────────────────────
113
- function handleClick(ev: TimelineEvent): void {
114
- oneventclick?.(ev);
115
- }
116
-
117
- function handleKeydown(e: KeyboardEvent, ev: TimelineEvent): void {
118
- if (e.key === 'Enter' || e.key === ' ') {
119
- e.preventDefault();
120
- oneventclick?.(ev);
121
- }
122
- }
123
-
124
- // ═════════════════════════════════════════════════════
125
- // DAY MODE
126
- // ═════════════════════════════════════════════════════
127
- const dayMs = $derived(focusDate ? sod(focusDate.getTime()) : clock.today);
128
- const dayEnd = $derived(dayMs + DAY_MS);
129
- const isToday = $derived(dayMs === clock.today);
130
- const isPastDay = $derived(dayMs < clock.today);
131
- const isFutureDay = $derived(dayMs > clock.today);
132
-
133
- const dayProgress = $derived((() => {
134
- if (!isToday) return 0;
135
- const d = new Date(clock.tick);
136
- return (d.getHours() * 60 + d.getMinutes()) / 1440;
137
- })());
138
-
139
- /** All events for this day, sorted chronologically — used for past/future day views */
140
- const allDayEvents = $derived.by((): TimelineEvent[] => {
141
- if (mode !== 'day') return [];
142
- return events
143
- .filter((ev) => ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs)
144
- .sort((a, b) => a.start.getTime() - b.start.getTime());
145
- });
146
-
147
- /** Total hours booked for the day */
148
- const dayHours = $derived((() => {
149
- const mins = allDayEvents.reduce((sum, ev) => {
150
- const s = Math.max(ev.start.getTime(), dayMs);
151
- const e = Math.min(ev.end.getTime(), dayEnd);
152
- return sum + (e - s) / 60000;
153
- }, 0);
154
- return Math.round((mins / 60) * 10) / 10;
155
- })());
156
-
157
- const dayCat = $derived.by(() => {
158
- if (mode !== 'day') return { past: [] as TimelineEvent[], current: [] as TimelineEvent[], upcomingSlots: [] as TimeSlot[], totalUp: 0 };
159
- const now = clock.tick;
160
- const past: TimelineEvent[] = [];
161
- const current: TimelineEvent[] = [];
162
- const upcoming: TimelineEvent[] = [];
163
- const dayEvents = events.filter(
164
- (ev) => ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs,
165
- );
166
- const sorted = [...dayEvents].sort((a, b) => a.start.getTime() - b.start.getTime());
167
- for (const ev of sorted) {
168
- const s = ev.start.getTime();
169
- const e = ev.end.getTime();
170
- if (e <= now) past.push(ev);
171
- else if (s <= now && e > now) current.push(ev);
172
- else upcoming.push(ev);
173
- }
174
- return { past, current, upcomingSlots: groupIntoSlots(upcoming), totalUp: upcoming.length };
175
- });
176
-
177
- const dayTotal = $derived(dayCat.past.length + dayCat.current.length + dayCat.totalUp);
178
-
179
- /** Flat list of next upcoming events (max 5) for the "Up next" column */
180
- const upcomingNext = $derived.by((): TimelineEvent[] => {
181
- if (mode !== 'day') return [];
182
- const all: TimelineEvent[] = [];
183
- for (const slot of dayCat.upcomingSlots) {
184
- for (const ev of slot.events) {
185
- all.push(ev);
186
- if (all.length >= 5) return all;
187
- }
188
- }
189
- return all;
190
- });
191
-
192
- /** Time until a specific event */
193
- function timeUntilEv(ev: TimelineEvent): string {
194
- const diff = ev.start.getTime() - clock.tick;
195
- if (diff <= 0) return 'now';
196
- const mins = Math.floor(diff / 60000);
197
- if (mins < 60) return `in ${mins}m`;
198
- const hrs = Math.floor(mins / 60);
199
- const rm = mins % 60;
200
- return rm > 0 ? `in ${hrs}h ${rm}m` : `in ${hrs}h`;
201
- }
202
-
203
- // ═════════════════════════════════════════════════════
204
- // WEEK MODE
205
- // ═════════════════════════════════════════════════════
206
- type DayTier = 'today' | 'tomorrow' | 'upcoming' | 'past';
207
-
208
- interface DayGroup {
209
- ms: number;
210
- dayName: string;
211
- dateLabel: string;
212
- tier: DayTier;
213
- events: TimelineEvent[];
214
- pastEvents: TimelineEvent[];
215
- currentEvents: TimelineEvent[];
216
- upcomingEvents: TimelineEvent[];
217
- totalHours: number;
218
- }
219
-
220
- const weekStartMs = $derived(
221
- focusDate
222
- ? startOfWeek(sod(focusDate.getTime()), mondayStart)
223
- : startOfWeek(clock.today, mondayStart),
224
- );
225
- const weekLabel = $derived(fmtWeekRange(weekStartMs, locale));
226
-
227
- const weekDays = $derived.by((): DayGroup[] => {
228
- if (mode !== 'week') return [];
229
- const now = clock.tick;
230
- const todayMs = clock.today;
231
- const tomorrowMs = todayMs + DAY_MS;
232
- const out: DayGroup[] = [];
233
- for (let i = 0; i < 7; i++) {
234
- const ms = weekStartMs + i * DAY_MS;
235
- const dEnd = ms + DAY_MS;
236
- const dayEvts = events
237
- .filter((ev) => ev.start.getTime() < dEnd && ev.end.getTime() > ms)
238
- .sort((a, b) => a.start.getTime() - b.start.getTime());
239
- const totalMinutes = dayEvts.reduce((sum, ev) => {
240
- const s = Math.max(ev.start.getTime(), ms);
241
- const e = Math.min(ev.end.getTime(), dEnd);
242
- return sum + (e - s) / 60000;
243
- }, 0);
244
- const pastEvents: TimelineEvent[] = [];
245
- const currentEvents: TimelineEvent[] = [];
246
- const upcomingEvents: TimelineEvent[] = [];
247
- for (const ev of dayEvts) {
248
- if (ev.end.getTime() <= now) pastEvents.push(ev);
249
- else if (ev.start.getTime() <= now && ev.end.getTime() > now) currentEvents.push(ev);
250
- else upcomingEvents.push(ev);
251
- }
252
- let tier: DayTier;
253
- if (ms === todayMs) tier = 'today';
254
- else if (ms === tomorrowMs) tier = 'tomorrow';
255
- else if (ms < todayMs) tier = 'past';
256
- else tier = 'upcoming';
257
-
258
- out.push({
259
- ms,
260
- dayName: tier === 'today' || tier === 'tomorrow' ? weekdayLong(ms, locale) : weekdayShort(ms, locale),
261
- dateLabel: `${monthShort(ms, locale)} ${dayNum(ms)}`,
262
- tier,
263
- events: dayEvts,
264
- pastEvents,
265
- currentEvents,
266
- upcomingEvents,
267
- totalHours: Math.round((totalMinutes / 60) * 10) / 10,
268
- });
269
- }
270
- return out;
271
- });
272
-
273
- const weekTotal = $derived(weekDays.reduce((s, d) => s + d.events.length, 0));
274
- const weekHours = $derived(Math.round(weekDays.reduce((s, d) => s + d.totalHours, 0) * 10) / 10);
275
- </script>
276
-
277
- <!-- ═══ Shared event card snippet ═══ -->
278
- {#snippet eventCard(ev: TimelineEvent, isNow: boolean)}
279
- <div
280
- class="ag-card"
281
- class:ag-card--selected={selectedEventId === ev.id}
282
- style:--ev-color={ev.color || 'var(--dt-accent)'}
283
- role="button"
284
- tabindex="0"
285
- aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}, {duration(ev)}"
286
- onclick={() => handleClick(ev)}
287
- onkeydown={(e) => handleKeydown(e, ev)}
288
- >
289
- <div class="ag-card-stripe"></div>
290
- <div class="ag-card-body">
291
- <span class="ag-card-title">{ev.title}</span>
292
- {#if ev.subtitle}
293
- <span class="ag-card-sub">{ev.subtitle}</span>
294
- {/if}
295
- <span class="ag-card-meta">
296
- {#if isNow}
297
- until {fmtTime(ev.end)}
298
- {:else}
299
- {fmtTime(ev.start)} – {fmtTime(ev.end)}
300
- {/if}
301
- <span class="ag-card-dur">{duration(ev)}</span>
302
- </span>
303
- {#if ev.tags?.length}
304
- <div class="ag-card-tags">
305
- {#each ev.tags as tag}
306
- <span class="ag-card-tag">{tag}</span>
307
- {/each}
308
- </div>
309
- {/if}
310
- {#if isNow}
311
- <div class="ag-card-progress">
312
- <div class="ag-card-progress-fill" style:width="{progress(ev) * 100}%"></div>
313
- </div>
314
- {/if}
315
- </div>
316
- </div>
317
- {/snippet}
318
-
319
- <div
320
- class="ag"
321
- class:ag--day={mode === 'day'}
322
- class:ag--week={mode === 'week'}
323
- style={style || undefined}
324
- style:height={mode === 'week' ? `${height}px` : undefined}
325
- >
326
- <!-- ═══ Header ═══ -->
327
- <header class="ag-header">
328
- {#if mode === 'day'}
329
- <div class="ag-header-left">
330
- <span class="ag-title">{fmtDay(dayMs, clock.today, { short: false }, locale)}</span>
331
- {#if isToday}
332
- <span class="ag-clock">{clock.hm}</span>
333
- {:else if isPastDay}
334
- <span class="ag-badge ag-badge--past">past</span>
335
- {:else}
336
- <span class="ag-badge ag-badge--future">ahead</span>
337
- {/if}
338
- </div>
339
- <div class="ag-header-right">
340
- <span class="ag-stat">{allDayEvents.length} event{allDayEvents.length !== 1 ? 's' : ''}</span>
341
- {#if isToday}
342
- <div class="ag-progress-track">
343
- <div class="ag-progress-fill" style:width="{dayProgress * 100}%"></div>
344
- </div>
345
- {:else if dayHours > 0}
346
- <span class="ag-stat-sep">·</span>
347
- <span class="ag-stat">{dayHours}h</span>
348
- {/if}
349
- </div>
350
- {:else}
351
- <div class="ag-header-left">
352
- <span class="ag-title">This Week</span>
353
- <span class="ag-subtitle">{weekLabel}</span>
354
- </div>
355
- <div class="ag-header-right">
356
- <span class="ag-stat">{weekTotal} events</span>
357
- <span class="ag-stat-sep">·</span>
358
- <span class="ag-stat">{weekHours}h booked</span>
359
- </div>
360
- {/if}
361
- </header>
362
-
363
- <!-- ═══ Body ═══ -->
364
- <div class="ag-body" role="list" aria-label={mode === 'day' ? "Today's lineup" : 'Week ahead'}>
365
- {#if mode === 'day'}
366
-
367
- {#if isToday}
368
- <!-- ─── Today: "The Queue" — upcoming is the hero ─── -->
369
- <div class="ag-q">
370
- <!-- NOW: slim status strip -->
371
- <div class="ag-q-status">
372
- <div class="ag-q-label">Now</div>
373
- {#if dayCat.current.length > 0}
374
- {#each dayCat.current as ev (ev.id)}
375
- <div
376
- class="ag-q-now"
377
- class:ag-q-now--selected={selectedEventId === ev.id}
378
- style:--ev-color={ev.color || 'var(--dt-accent)'}
379
- role="button"
380
- tabindex="0"
381
- aria-label="{ev.title}, happening now, {Math.round(progress(ev) * 100)}% complete"
382
- onclick={() => handleClick(ev)}
383
- onkeydown={(e) => handleKeydown(e, ev)}
384
- >
385
- <div class="ag-q-now-dot"></div>
386
- <div class="ag-q-now-title">{ev.title}</div>
387
- <div class="ag-q-now-time">until {fmtTime(ev.end)}</div>
388
- <div class="ag-q-now-track">
389
- <div class="ag-q-now-fill" style:width="{progress(ev) * 100}%"></div>
390
- </div>
391
- </div>
392
- {/each}
393
- {:else}
394
- <div class="ag-q-free">
395
- <div class="ag-q-free-label">Free</div>
396
- </div>
397
- {/if}
398
- </div>
399
-
400
- <!-- NEXT: the hero center column -->
401
- <div class="ag-q-queue">
402
- <div class="ag-q-label">Up next</div>
403
- {#if upcomingNext.length === 0}
404
- <div class="ag-q-empty">
405
- {dayCat.past.length > 0 ? 'All done for today' : 'Nothing scheduled'}
406
- </div>
407
- {:else}
408
- {#each upcomingNext as ev, i (ev.id)}
409
- <div
410
- class="ag-q-card"
411
- class:ag-q-card--hero={i === 0}
412
- class:ag-q-card--selected={selectedEventId === ev.id}
413
- style:--ev-color={ev.color || 'var(--dt-accent)'}
414
- role="button"
415
- tabindex="0"
416
- aria-label="{ev.title}, {fmtTime(ev.start)}, {duration(ev)}"
417
- onclick={() => handleClick(ev)}
418
- onkeydown={(e) => handleKeydown(e, ev)}
419
- >
420
- <div class="ag-q-card-stripe"></div>
421
- <div class="ag-q-card-body">
422
- <div class="ag-q-card-top">
423
- <span class="ag-q-card-title">{ev.title}</span>
424
- <span class="ag-q-card-eta">{timeUntilEv(ev)}</span>
425
- </div>
426
- {#if ev.subtitle}
427
- <span class="ag-q-card-sub">{ev.subtitle}</span>
428
- {/if}
429
- <div class="ag-q-card-meta">
430
- {fmtTime(ev.start)} – {fmtTime(ev.end)}
431
- <span class="ag-q-card-dur">{duration(ev)}</span>
432
- </div>
433
- {#if ev.tags?.length}
434
- <div class="ag-q-card-tags">
435
- {#each ev.tags as tag}
436
- <span class="ag-q-card-tag">{tag}</span>
437
- {/each}
438
- </div>
439
- {/if}
440
- </div>
441
- </div>
442
- {/each}
443
- {/if}
444
- </div>
445
-
446
- <!-- DONE: minimal right gutter -->
447
- <div class="ag-q-done">
448
- {#if dayCat.past.length > 0}
449
- <div class="ag-q-label">Done</div>
450
- {#each dayCat.past as ev (ev.id)}
451
- <div
452
- class="ag-q-done-item"
453
- class:ag-q-done-item--selected={selectedEventId === ev.id}
454
- role="button"
455
- tabindex="0"
456
- aria-label="{ev.title}, completed, {fmtTime(ev.start)}"
457
- onclick={() => handleClick(ev)}
458
- onkeydown={(e) => handleKeydown(e, ev)}
459
- >
460
- <span class="ag-q-done-check">✓</span>
461
- <span class="ag-q-done-title">{ev.title}</span>
462
- </div>
463
- {/each}
464
- {/if}
465
- </div>
466
- </div>
467
-
468
- {:else if isPastDay}
469
- <!-- ─── Past day: "The Log" — everything happened ─── -->
470
- <div class="ag-log">
471
- {#if allDayEvents.length === 0}
472
- <div class="ag-q-empty">Nothing was scheduled</div>
473
- {:else}
474
- {#each allDayEvents as ev (ev.id)}
475
- <div
476
- class="ag-log-row"
477
- class:ag-log-row--selected={selectedEventId === ev.id}
478
- style:--ev-color={ev.color || 'var(--dt-accent)'}
479
- role="button"
480
- tabindex="0"
481
- aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}"
482
- onclick={() => handleClick(ev)}
483
- onkeydown={(e) => handleKeydown(e, ev)}
484
- >
485
- <span class="ag-log-check">✓</span>
486
- <span class="ag-log-time">{fmtTime(ev.start)}</span>
487
- <span class="ag-log-dot" style:background={ev.color || 'var(--dt-accent)'}></span>
488
- <span class="ag-log-title">{ev.title}</span>
489
- <span class="ag-log-dur">{duration(ev)}</span>
490
- </div>
491
- {/each}
492
- {/if}
493
- </div>
494
-
495
- {:else}
496
- <!-- ─── Future day: "The Plan" — everything is ahead ─── -->
497
- <div class="ag-plan">
498
- {#if allDayEvents.length === 0}
499
- <div class="ag-q-empty">Nothing scheduled yet</div>
500
- {:else}
501
- {#each allDayEvents as ev, i (ev.id)}
502
- <div
503
- class="ag-plan-card"
504
- class:ag-plan-card--first={i === 0}
505
- class:ag-plan-card--selected={selectedEventId === ev.id}
506
- style:--ev-color={ev.color || 'var(--dt-accent)'}
507
- role="button"
508
- tabindex="0"
509
- aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}, {duration(ev)}"
510
- onclick={() => handleClick(ev)}
511
- onkeydown={(e) => handleKeydown(e, ev)}
512
- >
513
- <div class="ag-plan-stripe"></div>
514
- <div class="ag-plan-body">
515
- <div class="ag-plan-top">
516
- <span class="ag-plan-order">{i + 1}</span>
517
- <span class="ag-plan-title">{ev.title}</span>
518
- </div>
519
- {#if ev.subtitle}
520
- <span class="ag-plan-sub">{ev.subtitle}</span>
521
- {/if}
522
- <div class="ag-plan-meta">
523
- {fmtTime(ev.start)} – {fmtTime(ev.end)}
524
- <span class="ag-plan-dur">{duration(ev)}</span>
525
- </div>
526
- {#if ev.tags?.length}
527
- <div class="ag-plan-tags">
528
- {#each ev.tags as tag}
529
- <span class="ag-plan-tag">{tag}</span>
530
- {/each}
531
- </div>
532
- {/if}
533
- </div>
534
- </div>
535
- {/each}
536
- {/if}
537
- </div>
538
- {/if}
539
-
540
- {:else}
541
- <!-- ─── Week: rolling day groups ─── -->
542
- {#each weekDays as day (day.ms)}
543
- {@const expanded = day.tier === 'today' || day.tier === 'tomorrow'}
544
- <div
545
- class="ag-wday"
546
- class:ag-wday--today={day.tier === 'today'}
547
- class:ag-wday--tomorrow={day.tier === 'tomorrow'}
548
- class:ag-wday--past={day.tier === 'past'}
549
- role="listitem"
550
- >
551
- <!-- Day header -->
552
- <div class="ag-wday-head">
553
- <div class="ag-wday-head-left">
554
- {#if day.tier === 'today'}
555
- <span class="ag-wday-badge">Today</span>
556
- {:else if day.tier === 'tomorrow'}
557
- <span class="ag-wday-badge ag-wday-badge--muted">Tomorrow</span>
558
- {/if}
559
- <span class="ag-wday-name">{day.dayName}</span>
560
- <span class="ag-wday-date">{day.dateLabel}</span>
561
- </div>
562
- {#if day.events.length > 0}
563
- <span class="ag-wday-count">{day.events.length}</span>
564
- {/if}
565
- </div>
566
-
567
- {#if day.events.length === 0}
568
- <div class="ag-wday-empty">No events</div>
569
- {:else if expanded}
570
- <!-- Expanded: today/tomorrow get full slot treatment -->
571
- <div class="ag-wday-expanded">
572
- {#if day.currentEvents.length > 0}
573
- {#each day.currentEvents as ev (ev.id)}
574
- <div class="ag-wslot">
575
- <div class="ag-wslot-header">
576
- <span class="ag-wslot-now">now</span>
577
- </div>
578
- {@render eventCard(ev, true)}
579
- </div>
580
- {/each}
581
- {/if}
582
- {#each groupIntoSlots(day.upcomingEvents) as slot (slot.startMs)}
583
- <div class="ag-wslot">
584
- <div class="ag-wslot-header">
585
- <span class="ag-wslot-time">{fmtTime(new Date(slot.startMs))}</span>
586
- {#if day.tier === 'today'}
587
- <span class="ag-wslot-eta">{timeUntilMs(slot.startMs)}</span>
588
- {/if}
589
- </div>
590
- <div class="ag-wslot-cards" class:ag-wslot-cards--multi={slot.events.length > 1}>
591
- {#each slot.events as ev (ev.id)}
592
- {@render eventCard(ev, false)}
593
- {/each}
594
- </div>
595
- </div>
596
- {/each}
597
- {#if day.pastEvents.length > 0}
598
- <div class="ag-wday-past-line">✓ {day.pastEvents.length} completed</div>
599
- {/if}
600
- </div>
601
- {:else}
602
- <!-- Compact: future/past days get minimal rows -->
603
- <div class="ag-wday-compact">
604
- {#each day.events.slice(0, 3) as ev (ev.id)}
605
- <div
606
- class="ag-compact"
607
- class:ag-compact--past={ev.end.getTime() < clock.tick}
608
- class:ag-compact--selected={selectedEventId === ev.id}
609
- style:--ev-color={ev.color || 'var(--dt-accent)'}
610
- role="button"
611
- tabindex="0"
612
- aria-label="{ev.title}, {fmtTime(ev.start)}, {duration(ev)}"
613
- onclick={() => handleClick(ev)}
614
- onkeydown={(e) => handleKeydown(e, ev)}
615
- >
616
- <span class="ag-compact-dot"></span>
617
- <span class="ag-compact-time">{fmtTime(ev.start)}</span>
618
- <span class="ag-compact-title">{ev.title}</span>
619
- {#if ev.subtitle}
620
- <span class="ag-compact-sub">{ev.subtitle}</span>
621
- {/if}
622
- {#if ev.tags?.length}
623
- {#each ev.tags as tag}
624
- <span class="ag-compact-tag">{tag}</span>
625
- {/each}
626
- {/if}
627
- <span class="ag-compact-dur">{duration(ev)}</span>
628
- </div>
629
- {/each}
630
- {#if day.events.length > 3}
631
- <div class="ag-compact-more">+{day.events.length - 3} more</div>
632
- {/if}
633
- </div>
634
- {/if}
635
- </div>
636
- {/each}
637
- {/if}
638
- </div>
639
- </div>
640
-
641
- <style>
642
- /* ═══ Container ═══ */
643
- .ag {
644
- position: relative;
645
- background: var(--dt-bg, #111);
646
- border: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
647
- border-radius: 10px;
648
- overflow: hidden;
649
- user-select: none;
650
- display: flex;
651
- flex-direction: column;
652
- color: var(--dt-text, rgba(255, 255, 255, 0.92));
653
- font-family: var(--dt-sans, system-ui, sans-serif);
654
- }
655
-
656
- /* ═══ Header ═══ */
657
- .ag-header {
658
- display: flex;
659
- justify-content: space-between;
660
- align-items: center;
661
- padding: 14px 20px 10px;
662
- border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
663
- flex-shrink: 0;
664
- }
665
- .ag-header-left {
666
- display: flex;
667
- align-items: baseline;
668
- gap: 10px;
669
- }
670
- .ag-title {
671
- font-size: 14px;
672
- font-weight: 600;
673
- line-height: 1;
674
- }
675
- .ag-subtitle {
676
- font-size: 12px;
677
- font-family: var(--dt-mono, monospace);
678
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
679
- }
680
- .ag-clock {
681
- font-size: 13px;
682
- font-weight: 600;
683
- font-family: var(--dt-mono, monospace);
684
- color: var(--dt-accent, #ff6b4a);
685
- }
686
- .ag-header-right {
687
- display: flex;
688
- align-items: center;
689
- gap: 8px;
690
- }
691
- .ag-stat {
692
- font-size: 11px;
693
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
694
- font-family: var(--dt-mono, monospace);
695
- }
696
- .ag-stat-sep {
697
- font-size: 11px;
698
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
699
- }
700
- .ag-progress-track {
701
- width: 52px;
702
- height: 3px;
703
- background: var(--dt-border, rgba(255, 255, 255, 0.06));
704
- border-radius: 2px;
705
- overflow: hidden;
706
- }
707
- .ag-progress-fill {
708
- height: 100%;
709
- background: var(--dt-accent, #ff6b4a);
710
- border-radius: 2px;
711
- opacity: 0.5;
712
- transition: width 1s linear;
713
- }
714
-
715
- /* ═══ Body ═══ */
716
- .ag-body {
717
- flex: 1;
718
- overflow-y: auto;
719
- scrollbar-width: thin;
720
- scrollbar-color: var(--dt-border) transparent;
721
- }
722
- .ag-body::-webkit-scrollbar {
723
- width: 4px;
724
- }
725
- .ag-body::-webkit-scrollbar-thumb {
726
- background: var(--dt-border);
727
- border-radius: 2px;
728
- }
729
-
730
- /* ═══ Shared: event card ═══ */
731
- .ag-card {
732
- display: flex;
733
- align-items: stretch;
734
- border-radius: 8px;
735
- background: var(--dt-surface, #191919);
736
- border: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
737
- overflow: hidden;
738
- cursor: pointer;
739
- transition: border-color 150ms;
740
- }
741
- .ag-card:hover {
742
- border-color: color-mix(in srgb, var(--ev-color) 40%, transparent);
743
- }
744
- .ag-card:focus-visible {
745
- outline: 2px solid var(--dt-accent, #ff6b4a);
746
- outline-offset: 2px;
747
- }
748
- .ag-card--selected {
749
- border-color: var(--ev-color);
750
- background: color-mix(in srgb, var(--ev-color) 6%, var(--dt-surface, #191919));
751
- }
752
- .ag-card-stripe {
753
- width: 3px;
754
- background: var(--ev-color, var(--dt-accent));
755
- flex-shrink: 0;
756
- }
757
- .ag-card-body {
758
- padding: 10px 12px;
759
- display: flex;
760
- flex-direction: column;
761
- gap: 4px;
762
- min-width: 0;
763
- flex: 1;
764
- }
765
- .ag-card-title {
766
- font-size: 13px;
767
- font-weight: 600;
768
- line-height: 1.2;
769
- white-space: nowrap;
770
- overflow: hidden;
771
- text-overflow: ellipsis;
772
- }
773
- .ag-card-meta {
774
- font-size: 11px;
775
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
776
- font-family: var(--dt-mono, monospace);
777
- line-height: 1;
778
- }
779
- .ag-card-dur {
780
- margin-left: 6px;
781
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
782
- }
783
- .ag-card-sub {
784
- font-size: 11px;
785
- color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
786
- line-height: 1;
787
- }
788
- .ag-card-tags {
789
- display: flex;
790
- gap: 4px;
791
- flex-wrap: wrap;
792
- }
793
- .ag-card-tag {
794
- font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
795
- color: var(--ev-color, var(--dt-accent));
796
- background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
797
- padding: 2px 5px;
798
- border-radius: 3px;
799
- white-space: nowrap;
800
- }
801
- .ag-card-progress {
802
- height: 3px;
803
- background: var(--dt-border, rgba(255, 255, 255, 0.06));
804
- border-radius: 2px;
805
- overflow: hidden;
806
- margin-top: 2px;
807
- }
808
- .ag-card-progress-fill {
809
- height: 100%;
810
- background: var(--ev-color, var(--dt-accent));
811
- border-radius: 2px;
812
- transition: width 1s linear;
813
- }
814
-
815
- :global(.ag-empty) {
816
- display: flex;
817
- align-items: center;
818
- justify-content: center;
819
- padding: 40px 20px;
820
- font-size: 13px;
821
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
822
- }
823
-
824
- /* ═══════════════════════════════════════════════════
825
- DAY MODE: "The Queue" — upcoming events are the hero
826
- ═══════════════════════════════════════════════════ */
827
-
828
- /* 3-column grid: slim NOW | hero NEXT | minimal PAST */
829
- .ag-q {
830
- display: grid;
831
- grid-template-columns: 0.65fr 1.8fr 0.55fr;
832
- gap: 0;
833
- flex: 1;
834
- padding: 8px 0 10px;
835
- min-height: 0;
836
- }
837
-
838
- /* Shared column label */
839
- .ag-q-label {
840
- font-size: 8px;
841
- font-weight: 600;
842
- letter-spacing: 0.14em;
843
- text-transform: uppercase;
844
- color: var(--dt-text-3, rgba(255, 255, 255, 0.25));
845
- margin-bottom: 8px;
846
- padding: 0 12px;
847
- font-family: var(--dt-sans, system-ui, sans-serif);
848
- }
849
-
850
- .ag-q-empty {
851
- display: flex;
852
- align-items: center;
853
- justify-content: center;
854
- flex: 1;
855
- font-size: 13px;
856
- font-weight: 300;
857
- color: var(--dt-text-3, rgba(255, 255, 255, 0.25));
858
- }
859
-
860
- /* ── NOW: slim status strip (left) ── */
861
- .ag-q-status {
862
- padding: 0 0 0 14px;
863
- border-right: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
864
- display: flex;
865
- flex-direction: column;
866
- }
867
-
868
- .ag-q-now {
869
- padding: 8px 10px;
870
- border-radius: 8px;
871
- background: var(--dt-surface, #191919);
872
- border: 1px solid color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
873
- cursor: pointer;
874
- transition: border-color 150ms;
875
- margin-right: 10px;
876
- }
877
- .ag-q-now:hover {
878
- border-color: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 35%, transparent);
879
- }
880
- .ag-q-now:focus-visible {
881
- outline: 2px solid var(--dt-accent, #ff6b4a);
882
- outline-offset: 2px;
883
- }
884
- .ag-q-now--selected {
885
- border-color: var(--ev-color, var(--dt-accent));
886
- }
887
-
888
- .ag-q-now-dot {
889
- width: 6px;
890
- height: 6px;
891
- border-radius: 50%;
892
- background: var(--ev-color, var(--dt-accent, #ff6b4a));
893
- margin-bottom: 6px;
894
- animation: ag-pulse 2.5s ease-in-out infinite;
895
- }
896
- @keyframes ag-pulse {
897
- 0%, 100% { opacity: 1; }
898
- 50% { opacity: 0.4; }
899
- }
900
-
901
- .ag-q-now-title {
902
- font-size: 11px;
903
- font-weight: 600;
904
- line-height: 1.2;
905
- color: var(--dt-text, rgba(255, 255, 255, 0.92));
906
- white-space: nowrap;
907
- overflow: hidden;
908
- text-overflow: ellipsis;
909
- margin-bottom: 3px;
910
- }
911
- .ag-q-now-time {
912
- font-size: 9px;
913
- font-family: var(--dt-mono, monospace);
914
- color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
915
- margin-bottom: 6px;
916
- }
917
- .ag-q-now-track {
918
- height: 2px;
919
- background: var(--dt-border, rgba(255, 255, 255, 0.06));
920
- border-radius: 1px;
921
- overflow: hidden;
922
- }
923
- .ag-q-now-fill {
924
- height: 100%;
925
- background: var(--ev-color, var(--dt-accent, #ff6b4a));
926
- border-radius: 1px;
927
- transition: width 1s linear;
928
- }
929
-
930
- /* Free state */
931
- .ag-q-free {
932
- padding: 8px 10px;
933
- margin-right: 10px;
934
- }
935
- .ag-q-free-label {
936
- font-size: 12px;
937
- font-weight: 300;
938
- color: var(--dt-text-3, rgba(255, 255, 255, 0.25));
939
- margin-bottom: 2px;
940
- }
941
- :global(.ag-q-free-next) {
942
- font-size: 10px;
943
- font-weight: 500;
944
- line-height: 1.2;
945
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
946
- white-space: nowrap;
947
- overflow: hidden;
948
- text-overflow: ellipsis;
949
- margin-bottom: 2px;
950
- }
951
- :global(.ag-q-free-until) {
952
- font-size: 9px;
953
- font-family: var(--dt-mono, monospace);
954
- color: var(--dt-accent, #ff6b4a);
955
- opacity: 0.6;
956
- }
957
-
958
- /* ── NEXT: hero center column ── */
959
- .ag-q-queue {
960
- padding: 0 16px;
961
- overflow-y: auto;
962
- scrollbar-width: none;
963
- display: flex;
964
- flex-direction: column;
965
- }
966
- .ag-q-queue::-webkit-scrollbar {
967
- display: none;
968
- }
969
-
970
- /* Event card — the star of the show */
971
- .ag-q-card {
972
- display: flex;
973
- align-items: stretch;
974
- border-radius: 10px;
975
- background: var(--dt-surface, #191919);
976
- border: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
977
- overflow: hidden;
978
- cursor: pointer;
979
- margin-bottom: 6px;
980
- transition: border-color 150ms, transform 100ms;
981
- }
982
- .ag-q-card:hover {
983
- border-color: color-mix(in srgb, var(--ev-color) 40%, transparent);
984
- }
985
- .ag-q-card:focus-visible {
986
- outline: 2px solid var(--dt-accent, #ff6b4a);
987
- outline-offset: 2px;
988
- }
989
- .ag-q-card--selected {
990
- border-color: var(--ev-color);
991
- background: color-mix(in srgb, var(--ev-color) 6%, var(--dt-surface, #191919));
992
- }
993
-
994
- /* Hero card — first in queue, the imminent event */
995
- .ag-q-card--hero {
996
- background: color-mix(in srgb, var(--ev-color) 8%, var(--dt-surface, #191919));
997
- border-color: color-mix(in srgb, var(--ev-color) 25%, transparent);
998
- }
999
- .ag-q-card--hero .ag-q-card-title {
1000
- font-size: 16px;
1001
- font-weight: 700;
1002
- }
1003
- .ag-q-card--hero .ag-q-card-eta {
1004
- font-size: 11px;
1005
- background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 18%, transparent);
1006
- padding: 2px 7px;
1007
- border-radius: 4px;
1008
- }
1009
- .ag-q-card--hero .ag-q-card-body {
1010
- padding: 14px 16px;
1011
- }
1012
-
1013
- .ag-q-card-stripe {
1014
- width: 3.5px;
1015
- background: var(--ev-color, var(--dt-accent));
1016
- flex-shrink: 0;
1017
- }
1018
- .ag-q-card-body {
1019
- padding: 10px 12px;
1020
- display: flex;
1021
- flex-direction: column;
1022
- gap: 3px;
1023
- min-width: 0;
1024
- flex: 1;
1025
- }
1026
- .ag-q-card-top {
1027
- display: flex;
1028
- justify-content: space-between;
1029
- align-items: flex-start;
1030
- gap: 8px;
1031
- }
1032
- .ag-q-card-title {
1033
- font-size: 13px;
1034
- font-weight: 600;
1035
- line-height: 1.2;
1036
- white-space: nowrap;
1037
- overflow: hidden;
1038
- text-overflow: ellipsis;
1039
- flex: 1;
1040
- }
1041
- .ag-q-card-eta {
1042
- font-size: 9px;
1043
- font-weight: 600;
1044
- letter-spacing: 0.04em;
1045
- color: var(--dt-accent, #ff6b4a);
1046
- flex-shrink: 0;
1047
- white-space: nowrap;
1048
- }
1049
- .ag-q-card-meta {
1050
- font-size: 11px;
1051
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
1052
- font-family: var(--dt-mono, monospace);
1053
- line-height: 1;
1054
- }
1055
- .ag-q-card-dur {
1056
- margin-left: 6px;
1057
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1058
- }
1059
- .ag-q-card-sub {
1060
- font-size: 11px;
1061
- color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
1062
- line-height: 1;
1063
- }
1064
- .ag-q-card-tags {
1065
- display: flex;
1066
- gap: 4px;
1067
- flex-wrap: wrap;
1068
- margin-top: 2px;
1069
- }
1070
- .ag-q-card-tag {
1071
- font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
1072
- color: var(--ev-color, var(--dt-accent));
1073
- background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
1074
- padding: 2px 5px;
1075
- border-radius: 3px;
1076
- white-space: nowrap;
1077
- }
1078
-
1079
- /* ── PAST: minimal right gutter ── */
1080
- .ag-q-done {
1081
- padding: 0 14px 0 0;
1082
- border-left: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
1083
- overflow-y: auto;
1084
- scrollbar-width: none;
1085
- padding-left: 10px;
1086
- }
1087
- .ag-q-done::-webkit-scrollbar {
1088
- display: none;
1089
- }
1090
-
1091
- .ag-q-done-item {
1092
- display: flex;
1093
- align-items: center;
1094
- gap: 5px;
1095
- padding: 3px 0;
1096
- opacity: 0.35;
1097
- cursor: pointer;
1098
- }
1099
- .ag-q-done-item:hover {
1100
- opacity: 0.6;
1101
- }
1102
- .ag-q-done-item:focus-visible {
1103
- outline: 2px solid var(--dt-accent, #ff6b4a);
1104
- outline-offset: 2px;
1105
- opacity: 0.6;
1106
- }
1107
- .ag-q-done-item--selected {
1108
- opacity: 0.7;
1109
- }
1110
- .ag-q-done-check {
1111
- font-size: 9px;
1112
- color: var(--dt-success, rgba(120, 200, 140, 0.7));
1113
- flex-shrink: 0;
1114
- }
1115
- .ag-q-done-title {
1116
- font-size: 10px;
1117
- line-height: 1.2;
1118
- color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
1119
- white-space: nowrap;
1120
- overflow: hidden;
1121
- text-overflow: ellipsis;
1122
- text-decoration: line-through;
1123
- text-decoration-color: var(--dt-text-3, rgba(255, 255, 255, 0.15));
1124
- }
1125
-
1126
- /* ═══════════════════════════════════════════════════
1127
- PAST DAY: "The Log" — quiet chronological record
1128
- ═══════════════════════════════════════════════════ */
1129
- .ag-log {
1130
- flex: 1;
1131
- padding: 8px 20px 12px;
1132
- overflow-y: auto;
1133
- scrollbar-width: none;
1134
- opacity: 0.7;
1135
- }
1136
- .ag-log::-webkit-scrollbar {
1137
- display: none;
1138
- }
1139
-
1140
- .ag-log-row {
1141
- display: flex;
1142
- align-items: center;
1143
- gap: 10px;
1144
- padding: 8px 0;
1145
- border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.04));
1146
- cursor: pointer;
1147
- transition: opacity 150ms;
1148
- }
1149
- .ag-log-row:last-child {
1150
- border-bottom: none;
1151
- }
1152
- .ag-log-row:hover {
1153
- opacity: 1;
1154
- }
1155
- .ag-log-row:focus-visible {
1156
- outline: 2px solid var(--dt-accent, #ff6b4a);
1157
- outline-offset: 2px;
1158
- }
1159
- .ag-log-row--selected {
1160
- opacity: 1;
1161
- background: color-mix(in srgb, var(--ev-color) 6%, transparent);
1162
- border-radius: 6px;
1163
- padding-left: 8px;
1164
- padding-right: 8px;
1165
- }
1166
-
1167
- .ag-log-check {
1168
- font-size: 10px;
1169
- color: var(--dt-success, rgba(120, 200, 140, 0.5));
1170
- flex-shrink: 0;
1171
- }
1172
- .ag-log-time {
1173
- font-size: 11px;
1174
- font-family: var(--dt-mono, monospace);
1175
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1176
- width: 64px;
1177
- flex-shrink: 0;
1178
- }
1179
- .ag-log-dot {
1180
- width: 5px;
1181
- height: 5px;
1182
- border-radius: 50%;
1183
- flex-shrink: 0;
1184
- opacity: 0.6;
1185
- }
1186
- .ag-log-title {
1187
- font-size: 13px;
1188
- font-weight: 500;
1189
- line-height: 1.2;
1190
- color: var(--dt-text-2, rgba(255, 255, 255, 0.55));
1191
- flex: 1;
1192
- white-space: nowrap;
1193
- overflow: hidden;
1194
- text-overflow: ellipsis;
1195
- text-decoration: line-through;
1196
- text-decoration-color: var(--dt-border, rgba(255, 255, 255, 0.08));
1197
- }
1198
- .ag-log-dur {
1199
- font-size: 10px;
1200
- font-family: var(--dt-mono, monospace);
1201
- color: var(--dt-text-3, rgba(255, 255, 255, 0.2));
1202
- flex-shrink: 0;
1203
- }
1204
-
1205
- /* ═══════════════════════════════════════════════════
1206
- FUTURE DAY: "The Plan" — clean schedule list
1207
- ═══════════════════════════════════════════════════ */
1208
- .ag-plan {
1209
- flex: 1;
1210
- padding: 8px 20px 12px;
1211
- overflow-y: auto;
1212
- scrollbar-width: none;
1213
- display: flex;
1214
- flex-direction: column;
1215
- gap: 6px;
1216
- }
1217
- .ag-plan::-webkit-scrollbar {
1218
- display: none;
1219
- }
1220
-
1221
- .ag-plan-card {
1222
- display: flex;
1223
- align-items: stretch;
1224
- border-radius: 10px;
1225
- background: var(--dt-surface, #191919);
1226
- border: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
1227
- overflow: hidden;
1228
- cursor: pointer;
1229
- transition: border-color 150ms;
1230
- }
1231
- .ag-plan-card:hover {
1232
- border-color: color-mix(in srgb, var(--ev-color) 35%, transparent);
1233
- }
1234
- .ag-plan-card:focus-visible {
1235
- outline: 2px solid var(--dt-accent, #ff6b4a);
1236
- outline-offset: 2px;
1237
- }
1238
- .ag-plan-card--selected {
1239
- border-color: var(--ev-color);
1240
- background: color-mix(in srgb, var(--ev-color) 6%, var(--dt-surface, #191919));
1241
- }
1242
- .ag-plan-card--first {
1243
- background: color-mix(in srgb, var(--ev-color) 5%, var(--dt-surface, #191919));
1244
- border-color: color-mix(in srgb, var(--ev-color) 15%, transparent);
1245
- }
1246
-
1247
- .ag-plan-stripe {
1248
- width: 3.5px;
1249
- background: var(--ev-color, var(--dt-accent));
1250
- flex-shrink: 0;
1251
- }
1252
- .ag-plan-body {
1253
- padding: 12px 14px;
1254
- display: flex;
1255
- flex-direction: column;
1256
- gap: 3px;
1257
- min-width: 0;
1258
- flex: 1;
1259
- }
1260
- .ag-plan-top {
1261
- display: flex;
1262
- align-items: baseline;
1263
- gap: 8px;
1264
- }
1265
- .ag-plan-order {
1266
- font-size: 10px;
1267
- font-weight: 700;
1268
- color: var(--dt-text-3, rgba(255, 255, 255, 0.2));
1269
- font-family: var(--dt-mono, monospace);
1270
- flex-shrink: 0;
1271
- }
1272
- .ag-plan-title {
1273
- font-size: 14px;
1274
- font-weight: 600;
1275
- line-height: 1.2;
1276
- white-space: nowrap;
1277
- overflow: hidden;
1278
- text-overflow: ellipsis;
1279
- }
1280
- .ag-plan-card--first .ag-plan-title {
1281
- font-size: 16px;
1282
- font-weight: 700;
1283
- }
1284
- .ag-plan-meta {
1285
- font-size: 11px;
1286
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
1287
- font-family: var(--dt-mono, monospace);
1288
- line-height: 1;
1289
- }
1290
- .ag-plan-dur {
1291
- margin-left: 6px;
1292
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1293
- }
1294
- .ag-plan-sub {
1295
- font-size: 11px;
1296
- color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
1297
- line-height: 1;
1298
- padding-left: 22px;
1299
- }
1300
- .ag-plan-tags {
1301
- display: flex;
1302
- gap: 4px;
1303
- flex-wrap: wrap;
1304
- padding-left: 22px;
1305
- margin-top: 2px;
1306
- }
1307
- .ag-plan-tag {
1308
- font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
1309
- color: var(--ev-color, var(--dt-accent));
1310
- background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
1311
- padding: 2px 5px;
1312
- border-radius: 3px;
1313
- white-space: nowrap;
1314
- }
1315
-
1316
- /* Header badges for past/future days */
1317
- .ag-badge {
1318
- font-size: 9px;
1319
- font-weight: 600;
1320
- letter-spacing: 0.1em;
1321
- text-transform: uppercase;
1322
- padding: 2px 6px;
1323
- border-radius: 3px;
1324
- }
1325
- .ag-badge--past {
1326
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1327
- background: var(--dt-border, rgba(255, 255, 255, 0.06));
1328
- }
1329
- .ag-badge--future {
1330
- color: var(--dt-accent, #ff6b4a);
1331
- background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 12%, transparent);
1332
- }
1333
-
1334
- /* ═══════════════════════════════════════════════════
1335
- WEEK MODE: Rolling day groups
1336
- ═══════════════════════════════════════════════════ */
1337
- .ag-wday {
1338
- border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
1339
- }
1340
- .ag-wday--today {
1341
- background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 4%, transparent);
1342
- }
1343
- .ag-wday--past {
1344
- opacity: 0.4;
1345
- }
1346
-
1347
- .ag-wday-head {
1348
- display: flex;
1349
- justify-content: space-between;
1350
- align-items: center;
1351
- padding: 10px 20px;
1352
- }
1353
- .ag-wday-head-left {
1354
- display: flex;
1355
- align-items: center;
1356
- gap: 8px;
1357
- }
1358
- .ag-wday-badge {
1359
- font-size: 10px;
1360
- font-weight: 600;
1361
- letter-spacing: 0.08em;
1362
- text-transform: uppercase;
1363
- color: var(--dt-accent, #ff6b4a);
1364
- background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 12%, transparent);
1365
- padding: 2px 7px;
1366
- border-radius: 3px;
1367
- }
1368
- .ag-wday-badge--muted {
1369
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
1370
- background: color-mix(
1371
- in srgb,
1372
- var(--dt-text-2, rgba(255, 255, 255, 0.5)) 10%,
1373
- transparent
1374
- );
1375
- }
1376
- .ag-wday-name {
1377
- font-size: 13px;
1378
- font-weight: 600;
1379
- }
1380
- .ag-wday-date {
1381
- font-size: 11px;
1382
- font-family: var(--dt-mono, monospace);
1383
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1384
- }
1385
- .ag-wday-count {
1386
- font-size: 11px;
1387
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1388
- }
1389
- .ag-wday-empty {
1390
- padding: 4px 20px 10px;
1391
- font-size: 12px;
1392
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1393
- }
1394
-
1395
- /* Expanded day */
1396
- .ag-wday-expanded {
1397
- padding: 0 20px 12px;
1398
- }
1399
- .ag-wslot {
1400
- margin-bottom: 6px;
1401
- }
1402
- .ag-wslot-header {
1403
- display: flex;
1404
- align-items: baseline;
1405
- gap: 8px;
1406
- padding: 4px 0;
1407
- }
1408
- .ag-wslot-time {
1409
- font-size: 12px;
1410
- font-weight: 600;
1411
- font-family: var(--dt-mono, monospace);
1412
- }
1413
- .ag-wslot-now {
1414
- font-size: 9px;
1415
- font-weight: 700;
1416
- letter-spacing: 0.08em;
1417
- text-transform: uppercase;
1418
- color: var(--dt-accent, #ff6b4a);
1419
- }
1420
- .ag-wslot-eta {
1421
- font-size: 11px;
1422
- font-weight: 500;
1423
- color: var(--dt-accent, #ff6b4a);
1424
- }
1425
- .ag-wslot-cards {
1426
- display: flex;
1427
- flex-direction: column;
1428
- gap: 4px;
1429
- }
1430
- .ag-wslot-cards--multi {
1431
- display: grid;
1432
- grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
1433
- gap: 4px;
1434
- }
1435
- .ag-wday-past-line {
1436
- font-size: 10px;
1437
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1438
- padding: 6px 0 0;
1439
- opacity: 0.5;
1440
- }
1441
-
1442
- /* Compact day events */
1443
- .ag-wday-compact {
1444
- padding: 0 20px 10px;
1445
- }
1446
- .ag-compact {
1447
- display: flex;
1448
- align-items: center;
1449
- gap: 8px;
1450
- padding: 4px 0;
1451
- cursor: pointer;
1452
- }
1453
- .ag-compact:hover .ag-compact-title {
1454
- color: var(--dt-text);
1455
- }
1456
- .ag-compact:focus-visible {
1457
- outline: 2px solid var(--dt-accent, #ff6b4a);
1458
- outline-offset: 2px;
1459
- }
1460
- .ag-compact--past {
1461
- opacity: 0.4;
1462
- }
1463
- .ag-compact--past .ag-compact-title {
1464
- text-decoration: line-through;
1465
- text-decoration-color: var(--dt-text-3);
1466
- }
1467
- .ag-compact-dot {
1468
- width: 5px;
1469
- height: 5px;
1470
- border-radius: 50%;
1471
- background: var(--ev-color, var(--dt-accent));
1472
- flex-shrink: 0;
1473
- }
1474
- .ag-compact-time {
1475
- font-size: 11px;
1476
- font-family: var(--dt-mono, monospace);
1477
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
1478
- min-width: 64px;
1479
- flex-shrink: 0;
1480
- }
1481
- .ag-compact-title {
1482
- font-size: 12px;
1483
- font-weight: 500;
1484
- color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
1485
- flex: 1;
1486
- white-space: nowrap;
1487
- overflow: hidden;
1488
- text-overflow: ellipsis;
1489
- transition: color 150ms;
1490
- }
1491
- .ag-compact-dur {
1492
- font-size: 10px;
1493
- font-family: var(--dt-mono, monospace);
1494
- color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1495
- flex-shrink: 0;
1496
- }
1497
- .ag-compact-sub {
1498
- font-size: 10px;
1499
- color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
1500
- flex-shrink: 0;
1501
- }
1502
- .ag-compact-tag {
1503
- font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
1504
- color: var(--ev-color, var(--dt-accent));
1505
- background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 12%, transparent);
1506
- padding: 1px 4px;
1507
- border-radius: 3px;
1508
- white-space: nowrap;
1509
- flex-shrink: 0;
1510
- }
1511
- .ag-compact-more {
1512
- font-size: 11px;
1513
- color: var(--dt-text-3);
1514
- padding: 2px 0 0 13px;
1515
- }
1516
- </style>
1
+ <script lang="ts">
2
+ import AgendaDay from './AgendaDay.svelte';
3
+ import AgendaWeek from './AgendaWeek.svelte';
4
+
5
+ interface Props {
6
+ mode?: 'day' | 'week';
7
+ [key: string]: unknown;
8
+ }
9
+
10
+ let { mode = 'day', ...rest }: Props = $props();
11
+ </script>
12
+
13
+ {#if mode === 'day'}
14
+ <AgendaDay {...rest} />
15
+ {:else}
16
+ <AgendaWeek {...rest} />
17
+ {/if}