@nomideusz/svelte-calendar 0.6.8 → 0.7.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.
package/README.md CHANGED
@@ -50,6 +50,26 @@ Hide the pills when your app controls the view externally:
50
50
  <Calendar {adapter} view="day-planner" showModePills={false} />
51
51
  ```
52
52
 
53
+ ### Custom Views
54
+
55
+ The `views` prop replaces the built-in registry. Each entry maps a view ID to a Svelte component; your component receives `events`, `mode`, `focusDate`, `locale`, and the callbacks, and can read the calendar engines via context:
56
+
57
+ ```svelte
58
+ <script lang="ts">
59
+ import { Calendar, type CalendarView } from '@nomideusz/svelte-calendar';
60
+ import KanbanDay from './KanbanDay.svelte';
61
+
62
+ const views: CalendarView[] = [
63
+ { id: 'day-kanban', label: 'Kanban', mode: 'day', component: KanbanDay },
64
+ { id: 'week-kanban', label: 'Kanban', mode: 'week', component: KanbanDay },
65
+ ];
66
+ </script>
67
+
68
+ <Calendar {adapter} {views} view="day-kanban" />
69
+ ```
70
+
71
+ Building blocks for custom views are exported as primitives: `EventBlock` (a positioned event card), `TimeGutter` (hour labels), `DayHeader`, `NowIndicator`, and `EmptySlot` (click-to-create target).
72
+
53
73
  ## Mobile
54
74
 
55
75
  On narrow screens (`< 768px`), the calendar automatically remaps Planner views to touch-first Mobile views with swipe navigation, a centralized header with Day/Week pills, and a compact layout:
@@ -69,7 +89,7 @@ Mobile views include:
69
89
  - **MobileDay** — vertical time grid with hour labels, swipe left/right to change days, all-day event chips at the top, tap-to-create, and stable columns for overlapping events
70
90
  - **MobileWeek** — vertical day list showing each day's events with relative labels (Today, Tomorrow, etc.) and accessible event buttons inside each row
71
91
 
72
- Agenda views keep their list-based layout on mobile but hide desktop floating navigation in favor of the centralized header.
92
+ Agenda views keep their list-based layout on mobile; navigation stays in the calendar header on all screen sizes.
73
93
 
74
94
  ## Callbacks
75
95
 
@@ -229,6 +249,8 @@ The default `auto` preset probes the host page's design and generates a calendar
229
249
 
230
250
  It also watches for changes (e.g. dark mode toggle) and updates automatically.
231
251
 
252
+ The probing engine is exported for standalone use: `probeHostTheme(element, options?)` returns a `--dt-*` CSS string for the page around `element`; `observeHostTheme(element, callback, options?)` re-probes on theme changes and returns a stop function.
253
+
232
254
  Fine-tune auto-detection with the `autoTheme` prop:
233
255
 
234
256
  ```svelte
@@ -355,6 +377,8 @@ const palette = generatePalette('#e11d48');
355
377
  const adapter = createMemoryAdapter(events, { palette });
