@nomideusz/svelte-calendar 0.6.0 → 0.6.4

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 (50) hide show
  1. package/README.md +183 -5
  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 +11 -0
  9. package/dist/adapters/recurring.js +9 -13
  10. package/dist/adapters/types.d.ts +11 -2
  11. package/dist/calendar/Calendar.svelte +847 -767
  12. package/dist/calendar/Calendar.svelte.d.ts +14 -2
  13. package/dist/core/index.d.ts +3 -1
  14. package/dist/core/index.js +2 -0
  15. package/dist/core/measure.d.ts +104 -0
  16. package/dist/core/measure.js +195 -0
  17. package/dist/core/time.js +2 -2
  18. package/dist/core/types.d.ts +32 -0
  19. package/dist/engine/event-store.svelte.js +22 -7
  20. package/dist/headless/create-agenda.svelte.d.ts +67 -0
  21. package/dist/headless/create-agenda.svelte.js +139 -0
  22. package/dist/headless/create-calendar.svelte.d.ts +2 -0
  23. package/dist/headless/create-calendar.svelte.js +380 -0
  24. package/dist/headless/index.d.ts +4 -0
  25. package/dist/headless/index.js +2 -0
  26. package/dist/headless/types.d.ts +219 -0
  27. package/dist/headless/types.js +1 -0
  28. package/dist/index.d.ts +6 -4
  29. package/dist/index.js +4 -2
  30. package/dist/primitives/DayHeader.svelte +103 -103
  31. package/dist/primitives/EmptySlot.svelte +105 -105
  32. package/dist/primitives/EventBlock.svelte +397 -312
  33. package/dist/primitives/NowIndicator.svelte +178 -178
  34. package/dist/primitives/TimeGutter.svelte +104 -104
  35. package/dist/theme/presets.js +36 -36
  36. package/dist/views/agenda/AgendaDay.svelte +1167 -1055
  37. package/dist/views/agenda/AgendaWeek.svelte +987 -924
  38. package/dist/views/mobile/Mobile.svelte +17 -17
  39. package/dist/views/mobile/MobileDay.svelte +729 -665
  40. package/dist/views/mobile/MobileWeek.svelte +476 -456
  41. package/dist/views/planner/PlannerDay.svelte +1184 -1091
  42. package/dist/views/planner/PlannerWeek.svelte +924 -945
  43. package/dist/views/shared/context.svelte.d.ts +47 -0
  44. package/dist/views/shared/context.svelte.js +47 -0
  45. package/dist/views/shared/format.d.ts +19 -0
  46. package/dist/views/shared/format.js +46 -0
  47. package/dist/views/shared/index.d.ts +4 -0
  48. package/dist/views/shared/index.js +2 -0
  49. package/dist/widget/CalendarWidget.svelte +142 -142
  50. package/package.json +76 -71
