@use 'sprucecss/scss/spruce' as *;
.countdown {
background-color: color('surface');
border: config('border-width-base', $display) solid color('border');
border-radius: radius('lg');
padding: spacer-clamp('m', 'l');
text-align: center;
@include layout-stack('m');
&__title {
font-size: font-size('h4');
margin: 0;
}
&__timer {
display: flex;
flex-wrap: wrap;
gap: spacer('s');
justify-content: center;
}
&__unit {
background-color: color('surface-muted');
border-radius: radius('md');
display: flex;
flex-direction: column;
min-inline-size: 5rem;
padding: spacer('s');
}
&__value {
color: color('heading');
font-family: config('font-family-heading', $typography);
font-size: font-size('h2');
font-variant-numeric: tabular-nums;
font-weight: 700;
line-height: 1.1;
}
&__label {
color: color('muted');
font-size: 0.75rem;
font-weight: 600;
letter-spacing: 0.06em;
text-transform: uppercase;
}
}
<section class="countdown" aria-labelledby="countdown-title" data-countdown data-countdown-days="12">
<h2 class="countdown__title" id="countdown-title">The launch sale ends in</h2>
<p class="countdown__timer" role="timer" aria-live="off" data-countdown-timer>
<span class="countdown__unit"><span class="countdown__value" data-unit="days">12</span><span class="countdown__label">Days</span></span>
<span class="countdown__unit"><span class="countdown__value" data-unit="hours">00</span><span class="countdown__label">Hours</span></span>
<span class="countdown__unit"><span class="countdown__value" data-unit="minutes">00</span><span class="countdown__label">Minutes</span></span>
<span class="countdown__unit"><span class="countdown__value" data-unit="seconds">00</span><span class="countdown__label">Seconds</span></span>
</p>
<p class="sr-only" data-countdown-text></p>
<a class="btn btn--primary" href="#">Get 40% off</a>
</section>
(() => {
document.querySelectorAll('[data-countdown]').forEach((countdown) => {
const end = countdown.dataset.countdownEnd
? new Date(countdown.dataset.countdownEnd).getTime()
: Date.now() + Number(countdown.dataset.countdownDays || 0) * 864e5;
const units = Object.fromEntries([...countdown.querySelectorAll('[data-unit]')].map((element) => [element.dataset.unit, element]));
const text = countdown.querySelector('[data-countdown-text]');
let lastMinute = null;
const tick = () => {
const remaining = Math.max(0, end - Date.now());
const values = {
days: Math.floor(remaining / 864e5),
hours: Math.floor(remaining / 36e5) % 24,
minutes: Math.floor(remaining / 6e4) % 60,
seconds: Math.floor(remaining / 1e3) % 60
};
Object.entries(values).forEach(([unit, value]) => {
units[unit].textContent = String(value).padStart(2, '0');
});
if (values.minutes !== lastMinute) {
lastMinute = values.minutes;
text.textContent = `${values.days} days, ${values.hours} hours and ${values.minutes} minutes left.`;
}
if (remaining === 0) {
clearInterval(timer);
countdown.dispatchEvent(new CustomEvent('countdownend'));
}
};
const timer = setInterval(tick, 1000);
tick();
});
})();