your personal website on atproto - mirror
blento.app
1<script lang="ts">
2 import NumberFlow, { NumberFlowGroup } from '@number-flow/svelte';
3 import type { ContentComponentProps } from '../types';
4 import type { CountdownCardData } from './index';
5 import { onMount } from 'svelte';
6
7 let { item }: ContentComponentProps = $props();
8
9 let cardData = $derived(item.cardData as CountdownCardData);
10
11 let now = $state(new Date());
12
13 onMount(() => {
14 const interval = setInterval(() => {
15 now = new Date();
16 }, 1000);
17 return () => clearInterval(interval);
18 });
19
20 // Countdown to target date
21 let eventDiff = $derived.by(() => {
22 if (!cardData.targetDate) return null;
23 const target = new Date(cardData.targetDate);
24 return Math.max(0, target.getTime() - now.getTime());
25 });
26
27 let eventDays = $derived(eventDiff !== null ? Math.floor(eventDiff / (1000 * 60 * 60 * 24)) : 0);
28 let eventHours = $derived(
29 eventDiff !== null ? Math.floor((eventDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) : 0
30 );
31 let eventMinutes = $derived(
32 eventDiff !== null ? Math.floor((eventDiff % (1000 * 60 * 60)) / (1000 * 60)) : 0
33 );
34 let eventSeconds = $derived(
35 eventDiff !== null ? Math.floor((eventDiff % (1000 * 60)) / 1000) : 0
36 );
37
38 // Check if event is in the past (elapsed mode)
39 let isEventPast = $derived.by(() => {
40 if (!cardData.targetDate) return false;
41 return now.getTime() > new Date(cardData.targetDate).getTime();
42 });
43
44 // Elapsed time since past event
45 let elapsedDiff = $derived.by(() => {
46 if (!isEventPast || !cardData.targetDate) return null;
47 return now.getTime() - new Date(cardData.targetDate).getTime();
48 });
49
50 let elapsedYears = $derived(
51 elapsedDiff !== null ? Math.floor(elapsedDiff / (1000 * 60 * 60 * 24 * 365)) : 0
52 );
53 let elapsedDays = $derived(
54 elapsedDiff !== null
55 ? Math.floor((elapsedDiff % (1000 * 60 * 60 * 24 * 365)) / (1000 * 60 * 60 * 24))
56 : 0
57 );
58 let elapsedHours = $derived(
59 elapsedDiff !== null ? Math.floor((elapsedDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) : 0
60 );
61 let elapsedMinutes = $derived(
62 elapsedDiff !== null ? Math.floor((elapsedDiff % (1000 * 60 * 60)) / (1000 * 60)) : 0
63 );
64 let elapsedSeconds = $derived(
65 elapsedDiff !== null ? Math.floor((elapsedDiff % (1000 * 60)) / 1000) : 0
66 );
67</script>
68
69<div class="@container flex h-full w-full flex-col items-center justify-center p-4">
70 {#if isEventPast && elapsedDiff !== null}
71 <!-- Elapsed time since past event -->
72 <NumberFlowGroup>
73 <div
74 class="text-base-900 dark:text-base-100 accent:text-base-900 flex items-baseline gap-4 text-center @sm:gap-6 @md:gap-8"
75 style="font-variant-numeric: tabular-nums;"
76 >
77 {#if elapsedYears > 0}
78 <div class="flex flex-col items-center">
79 <NumberFlow
80 value={elapsedYears}
81 trend={1}
82 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
83 />
84 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs"
85 >{elapsedYears === 1 ? 'year' : 'years'}</span
86 >
87 </div>
88 {/if}
89 {#if elapsedYears > 0 || elapsedDays > 0}
90 <div class="flex flex-col items-center">
91 <NumberFlow
92 value={elapsedDays}
93 trend={1}
94 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
95 />
96 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs"
97 >{elapsedDays === 1 ? 'day' : 'days'}</span
98 >
99 </div>
100 {/if}
101 <div class="flex flex-col items-center">
102 <NumberFlow
103 value={elapsedHours}
104 trend={1}
105 format={{ minimumIntegerDigits: 2 }}
106 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
107 />
108 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">hrs</span>
109 </div>
110 <div class="flex flex-col items-center">
111 <NumberFlow
112 value={elapsedMinutes}
113 trend={1}
114 format={{ minimumIntegerDigits: 2 }}
115 digits={{ 1: { max: 5 } }}
116 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
117 />
118 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">min</span>
119 </div>
120 <div class="flex flex-col items-center">
121 <NumberFlow
122 value={elapsedSeconds}
123 trend={1}
124 format={{ minimumIntegerDigits: 2 }}
125 digits={{ 1: { max: 5 } }}
126 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
127 />
128 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">sec</span>
129 </div>
130 </div>
131 </NumberFlowGroup>
132 {:else if eventDiff !== null}
133 <!-- Countdown to future event -->
134 <NumberFlowGroup>
135 <div
136 class="text-base-900 dark:text-base-100 accent:text-base-900 flex items-baseline gap-4 text-center @sm:gap-6 @md:gap-8"
137 style="font-variant-numeric: tabular-nums;"
138 >
139 {#if eventDays > 0}
140 <div class="flex flex-col items-center">
141 <NumberFlow
142 value={eventDays}
143 trend={-1}
144 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
145 />
146 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs"
147 >{eventDays === 1 ? 'day' : 'days'}</span
148 >
149 </div>
150 {/if}
151 <div class="flex flex-col items-center">
152 <NumberFlow
153 value={eventHours}
154 trend={-1}
155 format={{ minimumIntegerDigits: 2 }}
156 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
157 />
158 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">hrs</span>
159 </div>
160 <div class="flex flex-col items-center">
161 <NumberFlow
162 value={eventMinutes}
163 trend={-1}
164 format={{ minimumIntegerDigits: 2 }}
165 digits={{ 1: { max: 5 } }}
166 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
167 />
168 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">min</span>
169 </div>
170 <div class="flex flex-col items-center">
171 <NumberFlow
172 value={eventSeconds}
173 trend={-1}
174 format={{ minimumIntegerDigits: 2 }}
175 digits={{ 1: { max: 5 } }}
176 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl"
177 />
178 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">sec</span>
179 </div>
180 </div>
181 </NumberFlowGroup>
182 {:else}
183 <div class="text-base-500 text-sm">Set a target date in settings</div>
184 {/if}
185</div>