@nomideusz/svelte-calendar 0.6.3 → 0.6.5

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 (47) hide show
  1. package/README.md +192 -10
  2. package/dist/adapters/composite.d.ts +15 -0
  3. package/dist/adapters/composite.js +65 -0
  4. package/dist/adapters/index.d.ts +5 -1
  5. package/dist/adapters/index.js +2 -0
  6. package/dist/adapters/mapped.d.ts +170 -0
  7. package/dist/adapters/mapped.js +343 -0
  8. package/dist/adapters/recurring.d.ts +4 -0
  9. package/dist/adapters/recurring.js +5 -10
  10. package/dist/adapters/types.d.ts +11 -2
  11. package/dist/assets/favicon.svg +9 -1
  12. package/dist/calendar/Calendar.svelte +161 -90
  13. package/dist/calendar/Calendar.svelte.d.ts +12 -0
  14. package/dist/core/index.d.ts +3 -1
  15. package/dist/core/index.js +2 -0
  16. package/dist/core/measure.d.ts +104 -0
  17. package/dist/core/measure.js +195 -0
  18. package/dist/core/time.js +2 -2
  19. package/dist/core/types.d.ts +32 -0
  20. package/dist/engine/event-store.svelte.js +22 -7
  21. package/dist/headless/create-agenda.svelte.d.ts +67 -0
  22. package/dist/headless/create-agenda.svelte.js +139 -0
  23. package/dist/headless/create-calendar.svelte.d.ts +2 -0
  24. package/dist/headless/create-calendar.svelte.js +380 -0
  25. package/dist/headless/index.d.ts +4 -0
  26. package/dist/headless/index.js +2 -0
  27. package/dist/headless/types.d.ts +219 -0
  28. package/dist/headless/types.js +1 -0
  29. package/dist/index.d.ts +6 -4
  30. package/dist/index.js +4 -2
  31. package/dist/primitives/DayHeader.svelte +6 -6
  32. package/dist/primitives/EmptySlot.svelte +3 -3
  33. package/dist/primitives/EventBlock.svelte +99 -14
  34. package/dist/primitives/NowIndicator.svelte +5 -5
  35. package/dist/views/agenda/AgendaDay.svelte +215 -116
  36. package/dist/views/agenda/AgendaWeek.svelte +166 -113
  37. package/dist/views/mobile/MobileDay.svelte +45 -21
  38. package/dist/views/mobile/MobileWeek.svelte +85 -35
  39. package/dist/views/planner/PlannerDay.svelte +116 -60
  40. package/dist/views/planner/PlannerWeek.svelte +228 -166
  41. package/dist/views/shared/context.svelte.d.ts +47 -0
  42. package/dist/views/shared/context.svelte.js +47 -0
  43. package/dist/views/shared/format.d.ts +19 -0
  44. package/dist/views/shared/format.js +46 -0
  45. package/dist/views/shared/index.d.ts +4 -0
  46. package/dist/views/shared/index.js +2 -0
  47. package/package.json +9 -2
@@ -12,14 +12,16 @@
12
12
  * Future day ("The Plan"):
13
13
  * Clean numbered schedule list.
14
14
  */
15
- import { getContext } from 'svelte';
16
15
  import { createClock } from '../../core/clock.svelte.js';
17
16
  import type { TimelineEvent } from '../../core/types.js';
18
17
  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';
18
+ import { getLabels } from '../../core/locale.js';
19
+ import { useCalendarContext } from '../shared/context.svelte.js';
20
+ import { fmtTime, duration, timeUntilMs, progress, groupIntoSlots } from '../shared/format.js';
21
+ import type { TimeSlot } from '../shared/format.js';
21
22
 
22
23
  const L = $derived(getLabels());
24
+ const ctx = useCalendarContext();
23
25
 
24
26
  interface Props {
25
27
  locale?: string;
@@ -43,17 +45,15 @@
43
45
  }: Props = $props();
44
46
 
45
47
  const clock = createClock();
