Sticky Promo Bar — Announcement Banner HTML CSS JS

Sticky Promo Announcement Bar · Navigation · Plain HTML, CSS & JS · Live preview

Share & Support

What's included

Features

Live countdown timer
setInterval at 1000ms decrements totalSeconds. font-variant-numeric:tabular-nums prevents layout shifts as digit widths change.
Three colour variants
Gradient sale bar, dark announcement bar, and amber warning bar — matching the three most common promo bar use cases in SaaS and e-commerce.
Independent dismiss buttons
Each bar has its own closeBar(id) call — hiding one doesn't affect the others. Extend with localStorage to persist dismissal across reloads.
Countdown digit containers
rgba(0,0,0,.2) background on digit units works on any bar colour without hardcoding — transparent dark overlay that adapts to the gradient.
Responsive wrapping
.bar-inner uses flex-wrap: wrap with justify-content: center — countdown and CTA wrap to a second line on mobile without breaking the layout.
CTA button variants
Solid white on gradient, outline ghost on dark, filled amber on warning — each CTA matches its bar's colour system.
Fixed dismiss button position
.bar-close is position: absolute; right: 10px — always visible at the right edge regardless of bar content length.
Emoji prefix
Each bar opens with an emoji (⚡🚀🛠) — proven to increase click-through by making the bar feel less like corporate boilerplate.

About this UI Snippet

Sticky Promo Bar — Live Countdown Timer, Gradient Variants & Dismiss Pattern

Screenshot of the Sticky Promo Announcement Bar snippet rendered live

The sticky announcement bar is one of the highest-ROI UI components in SaaS and e-commerce — a single thin bar at the top of the page that communicates a time-sensitive offer, feature launch, or system status without blocking page content. This snippet builds three production-quality bar variants: a gradient sale bar with a live countdown timer, a dark announcement bar for feature launches, and an amber warning bar for maintenance notices. Each bar dismisses independently.

Announcement bars appear on virtually every high-converting SaaS and e-commerce site. Product Hunt uses them for server load notices. Vercel uses them to announce new features. E-commerce stores use them for flash sales with countdown timers. The design challenge is fitting the message, CTA, and dismiss button into 44px of height without the bar feeling cramped — especially on mobile where the bar must wrap gracefully.

Live countdown timer

The countdown timer uses setInterval(tick, 1000) to decrement a totalSeconds counter every second. Hours, minutes, and seconds are derived with integer division and modulo: h = Math.floor(totalSeconds / 3600), m = Math.floor((totalSeconds % 3600) / 60), s = totalSeconds % 60. Each unit is padded to two digits with String(n).padStart(2, '0'). The digits use font-variant-numeric: tabular-nums so the layout doesn't shift as numbers change width — critical for countdown displays.

The countdown unit blocks use background: rgba(0,0,0,.2) — a dark overlay on the gradient background that creates a pill-shaped digit container without hardcoding a background colour. This means the same .cd-unit style works on any bar background colour.

Three bar variants