@@ -1,312 +1,397 @@
1
- <!--
2
- EventBlock — shared event rendering primitive.
3
-
4
- Three display variants:
5
- "chip" — compact colored chip (dot + title)
6
- "card" — full card (title, time, duration, color stripe)
7
- "row" — full-width row (time, title, duration — agenda style)
8
-
9
- Emits: onclick, ondragstart, onresize (for future interaction wiring)
10
- -->
11
- <script lang="ts">
12
- import type { TimelineEvent } from '../core/types.js';
13
- import { fmtTime, fmtDuration, getLabels } from '../core/locale.js';
14
- import type { Snippet } from 'svelte';
15
-
16
- const L = $derived(getLabels());
17
-
18
- interface Props {
19
- event: TimelineEvent;
20
- /** Display variant */
21
- variant?: 'chip' | 'card' | 'row';
22
- /** Is this event currently in-progress? */
23
- active?: boolean;
24
- /** Is this event in the past? */
25
- past?: boolean;
26
- /** Show the time range */
27
- showTime?: boolean;
28
- /** Show the duration */
29
- showDuration?: boolean;
30
- /** Editable (shows resize handles in future) */
31
- editable?: boolean;
32
- /** Click handler */
33
- onclick?: (event: TimelineEvent) => void;
34
- /** Custom content slot */
35
- children?: Snippet<[TimelineEvent]>;
36
- }
37
-
38
- let {
39
- event,
40
- variant = 'chip',
41
- active = false,
42
- past = false,
43
- showTime = false,
44
- showDuration = false,
45
- editable = false,
46
- onclick,
47
- children,
48
- }: Props = $props();
49
-
50
- const accentColor = $derived(event.color || 'var(--dt-accent, #ef4444)');
51
-
52
- const ariaLabel = $derived.by(() => {
53
- const t = event.title;
54
- const time = `${fmtTime(event.start)} to ${fmtTime(event.end)}`;
55
- const dur = fmtDuration(event.start, event.end);
56
- const status = active ? `, ${L.happeningNow}` : past ? `, ${L.past}` : '';
57
- return `${t}, ${time}, ${dur}${status}`;
58
- });
59
-
60
- function handleKeydown(e: KeyboardEvent) {
61
- if (e.key === 'Enter' || e.key === ' ') {
62
- e.preventDefault();
63
- onclick?.(event);
64
- }
65
- }
66
- </script>
67
-
68
- {#snippet content()}
69
- {#if children}
70
- {@render children(event)}
71
- {:else if variant === 'chip'}
72
- <span class="eb-dot"></span>
73
- <span class="eb-title">{event.title}</span>
74
- {#if active}<span class="eb-live">●</span>{/if}
75
- {:else if variant === 'card'}
76
- <div class="eb-stripe"></div>
77
- <div class="eb-body">
78
- <span class="eb-title">{event.title}</span>
79
- {#if event.subtitle}
80
- <span class="eb-subtitle">{event.subtitle}</span>
81
- {/if}
82
- {#if showTime}
83
- <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
84
- {/if}
85
- {#if showDuration}
86
- <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
87
- {/if}
88
- {#if event.tags && event.tags.length > 0}
89
- <div class="eb-tags">
90
- {#each event.tags as tag}
91
- <span class="eb-tag">{tag}</span>
92
- {/each}
93
- </div>
94
- {/if}
95
- {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
96
- </div>
97
- {:else}
98
- <!-- row -->
99
- <span class="eb-stripe"></span>
100
- <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
101
- <span class="eb-title">{event.title}</span>
102
- {#if event.subtitle}
103
- <span class="eb-subtitle">{event.subtitle}</span>
104
- {/if}
105
- {#if showDuration}
106
- <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
107
- {/if}
108
- {#if event.tags && event.tags.length > 0}
109
- <span class="eb-tags">
110
- {#each event.tags as tag}
111
- <span class="eb-tag">{tag}</span>
112
- {/each}
113
- </span>
114
- {/if}
115
- {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
116
- {/if}
117
- {/snippet}
118
-
119
- {#if onclick}
120
- <div
121
- class="eb eb-{variant}"
122
- class:eb-active={active}
123
- class:eb-past={past}
124
- class:eb-editable={editable}
125
- style="--eb-color: {accentColor}"
126
- role="button"
127
- tabindex="0"
128
- aria-label={ariaLabel}
129
- aria-current={active ? 'true' : undefined}
130
- onclick={() => onclick?.(event)}
131
- onkeydown={handleKeydown}
132
- >
133
- {@render content()}
134
- </div>
135
- {:else}
136
- <div
137
- class="eb eb-{variant}"
138
- class:eb-active={active}
139
- class:eb-past={past}
140
- class:eb-editable={editable}
141
- style="--eb-color: {accentColor}"
142
- role="article"
143
- aria-label={ariaLabel}
144
- aria-current={active ? 'true' : undefined}
145
- >
146
- {@render content()}
147
- </div>
148
- {/if}
149
-
150
- <style>
151
- .eb {
152
- --_color: var(--eb-color, var(--dt-accent, #ef4444));
153
- cursor: default;
154
- transition: opacity 120ms, transform 80ms;
155
- }
156
- .eb-editable {
157
- cursor: pointer;
158
- }
159
- .eb-editable:hover {
160
- transform: translateY(-1px);
161
- }
162
- .eb:focus-visible {
163
- outline: 2px solid var(--dt-accent, #ef4444);
164
- outline-offset: 2px;
165
- border-radius: 4px;
166
- }
167
- .eb-past {
168
- opacity: 0.5;
169
- }
170
-
171
- /* ── Chip ── */
172
- .eb-chip {
173
- display: inline-flex;
174
- align-items: center;
175
- gap: 5px;
176
- font: 400 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
177
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
178
- white-space: nowrap;
179
- overflow: hidden;
180
- text-overflow: ellipsis;
181
- }
182
- .eb-chip .eb-dot {
183
- width: 6px;
184
- height: 6px;
185
- border-radius: 50%;
186
- background: var(--_color);
187
- flex-shrink: 0;
188
- }
189
- .eb-chip .eb-title {
190
- overflow: hidden;
191
- text-overflow: ellipsis;
192
- }
193
- .eb-live {
194
- color: var(--_color);
195
- font-size: 8px;
196
- animation: eb-blink 1.5s ease-in-out infinite;
197
- }
198
-
199
- /* ── Card ── */
200
- .eb-card {
201
- display: flex;
202
- border-radius: 8px;
203
- background: var(--dt-surface, #10141c);
204
- border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
205
- overflow: hidden;
206
- }
207
- .eb-card .eb-stripe {
208
- width: 4px;
209
- flex-shrink: 0;
210
- background: var(--_color);
211
- }
212
- .eb-card .eb-body {
213
- display: flex;
214
- flex-direction: column;
215
- gap: 3px;
216
- padding: 8px 10px;
217
- min-width: 0;
218
- }
219
- .eb-card .eb-title {
220
- font: 500 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
221
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
222
- overflow: hidden;
223
- text-overflow: ellipsis;
224
- white-space: nowrap;
225
- }
226
- .eb-card .eb-time {
227
- font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
228
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
229
- }
230
- .eb-card .eb-dur {
231
- font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
232
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
233
- }
234
-
235
- /* ── Row ── */
236
- .eb-row {
237
- display: flex;
238
- align-items: center;
239
- gap: 10px;
240
- padding: 6px 12px;
241
- border-bottom: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
242
- }
243
- .eb-row .eb-stripe {
244
- width: 3px;
245
- height: 100%;
246
- min-height: 20px;
247
- border-radius: 2px;
248
- background: var(--_color);
249
- flex-shrink: 0;
250
- }
251
- .eb-row .eb-time {
252
- font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
253
- color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
254
- flex-shrink: 0;
255
- width: 90px;
256
- }
257
- .eb-row .eb-title {
258
- font: 400 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
259
- color: var(--dt-text, rgba(226, 232, 240, 0.85));
260
- flex: 1;
261
- overflow: hidden;
262
- text-overflow: ellipsis;
263
- white-space: nowrap;
264
- }
265
- .eb-row .eb-dur {
266
- font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
267
- color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
268
- flex-shrink: 0;
269
- }
270
-
271
- /* ── Shared ── */
272
- .eb-subtitle {
273
- font: 400 10px / 1.2 var(--dt-sans, 'Outfit', system-ui, sans-serif);
274
- color: var(--dt-text-2, rgba(148, 163, 184, 0.65));
275
- overflow: hidden;
276
- text-overflow: ellipsis;
277
- white-space: nowrap;
278
- }
279
- .eb-tags {
280
- display: inline-flex;
281
- flex-wrap: wrap;
282
- gap: 3px;
283
- }
284
- .eb-tag {
285
- display: inline-block;
286
- font: 500 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
287
- letter-spacing: 0.04em;
288
- text-transform: uppercase;
289
- padding: 2px 5px;
290
- border-radius: 3px;
291
- background: color-mix(in srgb, var(--_color) 18%, transparent);
292
- color: var(--_color);
293
- }
294
- .eb-live-badge {
295
- display: inline-flex;
296
- align-items: center;
297
- font: 700 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
298
- letter-spacing: 0.08em;
299
- text-transform: uppercase;
300
- color: var(--dt-btn-text, #fff);
301
- background: var(--_color);
302
- padding: 2px 6px;
303
- border-radius: 3px;
304
- width: fit-content;
305
- animation: eb-blink 2s ease-in-out infinite;
306
- }
307
-
308
- @keyframes eb-blink {
309
- 0%, 100% { opacity: 1; }
310
- 50% { opacity: 0.6; }
311
- }
312
- </style>
1
+ <!--
2
+ EventBlock — shared event rendering primitive.
3
+
4
+ Three display variants:
5
+ "chip" — compact colored chip (dot + title)
6
+ "card" — full card (title, time, duration, color stripe)
7
+ "row" — full-width row (time, title, duration — agenda style)
8
+
9
+ Emits: onclick, ondragstart, onresize (for future interaction wiring)
10
+ -->
11
+ <script lang="ts">
12
+ import type { TimelineEvent } from '../core/types.js';
13
+ import { fmtTime, fmtDuration, getLabels } from '../core/locale.js';
14
+ import type { Snippet } from 'svelte';
15
+
16
+ const L = $derived(getLabels());
17
+
18
+ interface Props {
19
+ event: TimelineEvent;
20
+ /** Display variant */
21
+ variant?: 'chip' | 'card' | 'row';
22
+ /** Is this event currently in-progress? */
23
+ active?: boolean;
24
+ /** Is this event in the past? */
25
+ past?: boolean;
26
+ /** Show the time range */
27
+ showTime?: boolean;
28
+ /** Show the duration */
29
+ showDuration?: boolean;
30
+ /** Editable (shows resize handles in future) */
31
+ editable?: boolean;
32
+ /** Click handler */
33
+ onclick?: (event: TimelineEvent) => void;
34
+ /** Custom content slot */
35
+ children?: Snippet<[TimelineEvent]>;
36
+ }
37
+
38
+ let {
39
+ event,
40
+ variant = 'chip',
41
+ active = false,
42
+ past = false,
43
+ showTime = false,
44
+ showDuration = false,
45
+ editable = false,
46
+ onclick,
47
+ children,
48
+ }: Props = $props();
49
+
50
+ const accentColor = $derived(event.color || 'var(--dt-accent, #ef4444)');
51
+ const isCancelled = $derived(event.status === 'cancelled');
52
+ const isTentative = $derived(event.status === 'tentative');
53
+ const isFull = $derived(event.status === 'full');
54
+ const isLimited = $derived(event.status === 'limited');
55
+
56
+ const ariaLabel = $derived.by(() => {
57
+ const t = event.title;
58
+ const time = `${fmtTime(event.start)} to ${fmtTime(event.end)}`;
59
+ const dur = fmtDuration(event.start, event.end);
60
+ const loc = event.location ? `, ${event.location}` : '';
61
+ const statusStr = isCancelled ? ', cancelled' : isTentative ? ', tentative' : isFull ? ', full' : isLimited ? ', limited' : '';
62
+ const activeStr = active ? `, ${L.happeningNow}` : past ? `, ${L.past}` : '';
63
+ return `${t}${loc}, ${time}, ${dur}${statusStr}${activeStr}`;
64
+ });
65
+
66
+ function handleKeydown(e: KeyboardEvent) {
67
+ if (e.key === 'Enter' || e.key === ' ') {
68
+ e.preventDefault();
69
+ onclick?.(event);
70
+ }
71
+ }
72
+ </script>
73
+
74
+ {#snippet content()}
75
+ {#if children}
76
+ {@render children(event)}
77
+ {:else if variant === 'chip'}
78
+ <span class="eb-dot"></span>
79
+ <span class="eb-title">{event.title}</span>
80
+ {#if active}<span class="eb-live">●</span>{/if}
81
+ {:else if variant === 'card'}
82
+ <div class="eb-stripe"></div>
83
+ <div class="eb-body">
84
+ <span class="eb-title">{event.title}</span>
85
+ {#if event.subtitle}
86
+ <span class="eb-subtitle">{event.subtitle}</span>
87
+ {/if}
88
+ {#if event.location}
89
+ <span class="eb-location">📍 {event.location}</span>
90
+ {/if}
91
+ {#if showTime}
92
+ <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
93
+ {/if}
94
+ {#if showDuration}
95
+ <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
96
+ {/if}
97
+ {#if event.tags && event.tags.length > 0}
98
+ <div class="eb-tags">
99
+ {#each event.tags as tag}
100
+ <span class="eb-tag">{tag}</span>
101
+ {/each}
102
+ </div>
103
+ {/if}
104
+ {#if isCancelled}<span class="eb-status-badge eb-cancelled-badge">Cancelled</span>{/if}
105
+ {#if isTentative}<span class="eb-status-badge eb-tentative-badge">Tentative</span>{/if}
106
+ {#if isFull}<span class="eb-status-badge eb-full-badge">Full</span>{/if}
107
+ {#if isLimited}<span class="eb-status-badge eb-limited-badge">Limited</span>{/if}
108
+ {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
109
+ </div>
110
+ {:else}
111
+ <!-- row -->
112
+ <span class="eb-stripe"></span>
113
+ <span class="eb-time">{fmtTime(event.start)} – {fmtTime(event.end)}</span>
114
+ <span class="eb-title">{event.title}</span>
115
+ {#if event.subtitle}
116
+ <span class="eb-subtitle">{event.subtitle}</span>
117
+ {/if}
118
+ {#if event.location}
119
+ <span class="eb-location">📍 {event.location}</span>
120
+ {/if}
121
+ {#if showDuration}
122
+ <span class="eb-dur">{fmtDuration(event.start, event.end)}</span>
123
+ {/if}
124
+ {#if event.tags && event.tags.length > 0}
125
+ <span class="eb-tags">
126
+ {#each event.tags as tag}
127
+ <span class="eb-tag">{tag}</span>
128
+ {/each}
129
+ </span>
130
+ {/if}
131
+ {#if isCancelled}<span class="eb-status-badge eb-cancelled-badge">Cancelled</span>{/if}
132
+ {#if isFull}<span class="eb-status-badge eb-full-badge">Full</span>{/if}
133
+ {#if isLimited}<span class="eb-status-badge eb-limited-badge">Limited</span>{/if}
134
+ {#if active}<span class="eb-live-badge">{L.now}</span>{/if}
135
+ {/if}
136
+ {/snippet}
137
+
138
+ {#if onclick}
139
+ <div
140
+ class="eb eb-{variant}"
141
+ class:eb-active={active}
142
+ class:eb-past={past}
143
+ class:eb-cancelled={isCancelled}
144
+ class:eb-tentative={isTentative}
145
+ class:eb-full={isFull}
146
+ class:eb-limited={isLimited}
147
+ class:eb-editable={editable}
148
+ style="--eb-color: {accentColor}"
149
+ role="button"
150
+ tabindex="0"
151
+ aria-label={ariaLabel}
152
+ aria-current={active ? 'true' : undefined}
153
+ onclick={() => onclick?.(event)}
154
+ onkeydown={handleKeydown}
155
+ >
156
+ {@render content()}
157
+ </div>
158
+ {:else}
159
+ <div
160
+ class="eb eb-{variant}"
161
+ class:eb-active={active}
162
+ class:eb-past={past}
163
+ class:eb-cancelled={isCancelled}
164
+ class:eb-tentative={isTentative}
165
+ class:eb-full={isFull}
166
+ class:eb-limited={isLimited}
167
+ class:eb-editable={editable}
168
+ style="--eb-color: {accentColor}"
169
+ role="article"
170
+ aria-label={ariaLabel}
171
+ aria-current={active ? 'true' : undefined}
172
+ >
173
+ {@render content()}
174
+ </div>
175
+ {/if}
176
+
177
+ <style>
178
+ .eb {
179
+ --_color: var(--eb-color, var(--dt-accent, #ef4444));
180
+ cursor: default;
181
+ transition: opacity 120ms, transform 80ms;
182
+ }
183
+ .eb-editable {
184
+ cursor: pointer;
185
+ }
186
+ .eb-editable:hover {
187
+ transform: translateY(-1px);
188
+ }
189
+ .eb:focus-visible {
190
+ outline: 2px solid var(--dt-accent, #ef4444);
191
+ outline-offset: 2px;
192
+ border-radius: 4px;
193
+ }
194
+ .eb-past {
195
+ opacity: 0.5;
196
+ }
197
+ .eb-cancelled {
198
+ opacity: 0.45;
199
+ }
200
+ .eb-cancelled .eb-title {
201
+ text-decoration: line-through;
202
+ }
203
+ .eb-tentative {
204
+ opacity: 0.7;
205
+ }
206
+ .eb-tentative .eb-stripe {
207
+ background: repeating-linear-gradient(
208
+ 45deg,
209
+ var(--_color),
210
+ var(--_color) 2px,
211
+ transparent 2px,
212
+ transparent 4px
213
+ );
214
+ }
215
+ .eb-full {
216
+ opacity: 0.55;
217
+ }
218
+ .eb-limited .eb-stripe {
219
+ border-left: 2px dashed var(--_color);
220
+ background: none;
221
+ }
222
+
223
+ /* ── Chip ── */
224
+ .eb-chip {
225
+ display: inline-flex;
226
+ align-items: center;
227
+ gap: 5px;
228
+ font: 400 11px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
229
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
230
+ white-space: nowrap;
231
+ overflow: hidden;
232
+ text-overflow: ellipsis;
233
+ }
234
+ .eb-chip .eb-dot {
235
+ width: 6px;
236
+ height: 6px;
237
+ border-radius: 50%;
238
+ background: var(--_color);
239
+ flex-shrink: 0;
240
+ }
241
+ .eb-chip .eb-title {
242
+ overflow: hidden;
243
+ text-overflow: ellipsis;
244
+ }
245
+ .eb-live {
246
+ color: var(--_color);
247
+ font-size: 8px;
248
+ animation: eb-blink 1.5s ease-in-out infinite;
249
+ }
250
+
251
+ /* ── Card ── */
252
+ .eb-card {
253
+ display: flex;
254
+ border-radius: 8px;
255
+ background: var(--dt-surface, #10141c);
256
+ border: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
257
+ overflow: hidden;
258
+ }
259
+ .eb-card .eb-stripe {
260
+ width: 4px;
261
+ flex-shrink: 0;
262
+ background: var(--_color);
263
+ }
264
+ .eb-card .eb-body {
265
+ display: flex;
266
+ flex-direction: column;
267
+ gap: 3px;
268
+ padding: 8px 10px;
269
+ min-width: 0;
270
+ }
271
+ .eb-card .eb-title {
272
+ font: 500 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
273
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
274
+ overflow: hidden;
275
+ text-overflow: ellipsis;
276
+ white-space: nowrap;
277
+ }
278
+ .eb-card .eb-time {
279
+ font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
280
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
281
+ }
282
+ .eb-card .eb-dur {
283
+ font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
284
+ color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
285
+ }
286
+
287
+ /* ── Row ── */
288
+ .eb-row {
289
+ display: flex;
290
+ align-items: center;
291
+ gap: 10px;
292
+ padding: 6px 12px;
293
+ border-bottom: 1px solid var(--dt-border, rgba(148, 163, 184, 0.07));
294
+ }
295
+ .eb-row .eb-stripe {
296
+ width: 3px;
297
+ height: 100%;
298
+ min-height: 20px;
299
+ border-radius: 2px;
300
+ background: var(--_color);
301
+ flex-shrink: 0;
302
+ }
303
+ .eb-row .eb-time {
304
+ font: 400 10px / 1 var(--dt-mono, 'SF Mono', monospace);
305
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.55));
306
+ flex-shrink: 0;
307
+ width: 90px;
308
+ }
309
+ .eb-row .eb-title {
310
+ font: 400 12px / 1.3 var(--dt-sans, 'Outfit', system-ui, sans-serif);
311
+ color: var(--dt-text, rgba(226, 232, 240, 0.85));
312
+ flex: 1;
313
+ overflow: hidden;
314
+ text-overflow: ellipsis;
315
+ white-space: nowrap;
316
+ }
317
+ .eb-row .eb-dur {
318
+ font: 300 10px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
319
+ color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
320
+ flex-shrink: 0;
321
+ }
322
+
323
+ /* ── Shared ── */
324
+ .eb-subtitle {
325
+ font: 400 10px / 1.2 var(--dt-sans, 'Outfit', system-ui, sans-serif);
326
+ color: var(--dt-text-2, rgba(148, 163, 184, 0.65));
327
+ overflow: hidden;
328
+ text-overflow: ellipsis;
329
+ white-space: nowrap;
330
+ }
331
+ .eb-tags {
332
+ display: inline-flex;
333
+ flex-wrap: wrap;
334
+ gap: 3px;
335
+ }
336
+ .eb-tag {
337
+ display: inline-block;
338
+ font: 500 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
339
+ letter-spacing: 0.04em;
340
+ text-transform: uppercase;
341
+ padding: 2px 5px;
342
+ border-radius: 3px;
343
+ background: color-mix(in srgb, var(--_color) 18%, transparent);
344
+ color: var(--_color);
345
+ }
346
+ .eb-location {
347
+ font: 400 9px / 1.2 var(--dt-sans, 'Outfit', system-ui, sans-serif);
348
+ color: var(--dt-text-3, rgba(100, 116, 139, 0.55));
349
+ overflow: hidden;
350
+ text-overflow: ellipsis;
351
+ white-space: nowrap;
352
+ }
353
+ .eb-status-badge {
354
+ display: inline-flex;
355
+ align-items: center;
356
+ font: 700 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
357
+ letter-spacing: 0.06em;
358
+ text-transform: uppercase;
359
+ padding: 2px 6px;
360
+ border-radius: 3px;
361
+ width: fit-content;
362
+ }
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);
366
+ }
367
+ .eb-tentative-badge {
368
+ color: var(--_color);
369
+ background: color-mix(in srgb, var(--_color) 12%, transparent);
370
+ }
371
+ .eb-full-badge {
372
+ color: #ef4444;
373
+ background: color-mix(in srgb, #ef4444 15%, transparent);
374
+ }
375
+ .eb-limited-badge {
376
+ color: var(--_color);
377
+ background: color-mix(in srgb, var(--_color) 12%, transparent);
378
+ }
379
+ .eb-live-badge {
380
+ display: inline-flex;
381
+ align-items: center;
382
+ font: 700 8px / 1 var(--dt-sans, 'Outfit', system-ui, sans-serif);
383
+ letter-spacing: 0.08em;
384
+ text-transform: uppercase;
385
+ color: var(--dt-btn-text, #fff);
386
+ background: var(--_color);
387
+ padding: 2px 6px;
388
+ border-radius: 3px;
389
+ width: fit-content;
390
+ animation: eb-blink 2s ease-in-out infinite;
391
+ }
392
+
393
+ @keyframes eb-blink {
394
+ 0%, 100% { opacity: 1; }
395
+ 50% { opacity: 0.6; }
396
+ }
397
+ </style>