356
378
  ```
357
379
 
380
+ Related exports: `VIVID_PALETTE` (the default palette) and `extractAccent(themeString)` (pull the accent color out of a `--dt-*` theme string).
381
+
358
382
  ### Multi-day & All-day
359
383
 
360
384
  Events spanning multiple days or flagged `allDay: true` render in a dedicated strip above timed events:
@@ -443,6 +467,31 @@ const adapter = createRestAdapter({
443
467
 
444
468
  The adapter calls `GET /events?start=...&end=...`, `POST /events`, `PATCH /events/:id`, and `DELETE /events/:id`.
445
469
 
470
+ ## JMAP Adapter
471
+
472
+ Read events from a [JMAP Calendars](https://jmap.io/) server (Fastmail, Stalwart, Cyrus):
473
+
474
+ ```ts
475
+ import { createJmapAdapter, type JmapClient } from '@nomideusz/svelte-calendar';
476
+
477
+ const client: JmapClient = {
478
+ request: (calls) =>
479
+ fetch('https://api.example.com/jmap', {
480
+ method: 'POST',
481
+ headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
482
+ body: JSON.stringify({ using: ['urn:ietf:params:jmap:calendars'], methodCalls: calls }),
483
+ }).then((r) => r.json()),
484
+ };
485
+
486
+ const adapter = createJmapAdapter(client, {
487
+ getAccountId: () => accountId,
488
+ calendarId: 'primary', // optional: restrict to one calendar
489
+ timeZone: 'Europe/Warsaw', // optional: default Etc/UTC
490
+ });
491
+ ```
492
+
493
+ Handles all-day events, ISO-duration ends, and per-calendar colors (pass `calendars` to map them).
494
+
446
495
  ## Mapped Adapter
447
496
 
448
497
  Wrap any static array of external records (yoga classes, gym schedules, appointments, timetables) without writing a custom adapter. Supply a declarative field mapping — or a `mapEvent` function for full control — and the adapter handles parsing, color assignment, tag extraction, and status coercion:
@@ -561,6 +610,8 @@ For full control over rendering, skip the `<Calendar>` component and drive every
561
610
 
562
611
  `cal.days` gives flat `HeadlessDay[]` with events attached; `cal.weeks` groups them into periods. `cal.todayQueue` returns `{ past, current, upcoming }` for today — updates every second via the built-in clock. `cal.hours` yields the visible hour numbers (cropped by `visibleHours`). Drag support is fully wired: `beginDragMove`, `beginDragCreate`, `updateDrag`, `commitDrag`, `cancelDrag`, plus `isDragging` / `dragPayload` / `dragMode` signals. Raw engines (`store`, `viewState`, `selection`, `dragState`, `clock`) are exposed for advanced cases.
563
612
 
613
+ The engine factories behind them are also exported individually — `createEventStore(adapter)`, `createViewState(options)`, `createSelection()`, `createDragState()`, `createClock()` — if you want to compose your own calendar loop instead of using `createCalendar()`.
614
+
564
615
  For a simpler day-only list view, use `createAgenda()`:
565
616
 
566
617
  ```svelte
@@ -589,6 +640,8 @@ The `locale` prop controls date/time formatting (BCP 47):
589
640
  <Calendar {adapter} locale="de-DE" />
590
641
  ```
591
642
 
643
+ `setDefaultLocale('de-DE')` sets the locale globally instead of per component (`getDefaultLocale()` reads it back, `is24HourLocale(tag)` tells you how times will format).
644
+
592
645
  Override UI labels for full translation:
593
646
 
594
647
  ```ts
@@ -601,7 +654,7 @@ setLabels({
601
654
  });