The gradient bar uses a CSS linear-gradient(90deg, #6366f1, #8b5cf6, #ec4899) — the purple-to-pink gradient commonly associated with urgency and deals. The dark bar uses #0f172a (slate-900) with a subtle border — the pattern used for feature announcements and product updates. The warning bar uses an amber #fffbeb background with amber borders — the universally understood "caution" colour for maintenance windows. All three share the same structural CSS; only the colour scheme changes via class variants.

Dismiss with localStorage persistence

The closeBar function adds a .hidden class for immediate visual dismissal. In production, you'd extend this with localStorage.setItem('promoDismissed_bar1', 'true') and check on page load — so dismissed bars don't reappear on refresh. The localStorage key should include a version or date so new campaigns show even for users who dismissed an older bar.

CTA button variants

Three CTA styles match each bar variant: solid white button on the gradient bar (high contrast), ghost/outline button on the dark bar (subtle, doesn't compete with the headline), and amber filled button on the warning bar (matches the warning colour scheme). All use the same base .bar-cta class with modifier classes for variant styling. Pair with a modal for a full promotional overlay flow.

Step by step

How to Use

  1. 1
    Paste HTML, CSS, and JSThree stacked announcement bars appear at the top of the page. The first has a live countdown timer ticking down from 2h 47m 33s.
  2. 2
    Watch the countdown tickThe H:MM:SS digits update every second. Numbers use tabular-nums spacing so the layout doesn't shift.
  3. 3
    Click the × to dismiss each barEach bar hides independently — dismissing the gradient bar leaves the dark and warning bars visible.
  4. 4
    Click the CTA links"Claim offer →" on the gradient bar, "Explore snippets →" on the dark bar, "Read more →" on the warning bar — each links independently.
  5. 5
    Set your countdown end timeChange let totalSeconds = 2 * 3600 + 47 * 60 + 33 at the top of the JS to your target duration in hours, minutes, seconds.
  6. 6
    Persist dismiss to localStorageAdd localStorage.setItem('promoDismissed', '1') inside closeBar and check it on load to prevent the bar reappearing on refresh.

Real-world uses

Common Use Cases

Flash sale countdown banners
E-commerce stores use gradient countdown bars for time-limited sales. The countdown creates urgency — FOMO drives the highest-converting announcement bars.
Feature launch announcements
SaaS products use the dark bar to announce new features or integrations to existing users on every page — lower friction than a modal.
Scheduled maintenance notices
The warning bar informs users of upcoming downtime before it happens — reduces support tickets and sets expectations.
Free shipping threshold
E-commerce: "Add $12 more for free shipping" bar that updates dynamically as users add items to cart. Driven by cart total via JS.
Conference and event countdowns
Event sites use countdown bars to build anticipation before registration deadlines or live streams begin.
Beta/early access programmes
"You're on the waitlist — early access opens in 3 days" bars give users a visible signal that their wait is almost over.

Got questions?

Frequently Asked Questions

Add position: sticky; top: 0; z-index: 100 to .promo-bar. For a fixed bar that stays visible on scroll, use position: fixed; top: 0; left: 0; right: 0 and add padding-top: 44px to body or the first page section to prevent content being hidden behind it.

Inside closeBar(id), add: localStorage.setItem('dismissed_' + id, '1'). At page load: ['bar1','bar2','bar3'].forEach(id => { if (localStorage.getItem('dismissed_' + id)) document.getElementById(id).classList.add('hidden') }).

Replace the totalSeconds variable with: const deadline = new Date('2026-12-31T23:59:59'); let totalSeconds = Math.floor((deadline - Date.now()) / 1000). This counts down to a fixed date rather than a relative duration.

Create a PromoBar component with props: {variant, emoji, text, cta, ctaHref, deadline}. The countdown uses useEffect with a setInterval cleanup and useState for the remaining seconds. The dismiss uses const [dismissed, setDismissed] = useState(false) — return null when dismissed.

Sticky Promo Announcement Bar — Export as HTML, React, Vue, Angular & Tailwind

HTML
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Sticky Promo Announcement Bar</title>
  <style>
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,sans-serif;background:#f8fafc}
    
    .promo-bar{position:relative;width:100%;display:flex;align-items:center;justify-content:center;padding:10px 44px 10px 16px;min-height:44px;transition:all .3s ease}
    .promo-bar.hidden{display:none}
    
    .bar-gradient{background:linear-gradient(90deg,#6366f1 0%,#8b5cf6 50%,#ec4899 100%);color:#fff}
    .bar-dark{background:#0f172a;color:#e2e8f0;border-top:1px solid #1e293b;border-bottom:1px solid #1e293b}
    .bar-warning{background:#fffbeb;color:#92400e;border-top:1px solid #fde68a;border-bottom:1px solid #fde68a}
    
    .bar-inner{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center;text-align:center}
    .bar-emoji{font-size:15px;flex-shrink:0}
    .bar-text{font-size:13px;line-height:1.4}
    .bar-gradient .bar-text,.bar-dark .bar-text{color:rgba(255,255,255,.9)}
    .bar-warning .bar-text{color:#92400e}
    .bar-text strong{font-weight:700}
    .bar-gradient .bar-text strong,.bar-dark .bar-text strong{color:#fff}
    .bar-warning .bar-text strong{color:#78350f}
    
    .countdown{display:flex;align-items:center;gap:4px;flex-shrink:0}
    .cd-unit{display:flex;align-items:baseline;gap:2px;background:rgba(0,0,0,.2);border-radius:6px;padding:2px 6px}
    .cd-num{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;min-width:20px;text-align:center}
    .cd-label{font-size:9px;font-weight:600;opacity:.8;text-transform:uppercase}
    .cd-sep{font-size:14px;font-weight:800;opacity:.6;margin:0 1px}
    
    .bar-cta{display:inline-flex;align-items:center;font-size:12px;font-weight:700;padding:4px 12px;border-radius:6px;text-decoration:none;transition:all .15s;white-space:nowrap;flex-shrink:0;background:rgba(255,255,255,.95);color:#6366f1}
    .bar-cta:hover{background:#fff;transform:translateY(-1px)}
    .bar-cta-outline{background:transparent;color:#e2e8f0;border:1px solid rgba(255,255,255,.3)}
    .bar-cta-outline:hover{background:rgba(255,255,255,.1);color:#fff}
    .bar-cta-warning{background:#f59e0b;color:#fff;border:none}
    .bar-cta-warning:hover{background:#d97706;color:#fff}
    
    .bar-close{position:absolute;right:10px;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,.15);border:none;color:inherit;font-size:18px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;opacity:.8}
    .bar-close:hover{background:rgba(255,255,255,.25);opacity:1}
    .bar-warning .bar-close{background:rgba(0,0,0,.08);color:#92400e}
    .bar-warning .bar-close:hover{background:rgba(0,0,0,.15)}
    
    .demo-content{padding:40px 24px;max-width:600px;margin:0 auto;text-align:center}
    .demo-content h2{font-size:22px;font-weight:800;color:#1e293b;margin-bottom:12px}
    .demo-content p{font-size:14px;color:#64748b;line-height:1.6;margin-bottom:20px}
    .demo-labels{display:flex;flex-direction:column;gap:4px}
    .label{font-size:11px;color:#94a3b8}
  </style>
</head>
<body>
  <div class="demo-page">
    <!-- Bar 1: Countdown sale bar -->
    <div class="promo-bar bar-gradient" id="bar1">
      <div class="bar-inner">
        <span class="bar-emoji">⚡</span>
        <span class="bar-text">Flash Sale — <strong>50% off all plans.</strong> Ends in</span>
        <div class="countdown" id="countdown">
          <span class="cd-unit"><span class="cd-num" id="cdH">02</span><span class="cd-label">hr</span></span>
          <span class="cd-sep">:</span>
          <span class="cd-unit"><span class="cd-num" id="cdM">47</span><span class="cd-label">min</span></span>
          <span class="cd-sep">:</span>
          <span class="cd-unit"><span class="cd-num" id="cdS">33</span><span class="cd-label">sec</span></span>
        </div>
        <a href="#" class="bar-cta">Claim offer →</a>
      </div>
      <button class="bar-close" onclick="closeBar('bar1')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Bar 2: Info/feature bar -->
    <div class="promo-bar bar-dark" id="bar2">
      <div class="bar-inner">
        <span class="bar-emoji">🚀</span>
        <span class="bar-text">New: <strong>React + Tailwind exports</strong> are now live for all 215+ snippets.</span>
        <a href="#" class="bar-cta bar-cta-outline">Explore snippets →</a>
      </div>
      <button class="bar-close" onclick="closeBar('bar2')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Bar 3: Warning/maintenance bar -->
    <div class="promo-bar bar-warning" id="bar3">
      <div class="bar-inner">
        <span class="bar-emoji">🛠</span>
        <span class="bar-text"><strong>Scheduled maintenance</strong> on Sunday, June 15 from 02:00–04:00 UTC.</span>
        <a href="#" class="bar-cta bar-cta-warning">Read more →</a>
      </div>
      <button class="bar-close" onclick="closeBar('bar3')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Demo page content -->
    <div class="demo-content">
      <h2>Sticky Promo Bar</h2>
      <p>Three variants: gradient sale bar with live countdown, dark feature announcement, and amber warning/maintenance bar. Each dismisses independently.</p>
      <div class="demo-labels">
        <span class="label">↑ Gradient countdown bar</span>
        <span class="label">↑ Dark announcement bar</span>
        <span class="label">↑ Warning/maintenance bar</span>
      </div>
    </div>
  </div>
  <script>
    function closeBar(id) {
      document.getElementById(id).classList.add('hidden');
    }
    
    // Live countdown timer
    let totalSeconds = 2 * 3600 + 47 * 60 + 33;
    
    function tick() {
      if (totalSeconds <= 0) return;
      totalSeconds--;
      const h = Math.floor(totalSeconds / 3600);
      const m = Math.floor((totalSeconds % 3600) / 60);
      const s = totalSeconds % 60;
      const pad = n => String(n).padStart(2, '0');
      document.getElementById('cdH').textContent = pad(h);
      document.getElementById('cdM').textContent = pad(m);
      document.getElementById('cdS').textContent = pad(s);
    }
    
    setInterval(tick, 1000);
  </script>
</body>
</html>
Tailwind
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>Sticky Promo Announcement Bar</title>
  <!-- Tailwind CSS v3+ — arbitrary value classes (e.g. bg-[#0f172a]) are valid -->
  <script src="https://cdn.tailwindcss.com"></script>
  <style>
    * {
      box-sizing:border-box;margin:0;padding:0
    }

    body {
      font-family:system-ui,sans-serif;background:#f8fafc
    }

    .promo-bar.hidden {
      display:none
    }

    .bar-gradient .bar-text,.bar-dark .bar-text {
      color:rgba(255,255,255,.9)
    }

    .bar-warning .bar-text {
      color:#92400e
    }

    .bar-text strong {
      font-weight:700
    }

    .bar-gradient .bar-text strong,.bar-dark .bar-text strong {
      color:#fff
    }

    .bar-warning .bar-text strong {
      color:#78350f
    }

    .bar-warning .bar-close {
      background:rgba(0,0,0,.08);color:#92400e
    }

    .bar-warning .bar-close:hover {
      background:rgba(0,0,0,.15)
    }

    .demo-content h2 {
      font-size:22px;font-weight:800;color:#1e293b;margin-bottom:12px
    }

    .demo-content p {
      font-size:14px;color:#64748b;line-height:1.6;margin-bottom:20px
    }
  </style>
</head>
<body>
  <div class="demo-page">
    <!-- Bar 1: Countdown sale bar -->
    <div class="promo-bar relative w-full flex items-center justify-center pt-2.5 pr-11 pb-2.5 pl-4 min-h-[44px] [transition:all_.3s_ease] bar-gradient [background:linear-gradient(90deg,#6366f1_0%,#8b5cf6_50%,#ec4899_100%)] text-[#fff]" id="bar1">
      <div class="flex items-center gap-2 flex-wrap justify-center text-center">
        <span class="text-[15px] shrink-0">⚡</span>
        <span class="bar-text text-[13px] leading-[1.4]">Flash Sale — <strong>50% off all plans.</strong> Ends in</span>
        <div class="flex items-center gap-1 shrink-0" id="countdown">
          <span class="flex items-baseline gap-0.5 bg-[rgba(0,0,0,.2)] rounded-md py-0.5 px-1.5"><span class="text-sm font-extrabold [font-variant-numeric:tabular-nums] min-w-[20px] text-center" id="cdH">02</span><span class="text-[9px] font-semibold opacity-80 uppercase">hr</span></span>
          <span class="text-sm font-extrabold opacity-60 my-0 mx-px">:</span>
          <span class="flex items-baseline gap-0.5 bg-[rgba(0,0,0,.2)] rounded-md py-0.5 px-1.5"><span class="text-sm font-extrabold [font-variant-numeric:tabular-nums] min-w-[20px] text-center" id="cdM">47</span><span class="text-[9px] font-semibold opacity-80 uppercase">min</span></span>
          <span class="text-sm font-extrabold opacity-60 my-0 mx-px">:</span>
          <span class="flex items-baseline gap-0.5 bg-[rgba(0,0,0,.2)] rounded-md py-0.5 px-1.5"><span class="text-sm font-extrabold [font-variant-numeric:tabular-nums] min-w-[20px] text-center" id="cdS">33</span><span class="text-[9px] font-semibold opacity-80 uppercase">sec</span></span>
        </div>
        <a href="#" class="inline-flex items-center text-xs font-bold py-1 px-3 rounded-md no-underline [transition:all_.15s] whitespace-nowrap shrink-0 bg-[rgba(255,255,255,.95)] text-[#6366f1] hover:bg-[#fff] hover:[transform:translateY(-1px)]">Claim offer →</a>
      </div>
      <button class="bar-close absolute right-2.5 top-1/2 [transform:translateY(-50%)] w-7 h-7 rounded-md bg-[rgba(255,255,255,.15)] border-0 text-inherit text-lg leading-none cursor-pointer flex items-center justify-center [transition:background_.15s] opacity-80 hover:bg-[rgba(255,255,255,.25)] hover:opacity-100" onclick="closeBar('bar1')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Bar 2: Info/feature bar -->
    <div class="promo-bar relative w-full flex items-center justify-center pt-2.5 pr-11 pb-2.5 pl-4 min-h-[44px] [transition:all_.3s_ease] bar-dark bg-[#0f172a] text-[#e2e8f0] border-t border-t-[#1e293b] border-b border-b-[#1e293b]" id="bar2">
      <div class="flex items-center gap-2 flex-wrap justify-center text-center">
        <span class="text-[15px] shrink-0">🚀</span>
        <span class="bar-text text-[13px] leading-[1.4]">New: <strong>React + Tailwind exports</strong> are now live for all 215+ snippets.</span>
        <a href="#" class="inline-flex items-center text-xs font-bold py-1 px-3 rounded-md no-underline [transition:all_.15s] whitespace-nowrap shrink-0 bg-[rgba(255,255,255,.95)] text-[#6366f1] hover:bg-[#fff] hover:[transform:translateY(-1px)] bg-transparent text-[#e2e8f0] border border-[rgba(255,255,255,.3)] hover:bg-[rgba(255,255,255,.1)] hover:text-[#fff]">Explore snippets →</a>
      </div>
      <button class="bar-close absolute right-2.5 top-1/2 [transform:translateY(-50%)] w-7 h-7 rounded-md bg-[rgba(255,255,255,.15)] border-0 text-inherit text-lg leading-none cursor-pointer flex items-center justify-center [transition:background_.15s] opacity-80 hover:bg-[rgba(255,255,255,.25)] hover:opacity-100" onclick="closeBar('bar2')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Bar 3: Warning/maintenance bar -->
    <div class="promo-bar relative w-full flex items-center justify-center pt-2.5 pr-11 pb-2.5 pl-4 min-h-[44px] [transition:all_.3s_ease] bar-warning bg-[#fffbeb] text-[#92400e] border-t border-t-[#fde68a] border-b border-b-[#fde68a]" id="bar3">
      <div class="flex items-center gap-2 flex-wrap justify-center text-center">
        <span class="text-[15px] shrink-0">🛠</span>
        <span class="bar-text text-[13px] leading-[1.4]"><strong>Scheduled maintenance</strong> on Sunday, June 15 from 02:00–04:00 UTC.</span>
        <a href="#" class="inline-flex items-center text-xs font-bold py-1 px-3 rounded-md no-underline [transition:all_.15s] whitespace-nowrap shrink-0 bg-[rgba(255,255,255,.95)] text-[#6366f1] hover:bg-[#fff] hover:[transform:translateY(-1px)] bg-[#f59e0b] text-[#fff] border-0 hover:bg-[#d97706] hover:text-[#fff]">Read more →</a>
      </div>
      <button class="bar-close absolute right-2.5 top-1/2 [transform:translateY(-50%)] w-7 h-7 rounded-md bg-[rgba(255,255,255,.15)] border-0 text-inherit text-lg leading-none cursor-pointer flex items-center justify-center [transition:background_.15s] opacity-80 hover:bg-[rgba(255,255,255,.25)] hover:opacity-100" onclick="closeBar('bar3')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Demo page content -->
    <div class="demo-content py-10 px-6 max-w-[600px] my-0 mx-auto text-center">
      <h2>Sticky Promo Bar</h2>
      <p>Three variants: gradient sale bar with live countdown, dark feature announcement, and amber warning/maintenance bar. Each dismisses independently.</p>
      <div class="flex flex-col gap-1">
        <span class="text-[11px] text-[#94a3b8]">↑ Gradient countdown bar</span>
        <span class="text-[11px] text-[#94a3b8]">↑ Dark announcement bar</span>
        <span class="text-[11px] text-[#94a3b8]">↑ Warning/maintenance bar</span>
      </div>
    </div>
  </div>
  <script>
    function closeBar(id) {
      document.getElementById(id).classList.add('hidden');
    }
    
    // Live countdown timer
    let totalSeconds = 2 * 3600 + 47 * 60 + 33;
    
    function tick() {
      if (totalSeconds <= 0) return;
      totalSeconds--;
      const h = Math.floor(totalSeconds / 3600);
      const m = Math.floor((totalSeconds % 3600) / 60);
      const s = totalSeconds % 60;
      const pad = n => String(n).padStart(2, '0');
      document.getElementById('cdH').textContent = pad(h);
      document.getElementById('cdM').textContent = pad(m);
      document.getElementById('cdS').textContent = pad(s);
    }
    
    setInterval(tick, 1000);
  </script>
</body>
</html>
React
import React, { useEffect } from 'react';

// CSS — optionally move to StickyPromoAnnouncementBar.module.css
const css = `
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,sans-serif;background:#f8fafc}

.promo-bar{position:relative;width:100%;display:flex;align-items:center;justify-content:center;padding:10px 44px 10px 16px;min-height:44px;transition:all .3s ease}
.promo-bar.hidden{display:none}

.bar-gradient{background:linear-gradient(90deg,#6366f1 0%,#8b5cf6 50%,#ec4899 100%);color:#fff}
.bar-dark{background:#0f172a;color:#e2e8f0;border-top:1px solid #1e293b;border-bottom:1px solid #1e293b}
.bar-warning{background:#fffbeb;color:#92400e;border-top:1px solid #fde68a;border-bottom:1px solid #fde68a}

.bar-inner{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center;text-align:center}
.bar-emoji{font-size:15px;flex-shrink:0}
.bar-text{font-size:13px;line-height:1.4}
.bar-gradient .bar-text,.bar-dark .bar-text{color:rgba(255,255,255,.9)}
.bar-warning .bar-text{color:#92400e}
.bar-text strong{font-weight:700}
.bar-gradient .bar-text strong,.bar-dark .bar-text strong{color:#fff}
.bar-warning .bar-text strong{color:#78350f}

.countdown{display:flex;align-items:center;gap:4px;flex-shrink:0}
.cd-unit{display:flex;align-items:baseline;gap:2px;background:rgba(0,0,0,.2);border-radius:6px;padding:2px 6px}
.cd-num{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;min-width:20px;text-align:center}
.cd-label{font-size:9px;font-weight:600;opacity:.8;text-transform:uppercase}
.cd-sep{font-size:14px;font-weight:800;opacity:.6;margin:0 1px}

.bar-cta{display:inline-flex;align-items:center;font-size:12px;font-weight:700;padding:4px 12px;border-radius:6px;text-decoration:none;transition:all .15s;white-space:nowrap;flex-shrink:0;background:rgba(255,255,255,.95);color:#6366f1}
.bar-cta:hover{background:#fff;transform:translateY(-1px)}
.bar-cta-outline{background:transparent;color:#e2e8f0;border:1px solid rgba(255,255,255,.3)}
.bar-cta-outline:hover{background:rgba(255,255,255,.1);color:#fff}
.bar-cta-warning{background:#f59e0b;color:#fff;border:none}
.bar-cta-warning:hover{background:#d97706;color:#fff}

.bar-close{position:absolute;right:10px;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,.15);border:none;color:inherit;font-size:18px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;opacity:.8}
.bar-close:hover{background:rgba(255,255,255,.25);opacity:1}
.bar-warning .bar-close{background:rgba(0,0,0,.08);color:#92400e}
.bar-warning .bar-close:hover{background:rgba(0,0,0,.15)}

.demo-content{padding:40px 24px;max-width:600px;margin:0 auto;text-align:center}
.demo-content h2{font-size:22px;font-weight:800;color:#1e293b;margin-bottom:12px}
.demo-content p{font-size:14px;color:#64748b;line-height:1.6;margin-bottom:20px}
.demo-labels{display:flex;flex-direction:column;gap:4px}
.label{font-size:11px;color:#94a3b8}
`;

export default function StickyPromoAnnouncementBar() {
  // Auto-generated escape hatch: the original snippet's vanilla JS runs once
  // after mount and queries the rendered DOM. For idiomatic React, lift this
  // into state + handlers.
  useEffect(() => {
    const _listeners = [];
    const _originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, listener, options) {
      _listeners.push({ target: this, type, listener, options });
      return _originalAddEventListener.call(this, type, listener, options);
    };
    function closeBar(id) {
      document.getElementById(id).classList.add('hidden');
    }
    
    // Live countdown timer
    let totalSeconds = 2 * 3600 + 47 * 60 + 33;
    
    function tick() {
      if (totalSeconds <= 0) return;
      totalSeconds--;
      const h = Math.floor(totalSeconds / 3600);
      const m = Math.floor((totalSeconds % 3600) / 60);
      const s = totalSeconds % 60;
      const pad = n => String(n).padStart(2, '0');
      document.getElementById('cdH').textContent = pad(h);
      document.getElementById('cdM').textContent = pad(m);
      document.getElementById('cdS').textContent = pad(s);
    }
    
    setInterval(tick, 1000);
    // Cleanup: restore addEventListener and remove all listeners
    return () => {
      EventTarget.prototype.addEventListener = _originalAddEventListener;
      for (const { target, type, listener, options } of _listeners) {
        target.removeEventListener(type, listener, options);
      }
    };

    // Expose handlers used by inline JSX events to the global scope
    if (typeof closeBar === 'function') window.closeBar = closeBar;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div className="demo-page">
        
        <div className="promo-bar bar-gradient" id="bar1">
          <div className="bar-inner">
            <span className="bar-emoji">⚡</span>
            <span className="bar-text">Flash Sale — <strong>50% off all plans.</strong> Ends in</span>
            <div className="countdown" id="countdown">
              <span className="cd-unit"><span className="cd-num" id="cdH">02</span><span className="cd-label">hr</span></span>
              <span className="cd-sep">:</span>
              <span className="cd-unit"><span className="cd-num" id="cdM">47</span><span className="cd-label">min</span></span>
              <span className="cd-sep">:</span>
              <span className="cd-unit"><span className="cd-num" id="cdS">33</span><span className="cd-label">sec</span></span>
            </div>
            <a href="#" className="bar-cta">Claim offer →</a>
          </div>
          <button className="bar-close" onClick={(event) => { closeBar('bar1') }} aria-label="Dismiss">×</button>
        </div>
      
        
        <div className="promo-bar bar-dark" id="bar2">
          <div className="bar-inner">
            <span className="bar-emoji">🚀</span>
            <span className="bar-text">New: <strong>React + Tailwind exports</strong> are now live for all 215+ snippets.</span>
            <a href="#" className="bar-cta bar-cta-outline">Explore snippets →</a>
          </div>
          <button className="bar-close" onClick={(event) => { closeBar('bar2') }} aria-label="Dismiss">×</button>
        </div>
      
        
        <div className="promo-bar bar-warning" id="bar3">
          <div className="bar-inner">
            <span className="bar-emoji">🛠</span>
            <span className="bar-text"><strong>Scheduled maintenance</strong> on Sunday, June 15 from 02:00–04:00 UTC.</span>
            <a href="#" className="bar-cta bar-cta-warning">Read more →</a>
          </div>
          <button className="bar-close" onClick={(event) => { closeBar('bar3') }} aria-label="Dismiss">×</button>
        </div>
      
        
        <div className="demo-content">
          <h2>Sticky Promo Bar</h2>
          <p>Three variants: gradient sale bar with live countdown, dark feature announcement, and amber warning/maintenance bar. Each dismisses independently.</p>
          <div className="demo-labels">
            <span className="label">↑ Gradient countdown bar</span>
            <span className="label">↑ Dark announcement bar</span>
            <span className="label">↑ Warning/maintenance bar</span>
          </div>
        </div>
      </div>
    </>
  );
}
React + Tailwind
import React, { useEffect } from 'react';
// Requires Tailwind CSS v3+ — https://tailwindcss.com/docs/installation
// Arbitrary value classes (e.g. bg-[#0f172a]) are valid Tailwind v3+

export default function StickyPromoAnnouncementBar() {
  // Auto-generated escape hatch: the original snippet's vanilla JS runs once
  // after mount and queries the rendered DOM. For idiomatic React, lift this
  // into state + handlers.
  useEffect(() => {
    const _listeners = [];
    const _originalAddEventListener = EventTarget.prototype.addEventListener;
    EventTarget.prototype.addEventListener = function(type, listener, options) {
      _listeners.push({ target: this, type, listener, options });
      return _originalAddEventListener.call(this, type, listener, options);
    };
    function closeBar(id) {
      document.getElementById(id).classList.add('hidden');
    }
    
    // Live countdown timer
    let totalSeconds = 2 * 3600 + 47 * 60 + 33;
    
    function tick() {
      if (totalSeconds <= 0) return;
      totalSeconds--;
      const h = Math.floor(totalSeconds / 3600);
      const m = Math.floor((totalSeconds % 3600) / 60);
      const s = totalSeconds % 60;
      const pad = n => String(n).padStart(2, '0');
      document.getElementById('cdH').textContent = pad(h);
      document.getElementById('cdM').textContent = pad(m);
      document.getElementById('cdS').textContent = pad(s);
    }
    
    setInterval(tick, 1000);
    // Cleanup: restore addEventListener and remove all listeners
    return () => {
      EventTarget.prototype.addEventListener = _originalAddEventListener;
      for (const { target, type, listener, options } of _listeners) {
        target.removeEventListener(type, listener, options);
      }
    };

    // Expose handlers used by inline JSX events to the global scope
    if (typeof closeBar === 'function') window.closeBar = closeBar;
  }, []);

  return (
    <>
      <style>{`
* {
  box-sizing:border-box;margin:0;padding:0
}

body {
  font-family:system-ui,sans-serif;background:#f8fafc
}

.promo-bar.hidden {
  display:none
}

.bar-gradient .bar-text,.bar-dark .bar-text {
  color:rgba(255,255,255,.9)
}

.bar-warning .bar-text {
  color:#92400e
}

.bar-text strong {
  font-weight:700
}

.bar-gradient .bar-text strong,.bar-dark .bar-text strong {
  color:#fff
}

.bar-warning .bar-text strong {
  color:#78350f
}

.bar-warning .bar-close {
  background:rgba(0,0,0,.08);color:#92400e
}

.bar-warning .bar-close:hover {
  background:rgba(0,0,0,.15)
}

.demo-content h2 {
  font-size:22px;font-weight:800;color:#1e293b;margin-bottom:12px
}

.demo-content p {
  font-size:14px;color:#64748b;line-height:1.6;margin-bottom:20px
}
      `}</style>
      <div className="demo-page">
        
        <div className="promo-bar relative w-full flex items-center justify-center pt-2.5 pr-11 pb-2.5 pl-4 min-h-[44px] [transition:all_.3s_ease] bar-gradient [background:linear-gradient(90deg,#6366f1_0%,#8b5cf6_50%,#ec4899_100%)] text-[#fff]" id="bar1">
          <div className="flex items-center gap-2 flex-wrap justify-center text-center">
            <span className="text-[15px] shrink-0">⚡</span>
            <span className="bar-text text-[13px] leading-[1.4]">Flash Sale — <strong>50% off all plans.</strong> Ends in</span>
            <div className="flex items-center gap-1 shrink-0" id="countdown">
              <span className="flex items-baseline gap-0.5 bg-[rgba(0,0,0,.2)] rounded-md py-0.5 px-1.5"><span className="text-sm font-extrabold [font-variant-numeric:tabular-nums] min-w-[20px] text-center" id="cdH">02</span><span className="text-[9px] font-semibold opacity-80 uppercase">hr</span></span>
              <span className="text-sm font-extrabold opacity-60 my-0 mx-px">:</span>
              <span className="flex items-baseline gap-0.5 bg-[rgba(0,0,0,.2)] rounded-md py-0.5 px-1.5"><span className="text-sm font-extrabold [font-variant-numeric:tabular-nums] min-w-[20px] text-center" id="cdM">47</span><span className="text-[9px] font-semibold opacity-80 uppercase">min</span></span>
              <span className="text-sm font-extrabold opacity-60 my-0 mx-px">:</span>
              <span className="flex items-baseline gap-0.5 bg-[rgba(0,0,0,.2)] rounded-md py-0.5 px-1.5"><span className="text-sm font-extrabold [font-variant-numeric:tabular-nums] min-w-[20px] text-center" id="cdS">33</span><span className="text-[9px] font-semibold opacity-80 uppercase">sec</span></span>
            </div>
            <a href="#" className="inline-flex items-center text-xs font-bold py-1 px-3 rounded-md no-underline [transition:all_.15s] whitespace-nowrap shrink-0 bg-[rgba(255,255,255,.95)] text-[#6366f1] hover:bg-[#fff] hover:[transform:translateY(-1px)]">Claim offer →</a>
          </div>
          <button className="bar-close absolute right-2.5 top-1/2 [transform:translateY(-50%)] w-7 h-7 rounded-md bg-[rgba(255,255,255,.15)] border-0 text-inherit text-lg leading-none cursor-pointer flex items-center justify-center [transition:background_.15s] opacity-80 hover:bg-[rgba(255,255,255,.25)] hover:opacity-100" onClick={(event) => { closeBar('bar1') }} aria-label="Dismiss">×</button>
        </div>
      
        
        <div className="promo-bar relative w-full flex items-center justify-center pt-2.5 pr-11 pb-2.5 pl-4 min-h-[44px] [transition:all_.3s_ease] bar-dark bg-[#0f172a] text-[#e2e8f0] border-t border-t-[#1e293b] border-b border-b-[#1e293b]" id="bar2">
          <div className="flex items-center gap-2 flex-wrap justify-center text-center">
            <span className="text-[15px] shrink-0">🚀</span>
            <span className="bar-text text-[13px] leading-[1.4]">New: <strong>React + Tailwind exports</strong> are now live for all 215+ snippets.</span>
            <a href="#" className="inline-flex items-center text-xs font-bold py-1 px-3 rounded-md no-underline [transition:all_.15s] whitespace-nowrap shrink-0 bg-[rgba(255,255,255,.95)] text-[#6366f1] hover:bg-[#fff] hover:[transform:translateY(-1px)] bg-transparent text-[#e2e8f0] border border-[rgba(255,255,255,.3)] hover:bg-[rgba(255,255,255,.1)] hover:text-[#fff]">Explore snippets →</a>
          </div>
          <button className="bar-close absolute right-2.5 top-1/2 [transform:translateY(-50%)] w-7 h-7 rounded-md bg-[rgba(255,255,255,.15)] border-0 text-inherit text-lg leading-none cursor-pointer flex items-center justify-center [transition:background_.15s] opacity-80 hover:bg-[rgba(255,255,255,.25)] hover:opacity-100" onClick={(event) => { closeBar('bar2') }} aria-label="Dismiss">×</button>
        </div>
      
        
        <div className="promo-bar relative w-full flex items-center justify-center pt-2.5 pr-11 pb-2.5 pl-4 min-h-[44px] [transition:all_.3s_ease] bar-warning bg-[#fffbeb] text-[#92400e] border-t border-t-[#fde68a] border-b border-b-[#fde68a]" id="bar3">
          <div className="flex items-center gap-2 flex-wrap justify-center text-center">
            <span className="text-[15px] shrink-0">🛠</span>
            <span className="bar-text text-[13px] leading-[1.4]"><strong>Scheduled maintenance</strong> on Sunday, June 15 from 02:00–04:00 UTC.</span>
            <a href="#" className="inline-flex items-center text-xs font-bold py-1 px-3 rounded-md no-underline [transition:all_.15s] whitespace-nowrap shrink-0 bg-[rgba(255,255,255,.95)] text-[#6366f1] hover:bg-[#fff] hover:[transform:translateY(-1px)] bg-[#f59e0b] text-[#fff] border-0 hover:bg-[#d97706] hover:text-[#fff]">Read more →</a>
          </div>
          <button className="bar-close absolute right-2.5 top-1/2 [transform:translateY(-50%)] w-7 h-7 rounded-md bg-[rgba(255,255,255,.15)] border-0 text-inherit text-lg leading-none cursor-pointer flex items-center justify-center [transition:background_.15s] opacity-80 hover:bg-[rgba(255,255,255,.25)] hover:opacity-100" onClick={(event) => { closeBar('bar3') }} aria-label="Dismiss">×</button>
        </div>
      
        
        <div className="demo-content py-10 px-6 max-w-[600px] my-0 mx-auto text-center">
          <h2>Sticky Promo Bar</h2>
          <p>Three variants: gradient sale bar with live countdown, dark feature announcement, and amber warning/maintenance bar. Each dismisses independently.</p>
          <div className="flex flex-col gap-1">
            <span className="text-[11px] text-[#94a3b8]">↑ Gradient countdown bar</span>
            <span className="text-[11px] text-[#94a3b8]">↑ Dark announcement bar</span>
            <span className="text-[11px] text-[#94a3b8]">↑ Warning/maintenance bar</span>
          </div>
        </div>
      </div>
    </>
  );
}
Vue
<template>
  <div class="demo-page">
    <!-- Bar 1: Countdown sale bar -->
    <div class="promo-bar bar-gradient" id="bar1">
      <div class="bar-inner">
        <span class="bar-emoji">⚡</span>
        <span class="bar-text">Flash Sale — <strong>50% off all plans.</strong> Ends in</span>
        <div class="countdown" id="countdown">
          <span class="cd-unit"><span class="cd-num" id="cdH">02</span><span class="cd-label">hr</span></span>
          <span class="cd-sep">:</span>
          <span class="cd-unit"><span class="cd-num" id="cdM">47</span><span class="cd-label">min</span></span>
          <span class="cd-sep">:</span>
          <span class="cd-unit"><span class="cd-num" id="cdS">33</span><span class="cd-label">sec</span></span>
        </div>
        <a href="#" class="bar-cta">Claim offer →</a>
      </div>
      <button class="bar-close" @click="closeBar('bar1')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Bar 2: Info/feature bar -->
    <div class="promo-bar bar-dark" id="bar2">
      <div class="bar-inner">
        <span class="bar-emoji">🚀</span>
        <span class="bar-text">New: <strong>React + Tailwind exports</strong> are now live for all 215+ snippets.</span>
        <a href="#" class="bar-cta bar-cta-outline">Explore snippets →</a>
      </div>
      <button class="bar-close" @click="closeBar('bar2')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Bar 3: Warning/maintenance bar -->
    <div class="promo-bar bar-warning" id="bar3">
      <div class="bar-inner">
        <span class="bar-emoji">🛠</span>
        <span class="bar-text"><strong>Scheduled maintenance</strong> on Sunday, June 15 from 02:00–04:00 UTC.</span>
        <a href="#" class="bar-cta bar-cta-warning">Read more →</a>
      </div>
      <button class="bar-close" @click="closeBar('bar3')" aria-label="Dismiss">×</button>
    </div>
  
    <!-- Demo page content -->
    <div class="demo-content">
      <h2>Sticky Promo Bar</h2>
      <p>Three variants: gradient sale bar with live countdown, dark feature announcement, and amber warning/maintenance bar. Each dismisses independently.</p>
      <div class="demo-labels">
        <span class="label">↑ Gradient countdown bar</span>
        <span class="label">↑ Dark announcement bar</span>
        <span class="label">↑ Warning/maintenance bar</span>
      </div>
    </div>
  </div>
</template>

<script setup>
import { onMounted } from 'vue';

function closeBar(id) {
  document.getElementById(id).classList.add('hidden');
}
// Live countdown timer
let totalSeconds = 2 * 3600 + 47 * 60 + 33;
function tick() {
  if (totalSeconds <= 0) return;
  totalSeconds--;
  const h = Math.floor(totalSeconds / 3600);
  const m = Math.floor((totalSeconds % 3600) / 60);
  const s = totalSeconds % 60;
  const pad = n => String(n).padStart(2, '0');
  document.getElementById('cdH').textContent = pad(h);
  document.getElementById('cdM').textContent = pad(m);
  document.getElementById('cdS').textContent = pad(s);
}

onMounted(() => {
  setInterval(tick, 1000);
});
</script>

<style scoped>
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:system-ui,sans-serif;background:#f8fafc}

.promo-bar{position:relative;width:100%;display:flex;align-items:center;justify-content:center;padding:10px 44px 10px 16px;min-height:44px;transition:all .3s ease}
.promo-bar.hidden{display:none}

.bar-gradient{background:linear-gradient(90deg,#6366f1 0%,#8b5cf6 50%,#ec4899 100%);color:#fff}
.bar-dark{background:#0f172a;color:#e2e8f0;border-top:1px solid #1e293b;border-bottom:1px solid #1e293b}
.bar-warning{background:#fffbeb;color:#92400e;border-top:1px solid #fde68a;border-bottom:1px solid #fde68a}

.bar-inner{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center;text-align:center}
.bar-emoji{font-size:15px;flex-shrink:0}
.bar-text{font-size:13px;line-height:1.4}
.bar-gradient .bar-text,.bar-dark .bar-text{color:rgba(255,255,255,.9)}
.bar-warning .bar-text{color:#92400e}
.bar-text strong{font-weight:700}
.bar-gradient .bar-text strong,.bar-dark .bar-text strong{color:#fff}
.bar-warning .bar-text strong{color:#78350f}

.countdown{display:flex;align-items:center;gap:4px;flex-shrink:0}
.cd-unit{display:flex;align-items:baseline;gap:2px;background:rgba(0,0,0,.2);border-radius:6px;padding:2px 6px}
.cd-num{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;min-width:20px;text-align:center}
.cd-label{font-size:9px;font-weight:600;opacity:.8;text-transform:uppercase}
.cd-sep{font-size:14px;font-weight:800;opacity:.6;margin:0 1px}

.bar-cta{display:inline-flex;align-items:center;font-size:12px;font-weight:700;padding:4px 12px;border-radius:6px;text-decoration:none;transition:all .15s;white-space:nowrap;flex-shrink:0;background:rgba(255,255,255,.95);color:#6366f1}
.bar-cta:hover{background:#fff;transform:translateY(-1px)}
.bar-cta-outline{background:transparent;color:#e2e8f0;border:1px solid rgba(255,255,255,.3)}
.bar-cta-outline:hover{background:rgba(255,255,255,.1);color:#fff}
.bar-cta-warning{background:#f59e0b;color:#fff;border:none}
.bar-cta-warning:hover{background:#d97706;color:#fff}

.bar-close{position:absolute;right:10px;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,.15);border:none;color:inherit;font-size:18px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;opacity:.8}
.bar-close:hover{background:rgba(255,255,255,.25);opacity:1}
.bar-warning .bar-close{background:rgba(0,0,0,.08);color:#92400e}
.bar-warning .bar-close:hover{background:rgba(0,0,0,.15)}

.demo-content{padding:40px 24px;max-width:600px;margin:0 auto;text-align:center}
.demo-content h2{font-size:22px;font-weight:800;color:#1e293b;margin-bottom:12px}
.demo-content p{font-size:14px;color:#64748b;line-height:1.6;margin-bottom:20px}
.demo-labels{display:flex;flex-direction:column;gap:4px}
.label{font-size:11px;color:#94a3b8}
</style>
Angular
// @ts-nocheck
// Note: vanilla JS DOM manipulation is preserved as-is inside ngAfterViewInit().
// For idiomatic Angular, replace document.getElementById() with @ViewChild() refs
// and move state into component properties with two-way binding.
import { Component, AfterViewInit, ViewEncapsulation } from '@angular/core';
import { CommonModule } from '@angular/common';

@Component({
  selector: 'app-sticky-promo-announcement-bar',
  standalone: true,
  imports: [CommonModule],
  encapsulation: ViewEncapsulation.None,
  template: `
    <div class="demo-page">
      <!-- Bar 1: Countdown sale bar -->
      <div class="promo-bar bar-gradient" id="bar1">
        <div class="bar-inner">
          <span class="bar-emoji">⚡</span>
          <span class="bar-text">Flash Sale — <strong>50% off all plans.</strong> Ends in</span>
          <div class="countdown" id="countdown">
            <span class="cd-unit"><span class="cd-num" id="cdH">02</span><span class="cd-label">hr</span></span>
            <span class="cd-sep">:</span>
            <span class="cd-unit"><span class="cd-num" id="cdM">47</span><span class="cd-label">min</span></span>
            <span class="cd-sep">:</span>
            <span class="cd-unit"><span class="cd-num" id="cdS">33</span><span class="cd-label">sec</span></span>
          </div>
          <a href="#" class="bar-cta">Claim offer →</a>
        </div>
        <button class="bar-close" (click)="closeBar('bar1')" aria-label="Dismiss">×</button>
      </div>
    
      <!-- Bar 2: Info/feature bar -->
      <div class="promo-bar bar-dark" id="bar2">
        <div class="bar-inner">
          <span class="bar-emoji">🚀</span>
          <span class="bar-text">New: <strong>React + Tailwind exports</strong> are now live for all 215+ snippets.</span>
          <a href="#" class="bar-cta bar-cta-outline">Explore snippets →</a>
        </div>
        <button class="bar-close" (click)="closeBar('bar2')" aria-label="Dismiss">×</button>
      </div>
    
      <!-- Bar 3: Warning/maintenance bar -->
      <div class="promo-bar bar-warning" id="bar3">
        <div class="bar-inner">
          <span class="bar-emoji">🛠</span>
          <span class="bar-text"><strong>Scheduled maintenance</strong> on Sunday, June 15 from 02:00–04:00 UTC.</span>
          <a href="#" class="bar-cta bar-cta-warning">Read more →</a>
        </div>
        <button class="bar-close" (click)="closeBar('bar3')" aria-label="Dismiss">×</button>
      </div>
    
      <!-- Demo page content -->
      <div class="demo-content">
        <h2>Sticky Promo Bar</h2>
        <p>Three variants: gradient sale bar with live countdown, dark feature announcement, and amber warning/maintenance bar. Each dismisses independently.</p>
        <div class="demo-labels">
          <span class="label">↑ Gradient countdown bar</span>
          <span class="label">↑ Dark announcement bar</span>
          <span class="label">↑ Warning/maintenance bar</span>
        </div>
      </div>
    </div>
  `,
  styles: [`
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:system-ui,sans-serif;background:#f8fafc}
    
    .promo-bar{position:relative;width:100%;display:flex;align-items:center;justify-content:center;padding:10px 44px 10px 16px;min-height:44px;transition:all .3s ease}
    .promo-bar.hidden{display:none}
    
    .bar-gradient{background:linear-gradient(90deg,#6366f1 0%,#8b5cf6 50%,#ec4899 100%);color:#fff}
    .bar-dark{background:#0f172a;color:#e2e8f0;border-top:1px solid #1e293b;border-bottom:1px solid #1e293b}
    .bar-warning{background:#fffbeb;color:#92400e;border-top:1px solid #fde68a;border-bottom:1px solid #fde68a}
    
    .bar-inner{display:flex;align-items:center;gap:8px;flex-wrap:wrap;justify-content:center;text-align:center}
    .bar-emoji{font-size:15px;flex-shrink:0}
    .bar-text{font-size:13px;line-height:1.4}
    .bar-gradient .bar-text,.bar-dark .bar-text{color:rgba(255,255,255,.9)}
    .bar-warning .bar-text{color:#92400e}
    .bar-text strong{font-weight:700}
    .bar-gradient .bar-text strong,.bar-dark .bar-text strong{color:#fff}
    .bar-warning .bar-text strong{color:#78350f}
    
    .countdown{display:flex;align-items:center;gap:4px;flex-shrink:0}
    .cd-unit{display:flex;align-items:baseline;gap:2px;background:rgba(0,0,0,.2);border-radius:6px;padding:2px 6px}
    .cd-num{font-size:14px;font-weight:800;font-variant-numeric:tabular-nums;min-width:20px;text-align:center}
    .cd-label{font-size:9px;font-weight:600;opacity:.8;text-transform:uppercase}
    .cd-sep{font-size:14px;font-weight:800;opacity:.6;margin:0 1px}
    
    .bar-cta{display:inline-flex;align-items:center;font-size:12px;font-weight:700;padding:4px 12px;border-radius:6px;text-decoration:none;transition:all .15s;white-space:nowrap;flex-shrink:0;background:rgba(255,255,255,.95);color:#6366f1}
    .bar-cta:hover{background:#fff;transform:translateY(-1px)}
    .bar-cta-outline{background:transparent;color:#e2e8f0;border:1px solid rgba(255,255,255,.3)}
    .bar-cta-outline:hover{background:rgba(255,255,255,.1);color:#fff}
    .bar-cta-warning{background:#f59e0b;color:#fff;border:none}
    .bar-cta-warning:hover{background:#d97706;color:#fff}
    
    .bar-close{position:absolute;right:10px;top:50%;transform:translateY(-50%);width:28px;height:28px;border-radius:6px;background:rgba(255,255,255,.15);border:none;color:inherit;font-size:18px;line-height:1;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:background .15s;opacity:.8}
    .bar-close:hover{background:rgba(255,255,255,.25);opacity:1}
    .bar-warning .bar-close{background:rgba(0,0,0,.08);color:#92400e}
    .bar-warning .bar-close:hover{background:rgba(0,0,0,.15)}
    
    .demo-content{padding:40px 24px;max-width:600px;margin:0 auto;text-align:center}
    .demo-content h2{font-size:22px;font-weight:800;color:#1e293b;margin-bottom:12px}
    .demo-content p{font-size:14px;color:#64748b;line-height:1.6;margin-bottom:20px}
    .demo-labels{display:flex;flex-direction:column;gap:4px}
    .label{font-size:11px;color:#94a3b8}
  `]
})
export class StickyPromoAnnouncementBarComponent implements AfterViewInit {
  ngAfterViewInit(): void {
    function closeBar(id) {
      document.getElementById(id).classList.add('hidden');
    }
    
    // Live countdown timer
    let totalSeconds = 2 * 3600 + 47 * 60 + 33;
    
    function tick() {
      if (totalSeconds <= 0) return;
      totalSeconds--;
      const h = Math.floor(totalSeconds / 3600);
      const m = Math.floor((totalSeconds % 3600) / 60);
      const s = totalSeconds % 60;
      const pad = n => String(n).padStart(2, '0');
      document.getElementById('cdH').textContent = pad(h);
      document.getElementById('cdM').textContent = pad(m);
      document.getElementById('cdS').textContent = pad(s);
    }
    
    setInterval(tick, 1000);

    // Bind inline-handler functions to the component so the template can call them
    Object.assign(this, { closeBar, tick });
  }
}