CTA Banners
Sticky bottom conversion bar
Persistent offer strip with dismiss
Persistent offer strip with dismiss
Create a sticky bottom conversion bar with Tailwind CSS: a fixed bar pinned to the bottom of the viewport with a blurred translucent dark background and a top border. Inside a max-width row, show a bold short offer on the left with a strikethrough original price and a highlighted discounted price, a countdown chip in the middle, a primary light button on the right, and a small dismiss X. Hide the countdown chip below sm. Then add JavaScript that ticks the countdown down to a fixed deadline once per second in days/hours/minutes/seconds, swaps the chip to an "Offer ended" state when it reaches zero, and dismisses the bar with a slide-down transition — remembering the dismissal in localStorage so it stays gone on the next visit. <div id="offerBar" class="fixed inset-x-0 bottom-0 z-50 translate-y-0 border-t border-white/10 bg-neutral-900/90 backdrop-blur transition-transform duration-300">
<div class="mx-auto flex max-w-6xl items-center gap-4 px-6 py-3.5">
<p class="flex-1 text-sm text-white">
<span class="font-semibold">Pro is 40% off through Friday</span>
<span class="ml-2 text-neutral-400 line-through">$24/mo</span>
<span class="ml-1.5 font-medium text-green-400">$14/mo</span>
</p>
<span id="countdown" class="hidden rounded-full bg-white/10 px-3 py-1 text-xs font-medium text-neutral-200 sm:block">Ends in 2d 06h</span>
<a href="#" class="shrink-0 rounded-lg bg-white px-4 py-2 text-sm font-medium text-neutral-900 hover:bg-neutral-100">Claim discount</a>
<button id="dismissOffer" class="shrink-0 text-neutral-500 transition hover:text-white" aria-label="Dismiss"><svg aria-hidden="true" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" class="inline-block h-[1em] w-[1em] shrink-0 align-[-0.125em] transition-transform"><path d="M6 6l12 12M18 6 6 18"/></svg></button>
</div>
</div> var STORAGE_KEY = "offer-bar-dismissed";
var DEADLINE = Date.now() + 2 * 24 * 3600 * 1000 + 6 * 3600 * 1000; // swap for a real date
var bar = document.getElementById("offerBar");
var chip = document.getElementById("countdown");
// localStorage throws in some embedded/private contexts — never let that break the bar.
function remembered(key) {
try { return localStorage.getItem(key); } catch (e) { return null; }
}
function remember(key, value) {
try { localStorage.setItem(key, value); } catch (e) {}
}
if (remembered(STORAGE_KEY)) {
bar.remove();
} else {
var tick = setInterval(function () {
var ms = DEADLINE - Date.now();
if (ms <= 0) {
clearInterval(tick);
chip.textContent = "Offer ended";
return;
}
var s = Math.floor(ms / 1000);
var d = Math.floor(s / 86400);
var h = Math.floor((s % 86400) / 3600);
var m = Math.floor((s % 3600) / 60);
var pad = function (n) { return n < 10 ? "0" + n : String(n); };
chip.textContent =
"Ends in " + (d > 0 ? d + "d " + pad(h) + "h" : pad(h) + "h " + pad(m) + "m " + pad(s % 60) + "s");
}, 1000);
document.getElementById("dismissOffer").addEventListener("click", function () {
bar.classList.add("translate-y-full");
remember(STORAGE_KEY, "1");
setTimeout(function () { bar.remove(); }, 300);
});
}