@nomideusz/svelte-calendar 0.4.0 → 0.5.1

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 +826 -0
  46. package/dist/views/{week/WeekGrid.svelte.d.ts → planner/PlannerWeek.svelte.d.ts} +4 -9
  47. package/dist/views/planner/index.d.ts +1 -0
  48. package/dist/views/planner/index.js +1 -0
  49. package/dist/widget/CalendarWidget.svelte +54 -33
  50. package/dist/widget/CalendarWidget.svelte.d.ts +1 -1
  51. package/package.json +70 -65
  52. package/widget/svelte-calendar.css +1 -0
  53. package/widget/widget.js +122 -260
  54. package/dist/calendar/Toolbar.svelte +0 -244
  55. package/dist/calendar/Toolbar.svelte.d.ts +0 -21
  56. package/dist/views/day/DayGrid.svelte +0 -1104
  57. package/dist/views/day/DayTimeline.svelte +0 -533
  58. package/dist/views/day/DayTimeline.svelte.d.ts +0 -8
  59. package/dist/views/day/index.d.ts +0 -2
  60. package/dist/views/day/index.js +0 -2
  61. package/dist/views/schedule/WeekSchedule.svelte +0 -137
  62. package/dist/views/schedule/WeekSchedule.svelte.d.ts +0 -40
  63. package/dist/views/schedule/index.d.ts +0 -1
  64. package/dist/views/schedule/index.js +0 -2
  65. package/dist/views/settings/Settings.svelte +0 -585
  66. package/dist/views/settings/Settings.svelte.d.ts +0 -39
  67. package/dist/views/settings/index.d.ts +0 -2
  68. package/dist/views/settings/index.js +0 -1
  69. package/dist/views/week/WeekGrid.svelte +0 -632
  70. package/dist/views/week/WeekHeatmap.svelte +0 -461
  71. package/dist/views/week/WeekHeatmap.svelte.d.ts +0 -9
  72. package/dist/views/week/index.d.ts +0 -2
  73. package/dist/views/week/index.js +0 -2
