| <script lang="ts"> |
| import { onMount } from 'svelte'; |
| import { EVENT } from '$lib/data/content'; |
| import Icon from './Icon.svelte'; |
|
|
| |
| 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; |
| } |
|
|
| |
| |
| 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); |
| } |
| |
| |
| .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> |
| |