46
- const viewState = getContext<ViewState>('calendar:viewState') as ViewState | undefined;
47
- const showNavCtx = getContext<{ current: boolean }>('calendar:showNavigation') as { current: boolean } | undefined;
48
- const equalDaysCtx = getContext<{ current: boolean }>('calendar:equalDays') as { current: boolean } | undefined;
49
- const showDatesCtx = getContext<{ current: boolean }>('calendar:showDates') as { current: boolean } | undefined;
50
- const mobileCtx = getContext<{ current: boolean }>('calendar:mobile') as { current: boolean } | undefined;
51
- const autoHeightCtx = getContext<{ current: boolean }>('calendar:autoHeight') as { current: boolean } | undefined;
52
- const showNav = $derived(showNavCtx?.current ?? true);
53
- const equalDays = $derived(equalDaysCtx?.current ?? false);
54
- const showDates = $derived(showDatesCtx?.current ?? true);
55
- const isMobile = $derived(mobileCtx?.current ?? false);
56
- const autoHeight = $derived(autoHeightCtx?.current ?? false);
48
+ const viewState = $derived(ctx.viewState);
49
+ const showNav = $derived(ctx.showNav);
50
+ const equalDays = $derived(ctx.equalDays);
51
+ const showDates = $derived(ctx.showDates);
52
+ const isMobile = $derived(ctx.isMobile);
53
+ const autoHeight = $derived(ctx.autoHeight);
54
+ const compact = $derived(ctx.compact);
55
+ const oneventhover = $derived(ctx.oneventhover);
56
+ const disabledSet = $derived(ctx.disabledSet);
57
57
 
58
58
  // ── Swipe navigation (mobile) ──────────────────────
59
59
  let swipeStartX = 0;
@@ -76,65 +76,10 @@
76
76
  }
77
77
  }
78
78
 
79
- // ── New feature contexts ──
80
- const callbacksCtx = getContext<{ oneventhover?: (event: TimelineEvent) => void }>('calendar:callbacks') as { oneventhover?: (event: TimelineEvent) => void } | undefined;
81
- const disabledDatesCtx = getContext<{ current: Date[] | undefined }>('calendar:disabledDates') as { current: Date[] | undefined } | undefined;
82
- const oneventhover = $derived(callbacksCtx?.oneventhover);
83
- const disabledDates = $derived(disabledDatesCtx?.current);
84
- const disabledSet = $derived(new Set(disabledDates?.map(d => sod(d.getTime())) ?? []));
85
-
86
- // ── Format helpers ──────────────────────────────────
87
- function fmtTime(d: Date): string {
88
- return _fmtTime(d, locale);
89
- }
90
-
91
- function duration(ev: TimelineEvent): string {
92
- return fmtDuration(ev.start, ev.end);
93
- }
94
-
95
- function timeUntilMs(ms: number): string {
96
- const diff = ms - clock.tick;
97
- if (diff <= 0) return L.now;
98
- const tMins = Math.floor(diff / 60000);
99
- if (tMins < 60) return `in ${tMins}m`;
100
- const hrs = Math.floor(tMins / 60);
101
- const rm = tMins % 60;
102
- if (hrs < 24) return rm > 0 ? `in ${hrs}h ${rm}m` : `in ${hrs}h`;
103
- const days = Math.floor(hrs / 24);
104
- return `in ${days}d`;
105
- }
106
-
107
- function progress(ev: TimelineEvent): number {
108
- const s = ev.start.getTime();
109
- const e = ev.end.getTime();
110
- return Math.min(1, Math.max(0, (clock.tick - s) / (e - s)));
111
- }
112
-
113
- // ── Overlap grouping ────────────────────────────────
114
- interface TimeSlot {
115
- startMs: number;
116
- endMs: number;
117
- events: TimelineEvent[];
118
- }
119
-
120
- function groupIntoSlots(evts: TimelineEvent[]): TimeSlot[] {
121
- const sorted = [...evts].sort((a, b) => a.start.getTime() - b.start.getTime());
122
- const slots: TimeSlot[] = [];
123
- for (const ev of sorted) {
124
- const last = slots[slots.length - 1];
125
- if (last && ev.start.getTime() < last.endMs) {
126
- last.events.push(ev);
127
- last.endMs = Math.max(last.endMs, ev.end.getTime());
128
- } else {
129
- slots.push({
130
- startMs: ev.start.getTime(),
131
- endMs: ev.end.getTime(),
132
- events: [ev],
133
- });
134
- }
135
- }
136
- return slots;
137
- }
79
+ // ── Format helpers (delegated to shared/format.ts) ──
80
+ const fmt = (d: Date) => fmtTime(d, locale);
81
+ const eta = (ms: number) => timeUntilMs(ms, clock.tick);
82
+ const prog = (ev: TimelineEvent) => progress(ev, clock.tick);
138
83
 
