@nomideusz/svelte-calendar 0.1.0

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