your personal website on atproto - mirror blento.app
at fix-build 170 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 }} /> 92 <span class="text-base-400 dark:text-base-500 accent:text-accent-950 mx-0.5">:</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 accent:text-accent-950 mx-0.5">:</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 value={eventDays} trend={-1} class="text-4xl font-bold" /> 125 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">days</span 126 > 127 </div> 128 {/if} 129 <div class="flex flex-col items-center"> 130 <NumberFlow 131 value={eventHours} 132 trend={-1} 133 format={{ minimumIntegerDigits: 2 }} 134 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl" 135 /> 136 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">hrs</span> 137 </div> 138 <div class="flex flex-col items-center"> 139 <NumberFlow 140 value={eventMinutes} 141 trend={-1} 142 format={{ minimumIntegerDigits: 2 }} 143 digits={{ 1: { max: 5 } }} 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">min</span> 147 </div> 148 <div class="flex flex-col items-center"> 149 <NumberFlow 150 value={eventSeconds} 151 trend={-1} 152 format={{ minimumIntegerDigits: 2 }} 153 digits={{ 1: { max: 5 } }} 154 class="text-3xl font-bold @xs:text-4xl @sm:text-5xl @md:text-6xl @lg:text-7xl" 155 /> 156 <span class="text-base-500 dark:text-base-400 accent:text-accent-950 text-xs">sec</span> 157 </div> 158 </div> 159 </NumberFlowGroup> 160 {:else if isEventComplete} 161 <div 162 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" 163 > 164 Event Started! 165 </div> 166 {:else} 167 <div class="text-base-500 text-sm">Set a target date in settings</div> 168 {/if} 169 {/if} 170</div>