field-guide / src /lib /components /Countdown.svelte
pngwn
fix
fdf3925
Raw
History Blame
4.48 kB
<script lang="ts">
import { onMount } from 'svelte';
import { EVENT } from '$lib/data/content';
import Icon from './Icon.svelte';
// Final submission deadline — read as UTC from the content model.
const DEADLINE = new Date(EVENT.deadlineISO).getTime();
const pad = (n: number) => String(n).padStart(2, '0');
type Parts = { days: string; hrs: string; min: string; sec: string };
let parts = $state<Parts | null>(null);
let done = $state(false);
function tick(): boolean {
const ms = DEADLINE - Date.now();
if (ms <= 0) {
done = true;
parts = { days: '00', hrs: '00', min: '00', sec: '00' };
return false;
}
const s = Math.floor(ms / 1000);
parts = {
days: pad(Math.floor(s / 86400)),
hrs: pad(Math.floor((s % 86400) / 3600)),
min: pad(Math.floor((s % 3600) / 60)),
sec: pad(s % 60)
};
return true;
}
// Only run on the client — the page is prerendered, so SSR renders the
// placeholder grid below and the real values pop in on hydration (no mismatch).
onMount(() => {
if (!tick()) return;
const id = setInterval(() => {
if (!tick()) clearInterval(id);
}, 1000);
return () => clearInterval(id);
});
const units = $derived([
{ k: 'days', v: parts?.days ?? '––' },
{ k: 'hrs', v: parts?.hrs ?? '––' },
{ k: 'min', v: parts?.min ?? '––' },
{ k: 'sec', v: parts?.sec ?? '––' }
]);
</script>
<div class="countdown" class:countdown--done={done}>
<div class="countdown__head">
<span class="eyebrow">{done ? 'Deadline passed' : 'Counting down to'}</span>
<span class="countdown__when">Jun 15 · 23:59 UTC</span>
</div>
{#if done}
<p class="countdown__closed">Submissions closed</p>
{:else}
<div
class="countdown__grid"
role="timer"
aria-label="Time remaining until the submission deadline"
>
{#each units as u (u.k)}
<div class="countdown__cell" class:is-live={u.k === 'sec'}>
<b class="countdown__num">{u.v}</b>
<span class="countdown__lbl">{u.k}</span>
</div>
{/each}
</div>
<div class="countdown__foot">
<span class="countdown__note"
>Built something? Make sure your README's tagged before the clock runs out.</span
>
<a class="btn btn--accent countdown__cta" href="/submit">
Validate your README <Icon name="arrow" size={18} stroke="#fff" />
</a>
</div>
{/if}
</div>
<style>
.countdown {
width: 100%;
background: var(--kraft);
border: 2px solid var(--ink);
border-radius: 0;
box-shadow: 8px 8px 0 var(--ink);
}
.countdown__head {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
padding: 14px 20px;
border-bottom: 2px solid var(--ink);
}
.countdown__when {
font-family: var(--font-mono);
font-size: 12px;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-2);
white-space: nowrap;
}
.countdown__grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
}
.countdown__cell {
padding: 22px 8px 18px;
text-align: center;
border-right: 2px solid var(--ink);
}
.countdown__cell:last-child {
border-right: none;
}
.countdown__num {
display: block;
font-family: var(--font-display);
font-weight: 800;
font-stretch: 115%;
font-variant-numeric: tabular-nums;
font-size: clamp(46px, 8vw, 96px);
line-height: 0.92;
letter-spacing: -0.02em;
color: var(--ink);
}
.countdown__cell.is-live .countdown__num {
color: var(--clay);
}
.countdown__lbl {
display: block;
margin-top: 10px;
font-family: var(--font-mono);
font-size: 11px;
letter-spacing: 0.16em;
text-transform: uppercase;
color: var(--ink-soft);
}
/* action bar — guides users straight to the README validator */
.countdown__foot {
display: flex;
align-items: center;
justify-content: space-between;
gap: 14px 24px;
flex-wrap: wrap;
padding: 16px 20px;
border-top: 2px solid var(--ink);
background: color-mix(in srgb, var(--amber) 12%, var(--kraft));
}
.countdown__note {
font-family: var(--font-mono);
font-size: 13px;
letter-spacing: 0.01em;
color: var(--ink-2);
max-width: 42ch;
}
.countdown__cta {
white-space: nowrap;
}
.countdown__closed {
margin: 0;
padding: 34px 20px;
text-align: center;
font-family: var(--font-display);
font-weight: 800;
font-stretch: 110%;
font-size: clamp(24px, 3.2vw, 34px);
color: var(--ink);
}
@media (max-width: 560px) {
.countdown__cta {
width: 100%;
justify-content: center;
}
}
</style>