602
655
  ```
603
656
 
604
- Call `resetLabels()` to restore English defaults.
657
+ Call `resetLabels()` to restore English defaults (`defaultLabels` exports them; `getLabels()` reads the active set).
605
658
 
606
659
  <details>
607
660
  <summary>All label keys</summary>
@@ -651,8 +704,41 @@ const utc = fromZonedTime(localDate, 'America/New_York');
651
704
 
652
705
  // Current time in a timezone
653
706
  const now = nowInZone('Asia/Tokyo');
707
+
708
+ // Format a date directly in a timezone
709
+ formatInTimeZone(date, 'America/New_York', { hour: '2-digit', minute: '2-digit' });
710
+ ```
711
+
712
+ ## Text Fitting (optional)
713
+
714
+ Event cards fit their labels using character-width heuristics. For pixel-precise fitting (long titles, narrow columns, custom fonts), initialize the optional [Pretext](https://www.npmjs.com/package/pretext) measurement engine once at app startup — everything falls back gracefully if you don't:
715
+
716
+ ```svelte
717
+ <script>
718
+ import { onMount } from 'svelte';
719
+ import { initTextMeasure } from '@nomideusz/svelte-calendar';
720
+
721
+ onMount(() => initTextMeasure()); // resolves true if Pretext loaded
722
+ </script>
654
723
  ```
655
724
 
725
+ Custom views can measure text themselves via `createTextMeasure(options)` → `fitContent({ title, subtitle, maxWidth, maxHeight, … })`.
726
+
727
+ ## Utilities
728
+
729
+ Small helpers used by the built-in views, exported for custom rendering:
730
+
731
+ | Export | Purpose |
732
+ |--------|---------|
733
+ | `fmtTime(date, locale?)` / `fmtH(hour, locale?)` | Locale-aware time / hour labels |
734
+ | `fmtDuration(ms)` | `"1h 30m"`-style durations |
735
+ | `fmtDay` / `fmtWeekRange` / `dateShort` / `dateWithWeekday` | Date labels |
736
+ | `weekdayShort` / `weekdayLong` / `monthShort` / `monthLong` | Name parts from a day timestamp |
737
+ | `startOfWeek(ms, mondayStart)` | Start-of-week timestamp |
738
+ | `isAllDay(ev)` / `isMultiDay(ev)` | Event classification |
739
+ | `segmentForDay(ev, dayMs)` | The slice of a multi-day event that falls on one day |
740
+ | `createClock()` | Reactive clock (`tick`, `today`) driving now-lines and relative labels |
741
+
656
742
  ## Embeddable Widget
657
743
 
658
744
  Drop into any HTML page — no build tools needed. Registers a `<day-calendar>` custom element:
@@ -353,7 +353,6 @@
353
353
  get snapInterval() { return snapInterval; },
354
354
  get eventSnippet() { return eventSnippet; },
355
355
  get emptySnippet() { return emptySnippet; },
356
- get showNavigation() { return showNavigation; },
357
356
  get equalDays() { return equalDays; },
358
357
  get showDates() { return showDates; },
359
358
  get hideDays() { return hideDays; },
@@ -571,23 +570,44 @@
571
570
  </div>
572
571
  {/if}
573
572
 
574
- <!-- ─── Desktop chrome ─── -->
575
- {:else}
576
- <!-- Floating mode pills (hidden for Agenda views) -->
577
- {#if showModePills && modes.length > 1 && activeView?.label !== 'Agenda'}
578
- <div class="cal-pills" role="group" aria-label={L.viewMode}>
579
- {#each modes as g}
580
- <button
581
- class="cal-pill"
582
- class:cal-pill--active={viewState.mode === g}
583
- aria-pressed={viewState.mode === g}
584
- onclick={() => switchMode(g)}
585
- >
586
- {g === 'day' ? L.day : L.week}
573
+ <!-- ─── Desktop header ─── -->
574
+ {:else if showNavigation || (showModePills && modes.length > 1) || dateLabel}
575
+ <div class="cal-hd">
576
+ <div class="cal-hd-side">
577
+ {#if navigationSnippet}
578
+ {@render navigationSnippet(navCtx)}
579
+ {:else if showNavigation}
580
+ {#if !viewIncludesToday}
581
+ <button class="cal-hd-today" onclick={() => viewState.goToday()} title={L.goToToday}>
582
+ {L.today}
583
+ </button>
584
+ {/if}
585
+ <button class="cal-hd-btn" onclick={() => viewState.prev()} aria-label={viewState.mode === 'day' ? L.previousDay : L.previousWeek}>
586
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16" aria-hidden="true"><path d="M10 3 5 8l5 5"/></svg>
587
587
  </button>
588
- {/each}
588
+ <button class="cal-hd-btn" onclick={() => viewState.next()} aria-label={viewState.mode === 'day' ? L.nextDay : L.nextWeek}>
589
+ <svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" width="16" height="16" aria-hidden="true"><path d="M6 3l5 5-5 5"/></svg>
590
+ </button>
591
+ {/if}
589
592
  </div>
590
- {/if}
593
+ <span class="cal-hd-title">{dateLabel}</span>
594
+ <div class="cal-hd-side cal-hd-side--end">
595
+ {#if showModePills && modes.length > 1}
596
+ <div class="cal-pills" role="group" aria-label={L.viewMode}>
597
+ {#each modes as g}
598
+ <button
599
+ class="cal-pill"
600
+ class:cal-pill--active={viewState.mode === g}
601
+ aria-pressed={viewState.mode === g}
602
+ onclick={() => switchMode(g)}
603
+ >
604
+ {g === 'day' ? L.day : L.week}
605
+ </button>
606
+ {/each}
607
+ </div>
608
+ {/if}
609
+ </div>
610
+ </div>
591
611
  {/if}
592
612
 
593
613
  <div class="cal-body">
@@ -629,7 +649,7 @@
629
649
  overflow: clip;
630
650
  display: flex;
631
651
  flex-direction: column;
632
- border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
652
+ border: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
633
653
  box-sizing: border-box;
634
654
  }
635
655
  .cal--auto {
@@ -638,38 +658,105 @@
638
658
  }
639
659
 
640
660
 
641
- /* ── Floating pills ── */
661
+ /* ── Desktop header ── */
662
+ .cal-hd {
663
+ display: flex;
664
+ align-items: center;
665
+ gap: 8px;
666
+ padding: 8px 12px;
667
+ min-height: 48px;
668
+ box-sizing: border-box;
669
+ border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
670
+ flex-shrink: 0;
671
+ }
672
+
673
+ .cal-hd-side {
674
+ display: flex;
675
+ align-items: center;
676
+ gap: 4px;
677
+ flex: 1;
678
+ min-width: 0;
679
+ }
680
+
681
+ .cal-hd-side--end {
682
+ justify-content: flex-end;
683
+ }
684
+
685
+ .cal-hd-title {
686
+ font: 600 14px/1.2 var(--dt-sans, system-ui, sans-serif);
687
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
688
+ white-space: nowrap;
689
+ overflow: hidden;
690
+ text-overflow: ellipsis;
691
+ }
692
+
693
+ .cal-hd-btn {
694
+ display: flex;
695
+ align-items: center;
696
+ justify-content: center;
697
+ width: 28px;
698
+ height: 28px;
699
+ border: none;
700
+ background: transparent;
701
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
702
+ border-radius: 6px;
703
+ cursor: pointer;
704
+ transition: background 120ms, color 120ms;
705
+ }
706
+
707
+ .cal-hd-btn:hover {
708
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
709
+ background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 8%, transparent);
710
+ }
711
+
712
+ .cal-hd-btn:focus-visible,
713
+ .cal-hd-today:focus-visible,
714
+ .cal-pill:focus-visible {
715
+ outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
716
+ outline-offset: 2px;
717
+ }
718
+
719
+ .cal-hd-today {
720
+ font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
721
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
722
+ background: transparent;
723
+ border: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
724
+ padding: 6px 10px;
725
+ border-radius: 6px;
726
+ cursor: pointer;
727
+ white-space: nowrap;
728
+ margin-right: 2px;
729
+ transition: background 120ms, color 120ms, border-color 120ms;
730
+ }
731
+
732
+ .cal-hd-today:hover {
733
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
734
+ border-color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
735
+ }
736
+
642
737
  .cal-pills {
643
- position: absolute;
644
- top: 22px;
645
- bottom: auto;
646
- left: 10px;
647
- z-index: 20;
648
738
  display: flex;
649
739
  gap: 2px;
650
740
  background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
651
- backdrop-filter: blur(6px);
652
- -webkit-backdrop-filter: blur(6px);
653
741
  border-radius: 8px;
654
742
  padding: 2px;
655
- border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
743
+ border: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
744
+ flex-shrink: 0;
656
745
  }
657
746
 
658
747
  .cal-pill {
659
748
  border: none;
660
749
  background: transparent;
661
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
750
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
662
751
  cursor: pointer;
663
- font: 600 11px / 1 var(--dt-sans, system-ui, sans-serif);
664
- padding: 6px 12px;
752
+ font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
753
+ padding: 5px 12px;
665
754
  border-radius: 6px;
666
- letter-spacing: 0.04em;
667
- text-transform: uppercase;
668
755
  transition: background 100ms, color 100ms;
669
756
  }
670
757
 
671
758
  .cal-pill:hover {
672
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
759
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
673
760
  }
674
761
 
675
762
  .cal-pill--active {
@@ -677,11 +764,6 @@
677
764
  color: var(--dt-btn-text, #fff);
678
765
  }
679
766
 
680
- .cal-pill:focus-visible {
681
- outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
682
- outline-offset: 2px;
683
- }
684
-
685
767
  .cal-body {
686
768
  flex: 1;
687
769
  min-height: 0;
@@ -698,7 +780,7 @@
698
780
  justify-content: center;
699
781
  height: 100%;
700
782
  font: 400 13px / 1 var(--dt-sans, system-ui, sans-serif);
701
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
783
+ color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
702
784
  }
703
785
 
704
786
  .cal-loading {
@@ -727,7 +809,7 @@
727
809
  align-items: center;
728
810
  gap: 4px;
729
811
  padding: 8px 8px 6px;
730
- border-bottom: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
812
+ border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
731
813
  flex-shrink: 0;
732
814
  min-height: 44px;
733
815
  }
@@ -752,7 +834,7 @@
752
834
  height: 32px;
753
835
  border: none;
754
836
  background: transparent;
755
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
837
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
756
838
  border-radius: 50%;
757
839
  cursor: pointer;
758
840
  transition: background 120ms, color 120ms;
@@ -760,11 +842,11 @@
760
842
  flex-shrink: 0;
761
843
  }
762
844
  .cal-m-nav:hover {
763
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
764
- background: color-mix(in srgb, var(--dt-text, rgba(226, 232, 240, 0.85)) 8%, transparent);
845
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
846
+ background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 8%, transparent);
765
847
  }
766
848
  .cal-m-nav:active {
767
- background: var(--dt-accent-dim, rgba(239, 68, 68, 0.12));
849
+ background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
768
850
  }
769
851
  .cal-m-nav:focus-visible {
770
852
  outline: 2px solid color-mix(in srgb, var(--dt-accent, #2563eb) 55%, transparent);
@@ -777,13 +859,13 @@
777
859
  background: color-mix(in srgb, var(--dt-surface, var(--dt-bg, #ffffff)) 85%, transparent);
778
860
  border-radius: 8px;
779
861
  padding: 2px;
780
- border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
862
+ border: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
781
863
  flex-shrink: 0;
782
864
  }
783
865
  .cal-m-pill {
784
866
  border: none;
785
867
  background: transparent;
786
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
868
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
787
869
  cursor: pointer;
788
870
  font: 600 11px / 1 var(--dt-sans, system-ui, sans-serif);
789
871
  padding: 5px 10px;
@@ -794,7 +876,7 @@
794
876
  -webkit-tap-highlight-color: transparent;
795
877
  }
796
878
  .cal-m-pill:hover {
797
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
879
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
798
880
  }
799
881
  .cal-m-pill--active {
800
882
  background: var(--dt-accent, #2563eb);
@@ -805,7 +887,7 @@
805
887
  flex: 1;
806
888
  text-align: center;
807
889
  font: 600 14px / 1.2 var(--dt-sans, system-ui, sans-serif);
808
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
890
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
809
891
  white-space: nowrap;
810
892
  overflow: hidden;
811
893
  text-overflow: ellipsis;
@@ -64,18 +64,18 @@
64
64
  font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
65
65
  letter-spacing: 0.04em;
66
66
  text-transform: uppercase;
67
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
67
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
68
68
  }
69
69
  .dh-name-long {
70
70
  font-size: 12px;
71
71
  text-transform: none;
72
72
  letter-spacing: 0.01em;
73
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
73
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
74
74
  }
75
75
 
76
76
  .dh-num {
77
77
  font: 600 14px / 1 var(--dt-sans, system-ui, sans-serif);
78
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
78
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
79
79
  width: 28px;
80
80
  height: 28px;
81
81
  display: flex;
@@ -90,12 +90,12 @@
90
90
 
91
91
  .dh-date {
92
92
  font: 400 10px / 1 var(--dt-sans, system-ui, sans-serif);
93
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
93
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
94
94
  }
95
95
 
96
96
  .dh-rel {
97
97
  font: 500 12px / 1.3 var(--dt-sans, system-ui, sans-serif);
98
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
98
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
99
99
  }
100
100
  .dh-today .dh-rel {
101
101
  color: var(--dt-accent, #2563eb);
@@ -64,13 +64,13 @@
64
64
  min-height: 24px;
65
65
  }
66
66
  .es:hover {
67
- border-color: var(--dt-accent-dim, rgba(239, 68, 68, 0.18));
68
- background: var(--dt-accent-dim, rgba(239, 68, 68, 0.05));
67
+ border-color: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
68
+ background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
69
69
  }
70
70
  .es:focus-visible {
71
71
  outline: 2px solid var(--dt-accent, #2563eb);
72
72
  outline-offset: 2px;
73
- border-color: var(--dt-accent-dim, rgba(239, 68, 68, 0.18));
73
+ border-color: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
74
74
  }
75
75
 
76
76
  .es-hint {
@@ -93,7 +93,7 @@
93
93
  }
94
94
  .es-range {
95
95
  font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
96
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
96
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
97
97
  }
98
98
 
99
99
  .es-v {
@@ -226,7 +226,7 @@
226
226
  align-items: center;
227
227
  gap: 5px;
228
228
  font: 400 11px / 1 var(--dt-sans, system-ui, sans-serif);
229
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
229
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
230
230
  white-space: nowrap;
231
231
  overflow: hidden;
232
232
  text-overflow: ellipsis;
@@ -253,7 +253,7 @@
253
253
  display: flex;
254
254
  border-radius: 8px;
255
255
  background: var(--dt-surface, var(--dt-bg, #ffffff));
256
- border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
256
+ border: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
257
257
  overflow: hidden;
258
258
  }
259
259
  .eb-card .eb-stripe {
@@ -270,18 +270,18 @@
270
270
  }
271
271
  .eb-card .eb-title {
272
272
  font: 500 12px / 1.3 var(--dt-sans, system-ui, sans-serif);
273
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
273
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
274
274
  overflow: hidden;
275
275
  text-overflow: ellipsis;
276
276
  white-space: nowrap;
277
277
  }
278
278
  .eb-card .eb-time {
279
279
  font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
280
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
280
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
281
281
  }
282
282
  .eb-card .eb-dur {
283
283
  font: 300 10px / 1 var(--dt-sans, system-ui, sans-serif);
284
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
284
+ color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
285
285
  }
286
286
 
287
287
  /* ── Row ── */
@@ -290,7 +290,7 @@
290
290
  align-items: center;
291
291
  gap: 10px;
292
292
  padding: 6px 12px;
293
- border-bottom: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
293
+ border-bottom: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
294
294
  }
295
295
  .eb-row .eb-stripe {
296
296
  width: 3px;
@@ -302,13 +302,13 @@
302
302
  }
303
303
  .eb-row .eb-time {
304
304
  font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
305
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
305
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
306
306
  flex-shrink: 0;
307
307
  width: 90px;
308
308
  }
309
309
  .eb-row .eb-title {
310
310
  font: 400 12px / 1.3 var(--dt-sans, system-ui, sans-serif);
311
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
311
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
312
312
  flex: 1;
313
313
  overflow: hidden;
314
314
  text-overflow: ellipsis;
@@ -316,14 +316,14 @@
316
316
  }
317
317
  .eb-row .eb-dur {
318
318
  font: 300 10px / 1 var(--dt-sans, system-ui, sans-serif);
319
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
319
+ color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
320
320
  flex-shrink: 0;
321
321
  }
322
322
 
323
323
  /* ── Shared ── */
324
324
  .eb-subtitle {
325
325
  font: 400 10px / 1.2 var(--dt-sans, system-ui, sans-serif);
326
- color: var(--dt-text-2, rgba(148, 163, 184, 0.65));
326
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
327
327
  overflow: hidden;
328
328
  text-overflow: ellipsis;
329
329
  white-space: nowrap;
@@ -345,7 +345,7 @@
345
345
  }
346
346
  .eb-location {
347
347
  font: 400 9px / 1.2 var(--dt-sans, system-ui, sans-serif);
348
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
348
+ color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
349
349
  overflow: hidden;
350
350
  text-overflow: ellipsis;
351
351
  white-space: nowrap;
@@ -361,8 +361,8 @@
361
361
  width: fit-content;
362
362
  }
363
363
  .eb-cancelled-badge {
364
- color: var(--dt-text-2, rgba(148, 163, 184, 0.65));
365
- background: color-mix(in srgb, var(--dt-text-2, rgba(148, 163, 184, 0.3)) 15%, transparent);
364
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
365
+ background: color-mix(in srgb, var(--dt-text-2, rgba(0, 0, 0, 0.54)) 15%, transparent);
366
366
  }
367
367
  .eb-tentative-badge {
368
368
  color: var(--_color);
@@ -123,7 +123,7 @@
123
123
  height: 10px;
124
124
  border-radius: 50%;
125
125
  background: var(--ni-color, var(--dt-accent, #2563eb));
126
- box-shadow: 0 0 6px var(--ni-color, var(--dt-glow, rgba(239, 68, 68, 0.35)));
126
+ box-shadow: 0 0 6px var(--ni-color, var(--dt-glow, rgba(37, 99, 235, 0.25)));
127
127
  }
128
128
  .ni-dot.vertical .ni-dot-circle {
129
129
  margin-left: -5px;
@@ -68,7 +68,7 @@
68
68
  top: 0;
69
69
  bottom: 0;
70
70
  width: 0;
71
- border-left: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
71
+ border-left: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
72
72
  }
73
73
  .tg-tick-half {
74
74
  border-left-style: dashed;
@@ -79,7 +79,7 @@
79
79
  top: 4px;
80
80
  left: 6px;
81
81
  font: 400 9px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
82
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
82
+ color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
83
83
  white-space: nowrap;
84
84
  }
85
85
 
@@ -96,10 +96,10 @@
96
96
  align-items: flex-start;
97
97
  justify-content: flex-end;
98
98
  padding: 2px 8px 0 0;
99
- border-top: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
99
+ border-top: 1px solid var(--dt-border, rgba(0, 0, 0, 0.08));
100
100
  }
101
101
  .tg-v .tg-label {
102
102
  font: 400 9px / 1 var(--dt-mono, 'SF Mono', 'Fira Code', monospace);
103
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
103
+ color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
104
104
  }
105
105
  </style>