@nomideusz/svelte-calendar 1.1.1 → 1.3.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.
@@ -12,6 +12,7 @@ import { createClock } from "../../core/clock.svelte.js";
12
12
  import { HOUR_MS, sod, addDaysMs, diffDays, isAllDay, segmentForDay } from "../../core/time.js";
13
13
  import { fmtHParts, fmtTime } from "../../core/locale.js";
14
14
  import { createSwipe } from "./swipe.js";
15
+ import { dateLong } from "../shared/format.js";
15
16
  let { height = null, events = [], style = "", locale, focusDate, oneventclick, oneventcreate, selectedEventId = null, readOnly = false, visibleHours } = $props();
16
17
  // ── Context ────────────────────────────────────────
17
18
  const ctx = useCalendarContext();
@@ -475,7 +476,7 @@ $effect(() => {
475
476
  style={style || undefined}
476
477
  style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')}
477
478
  role="region"
478
- aria-label={L.dayPlanner}
479
+ aria-label="{L.dayPlanner}, {dateLong(dayMs, locale)}"
479
480
  ontouchstart={swipe.ontouchstart}
480
481
  ontouchmove={swipe.ontouchmove}
481
482
  ontouchend={swipe.ontouchend}
@@ -588,11 +589,11 @@ $effect(() => {
588
589
  <div class="mb-ev-stripe"></div>
589
590
  <div class="mb-ev-body">
590
591
  <EventContent event={p.ev}>
591
- <!-- Line thresholds must track real content height (title 18 +
592
- time 13 + sub 14 + loc 12 + tags 19 + padding 8) — showing
592
+ <!-- Line thresholds must track real content height (title 19 +
593
+ time 14 + sub 14 + loc 13 + tags 19 + padding 8) — showing
593
594
  a line the block can't hold clips text at both ends. -->
594
595
  <span class="mb-ev-title">{p.ev.title}</span>
595
- {#if p.height > 32}
596
+ {#if p.height > 36}
596
597
  <span class="mb-ev-time">{fmtTime(p.ev.start, locale)} – {fmtTime(p.ev.end, locale)}</span>
597
598
  {/if}
598
599
  {#if p.ev.subtitle && p.height > 56}
@@ -753,7 +754,7 @@ $effect(() => {
753
754
  }
754
755
 
755
756
  .mb-allday-title {
756
- font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
757
+ font: 500 13.5px/1 var(--dt-sans, system-ui, sans-serif);
757
758
  color: inherit;
758
759
  white-space: nowrap;
759
760
  max-width: 100px;
@@ -762,14 +763,14 @@ $effect(() => {
762
763
  }
763
764
 
764
765
  .mb-allday-span {
765
- font: 400 11px/1 var(--dt-sans, system-ui, sans-serif);
766
+ font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
766
767
  font-variant-numeric: tabular-nums;
767
768
  color: inherit;
768
769
  opacity: 0.75;
769
770
  }
770
771
 
771
772
  .mb-allday-more {
772
- font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
773
+ font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
773
774
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
774
775
  white-space: nowrap;
775
776
  flex-shrink: 0;
@@ -846,8 +847,8 @@ $effect(() => {
846
847
  visibility: hidden;
847
848
  }
848
849
  .mb-hour-label small {
849
- font-size: 9px;
850
- font-weight: 600;
850
+ font-size: 10px;
851
+ font-weight: 500;
851
852
  vertical-align: top;
852
853
  margin-left: 1px;
853
854
  letter-spacing: 0;
@@ -859,7 +860,7 @@ $effect(() => {
859
860
  start at — content-box pushed digits flush under the event edge */
860
861
  box-sizing: border-box;
861
862
  flex-shrink: 0;
862
- font: 700 15px/1 var(--dt-sans, system-ui, sans-serif);
863
+ font: 400 15px/1 var(--dt-sans, system-ui, sans-serif);
863
864
  font-stretch: 90%;
864
865
  font-variant-numeric: tabular-nums;
865
866
  letter-spacing: -0.02em;
@@ -892,18 +893,20 @@ $effect(() => {
892
893
  left: 44px;
893
894
  top: 50%;
894
895
  transform: translateY(-50%);
895
- font: 500 10px/1 var(--dt-sans, system-ui, sans-serif);
896
+ font: 500 11px/1 var(--dt-sans, system-ui, sans-serif);
896
897
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
897
898
  text-transform: uppercase;
898
899
  letter-spacing: 0.04em;
899
900
  }
900
901
 
901
902
  /* ─── Now line ───────────────────────────────────── */
903
+ /* No stacking context of its own: the plate and the dot sit above the
904
+ events, the line under them (the event it crosses is opaque), so the
905
+ line never strikes through a title. */
902
906
  .mb-now {
903
907
  position: absolute;
904
908
  left: 0;
905
909
  right: 0;
906
- z-index: 10;
907
910
  display: flex;
908
911
  align-items: center;
909
912
  pointer-events: none;
@@ -911,18 +914,32 @@ $effect(() => {
911
914
 
912
915
  /* The clock is a reversed plate on the accent, as in the desktop gutter. */
913
916
  .mb-now-label {
917
+ position: relative;
918
+ z-index: 10;
914
919
  width: 40px;
915
920
  box-sizing: border-box;
916
921
  flex-shrink: 0;
917
922
  padding: 0 4px 0 0;
918
923
  text-align: right;
924
+ white-space: nowrap;
925
+ }
926
+ .mb-now-label::after {
927
+ content: '';
928
+ position: absolute;
929
+ right: -4px;
930
+ top: 50%;
931
+ width: 8px;
932
+ height: 8px;
933
+ margin-top: -4px;
934
+ border-radius: 50%;
935
+ background: var(--dt-accent, #2563eb);
919
936
  }
920
937
  .mb-now-label span {
921
938
  display: inline-block;
922
939
  padding: 2px 3px;
923
940
  border-radius: 0;
924
941
  background: var(--dt-accent, #2563eb);
925
- font: 700 10px/1.2 var(--dt-sans, system-ui, sans-serif);
942
+ font: 500 11px/1.2 var(--dt-sans, system-ui, sans-serif);
926
943
  font-variant-numeric: tabular-nums;
927
944
  color: var(--dt-btn-text, #ffffff);
928
945
  }
@@ -933,17 +950,7 @@ $effect(() => {
933
950
  border-top: 2px solid var(--dt-accent, #2563eb);
934
951
  box-shadow: none;
935
952
  position: relative;
936
- }
937
-
938
- .mb-now-line::before {
939
- content: '';
940
- position: absolute;
941
- left: -4px;
942
- top: -4px;
943
- width: 10px;
944
- height: 10px;
945
- border-radius: 50%;
946
- background: var(--dt-accent, #2563eb);
953
+ z-index: 4;
947
954
  }
948
955
 
949
956
  /* ─── Events ─────────────────────────────────────── */
@@ -990,8 +997,20 @@ $effect(() => {
990
997
  .mb-event--selected {
991
998
  box-shadow: inset 0 0 0 1px var(--ev-color);
992
999
  }
993
- .mb-event--current {
994
- background: var(--dt-accent-dim, rgba(37, 99, 235, 0.12));
1000
+ /* The event in progress is the one the now line crosses: its wash is
1001
+ laid over the paper, opaque, so the line runs beneath its text. */
1002
+ .mb-event.mb-event--current {
1003
+ background:
1004
+ linear-gradient(var(--dt-accent-dim, rgba(37, 99, 235, 0.12)), var(--dt-accent-dim, rgba(37, 99, 235, 0.12))),
1005
+ var(--dt-paper, none),
1006
+ var(--dt-bg, #ffffff);
1007
+ }
1008
+ .mb-event.mb-event--current:hover,
1009
+ .mb-event.mb-event--current:active {
1010
+ background:
1011
+ linear-gradient(var(--dt-hover, rgba(0, 0, 0, 0.04)), var(--dt-hover, rgba(0, 0, 0, 0.04))),
1012
+ var(--dt-paper, none),
1013
+ var(--dt-bg, #ffffff);
995
1014
  }
996
1015
  /* Printed states: a cancelled class is struck through; a tentative or
997
1016
  limited one is set in grey with a dashed bar — never a bare opacity. */
@@ -1092,7 +1111,7 @@ $effect(() => {
1092
1111
  pointer-events: none;
1093
1112
  }
1094
1113
  .mb-create-ghost-time {
1095
- font: 700 11px/1 var(--dt-sans, system-ui, sans-serif);
1114
+ font: 500 11px/1 var(--dt-sans, system-ui, sans-serif);
1096
1115
  font-variant-numeric: tabular-nums;
1097
1116
  color: var(--dt-accent, #2563eb);
1098
1117
  padding: 4px 8px;
@@ -1125,13 +1144,13 @@ $effect(() => {
1125
1144
  }
1126
1145
 
1127
1146
  .mb-ev-time {
1128
- font: 400 12px/1 var(--dt-sans, system-ui, sans-serif);
1147
+ font: 400 14px/1 var(--dt-sans, system-ui, sans-serif);
1129
1148
  font-variant-numeric: tabular-nums;
1130
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1149
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
1131
1150
  }
1132
1151
 
1133
1152
  .mb-ev-sub {
1134
- font: 400 12px/1.1 var(--dt-sans, system-ui, sans-serif);
1153
+ font: 400 12.5px/1.1 var(--dt-sans, system-ui, sans-serif);
1135
1154
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1136
1155
  white-space: nowrap;
1137
1156
  overflow: hidden;
@@ -1139,7 +1158,7 @@ $effect(() => {
1139
1158
  }
1140
1159
 
1141
1160
  .mb-ev-loc {
1142
- font: 400 11px/1 var(--dt-sans, system-ui, sans-serif);
1161
+ font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
1143
1162
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1144
1163
  white-space: nowrap;
1145
1164
  overflow: hidden;
@@ -1178,7 +1197,7 @@ $effect(() => {
1178
1197
  position: absolute;
1179
1198
  top: 4px;
1180
1199
  right: 4px;
1181
- font: 600 11px/1 var(--dt-sans, system-ui, sans-serif);
1200
+ font: 500 11px/1 var(--dt-sans, system-ui, sans-serif);
1182
1201
  color: var(--ev-color, var(--dt-accent));
1183
1202
  background: color-mix(in srgb, var(--ev-color, var(--dt-accent)) 15%, transparent);
1184
1203
  padding: 2px 5px;
@@ -10,7 +10,8 @@ import EventContent from "../shared/EventContent.svelte";
10
10
  import { createClock } from "../../core/clock.svelte.js";
11
11
  import { sod, addDaysMs, isAllDay } from "../../core/time.js";
12
12
  import { startOfWeek as sowFn } from "../../core/time.js";
13
- import { fmtTime as _fmtTime, weekdayShort } from "../../core/locale.js";
13
+ import { fmtTime as _fmtTime, fmtWeekRange, weekdayShort } from "../../core/locale.js";
14
+ import { dateLong } from "../shared/format.js";
14
15
  import { createSwipe } from "./swipe.js";
15
16
  let { mondayStart = true, locale, height = null, events = [], style = "", focusDate, oneventclick, selectedEventId = null } = $props();
16
17
  // ── Context ────────────────────────────────────────
@@ -165,7 +166,9 @@ function handleDayKeydown(e, dayMs) {
165
166
  style={style || undefined}
166
167
  style:height={autoHeight ? undefined : (height ? `${height}px` : '100%')}
167
168
  role="region"
168
- aria-label={L.weekAhead}
169
+ aria-label="{L.weekAhead}{dayCells.length
170
+ ? `, ${fmtWeekRange(dayCells[0].ms, locale, dayCells[dayCells.length - 1].ms)}`
171
+ : ''}"
169
172
  ontouchstart={swipe.ontouchstart}
170
173
  ontouchmove={swipe.ontouchmove}
171
174
  ontouchend={swipe.ontouchend}
@@ -195,7 +198,7 @@ function handleDayKeydown(e, dayMs) {
195
198
  disabled={cell.isDisabled}
196
199
  onclick={() => handleDayTap(cell.ms)}
197
200
  onkeydown={(e) => handleDayKeydown(e, cell.ms)}
198
- aria-label="{cell.dayName} {cell.dayNum}{cell.isToday ? `, ${L.today}` : ''}"
201
+ aria-label="{dateLong(cell.ms, locale)}{cell.isToday ? `, ${L.today}` : ''}"
199
202
  ></button>
200
203
  <!-- Date column -->
201
204
  <div class="mw-date">
@@ -322,6 +325,7 @@ function handleDayKeydown(e, dayMs) {
322
325
  background: color-mix(in srgb, var(--dt-text, rgba(0, 0, 0, 0.87)) 3%, transparent);
323
326
  }
324
327
  .mw-row--past :global(.mw-ev-title),
328
+ .mw-row--past :global(.mw-ev-time),
325
329
  .mw-row--past .mw-day-num {
326
330
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
327
331
  }
@@ -366,8 +370,8 @@ function handleDayKeydown(e, dayMs) {
366
370
  }
367
371
 
368
372
  .mw-day-name {
369
- font: 600 11px/1 var(--dt-sans, system-ui, sans-serif);
370
- letter-spacing: 0.06em;
373
+ font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
374
+ letter-spacing: 0.08em;
371
375
  text-transform: uppercase;
372
376
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
373
377
  }
@@ -375,13 +379,16 @@ function handleDayKeydown(e, dayMs) {
375
379
  color: var(--dt-accent, #2563eb);
376
380
  }
377
381
 
382
+ /* The date is the typography: large and regular, narrowed like the planner's */
378
383
  .mw-day-num {
379
- font: 700 18px/1 var(--dt-sans, system-ui, sans-serif);
384
+ font: 400 22px/1 var(--dt-sans, system-ui, sans-serif);
385
+ font-stretch: 90%;
386
+ font-variant-numeric: tabular-nums;
387
+ letter-spacing: -0.02em;
380
388
  color: var(--dt-text, rgba(0, 0, 0, 0.87));
381
389
  }
382
390
  .mw-day-num--today {
383
391
  color: var(--dt-accent, #2563eb);
384
- font-weight: 500;
385
392
  }
386
393
 
387
394
  /* ─── Events column ──────────────────────────────── */
@@ -502,15 +509,15 @@ function handleDayKeydown(e, dayMs) {
502
509
  }
503
510
 
504
511
  .mw-ev-time {
505
- font: 400 12px/1 var(--dt-sans, system-ui, sans-serif);
512
+ font: 400 14px/1 var(--dt-sans, system-ui, sans-serif);
506
513
  font-variant-numeric: tabular-nums;
507
- color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
514
+ color: var(--dt-text, rgba(0, 0, 0, 0.87));
508
515
  white-space: nowrap;
509
516
  flex-shrink: 0;
510
517
  }
511
518
 
512
519
  .mw-ev-more {
513
- font: 500 12px/1 var(--dt-sans, system-ui, sans-serif);
520
+ font: 400 12.5px/1 var(--dt-sans, system-ui, sans-serif);
514
521
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
515
522
  padding: 2px 4px;
516
523
  min-height: 32px;
@@ -28,6 +28,39 @@ const todayMs = $derived(clock.today);
28
28
  // cell tap (day drill-down) carry the interaction.
29
29
  const dotsMode = $derived(isMobile);
30
30
  const MAX_CHIPS = 3;
31
+ // How many chips a cell has room for, measured, never more than MAX_CHIPS.
32
+ // A cell that overflows must also show its "+N more", so it gets one row
33
+ // fewer when that row would not fit beside its chips: a hidden event is
34
+ // always counted, never clipped away. Auto height and dots grow instead.
35
+ let fitAll = $state(MAX_CHIPS);
36
+ let fitMore = $state(MAX_CHIPS);
37
+ function measureFit() {
38
+ const cell = bodyEl?.querySelector(".mg-cell");
39
+ const chip = bodyEl?.querySelector(".mg-chip");
40
+ if (!cell || !chip) return;
41
+ const num = cell.querySelector(".mg-daynum");
42
+ const chips = cell.querySelector(".mg-chips");
43
+ const cs = getComputedStyle(cell);
44
+ const gap = chips ? parseFloat(getComputedStyle(chips).rowGap) || 0 : 0;
45
+ const room = cell.clientHeight - parseFloat(cs.paddingTop) - parseFloat(cs.paddingBottom) - (num?.offsetHeight ?? 0) - (parseFloat(cs.rowGap) || 0);
46
+ const chipH = chip.offsetHeight;
47
+ const moreH = bodyEl?.querySelector(".mg-more")?.offsetHeight ?? chipH;
48
+ if (!chipH) return;
49
+ fitAll = Math.min(MAX_CHIPS, Math.floor((room + gap) / (chipH + gap)));
50
+ fitMore = Math.max(0, Math.min(MAX_CHIPS, Math.floor((room - moreH) / (chipH + gap))));
51
+ }
52
+ $effect(() => {
53
+ if (!bodyEl || autoHeight || dotsMode) {
54
+ fitAll = fitMore = MAX_CHIPS;
55
+ return;
56
+ }
57
+ void events;
58
+ measureFit();
59
+ const ro = new ResizeObserver(measureFit);
60
+ ro.observe(bodyEl);
61
+ document.fonts?.ready.then(measureFit);
62
+ return () => ro.disconnect();
63
+ });
31
64
  const range = $derived(viewState?.range);
32
65
  const focusMonth = $derived((focusDate ?? new Date()).getMonth());
33
66
  $effect(() => {
@@ -143,6 +176,7 @@ const weeks = $derived.by(() => {
143
176
  const date = new Date(cellMs);
144
177
  const jsDay = date.getDay();
145
178
  const dayEvents = eventsForDay(cellMs);
179
+ const shown = dayEvents.length <= fitAll ? dayEvents.length : fitMore;
146
180
  row.push({
147
181
  ms: cellMs,
148
182
  date,
@@ -151,9 +185,9 @@ const weeks = $derived.by(() => {
151
185
  isToday: cellMs === todayMs,
152
186
  isWeekend: jsDay === 0 || jsDay === 6,
153
187
  isDisabled: disabledSet.has(cellMs),
154
- chips: dayEvents.slice(0, MAX_CHIPS),
188
+ chips: dayEvents.slice(0, shown),
155
189
  all: dayEvents,
156
- overflow: Math.max(0, dayEvents.length - MAX_CHIPS)
190
+ overflow: dayEvents.length - shown
157
191
  });
158
192
  }
159
193
  rows.push(row);
@@ -300,13 +334,11 @@ function chipTime(e) {
300
334
  }
301
335
  .mg-head-cell {
302
336
  padding: 6px 8px;
303
- font-family: var(--dt-sans, system-ui, sans-serif);
337
+ font: 500 12px/1.2 var(--dt-sans, system-ui, sans-serif);
304
338
  font-variant-numeric: tabular-nums;
305
- font-size: 11px;
306
- font-weight: 600;
307
339
  text-transform: uppercase;
308
- letter-spacing: 0.06em;
309
- color: var(--dt-text-3);
340
+ letter-spacing: 0.08em;
341
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.66));
310
342
  }
311
343
 
312
344
  .mg-body {
@@ -342,12 +374,13 @@ function chipTime(e) {
342
374
  .mg-cell--weekend {
343
375
  background: var(--dt-weekend-bg, rgba(0, 0, 0, 0.02));
344
376
  }
345
- /* Dim only the day number for adjacent-month cells — their events stay legible */
377
+ /* Dim only the day number for adjacent-month cells — their events stay
378
+ legible. text-2, not text-3: a 22px numeral still needs 4.5:1. */
346
379
  .mg-cell--out {
347
380
  background: var(--dt-surface, transparent);
348
381
  }
349
382
  .mg-cell--out .mg-daynum {
350
- color: var(--dt-text-3);
383
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.66));
351
384
  }
352
385
  .mg-cell--today {
353
386
  background: var(--dt-today-bg);
@@ -370,18 +403,20 @@ function chipTime(e) {
370
403
  outline-offset: -2px;
371
404
  }
372
405
 
406
+ /* Starts where the weekday name above it starts (6px cell + 2px = the
407
+ header's 8px), and is only as wide as its digits, so today's rule is too. */
373
408
  .mg-daynum {
374
409
  flex: none;
410
+ align-self: flex-start;
411
+ padding: 0 2px;
375
412
  font-family: var(--dt-sans, system-ui, sans-serif);
376
413
  font-size: 22px;
377
414
  font-weight: 400;
378
415
  font-variant-numeric: tabular-nums;
379
416
  color: var(--dt-text);
380
- min-width: 26px;
381
417
  height: 26px;
382
418
  display: inline-flex;
383
419
  align-items: center;
384
- justify-content: center;
385
420
  }
386
421
  /* Today: accent numeral on a 2px rule — the same mark in every view. */
387
422
  .mg-daynum--today {
@@ -404,9 +439,7 @@ function chipTime(e) {
404
439
  background: none;
405
440
  padding: 2px 4px;
406
441
  border-radius: 0;
407
- font-family: var(--dt-sans);
408
- font-size: 12px;
409
- line-height: 1.3;
442
+ font: 400 12.5px/1.3 var(--dt-sans, system-ui, sans-serif);
410
443
  color: var(--dt-text);
411
444
  cursor: pointer;
412
445
  min-width: 0;
@@ -440,9 +473,7 @@ function chipTime(e) {
440
473
  }
441
474
  .mg-chip-time {
442
475
  flex: none;
443
- font-family: var(--dt-sans, system-ui, sans-serif);
444
476
  font-variant-numeric: tabular-nums;
445
- font-size: 11px;
446
477
  color: var(--dt-text-2);
447
478
  }
448
479
  .mg-chip-title {
@@ -456,9 +487,8 @@ function chipTime(e) {
456
487
  background: none;
457
488
  padding: 1px 4px;
458
489
  border-radius: 0;
459
- font-family: var(--dt-sans, system-ui, sans-serif);
490
+ font: 400 12.5px/1.3 var(--dt-sans, system-ui, sans-serif);
460
491
  font-variant-numeric: tabular-nums;
461
- font-size: 11px;
462
492
  color: var(--dt-text-2);
463
493
  cursor: pointer;
464
494
  }
@@ -503,14 +533,17 @@ function chipTime(e) {
503
533
  .mg--dots .mg-chip-time {
504
534
  display: none;
505
535
  }
536
+ /* Marks are circles, like every dot in the calendar */
506
537
  .mg--dots .mg-chip-dot {
507
- width: 8px;
508
- height: 8px;
538
+ align-self: center;
539
+ width: 7px;
540
+ height: 7px;
541
+ border-radius: 50%;
509
542
  }
510
543
  .mg--dots .mg-more {
511
544
  padding: 0 3px;
512
545
  min-height: 0;
513
546
  align-self: center;
514
- font-size: 10px;
547
+ font-size: 11px;
515
548
  }
516
549
  </style>
@@ -64,6 +64,7 @@ const MAX_EVENTS_SHOWN = 5;
64
64
  // mount re-centres once the events have landed.
65
65
  const CHIP_H = 22;
66
66
  const CHIP_GAP = 3;
67
+ const NOW_H = 18;
67
68
  /** The day's hours at their floor height — a quiet week is short, a busy
68
69
  * day grows its row. Chips sit on this axis by start time. */
69
70
  const TIME_H = 120;
@@ -122,26 +123,31 @@ function timeTop(hoursIntoDay) {
122
123
  return Math.min(Math.max(f, 0), 1) * (TIME_H - CHIP_H);
123
124
  }
124
125
  /** Each chip at its start time, or just under the chip before it when that
125
- * one is in the way: the order is time, the height is time, nothing overlaps. */
126
- function chipTops(list, dayMs) {
126
+ * one is in the way: the order is time, the height is time, nothing overlaps.
127
+ * On today the now line is one more item on the axis: at the clock's
128
+ * height, under every chip that has started (pushed-down chips would read
129
+ * as still to come), and it pushes the chips to come down by its plate,
130
+ * so the plate never covers a chip's text. `end` is where the last item ends. */
131
+ function chipTops(list, dayMs, withNow = false) {
127
132
  const tops = [];
128
133
  let min = 0;
134
+ let nowY = null;
135
+ const placeNow = () => {
136
+ nowY = Math.max(timeTop(hourOf(clock.tick, dayMs)), min - CHIP_GAP + NOW_H / 2);
137
+ min = nowY + NOW_H / 2 + CHIP_GAP;
138
+ };
129
139
  for (const ev of list) {
140
+ if (withNow && nowY === null && ev.start.getTime() > clock.tick) placeNow();
130
141
  const top = Math.max(timeTop(hourOf(ev.start.getTime(), dayMs)), min);
131
142
  tops.push(top);
132
143
  min = top + CHIP_H + CHIP_GAP;
133
144
  }
134
- return tops;
135
- }
136
- /** The now-line: at the clock's height (pinned to the axis ends outside
137
- * it), but never above a chip that has already started — pushed-down
138
- * chips would read as still to come. */
139
- function nowTop(list, tops, dayMs) {
140
- let y = timeTop(hourOf(clock.tick, dayMs));
141
- list.forEach((ev, i) => {
142
- if (ev.start.getTime() <= clock.tick) y = Math.max(y, tops[i] + CHIP_H + CHIP_GAP / 2);
143
- });
144
- return y;
145
+ if (withNow && nowY === null) placeNow();
146
+ return {
147
+ tops,
148
+ nowY,
149
+ end: Math.max(0, min - CHIP_GAP)
150
+ };
145
151
  }
146
152
  const _initMs = untrack(() => sod(focusDate?.getTime() ?? Date.now()));
147
153
  let internalFocusMs = $state(_initMs);
@@ -836,8 +842,9 @@ function onCellKeydown(e, ms) {
836
842
  {@const previewTimedEvent = dragPreviewTimedForDay(day.ms)}
837
843
  {@const previewSegment = dragPreviewSegmentForDay(day.ms)}
838
844
  {@const shownTimed = visibleTimedEvents.slice(0, isExpanded ? visibleTimedEvents.length : timedCap)}
839
- {@const tops = chipTops(shownTimed, day.ms)}
840
- {@const nowY = day.isToday ? nowTop(shownTimed, tops, day.ms) : null}
845
+ {@const axis = chipTops(shownTimed, day.ms, day.isToday)}
846
+ {@const tops = axis.tops}
847
+ {@const nowY = axis.nowY}
841
848
  {@const inset = day.monthLabel ? MONTH_INSET : 0}
842
849
  <div
843
850
  class="wg-cell"
@@ -935,6 +942,7 @@ function onCellKeydown(e, ms) {
935
942
  <button
936
943
  type="button"
937
944
  class="wg-ev-more"
945
+ style:margin-top="{axis.end - (tops.length ? tops[tops.length - 1] + CHIP_H : 0) + 2}px"
938
946
  aria-expanded={isExpanded}
939
947
  onclick={(e) => { e.stopPropagation(); expandedCells[day.ms] = !isExpanded; }}
940
948
  >{isExpanded ? L.showLess : L.nMore(hiddenCount)}</button>
@@ -1112,7 +1120,7 @@ function onCellKeydown(e, ms) {
1112
1120
  transform: rotate(180deg);
1113
1121
  text-align: end; /* the rotation puts the end at the top */
1114
1122
  padding: 6px 0;
1115
- font: 700 11px / 22px var(--dt-sans, system-ui, sans-serif);
1123
+ font: 500 12px / 22px var(--dt-sans, system-ui, sans-serif);
1116
1124
  letter-spacing: 0.08em;
1117
1125
  white-space: nowrap;
1118
1126
  overflow: hidden;
@@ -1184,8 +1192,8 @@ function onCellKeydown(e, ms) {
1184
1192
  }
1185
1193
 
1186
1194
  .wg-blocked-label {
1187
- font: 500 10px/1 var(--dt-sans, system-ui, sans-serif);
1188
- color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
1195
+ font: 500 11px/1 var(--dt-sans, system-ui, sans-serif);
1196
+ color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1189
1197
  text-transform: uppercase;
1190
1198
  letter-spacing: 0.04em;
1191
1199
  white-space: nowrap;
@@ -1208,8 +1216,8 @@ function onCellKeydown(e, ms) {
1208
1216
  }
1209
1217
 
1210
1218
  .wg-day-wd {
1211
- font: 400 11px / 1 var(--dt-sans, system-ui, sans-serif);
1212
- letter-spacing: 0.04em;
1219
+ font: 500 12px / 1 var(--dt-sans, system-ui, sans-serif);
1220
+ letter-spacing: 0.08em;
1213
1221
  text-transform: uppercase;
1214
1222
  color: var(--dt-text-3, rgba(0, 0, 0, 0.38));
1215
1223
  }
@@ -1219,7 +1227,8 @@ function onCellKeydown(e, ms) {
1219
1227
  }
1220
1228
 
1221
1229
  .wg-day-num {
1222
- font: 700 14px / 1 var(--dt-sans, system-ui, sans-serif);
1230
+ font: 400 15px / 1 var(--dt-sans, system-ui, sans-serif);
1231
+ font-variant-numeric: tabular-nums;
1223
1232
  color: var(--dt-text, rgba(0, 0, 0, 0.87));
1224
1233
  }
1225
1234
 
@@ -1286,7 +1295,7 @@ function onCellKeydown(e, ms) {
1286
1295
  }
1287
1296
 
1288
1297
  .wg-ad-title {
1289
- font: 500 11px / 1.1 var(--dt-sans, system-ui, sans-serif);
1298
+ font: 500 13.5px / 1.1 var(--dt-sans, system-ui, sans-serif);
1290
1299
  color: inherit;
1291
1300
  white-space: nowrap;
1292
1301
  overflow: hidden;
@@ -1335,7 +1344,8 @@ function onCellKeydown(e, ms) {
1335
1344
  border-top: 2px solid var(--dt-accent, #2563eb);
1336
1345
  }
1337
1346
  .wg-now-time {
1338
- font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
1347
+ font: 500 11px / 1.2 var(--dt-sans, system-ui, sans-serif);
1348
+ font-variant-numeric: tabular-nums;
1339
1349
  padding: 2px 4px;
1340
1350
  border-radius: 0;
1341
1351
  background: var(--dt-accent, #2563eb);
@@ -1348,9 +1358,6 @@ function onCellKeydown(e, ms) {
1348
1358
  .wg-drop::after {
1349
1359
  border-top-style: dashed;
1350
1360
  }
1351
- .wg-drop .wg-now-time {
1352
- font-weight: 600;
1353
- }
1354
1361
 
1355
1362
  .wg-ev {
1356
1363
  position: relative; /* above the blocked bands */
@@ -1456,12 +1463,11 @@ function onCellKeydown(e, ms) {
1456
1463
  border-radius: 0;
1457
1464
  text-align: left;
1458
1465
  align-self: flex-start;
1459
- font: 500 10px / 1 var(--dt-sans, system-ui, sans-serif);
1466
+ font: 400 12.5px / 1 var(--dt-sans, system-ui, sans-serif);
1460
1467
  color: var(--dt-text-2, rgba(0, 0, 0, 0.54));
1461
1468
  padding: 2px 8px;
1462
1469
  cursor: pointer;
1463
1470
  flex-shrink: 0;
1464
- margin-top: 2px;
1465
1471
  }
1466
1472
 
1467
1473
  .wg-ev-more:hover {