your personal website on atproto - mirror blento.app
at invalid-handle-fix 173 lines 5.7 kB view raw
1<script lang="ts"> 2 import NumberFlow, { NumberFlowGroup } from '@number-flow/svelte'; 3 import type { ContentComponentProps } from '../types'; 4 import type { TimerCardData } from './index'; 5 import { onMount } from 'svelte'; 6 7 let { item }: ContentComponentProps = $props(); 8 9 let cardData = $derived(item.cardData as TimerCardData); 10 11 // For clock and event modes - current time 12 let now = $state(new Date()); 13 14 onMount(() => { 15 const interval = setInterval(() => { 16 now = new Date(); 17 }, 1000); 18 return () => clearInterval(interval); 19 }); 20 21 // Clock mode: get time parts for timezone 22 let clockParts = $derived.by(() => { 23 if (cardData.mode !== 'clock') return null; 24 try { 25 return new Intl.DateTimeFormat('en-US', { 26 timeZone: cardData.timezone || 'UTC', 27 hour: '2-digit', 28 minute: '2-digit', 29 second: '2-digit', 30 hour12: false 31 }).formatToParts(now); 32 } catch { 33 return null; 34 } 35 }); 36 37 let clockHours = $derived( 38 clockParts ? parseInt(clockParts.find((p) => p.type === 'hour')?.value || '0') : 0 39 ); 40 let clockMinutes = $derived( 41 clockParts ? parseInt(clockParts.find((p) => p.type === 'minute')?.value || '0') : 0 42 ); 43 let clockSeconds = $derived( 44 clockParts ? parseInt(clockParts.find((p) => p.type === 'second')?.value || '0') : 0 45 ); 46 47 // Event mode: countdown to target date 48 let eventDiff = $derived.by(() => { 49 if (cardData.mode !== 'event' || !cardData.targetDate) return null; 50 const target = new Date(cardData.targetDate); 51 return Math.max(0, target.getTime() - now.getTime()); 52 }); 53 54 let eventDays = $derived(eventDiff !== null ? Math.floor(eventDiff / (1000 * 60 * 60 * 24)) : 0); 55 let eventHours = $derived( 56 eventDiff !== null ? Math.floor((eventDiff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)) : 0 57 ); 58 let eventMinutes = $derived( 59 eventDiff !== null ? Math.floor((eventDiff % (1000 * 60 * 60)) / (1000 * 60)) : 0 60 ); 61 let eventSeconds = $derived( 62 eventDiff !== null ? Math.floor((eventDiff % (1000 * 60)) / 1000) : 0 63 ); 64 65 let isEventComplete = $derived(cardData.mode === 'event' && eventDiff === 0); 66 67 // Get timezone display name 68 let timezoneDisplay = $derived.by(() => { 69 if (!cardData.timezone) return ''; 70 try { 71 const formatter = new Intl.DateTimeFormat('en-US', { 72 timeZone: cardData.timezone, 73 timeZoneName: 'short' 74 }); 75 const parts = formatter.formatToParts(now); 76 return parts.find((p) => p.type === 'timeZoneName')?.value || cardData.timezone; 77 } catch { 78 return cardData.timezone; 79 } 80 }); 81</script> 82 83<div class="@container flex h-full w-full flex-col items-center justify-center p-4"> 84 <!-- Clock Mode --> 85 {#if cardData.mode === 'clock'} 86 <NumberFlowGroup> 87 <div 88 class="text-base-900 dark:text-base-100 accent:text-base-900 flex items-center text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl" 89 style="font-variant-numeric: tabular-nums;" 90 > 91 <NumberFlow value={clockHours} format={{ minimumIntegerDigits: 2 }} trend={1} /> 92 <span class="text-base-400 dark:text-base-500 mx-0.5 @sm:mx-1">:</span> 93 <NumberFlow 94 value={clockMinutes} 95 format={{ minimumIntegerDigits: 2 }} 96 digits={{ 1: { max: 5 } }} 97 trend={1} 98 /> 99 <span class="text-base-400 dark:text-base-500 mx-0.5 @sm:mx-1">:</span> 100 <NumberFlow 101 value={clockSeconds} 102 format={{ minimumIntegerDigits: 2 }} 103 digits={{ 1: { max: 5 } }} 104 trend={1} 105 /> 106 </div> 107 </NumberFlowGroup> 108 {#if timezoneDisplay} 109 <div class="text-base-500 dark:text-base-400 accent:text-base-600 mt-1 text-xs @sm:text-sm"> 110 {timezoneDisplay} 111 </div> 112 {/if} 113 114 <!-- Event Countdown Mode --> 115 {:else if cardData.mode === 'event'} 116 {#if eventDiff !== null && !isEventComplete} 117 <NumberFlowGroup> 118 <div 119 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" 120 style="font-variant-numeric: tabular-nums;" 121 > 122 {#if eventDays > 0} 123 <div class="flex flex-col items-center"> 124 <NumberFlow 125 value={eventDays} 126 trend={-1} 127 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl" 128 /> 129 <span class="text-base-500 dark:text-base-400 text-xs @sm:text-sm">days</span> 130 </div> 131 {/if} 132 <div class="flex flex-col items-center"> 133 <NumberFlow 134 value={eventHours} 135 trend={-1} 136 format={{ minimumIntegerDigits: 2 }} 137 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl" 138 /> 139 <span class="text-base-500 dark:text-base-400 text-xs @sm:text-sm">hrs</span> 140 </div> 141 <div class="flex flex-col items-center"> 142 <NumberFlow 143 value={eventMinutes} 144 trend={-1} 145 format={{ minimumIntegerDigits: 2 }} 146 digits={{ 1: { max: 5 } }} 147 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl" 148 /> 149 <span class="text-base-500 dark:text-base-400 text-xs @sm:text-sm">min</span> 150 </div> 151 <div class="flex flex-col items-center"> 152 <NumberFlow 153 value={eventSeconds} 154 trend={-1} 155 format={{ minimumIntegerDigits: 2 }} 156 digits={{ 1: { max: 5 } }} 157 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl" 158 /> 159 <span class="text-base-500 dark:text-base-400 text-xs @sm:text-sm">sec</span> 160 </div> 161 </div> 162 </NumberFlowGroup> 163 {:else if isEventComplete} 164 <div 165 class="text-accent-600 dark:text-accent-400 accent:text-accent-900 text-xl font-bold @xs:text-2xl @sm:text-3xl @md:text-4xl" 166 > 167 Event Started! 168 </div> 169 {:else} 170 <div class="text-base-500 text-sm">Set a target date in settings</div> 171 {/if} 172 {/if} 173</div>