@@ -0,0 +1,976 @@
1
+ <script lang="ts">
2
+ /**
3
+ * AgendaDay — single-day agenda view.
4
+ *
5
+ * Today ("The Queue"):
6
+ * 3-column layout: Done | Now | Up next (hero).
7
+ * Answers: "What's coming up next?"
8
+ *
9
+ * Past day ("The Log"):
10
+ * Quiet chronological record of completed events.
11
+ *
12
+ * Future day ("The Plan"):
13
+ * Clean numbered schedule list.
14
+ */
15
+ import { getContext } from 'svelte';
16
+ import { createClock } from '../../core/clock.svelte.js';
17
+ import type { TimelineEvent } from '../../core/types.js';
18
+ import { sod, DAY_MS, isAllDay, isMultiDay } from '../../core/time.js';
19
+ import { fmtTime as _fmtTime, fmtDuration, getLabels } from '../../core/locale.js';
20
+ import type { ViewState } from '../../engine/view-state.svelte.js';
21
+
22
+ const L = $derived(getLabels());
23
+
24
+ interface Props {
25
+ locale?: string;
26
+ height?: number;
27
+ events?: TimelineEvent[];
28
+ style?: string;
29
+ focusDate?: Date;
30
+ oneventclick?: (event: TimelineEvent) => void;
31
+ selectedEventId?: string | null;
32
+ [key: string]: unknown;
33
+ }
34
+
35
+ let {
36
+ locale,
37
+ height,
38
+ events = [],
39
+ style = '',
40
+ focusDate,
41
+ oneventclick,
42
+ selectedEventId = null,
43
+ }: Props = $props();
44
+
45
+ const clock = createClock();
46
+ const viewState = getContext<ViewState>('calendar:viewState') as ViewState | undefined;
47
+
48
+ // ── Format helpers ──────────────────────────────────
49
+ function fmtTime(d: Date): string {
50
+ return _fmtTime(d, locale);
51
+ }
52
+
53
+ function duration(ev: TimelineEvent): string {
54
+ return fmtDuration(ev.start, ev.end);
55
+ }
56
+
57
+ function timeUntilMs(ms: number): string {
58
+ const diff = ms - clock.tick;
59
+ if (diff <= 0) return L.now;
60
+ const tMins = Math.floor(diff / 60000);
61
+ if (tMins < 60) return `in ${tMins}m`;
62
+ const hrs = Math.floor(tMins / 60);
63
+ const rm = tMins % 60;
64
+ if (hrs < 24) return rm > 0 ? `in ${hrs}h ${rm}m` : `in ${hrs}h`;
65
+ const days = Math.floor(hrs / 24);
66
+ return `in ${days}d`;
67
+ }
68
+
69
+ function progress(ev: TimelineEvent): number {
70
+ const s = ev.start.getTime();
71
+ const e = ev.end.getTime();
72
+ return Math.min(1, Math.max(0, (clock.tick - s) / (e - s)));
73
+ }
74
+
75
+ // ── Overlap grouping ────────────────────────────────
76
+ interface TimeSlot {
77
+ startMs: number;
78
+ endMs: number;
79
+ events: TimelineEvent[];
80
+ }
81
+
82
+ function groupIntoSlots(evts: TimelineEvent[]): TimeSlot[] {
83
+ const sorted = [...evts].sort((a, b) => a.start.getTime() - b.start.getTime());
84
+ const slots: TimeSlot[] = [];
85
+ for (const ev of sorted) {
86
+ const last = slots[slots.length - 1];
87
+ if (last && ev.start.getTime() < last.endMs) {
88
+ last.events.push(ev);
89
+ last.endMs = Math.max(last.endMs, ev.end.getTime());
90
+ } else {
91
+ slots.push({
92
+ startMs: ev.start.getTime(),
93
+ endMs: ev.end.getTime(),
94
+ events: [ev],
95
+ });
96
+ }
97
+ }
98
+ return slots;
99
+ }
100
+
101
+ // ── Event handlers ──────────────────────────────────
102
+ function handleClick(ev: TimelineEvent): void {
103
+ oneventclick?.(ev);
104
+ }
105
+
106
+ function handleKeydown(e: KeyboardEvent, ev: TimelineEvent): void {
107
+ if (e.key === 'Enter' || e.key === ' ') {
108
+ e.preventDefault();
109
+ oneventclick?.(ev);
110
+ }
111
+ }
112
+
113
+ // ── Day derivations ─────────────────────────────────
114
+ const dayMs = $derived(focusDate ? sod(focusDate.getTime()) : clock.today);
115
+ const dayEnd = $derived(dayMs + DAY_MS);
116
+ const isToday = $derived(dayMs === clock.today);
117
+ const isPastDay = $derived(dayMs < clock.today);
118
+
119
+ const dateLabel = $derived(
120
+ new Date(dayMs).toLocaleDateString(locale ?? 'en-US', {
121
+ weekday: 'long',
122
+ month: 'long',
123
+ day: 'numeric',
124
+ })
125
+ );
126
+
127
+ /** All events for this day, sorted chronologically */
128
+ const dayEvents = $derived.by((): TimelineEvent[] => {
129
+ return events
130
+ .filter((ev) => ev.start.getTime() < dayEnd && ev.end.getTime() > dayMs)
131
+ .sort((a, b) => a.start.getTime() - b.start.getTime());
132
+ });
133
+
134
+ /** All-day / multi-day events shown in a separate strip */
135
+ const allDayBanner = $derived(dayEvents.filter((ev) => isAllDay(ev) || isMultiDay(ev)));
136
+
137
+ /** Timed events (non-all-day) for normal slot rendering */
138
+ const timedDayEvents = $derived(dayEvents.filter((ev) => !isAllDay(ev) && !isMultiDay(ev)));
139
+
140
+ const dayCat = $derived.by(() => {
141
+ const now = clock.tick;
142
+ const past: TimelineEvent[] = [];
143
+ const current: TimelineEvent[] = [];
144
+ const upcoming: TimelineEvent[] = [];
145
+ for (const ev of timedDayEvents) {
146
+ const s = ev.start.getTime();
147
+ const e = ev.end.getTime();
148
+ if (e <= now) past.push(ev);
149
+ else if (s <= now && e > now) current.push(ev);
150
+ else upcoming.push(ev);
151
+ }
152
+ return { past, current, upcomingSlots: groupIntoSlots(upcoming), totalUp: upcoming.length };
153
+ });
154
+
155
+ /** Flat list of next upcoming events (max 5) for the "Up next" column */
156
+ const upcomingNext = $derived.by((): TimelineEvent[] => {
157
+ const all: TimelineEvent[] = [];
158
+ for (const slot of dayCat.upcomingSlots) {
159
+ for (const ev of slot.events) {
160
+ all.push(ev);
161
+ if (all.length >= 5) return all;
162
+ }
163
+ }
164
+ return all;
165
+ });
166
+ </script>
167
+
168
+ <div
169
+ class="ag ag--day"
170
+ style={style || undefined}
171
+ style:height={height ? `${height}px` : undefined}
172
+ >
173
+ <div class="ag-body" role="list" aria-label={L.todaysLineup}>
174
+ {#if allDayBanner.length > 0}
175
+ <!-- ─── All-day / multi-day events ─── -->
176
+ <div class="ag-allday">
177
+ <div class="ag-allday-label">{L.allDay}</div>
178
+ <div class="ag-allday-items">
179
+ {#each allDayBanner as ev (ev.id)}
180
+ <div
181
+ class="ag-allday-chip"
182
+ class:ag-allday-chip--selected={selectedEventId === ev.id}
183
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
184
+ role="button"
185
+ tabindex="0"
186
+ aria-label="{ev.title}, {L.allDay}"
187
+ onclick={() => handleClick(ev)}
188
+ onkeydown={(e) => handleKeydown(e, ev)}
189
+ >
190
+ <span class="ag-allday-dot"></span>
191
+ <span class="ag-allday-title">{ev.title}</span>
192
+ </div>
193
+ {/each}
194
+ </div>
195
+ </div>
196
+ {/if}
197
+
198
+ {#if isToday}
199
+ <!-- ─── Today: "The Queue" — upcoming is the hero ─── -->
200
+ <div class="ag-q">
201
+ <!-- NOW column: past events stacked above NOW strip -->
202
+ <div class="ag-q-status">
203
+ {#if dayCat.past.length > 0}
204
+ <div class="ag-q-done-section">
205
+ <div class="ag-q-label">{L.done}</div>
206
+ {#each dayCat.past as ev (ev.id)}
207
+ <div
208
+ class="ag-q-done-item"
209
+ class:ag-q-done-item--selected={selectedEventId === ev.id}
210
+ role="button"
211
+ tabindex="0"
212
+ aria-label="{ev.title}, {L.completed}, {fmtTime(ev.start)}"
213
+ onclick={() => handleClick(ev)}
214
+ onkeydown={(e) => handleKeydown(e, ev)}
215
+ >
216
+ <span class="ag-q-done-check">✓</span>
217
+ <span class="ag-q-done-title">{ev.title}</span>
218
+ </div>
219
+ {/each}
220
+ </div>
221
+ {/if}
222
+
223
+ <div class="ag-q-label">{L.now} <span class="ag-q-clock">{clock.hm}</span></div>
224
+ {#if dayCat.current.length > 0}
225
+ {#each dayCat.current as ev (ev.id)}
226
+ <div
227
+ class="ag-q-now"
228
+ class:ag-q-now--selected={selectedEventId === ev.id}
229
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
230
+ role="button"
231
+ tabindex="0"
232
+ aria-label="{ev.title}, {L.happeningNow}, {L.percentComplete(Math.round(progress(ev) * 100))}"
233
+ onclick={() => handleClick(ev)}
234
+ onkeydown={(e) => handleKeydown(e, ev)}
235
+ >
236
+ <div class="ag-q-now-dot"></div>
237
+ <div class="ag-q-now-title">{ev.title}</div>
238
+ <div class="ag-q-now-time">{L.until} {fmtTime(ev.end)}</div>
239
+ <div class="ag-q-now-track">
240
+ <div class="ag-q-now-fill" style:width="{progress(ev) * 100}%"></div>
241
+ </div>
242
+ </div>
243
+ {/each}
244
+ {:else}
245
+ <div class="ag-q-free">
246
+ <div class="ag-q-free-label">{L.free}</div>
247
+ </div>
248
+ {/if}
249
+ </div>
250
+
251
+ <!-- NEXT: the hero center column -->
252
+ <div class="ag-q-queue">
253
+ <div class="ag-q-label">{L.upNext}</div>
254
+ {#if upcomingNext.length === 0}
255
+ <div class="ag-q-empty">
256
+ {dayCat.past.length > 0 ? L.allDoneForToday : L.nothingScheduled}
257
+ </div>
258
+ {:else}
259
+ {#each upcomingNext as ev, i (ev.id)}
260
+ <div
261
+ class="ag-card ag-card--q"
262
+ class:ag-card--hero={i === 0}
263
+ class:ag-card--selected={selectedEventId === ev.id}
264
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
265
+ role="button"
266
+ tabindex="0"
267
+ aria-label="{ev.title}, {fmtTime(ev.start)}, {duration(ev)}"
268
+ onclick={() => handleClick(ev)}
269
+ onkeydown={(e) => handleKeydown(e, ev)}
270
+ >
271
+ <div class="ag-card-stripe"></div>
272
+ <div class="ag-card-body">
273
+ <div class="ag-card-top">
274
+ <span class="ag-card-title">{ev.title}</span>
275
+ <span class="ag-card-eta">{timeUntilMs(ev.start.getTime())}</span>
276
+ </div>
277
+ {#if ev.subtitle}
278
+ <span class="ag-card-sub">{ev.subtitle}</span>
279
+ {/if}
280
+ <div class="ag-card-meta">
281
+ {fmtTime(ev.start)} – {fmtTime(ev.end)}
282
+ <span class="ag-card-dur">{duration(ev)}</span>
283
+ </div>
284
+ {#if ev.tags?.length}
285
+ <div class="ag-card-tags">
286
+ {#each ev.tags as tag}
287
+ <span class="ag-card-tag">{tag}</span>
288
+ {/each}
289
+ </div>
290
+ {/if}
291
+ </div>
292
+ </div>
293
+ {/each}
294
+ {/if}
295
+ </div>
296
+ </div>
297
+
298
+ {:else if isPastDay}
299
+ <!-- ─── Past day: "The Log" — everything happened ─── -->
300
+ <div class="ag-log">
301
+ {#if timedDayEvents.length === 0 && allDayBanner.length === 0}
302
+ <div class="ag-q-empty">{L.nothingWasScheduled}</div>
303
+ {:else}
304
+ {#each timedDayEvents as ev (ev.id)}
305
+ <div
306
+ class="ag-log-row"
307
+ class:ag-log-row--selected={selectedEventId === ev.id}
308
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
309
+ role="button"
310
+ tabindex="0"
311
+ aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}"
312
+ onclick={() => handleClick(ev)}
313
+ onkeydown={(e) => handleKeydown(e, ev)}
314
+ >
315
+ <span class="ag-log-check">✓</span>
316
+ <span class="ag-log-time">{fmtTime(ev.start)}</span>
317
+ <span class="ag-log-dot" style:background={ev.color || 'var(--dt-accent)'}></span>
318
+ <span class="ag-log-title">{ev.title}</span>
319
+ <span class="ag-log-dur">{duration(ev)}</span>
320
+ </div>
321
+ {/each}
322
+ {/if}
323
+ </div>
324
+
325
+ {:else}
326
+ <!-- ─── Future day: "The Plan" — everything is ahead ─── -->
327
+ <div class="ag-plan">
328
+ {#if timedDayEvents.length === 0 && allDayBanner.length === 0}
329
+ <div class="ag-q-empty">{L.nothingScheduledYet}</div>
330
+ {:else}
331
+ {#each timedDayEvents as ev, i (ev.id)}
332
+ <div
333
+ class="ag-card ag-card--plan"
334
+ class:ag-card--first={i === 0}
335
+ class:ag-card--selected={selectedEventId === ev.id}
336
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
337
+ role="button"
338
+ tabindex="0"
339
+ aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}, {duration(ev)}"
340
+ onclick={() => handleClick(ev)}
341
+ onkeydown={(e) => handleKeydown(e, ev)}
342
+ >
343
+ <div class="ag-card-stripe"></div>
344
+ <div class="ag-card-body">
345
+ <div class="ag-card-top">
346
+ <span class="ag-card-order">{i + 1}</span>
347
+ <span class="ag-card-title">{ev.title}</span>
348
+ </div>
349
+ {#if ev.subtitle}
350
+ <span class="ag-card-sub">{ev.subtitle}</span>
351
+ {/if}
352
+ <div class="ag-card-meta">
353
+ {fmtTime(ev.start)} – {fmtTime(ev.end)}
354
+ <span class="ag-card-dur">{duration(ev)}</span>
355
+ </div>
356
+ {#if ev.tags?.length}
357
+ <div class="ag-card-tags">
358
+ {#each ev.tags as tag}
359
+ <span class="ag-card-tag">{tag}</span>
360
+ {/each}
361
+ </div>
362
+ {/if}
363
+ </div>
364
+ </div>
365
+ {/each}
366
+ {/if}
367
+ </div>
368
+ {/if}
369
+ </div>
370
+
371
+ <div class="ag-date-label">{dateLabel}</div>
372
+
373
+ <!-- ── Floating nav pills ── -->
374
+ <nav class="ag-nav" aria-label={L.dayNavigation}>
375
+ <button
376
+ class="ag-nav-pill ag-nav-today"
377
+ class:ag-nav-today--hidden={isToday}
378
+ onclick={() => viewState?.goToday()}
379
+ aria-label={L.goToToday}
380
+ tabindex={isToday ? -1 : 0}
381
+ >
382
+ {L.today}
383
+ </button>
384
+ <button
385
+ class="ag-nav-pill"
386
+ onclick={() => viewState?.prev()}
387
+ aria-label={L.previousDay}
388
+ >
389
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><path d="M10 3 5 8l5 5"/></svg>
390
+ </button>
391
+ <button
392
+ class="ag-nav-pill"
393
+ onclick={() => viewState?.next()}
394
+ aria-label={L.nextDay}
395
+ >
396
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="12" height="12" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
397
+ </button>
398
+ </nav>
399
+ </div>
400
+
401
+ <style>
402
+ /* ═══ Floating date label ═══ */
403
+ .ag-date-label {
404
+ position: absolute;
405
+ top: 10px;
406
+ left: 50%;
407
+ transform: translateX(-50%);
408
+ z-index: 20;
409
+ font: 600 11px/1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
410
+ letter-spacing: 0.04em;
411
+ text-transform: uppercase;
412
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
413
+ background: color-mix(in srgb, var(--dt-surface, #10141c) 85%, transparent);
414
+ backdrop-filter: blur(6px);
415
+ -webkit-backdrop-filter: blur(6px);
416
+ padding: 8px 16px;
417
+ border-radius: 8px;
418
+ border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
419
+ pointer-events: none;
420
+ white-space: nowrap;
421
+ }
422
+
423
+ /* ═══ Floating nav pills ═══ */
424
+ .ag-nav {
425
+ position: absolute;
426
+ top: 10px;
427
+ right: 14px;
428
+ z-index: 20;
429
+ display: flex;
430
+ gap: 2px;
431
+ background: color-mix(in srgb, var(--dt-surface, #10141c) 85%, transparent);
432
+ backdrop-filter: blur(6px);
433
+ -webkit-backdrop-filter: blur(6px);
434
+ border-radius: 8px;
435
+ padding: 2px;
436
+ border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
437
+ }
438
+ .ag-nav-pill {
439
+ border: none;
440
+ background: transparent;
441
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
442
+ cursor: pointer;
443
+ font: 600 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
444
+ padding: 6px 12px;
445
+ border-radius: 6px;
446
+ letter-spacing: 0.04em;
447
+ text-transform: uppercase;
448
+ transition: background 100ms, color 100ms;
449
+ display: inline-flex;
450
+ align-items: center;
451
+ justify-content: center;
452
+ }
453
+ .ag-nav-pill:hover {
454
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
455
+ }
456
+ .ag-nav-today {
457
+ max-width: 60px;
458
+ overflow: hidden;
459
+ white-space: nowrap;
460
+ transition: max-width 250ms ease, padding 250ms ease, opacity 200ms ease;
461
+ }
462
+ .ag-nav-today--hidden {
463
+ max-width: 0;
464
+ padding-left: 0;
465
+ padding-right: 0;
466
+ opacity: 0;
467
+ pointer-events: none;
468
+ }
469
+ .ag-nav-pill:focus-visible {
470
+ outline: 2px solid color-mix(in srgb, var(--dt-accent, #ef4444) 55%, transparent);
471
+ outline-offset: 2px;
472
+ }
473
+
474
+ /* ═══ Container ═══ */
475
+ .ag {
476
+ position: relative;
477
+ overflow: hidden;
478
+ user-select: none;
479
+ display: flex;
480
+ flex-direction: column;
481
+ height: 100%;
482
+ color: var(--dt-text, rgba(255, 255, 255, 0.92));
483
+ font-family: var(--dt-sans, system-ui, sans-serif);
484
+ }
485
+
486
+ /* ═══ Body ═══ */
487
+ .ag-body {
488
+ flex: 1;
489
+ display: flex;
490
+ flex-direction: column;
491
+ overflow-y: auto;
492
+ padding-top: 44px;
493
+ scrollbar-width: thin;
494
+ scrollbar-color: var(--dt-border) transparent;
495
+ }
496
+ .ag-body::-webkit-scrollbar {
497
+ width: 4px;
498
+ }
499
+ .ag-body::-webkit-scrollbar-thumb {
500
+ background: var(--dt-border);
501
+ border-radius: 2px;
502
+ }
503
+
504
+ /* ═══ All-day strip ═══ */
505
+ .ag-allday {
506
+ display: flex;
507
+ align-items: center;
508
+ gap: 8px;
509
+ padding: 6px 16px;
510
+ border-bottom: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
511
+ }
512
+ .ag-allday-label {
513
+ font: 600 10px/1 var(--dt-sans, system-ui, sans-serif);
514
+ text-transform: uppercase;
515
+ letter-spacing: 0.06em;
516
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
517
+ white-space: nowrap;
518
+ flex-shrink: 0;
519
+ }
520
+ .ag-allday-items {
521
+ display: flex;
522
+ flex-wrap: wrap;
523
+ gap: 6px;
524
+ }
525
+ .ag-allday-chip {
526
+ display: flex;
527
+ align-items: center;
528
+ gap: 5px;
529
+ padding: 3px 10px;
530
+ border-radius: 6px;
531
+ background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, #10141c));
532
+ border: 1px solid color-mix(in srgb, var(--ev-color) 20%, transparent);
533
+ cursor: pointer;
534
+ transition: background 0.15s, border-color 0.15s;
535
+ }
536
+ .ag-allday-chip:hover {
537
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #10141c));
538
+ border-color: color-mix(in srgb, var(--ev-color) 35%, transparent);
539
+ }
540
+ .ag-allday-chip:focus-visible {
541
+ outline: 2px solid var(--dt-accent, #ff6b4a);
542
+ outline-offset: 2px;
543
+ }
544
+ .ag-allday-chip--selected {
545
+ border-color: var(--ev-color);
546
+ background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, #10141c));
547
+ }
548
+ .ag-allday-dot {
549
+ width: 6px;
550
+ height: 6px;
551
+ border-radius: 50%;
552
+ background: var(--ev-color);
553
+ flex-shrink: 0;
554
+ }
555
+ .ag-allday-title {
556
+ font: 500 0.75rem/1.2 var(--dt-sans, system-ui, sans-serif);
557
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
558
+ white-space: nowrap;
559
+ }
560
+
561
+ /* ═══ Shared: event card ═══ */
562
+ .ag-card {
563
+ display: flex;
564
+ align-items: stretch;
565
+ border-radius: 10px;
566
+ background: var(--dt-surface, #191919);
567
+ border: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
568
+ overflow: hidden;
569
+ cursor: pointer;
570
+ transition: border-color 150ms;
571
+ }
572
+ .ag-card:hover {
573
+ border-color: color-mix(in srgb, var(--ev-color) 40%, transparent);
574
+ }
575
+ .ag-card:focus-visible {
576
+ outline: 2px solid var(--dt-accent, #ff6b4a);
577
+ outline-offset: 2px;
578
+ }
579
+ .ag-card--selected {
580
+ border-color: var(--ev-color);
581
+ background: color-mix(in srgb, var(--ev-color) 6%, var(--dt-surface, #191919));
582
+ }
583
+ .ag-card-stripe {
584
+ width: 3px;
585
+ background: var(--ev-color, var(--dt-accent));
586
+ flex-shrink: 0;
587
+ }
588
+ .ag-card-body {
589
+ padding: 10px 12px;
590
+ display: flex;
591
+ flex-direction: column;
592
+ gap: 4px;
593
+ min-width: 0;
594
+ flex: 1;
595
+ }
596
+ .ag-card-top {
597
+ display: flex;
598
+ justify-content: space-between;
599
+ align-items: flex-start;
600
+ gap: 8px;
601
+ }
602
+ .ag-card-title {
603
+ font-size: 13px;
604
+ font-weight: 600;
605
+ line-height: 1.2;
606
+ white-space: nowrap;
607
+ overflow: hidden;
608
+ text-overflow: ellipsis;
609
+ flex: 1;
610
+ }
611
+ .ag-card-meta {
612
+ font-size: 11px;
613
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
614
+ font-family: var(--dt-mono, monospace);
615
+ line-height: 1;
616
+ }
617
+ .ag-card-dur {
618
+ margin-left: 6px;
619
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
620
+ }
621
+ .ag-card-sub {
622
+ font-size: 11px;
623
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
624
+ line-height: 1;
625
+ }
626
+ .ag-card-tags {
627
+ display: flex;
628
+ gap: 4px;
629
+ flex-wrap: wrap;
630
+ }
631
+ .ag-card-tag {
632
+ font: 500 9px / 1 var(--dt-sans, system-ui, sans-serif);
633
+ color: var(--ev-color, var(--dt-accent));
634
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
635
+ padding: 2px 5px;
636
+ border-radius: 3px;
637
+ white-space: nowrap;
638
+ }
639
+
640
+ /* ── Queue card variant ── */
641
+ .ag-card--q {
642
+ margin-bottom: 6px;
643
+ transition: border-color 150ms, transform 100ms;
644
+ }
645
+ .ag-card--q .ag-card-stripe {
646
+ width: 3.5px;
647
+ }
648
+ .ag-card--q .ag-card-body {
649
+ gap: 3px;
650
+ }
651
+ .ag-card--q .ag-card-tags {
652
+ margin-top: 2px;
653
+ }
654
+ .ag-card-eta {
655
+ font-size: 9px;
656
+ font-weight: 600;
657
+ letter-spacing: 0.04em;
658
+ color: var(--dt-accent, #ff6b4a);
659
+ flex-shrink: 0;
660
+ white-space: nowrap;
661
+ }
662
+ .ag-card--hero {
663
+ background: color-mix(in srgb, var(--ev-color) 8%, var(--dt-surface, #191919));
664
+ border-color: color-mix(in srgb, var(--ev-color) 25%, transparent);
665
+ }
666
+ .ag-card--hero .ag-card-title {
667
+ font-size: 16px;
668
+ font-weight: 700;
669
+ }
670
+ .ag-card--hero .ag-card-eta {
671
+ font-size: 11px;
672
+ background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 18%, transparent);
673
+ padding: 2px 7px;
674
+ border-radius: 4px;
675
+ }
676
+ .ag-card--hero .ag-card-body {
677
+ padding: 14px 16px;
678
+ }
679
+
680
+ /* ── Plan card variant ── */
681
+ .ag-card--plan .ag-card-stripe {
682
+ width: 3.5px;
683
+ }
684
+ .ag-card--plan .ag-card-body {
685
+ padding: 12px 14px;
686
+ gap: 3px;
687
+ }
688
+ .ag-card--plan .ag-card-top {
689
+ align-items: baseline;
690
+ }
691
+ .ag-card-order {
692
+ font-size: 10px;
693
+ font-weight: 700;
694
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.2));
695
+ font-family: var(--dt-mono, monospace);
696
+ flex-shrink: 0;
697
+ }
698
+ .ag-card--plan .ag-card-title {
699
+ font-size: 14px;
700
+ }
701
+ .ag-card--first {
702
+ background: color-mix(in srgb, var(--ev-color) 5%, var(--dt-surface, #191919));
703
+ border-color: color-mix(in srgb, var(--ev-color) 15%, transparent);
704
+ }
705
+ .ag-card--first .ag-card-title {
706
+ font-size: 16px;
707
+ font-weight: 700;
708
+ }
709
+ .ag-card--plan .ag-card-sub {
710
+ padding-left: 22px;
711
+ }
712
+ .ag-card--plan .ag-card-tags {
713
+ padding-left: 22px;
714
+ margin-top: 2px;
715
+ }
716
+
717
+ /* ═══ The Queue: 2-column grid ═══ */
718
+ .ag-q {
719
+ display: grid;
720
+ grid-template-columns: 1fr 1.8fr;
721
+ gap: 0;
722
+ flex: 1;
723
+ padding: 8px 0 10px;
724
+ min-height: 0;
725
+ }
726
+ .ag-q-label {
727
+ font-size: 8px;
728
+ font-weight: 600;
729
+ letter-spacing: 0.14em;
730
+ text-transform: uppercase;
731
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.25));
732
+ margin-bottom: 8px;
733
+ padding: 0 12px;
734
+ font-family: var(--dt-sans, system-ui, sans-serif);
735
+ }
736
+ .ag-q-empty {
737
+ display: flex;
738
+ align-items: center;
739
+ justify-content: center;
740
+ flex: 1;
741
+ font-size: 13px;
742
+ font-weight: 300;
743
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.25));
744
+ }
745
+
746
+ /* ── NOW column (includes Done above) ── */
747
+ .ag-q-status {
748
+ padding: 0 10px 0 14px;
749
+ border-right: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
750
+ display: flex;
751
+ flex-direction: column;
752
+ overflow-y: auto;
753
+ scrollbar-width: none;
754
+ }
755
+ .ag-q-status::-webkit-scrollbar {
756
+ display: none;
757
+ }
758
+ .ag-q-done-section {
759
+ margin-bottom: 10px;
760
+ padding-bottom: 8px;
761
+ border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.06));
762
+ }
763
+ .ag-q-clock {
764
+ font-size: 10px;
765
+ font-weight: 600;
766
+ font-family: var(--dt-mono, monospace);
767
+ color: var(--dt-accent, #ff6b4a);
768
+ margin-left: 4px;
769
+ }
770
+ .ag-q-now {
771
+ padding: 8px 10px;
772
+ border-radius: 8px;
773
+ background: var(--dt-surface, #191919);
774
+ border: 1px solid color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
775
+ cursor: pointer;
776
+ transition: border-color 150ms;
777
+ margin-right: 10px;
778
+ }
779
+ .ag-q-now:hover {
780
+ border-color: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 35%, transparent);
781
+ }
782
+ .ag-q-now:focus-visible {
783
+ outline: 2px solid var(--dt-accent, #ff6b4a);
784
+ outline-offset: 2px;
785
+ }
786
+ .ag-q-now--selected {
787
+ border-color: var(--ev-color, var(--dt-accent));
788
+ }
789
+ .ag-q-now-dot {
790
+ width: 6px;
791
+ height: 6px;
792
+ border-radius: 50%;
793
+ background: var(--ev-color, var(--dt-accent, #ff6b4a));
794
+ margin-bottom: 6px;
795
+ animation: ag-pulse 2.5s ease-in-out infinite;
796
+ }
797
+ @keyframes ag-pulse {
798
+ 0%, 100% { opacity: 1; }
799
+ 50% { opacity: 0.4; }
800
+ }
801
+ .ag-q-now-title {
802
+ font-size: 11px;
803
+ font-weight: 600;
804
+ line-height: 1.2;
805
+ color: var(--dt-text, rgba(255, 255, 255, 0.92));
806
+ white-space: nowrap;
807
+ overflow: hidden;
808
+ text-overflow: ellipsis;
809
+ margin-bottom: 3px;
810
+ }
811
+ .ag-q-now-time {
812
+ font-size: 9px;
813
+ font-family: var(--dt-mono, monospace);
814
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
815
+ margin-bottom: 6px;
816
+ }
817
+ .ag-q-now-track {
818
+ height: 2px;
819
+ background: var(--dt-border, rgba(255, 255, 255, 0.06));
820
+ border-radius: 1px;
821
+ overflow: hidden;
822
+ }
823
+ .ag-q-now-fill {
824
+ height: 100%;
825
+ background: var(--ev-color, var(--dt-accent, #ff6b4a));
826
+ border-radius: 1px;
827
+ transition: width 1s linear;
828
+ }
829
+ .ag-q-free {
830
+ padding: 8px 10px;
831
+ margin-right: 10px;
832
+ }
833
+ .ag-q-free-label {
834
+ font-size: 12px;
835
+ font-weight: 300;
836
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.25));
837
+ margin-bottom: 2px;
838
+ }
839
+
840
+ /* ── NEXT: hero center column ── */
841
+ .ag-q-queue {
842
+ padding: 0 16px;
843
+ overflow-y: auto;
844
+ scrollbar-width: none;
845
+ display: flex;
846
+ flex-direction: column;
847
+ }
848
+ .ag-q-queue::-webkit-scrollbar {
849
+ display: none;
850
+ }
851
+
852
+
853
+ .ag-q-done-item {
854
+ display: flex;
855
+ align-items: center;
856
+ gap: 5px;
857
+ padding: 3px 0;
858
+ opacity: 0.35;
859
+ cursor: pointer;
860
+ }
861
+ .ag-q-done-item:hover {
862
+ opacity: 0.6;
863
+ }
864
+ .ag-q-done-item:focus-visible {
865
+ outline: 2px solid var(--dt-accent, #ff6b4a);
866
+ outline-offset: 2px;
867
+ opacity: 0.6;
868
+ }
869
+ .ag-q-done-item--selected {
870
+ opacity: 0.7;
871
+ }
872
+ .ag-q-done-check {
873
+ font-size: 9px;
874
+ color: var(--dt-success, rgba(120, 200, 140, 0.7));
875
+ flex-shrink: 0;
876
+ }
877
+ .ag-q-done-title {
878
+ font-size: 10px;
879
+ line-height: 1.2;
880
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
881
+ white-space: nowrap;
882
+ overflow: hidden;
883
+ text-overflow: ellipsis;
884
+ text-decoration: line-through;
885
+ text-decoration-color: var(--dt-text-3, rgba(255, 255, 255, 0.15));
886
+ }
887
+
888
+ /* ═══ Past Day: "The Log" ═══ */
889
+ .ag-log {
890
+ flex: 1;
891
+ padding: 8px 20px 12px;
892
+ overflow-y: auto;
893
+ scrollbar-width: none;
894
+ opacity: 0.7;
895
+ }
896
+ .ag-log::-webkit-scrollbar {
897
+ display: none;
898
+ }
899
+ .ag-log-row {
900
+ display: flex;
901
+ align-items: center;
902
+ gap: 10px;
903
+ padding: 8px 0;
904
+ border-bottom: 1px solid var(--dt-border, rgba(255, 255, 255, 0.04));
905
+ cursor: pointer;
906
+ transition: opacity 150ms;
907
+ }
908
+ .ag-log-row:last-child {
909
+ border-bottom: none;
910
+ }
911
+ .ag-log-row:hover {
912
+ opacity: 1;
913
+ }
914
+ .ag-log-row:focus-visible {
915
+ outline: 2px solid var(--dt-accent, #ff6b4a);
916
+ outline-offset: 2px;
917
+ }
918
+ .ag-log-row--selected {
919
+ opacity: 1;
920
+ background: color-mix(in srgb, var(--ev-color) 6%, transparent);
921
+ border-radius: 6px;
922
+ padding-left: 8px;
923
+ padding-right: 8px;
924
+ }
925
+ .ag-log-check {
926
+ font-size: 10px;
927
+ color: var(--dt-success, rgba(120, 200, 140, 0.5));
928
+ flex-shrink: 0;
929
+ }
930
+ .ag-log-time {
931
+ font-size: 11px;
932
+ font-family: var(--dt-mono, monospace);
933
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
934
+ width: 64px;
935
+ flex-shrink: 0;
936
+ }
937
+ .ag-log-dot {
938
+ width: 5px;
939
+ height: 5px;
940
+ border-radius: 50%;
941
+ flex-shrink: 0;
942
+ opacity: 0.6;
943
+ }
944
+ .ag-log-title {
945
+ font-size: 13px;
946
+ font-weight: 500;
947
+ line-height: 1.2;
948
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.55));
949
+ flex: 1;
950
+ white-space: nowrap;
951
+ overflow: hidden;
952
+ text-overflow: ellipsis;
953
+ text-decoration: line-through;
954
+ text-decoration-color: var(--dt-border, rgba(255, 255, 255, 0.08));
955
+ }
956
+ .ag-log-dur {
957
+ font-size: 10px;
958
+ font-family: var(--dt-mono, monospace);
959
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.2));
960
+ flex-shrink: 0;
961
+ }
962
+
963
+ /* ═══ Future Day: "The Plan" ═══ */
964
+ .ag-plan {
965
+ flex: 1;
966
+ padding: 8px 20px 12px;
967
+ overflow-y: auto;
968
+ scrollbar-width: none;
969
+ display: flex;
970
+ flex-direction: column;
971
+ gap: 6px;
972
+ }
973
+ .ag-plan::-webkit-scrollbar {
974
+ display: none;
975
+ }
976
+ </style>