139
84
  // ── Event handlers ──────────────────────────────────
140
85
  function handleClick(ev: TimelineEvent): void {
@@ -244,7 +189,44 @@
244
189
  </div>
245
190
  {/if}
246
191
 
247
- {#if isToday}
192
+ {#if compact}
193
+ <!-- ─── Compact: minimal text rows ─── -->
194
+ <div class="ag-compact-list">
195
+ {#if timedDayEvents.length === 0 && allDayBanner.length === 0}
196
+ <div class="ag-q-empty">{L.nothingScheduledYet}</div>
197
+ {:else}
198
+ {#each timedDayEvents as ev (ev.id)}
199
+ <div
200
+ class="ag-compact-row"
201
+ class:ag-compact-row--selected={selectedEventId === ev.id}
202
+ class:ag-compact-row--cancelled={ev.status === 'cancelled'}
203
+ class:ag-compact-row--tentative={ev.status === 'tentative'}
204
+ style:--ev-color={ev.color || 'var(--dt-accent)'}
205
+ role="button"
206
+ tabindex="0"
207
+ aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
208
+ onclick={() => handleClick(ev)}
209
+ onpointerenter={() => oneventhover?.(ev)}
210
+ onkeydown={(e) => handleKeydown(e, ev)}
211
+ >
212
+ <span class="ag-compact-row-dot"></span>
213
+ <span class="ag-compact-row-time">{fmt(ev.start)}</span>
214
+ <span class="ag-compact-row-title">{ev.title}</span>
215
+ {#if ev.subtitle}
216
+ <span class="ag-compact-row-sub">{ev.subtitle}</span>
217
+ {/if}
218
+ {#if ev.tags?.length}
219
+ {#each ev.tags as tag}
220
+ <span class="ag-compact-row-tag">{tag}</span>
221
+ {/each}
222
+ {/if}
223
+ <span class="ag-compact-row-dur">{duration(ev)}</span>
224
+ </div>
225
+ {/each}
226
+ {/if}
227
+ </div>
228
+
229
+ {:else if isToday}
248
230
  <!-- ─── Today: "The Queue" — upcoming is the hero ─── -->
249
231
  <div class="ag-q">
250
232
  <!-- NOW column: past events stacked above NOW strip -->
@@ -258,7 +240,7 @@
258
240
  class:ag-q-done-item--selected={selectedEventId === ev.id}
259
241
  role="button"
260
242
  tabindex="0"
261
- aria-label="{ev.title}, {L.completed}, {fmtTime(ev.start)}"
243
+ aria-label="{ev.title}, {L.completed}, {fmt(ev.start)}"
262
244
  onclick={() => handleClick(ev)}
263
245
  onkeydown={(e) => handleKeydown(e, ev)}
264
246
  >
@@ -278,14 +260,14 @@
278
260
  style:--ev-color={ev.color || 'var(--dt-accent)'}
279
261
  role="button"
280
262
  tabindex="0"
281
- aria-label="{ev.title}, {L.happeningNow}, {L.percentComplete(Math.round(progress(ev) * 100))}"
263
+ aria-label="{ev.title}, {L.happeningNow}, {L.percentComplete(Math.round(prog(ev) * 100))}"
282
264
  onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
283
265
  >
284
266
  <div class="ag-q-now-dot"></div>
285
267
  <div class="ag-q-now-title">{ev.title}</div>
286
- <div class="ag-q-now-time">{L.until} {fmtTime(ev.end)}</div>
268
+ <div class="ag-q-now-time">{L.until} {fmt(ev.end)}</div>
287
269
  <div class="ag-q-now-track">
288
- <div class="ag-q-now-fill" style:width="{progress(ev) * 100}%"></div>
270
+ <div class="ag-q-now-fill" style:width="{prog(ev) * 100}%"></div>
289
271
  </div>
290
272
  </div>
291
273
  {/each}
@@ -312,7 +294,7 @@
312
294
  style:--ev-color={ev.color || 'var(--dt-accent)'}
313
295
  role="button"
314
296
  tabindex="0"
315
- aria-label="{ev.title}, {fmtTime(ev.start)}, {duration(ev)}"
297
+ aria-label="{ev.title}, {fmt(ev.start)}, {duration(ev)}"
316
298
  onclick={() => handleClick(ev)}
317
299
  onpointerenter={() => oneventhover?.(ev)}
318
300
  onkeydown={(e) => handleKeydown(e, ev)}
@@ -320,13 +302,13 @@
320
302
  <div class="ag-card-body">
321
303
  <div class="ag-card-top">
322
304
  <span class="ag-card-title">{ev.title}</span>
323
- <span class="ag-card-eta">{timeUntilMs(ev.start.getTime())}</span>
305
+ <span class="ag-card-eta">{eta(ev.start.getTime())}</span>
324
306
  </div>
325
307
  {#if ev.subtitle}
326
308
  <span class="ag-card-sub">{ev.subtitle}</span>
327
309
  {/if}
328
310
  <div class="ag-card-meta">
329
- {fmtTime(ev.start)} – {fmtTime(ev.end)}
311
+ {fmt(ev.start)} – {fmt(ev.end)}
330
312
  <span class="ag-card-dur">{duration(ev)}</span>
331
313
  </div>
332
314
  {#if ev.tags?.length}
@@ -356,11 +338,11 @@
356
338
  style:--ev-color={ev.color || 'var(--dt-accent)'}
357
339
  role="button"
358
340
  tabindex="0"
359
- aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}"
341
+ aria-label="{ev.title}, {fmt(ev.start)} to {fmt(ev.end)}"
360
342
  onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
361
343
  >
362
344
  <span class="ag-log-check">✓</span>
363
- <span class="ag-log-time">{fmtTime(ev.start)}</span>
345
+ <span class="ag-log-time">{fmt(ev.start)}</span>
364
346
  <span class="ag-log-dot" style:background={ev.color || 'var(--dt-accent)'}></span>
365
347
  <span class="ag-log-title">{ev.title}</span>
366
348
  <span class="ag-log-dur">{duration(ev)}</span>
@@ -380,10 +362,14 @@
380
362
  class="ag-card ag-card--plan"
381
363
  class:ag-card--first={i === 0}
382
364
  class:ag-card--selected={selectedEventId === ev.id}
365
+ class:ag-card--cancelled={ev.status === 'cancelled'}
366
+ class:ag-card--tentative={ev.status === 'tentative'}
367
+ class:ag-card--full={ev.status === 'full'}
368
+ class:ag-card--limited={ev.status === 'limited'}
383
369
  style:--ev-color={ev.color || 'var(--dt-accent)'}
384
370
  role="button"
385
371
  tabindex="0"
386
- aria-label="{ev.title}, {fmtTime(ev.start)} to {fmtTime(ev.end)}, {duration(ev)}"
372
+ aria-label="{ev.title}{ev.status === 'cancelled' ? ' (cancelled)' : ''}{ev.status === 'tentative' ? ' (tentative)' : ''}{ev.status === 'full' ? ' (full)' : ''}{ev.status === 'limited' ? ' (limited)' : ''}, {fmt(ev.start)} to {fmt(ev.end)}, {duration(ev)}"
387
373
  onclick={() => handleClick(ev)} onpointerenter={() => oneventhover?.(ev)} onkeydown={(e) => handleKeydown(e, ev)}
388
374
  >
389
375
  <div class="ag-card-body">
@@ -394,8 +380,11 @@
394
380
  {#if ev.subtitle}
395
381
  <span class="ag-card-sub">{ev.subtitle}</span>
396
382
  {/if}
383
+ {#if ev.location}
384
+ <span class="ag-card-loc">{ev.location}</span>
385
+ {/if}
397
386
  <div class="ag-card-meta">
398
- {fmtTime(ev.start)} – {fmtTime(ev.end)}
387
+ {fmt(ev.start)} – {fmt(ev.end)}
399
388
  <span class="ag-card-dur">{duration(ev)}</span>
400
389
  </div>
401
390
  {#if ev.tags?.length}
@@ -455,11 +444,11 @@
455
444
  left: 50%;
456
445
  transform: translateX(-50%);
457
446
  z-index: 20;
458
- font: 600 11px/1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
447
+ font: 600 11px/1 var(--dt-sans, system-ui, sans-serif);
459
448
  letter-spacing: 0.04em;
460
449
  text-transform: uppercase;
461
450
  color: var(--dt-text, rgba(226, 232, 240, 0.85));
462
- background: color-mix(in srgb, var(--dt-surface, #10141c) 85%, transparent);
451
+ background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
463
452
  backdrop-filter: blur(6px);
464
453
  -webkit-backdrop-filter: blur(6px);
465
454
  padding: 8px 16px;
@@ -477,7 +466,7 @@
477
466
  z-index: 20;
478
467
  display: flex;
479
468
  gap: 2px;
480
- background: color-mix(in srgb, var(--dt-surface, #10141c) 85%, transparent);
469
+ background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
481
470
  backdrop-filter: blur(6px);
482
471
  -webkit-backdrop-filter: blur(6px);
483
472
  border-radius: 8px;
@@ -489,7 +478,7 @@
489
478
  background: transparent;
490
479
  color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
491
480
  cursor: pointer;
492
- font: 600 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
481
+ font: 600 11px / 1 var(--dt-sans, system-ui, sans-serif);
493
482
  padding: 6px 12px;
494
483
  border-radius: 6px;
495
484
  letter-spacing: 0.04em;
@@ -516,7 +505,7 @@
516
505
  pointer-events: none;
517
506
  }
518
507
  .ag-nav-pill:focus-visible {
519
- outline: 2px solid color-mix(in srgb, var(--dt-accent, #ef4444) 55%, transparent);
508
+ outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
520
509
  outline-offset: 2px;
521
510
  }
522
511
 
@@ -528,6 +517,9 @@
528
517
  display: flex;
529
518
  flex-direction: column;
530
519
  height: 100%;
520
+ width: 100%;
521
+ min-width: 0;
522
+ box-sizing: border-box;
531
523
  color: var(--dt-text, rgba(255, 255, 255, 0.92));
532
524
  font-family: var(--dt-sans, system-ui, sans-serif);
533
525
  }
@@ -535,17 +527,24 @@
535
527
  .ag--auto { height: auto; overflow: visible; }
536
528
 
537
529
  .ag--disabled {
538
- opacity: 0.4;
539
- pointer-events: none;
530
+ background-image: repeating-linear-gradient(
531
+ 135deg,
532
+ transparent,
533
+ transparent 6px,
534
+ color-mix(in srgb, var(--dt-text, rgba(255, 255, 255, 0.92)) 4%, transparent) 6px,
535
+ color-mix(in srgb, var(--dt-text, rgba(255, 255, 255, 0.92)) 4%, transparent) 12px
536
+ );
540
537
  }
541
538
 
542
539
  /* ═══ Body ═══ */
543
540
  .ag-body {
544
541
  flex: 1;
545
542
  min-height: 0;
543
+ min-width: 0;
546
544
  display: flex;
547
545
  flex-direction: column;
548
546
  overflow-y: auto;
547
+ overflow-x: hidden;
549
548
  padding-top: 44px;
550
549
  scrollbar-width: thin;
551
550
  scrollbar-color: var(--dt-border) transparent;
@@ -587,22 +586,22 @@
587
586
  gap: 5px;
588
587
  padding: 3px 10px;
589
588
  border-radius: 6px;
590
- background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, #10141c));
589
+ background: color-mix(in srgb, var(--ev-color) 12%, var(--dt-surface, var(--dt-bg, #ffffff)));
591
590
  border: 1px solid color-mix(in srgb, var(--ev-color) 20%, transparent);
592
591
  cursor: pointer;
593
592
  transition: background 0.15s, border-color 0.15s;
594
593
  }
595
594
  .ag-allday-chip:hover {
596
- background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #10141c));
595
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
597
596
  border-color: color-mix(in srgb, var(--ev-color) 35%, transparent);
598
597
  }
599
598
  .ag-allday-chip:focus-visible {
600
- outline: 2px solid var(--dt-accent, #ff6b4a);
599
+ outline: 2px solid var(--dt-accent, #2563eb);
601
600
  outline-offset: 2px;
602
601
  }
603
602
  .ag-allday-chip--selected {
604
603
  border-color: var(--ev-color);
605
- background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, #10141c));
604
+ background: color-mix(in srgb, var(--ev-color) 18%, var(--dt-surface, var(--dt-bg, #ffffff)));
606
605
  }
607
606
  .ag-allday-dot {
608
607
  width: 6px;
@@ -622,23 +621,40 @@
622
621
  display: flex;
623
622
  align-items: stretch;
624
623
  border-radius: 10px;
625
- background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, #191919));
624
+ background: color-mix(in srgb, var(--ev-color) 15%, var(--dt-surface, var(--dt-bg, #ffffff)));
626
625
  border: 1px solid color-mix(in srgb, var(--ev-color) 10%, var(--dt-border, rgba(255, 255, 255, 0.06)));
627
626
  overflow: hidden;
628
627
  cursor: pointer;
629
628
  transition: background 150ms, border-color 150ms;
630
629
  }
631
630
  .ag-card:hover {
632
- background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, #191919));
631
+ background: color-mix(in srgb, var(--ev-color) 25%, var(--dt-surface, var(--dt-bg, #ffffff)));
633
632
  border-color: color-mix(in srgb, var(--ev-color) 40%, transparent);
634
633
  }
635
634
  .ag-card:focus-visible {
636
- outline: 2px solid var(--dt-accent, #ff6b4a);
635
+ outline: 2px solid var(--dt-accent, #2563eb);
637
636
  outline-offset: 2px;
638
637
  }
639
638
  .ag-card--selected {
640
639
  border-color: var(--ev-color);
641
- background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #191919));
640
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
641
+ }
642
+ .ag-card--cancelled {
643
+ opacity: 0.5;
644
+ }
645
+ .ag-card--cancelled .ag-card-title {
646
+ text-decoration: line-through;
647
+ }
648
+ .ag-card--tentative {
649
+ opacity: 0.65;
650
+ border-style: dashed;
651
+ }
652
+ .ag-card--full {
653
+ opacity: 0.55;
654
+ }
655
+ .ag-card--limited {
656
+ opacity: 0.65;
657
+ border-style: dashed;
642
658
  }
643
659
  .ag-card-body {
644
660
  padding: 10px 12px;
@@ -653,15 +669,15 @@
653
669
  justify-content: space-between;
654
670
  align-items: flex-start;
655
671
  gap: 8px;
672
+ min-width: 0;
656
673
  }
657
674
  .ag-card-title {
658
675
  font-size: 13px;
659
676
  font-weight: 600;
660
- line-height: 1.2;
661
- white-space: nowrap;
662
- overflow: hidden;
663
- text-overflow: ellipsis;
677
+ line-height: 1.3;
678
+ word-break: break-word;
664
679
  flex: 1;
680
+ min-width: 0;
665
681
  }
666
682
  .ag-card-meta {
667
683
  font-size: 11px;
@@ -678,6 +694,11 @@
678
694
  color: var(--dt-text-2, rgba(255, 255, 255, 0.45));
679
695
  line-height: 1;
680
696
  }
697
+ .ag-card-loc {
698
+ font-size: 10px;
699
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
700
+ line-height: 1;
701
+ }
681
702
  .ag-card-tags {
682
703
  display: flex;
683
704
  gap: 4px;
@@ -708,12 +729,12 @@
708
729
  font-size: 9px;
709
730
  font-weight: 600;
710
731
  letter-spacing: 0.04em;
711
- color: var(--dt-accent, #ff6b4a);
732
+ color: var(--dt-accent, #2563eb);
712
733
  flex-shrink: 0;
713
734
  white-space: nowrap;
714
735
  }
715
736
  .ag-card--hero {
716
- background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, #191919));
737
+ background: color-mix(in srgb, var(--ev-color) 22%, var(--dt-surface, var(--dt-bg, #ffffff)));
717
738
  border-color: color-mix(in srgb, var(--ev-color) 30%, transparent);
718
739
  }
719
740
  .ag-card--hero .ag-card-title {
@@ -722,7 +743,7 @@
722
743
  }
723
744
  .ag-card--hero .ag-card-eta {
724
745
  font-size: 11px;
725
- background: color-mix(in srgb, var(--dt-accent, #ff6b4a) 18%, transparent);
746
+ background: color-mix(in srgb, var(--dt-accent, #2563eb) 18%, transparent);
726
747
  padding: 2px 7px;
727
748
  border-radius: 4px;
728
749
  }
@@ -750,7 +771,7 @@
750
771
  font-size: 14px;
751
772
  }
752
773
  .ag-card--first {
753
- background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, #191919));
774
+ background: color-mix(in srgb, var(--ev-color) 20%, var(--dt-surface, var(--dt-bg, #ffffff)));
754
775
  border-color: color-mix(in srgb, var(--ev-color) 25%, transparent);
755
776
  }
756
777
  .ag-card--first .ag-card-title {
@@ -860,24 +881,24 @@
860
881
  font-size: 10px;
861
882
  font-weight: 600;
862
883
  font-family: var(--dt-mono, monospace);
863
- color: var(--dt-accent, #ff6b4a);
884
+ color: var(--dt-accent, #2563eb);
864
885
  margin-left: 4px;
865
886
  }
866
887
  .ag-q-now {
867
888
  padding: 8px 10px;
868
889
  border-radius: 8px;
869
- background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, var(--dt-surface, #191919));
890
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, var(--dt-surface, var(--dt-bg, #ffffff)));
870
891
  border: 1px solid color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
871
892
  cursor: pointer;
872
893
  transition: background 150ms, border-color 150ms;
873
894
  margin-right: 10px;
874
895
  }
875
896
  .ag-q-now:hover {
876
- background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 25%, var(--dt-surface, #191919));
897
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 25%, var(--dt-surface, var(--dt-bg, #ffffff)));
877
898
  border-color: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 35%, transparent);
878
899
  }
879
900
  .ag-q-now:focus-visible {
880
- outline: 2px solid var(--dt-accent, #ff6b4a);
901
+ outline: 2px solid var(--dt-accent, #2563eb);
881
902
  outline-offset: 2px;
882
903
  }
883
904
  .ag-q-now--selected {
@@ -887,7 +908,7 @@
887
908
  width: 6px;
888
909
  height: 6px;
889
910
  border-radius: 50%;
890
- background: var(--ev-color, var(--dt-accent, #ff6b4a));
911
+ background: var(--ev-color, var(--dt-accent, #2563eb));
891
912
  margin-bottom: 6px;
892
913
  animation: ag-pulse 2.5s ease-in-out infinite;
893
914
  }
@@ -919,7 +940,7 @@
919
940
  }
920
941
  .ag-q-now-fill {
921
942
  height: 100%;
922
- background: var(--ev-color, var(--dt-accent, #ff6b4a));
943
+ background: var(--ev-color, var(--dt-accent, #2563eb));
923
944
  border-radius: 1px;
924
945
  transition: width 1s linear;
925
946
  }
@@ -959,7 +980,7 @@
959
980
  opacity: 0.6;
960
981
  }
961
982
  .ag-q-done-item:focus-visible {
962
- outline: 2px solid var(--dt-accent, #ff6b4a);
983
+ outline: 2px solid var(--dt-accent, #2563eb);
963
984
  outline-offset: 2px;
964
985
  opacity: 0.6;
965
986
  }
@@ -1009,7 +1030,7 @@
1009
1030
  opacity: 1;
1010
1031
  }
1011
1032
  .ag-log-row:focus-visible {
1012
- outline: 2px solid var(--dt-accent, #ff6b4a);
1033
+ outline: 2px solid var(--dt-accent, #2563eb);
1013
1034
  outline-offset: 2px;
1014
1035
  }
1015
1036
  .ag-log-row--selected {
@@ -1057,6 +1078,84 @@
1057
1078
  flex-shrink: 0;
1058
1079
  }
1059
1080
 
1081
+ /* ═══ Compact Day ═══ */
1082
+ .ag-compact-list {
1083
+ flex: 1;
1084
+ padding: 8px 20px 12px;
1085
+ overflow-y: auto;
1086
+ scrollbar-width: none;
1087
+ }
1088
+ .ag-compact-list::-webkit-scrollbar { display: none; }
1089
+ .ag-compact-row {
1090
+ display: flex;
1091
+ align-items: center;
1092
+ gap: 8px;
1093
+ padding: 4px 0;
1094
+ cursor: pointer;
1095
+ }
1096
+ .ag-compact-row--selected {
1097
+ background: color-mix(in srgb, var(--ev-color) 10%, transparent);
1098
+ border-radius: 4px;
1099
+ padding-left: 6px;
1100
+ padding-right: 6px;
1101
+ }
1102
+ .ag-compact-row:hover .ag-compact-row-title { color: var(--dt-text); }
1103
+ .ag-compact-row:focus-visible {
1104
+ outline: 2px solid var(--dt-accent, #2563eb);
1105
+ outline-offset: 2px;
1106
+ }
1107
+ .ag-compact-row-dot {
1108
+ width: 5px;
1109
+ height: 5px;
1110
+ border-radius: 50%;
1111
+ background: var(--ev-color, var(--dt-accent));
1112
+ flex-shrink: 0;
1113
+ }
1114
+ .ag-compact-row-time {
1115
+ font-size: 11px;
1116
+ font-family: var(--dt-mono, monospace);
1117
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
1118
+ min-width: 64px;
1119
+ flex-shrink: 0;
1120
+ }
1121
+ .ag-compact-row-title {
1122
+ font-size: 12px;
1123
+ font-weight: 500;
1124
+ color: var(--dt-text-2, rgba(255, 255, 255, 0.5));
1125
+ flex: 1;
1126
+ white-space: nowrap;
1127
+ overflow: hidden;
1128
+ text-overflow: ellipsis;
1129
+ transition: color 150ms;
1130
+ }
1131
+ .ag-compact-row-dur {
1132
+ font-size: 10px;
1133
+ font-family: var(--dt-mono, monospace);
1134
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.3));
1135
+ flex-shrink: 0;
1136
+ }
1137
+ .ag-compact-row-sub {
1138
+ font-size: 10px;
1139
+ color: var(--dt-text-3, rgba(255, 255, 255, 0.35));
1140
+ flex-shrink: 0;
1141
+ }
1142
+ .ag-compact-row-tag {
1143
+ font: 500 8px / 1 var(--dt-sans, system-ui, sans-serif);
1144
+ color: var(--ev-color, var(--dt-accent));
1145
+ background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 12%, transparent);
1146
+ padding: 1px 4px;
1147
+ border-radius: 3px;
1148
+ white-space: nowrap;
1149
+ flex-shrink: 0;
1150
+ }
1151
+ .ag-compact-row--cancelled { opacity: 0.5; }
1152
+ .ag-compact-row--cancelled .ag-compact-row-title { text-decoration: line-through; }
1153
+ .ag-compact-row--tentative { opacity: 0.65; }
1154
+ /* Mobile: larger touch targets for compact rows */
1155
+ .ag--mobile .ag-compact-row { padding: 8px 0; }
1156
+ .ag--mobile .ag-compact-row-title { font-size: 14px; }
1157
+ .ag--mobile .ag-compact-row-time { font-size: 12px; }
1158
+
1060
1159
  /* ═══ Future Day: "The Plan" ═══ */
1061
1160
  .ag-plan {
1062
1161
  flex: 1;