A Black Friday bar with a countdown and a coupon code

A sale needs three things on the page: a reason to act now, the code, and a way to use it without hunting for it. This bar does all three with one custom code block. It counts down to the end of your sale, shows the coupon code with a copy button, and takes itself down when the sale is over.

Running on this page

The bar, live

This course is free, so there's nothing to discount, but the bar works the same. Try the Copy button, and the close button on the right.

Black Friday demo: on a paid course, this code would take 30% off at checkout.

00Days
:
00Hrs
:
00Min
:
00Sec
Code BLACKFRIDAY30

What it does

  • Counts down to the end of your sale, in days, hours, minutes and seconds.
  • Shows the code in a box, with a Copy button. One click and it's ready to paste at checkout.
  • Takes itself down when the countdown reaches zero, so you don't need to remember to remove it at midnight.
  • Remembers a close. If someone closes it, it stays closed for them on that browser. It's saved in the browser's local storage as ph-announce-bf-dismissed, not as a cookie, and nothing is saved unless they close it. Change the bar's ID and it comes back for everyone.
  • Sits at the top or the bottom. At the bottom it stays on screen as people scroll.

Set up the coupon first

The bar only shows a code, it doesn't create one. Make the coupon in Teachable first (there's more on coupons in Coupons, bundles and upsells, back in section 2), and give it the same end date as your countdown. Otherwise the bar says the sale is over while the code still works, or worse, the other way round.

Copy the code across exactly as you typed it in Teachable. If you'd rather nobody had to type anything, Teachable can also give you a link with the coupon already applied, which you could put in the message.

Adding it to a page

  1. Open the page in Teachable's page editor (your home page, or a sales page).
  2. Add a Custom code block and move it to the very top.
  3. Paste the whole of the code below into it, change the settings, and save.

It only appears on pages where you add the block, so if you want it on your home page and two sales pages, that's three blocks. It won't appear at checkout, because nothing you add reaches checkout.

If Teachable's own header is still showing on the page, the bar will sit under it rather than above. If that bothers you, set the position to 'bottom' and it'll stay fixed along the bottom of the screen instead.

The settings

Everything you'd want to change is in a short list at the top of the script. The message itself is in the HTML, just above it, in the line that starts <strong>Black Friday:.

Setting What it does
PH_ANNOUNCE_END When the sale ends. Keep the time zone on the end (see below).
PH_ANNOUNCE_COUPON The code to show. Leave it blank and the box disappears.
PH_ANNOUNCE_POSITION 'top' or 'bottom'.
PH_ANNOUNCE_ID Change it for a new sale, so people who closed the last one see this one.
PH_ANNOUNCE_DISMISSIBLE false removes the close button.
PH_ANNOUNCE_HIDE_ON_END false keeps the bar up after the end, showing PH_ANNOUNCE_ENDED_MSG instead.
Time zones

The end time has a time zone on the end of it: 2026-11-27T23:59:59+00:00 is midnight in Ireland and the UK. Use -05:00 for New York or -08:00 for Los Angeles. Leave it off and every visitor gets midnight in their own time zone, so the sale ends at a different moment for each of them, and not when your coupon does.

Get the code

Free to use

Use it on your own school or a client's. The colours are fixed, so it doesn't need anything else from this school to work.

Show the code
<style>
    /* =================================================================
       Black Friday announcement bar, with countdown and coupon code
       From purplehippo.teachable.com, free to use
       Self-contained: fixed colours, no shared tokens needed, so it
       works on any Teachable school as a single custom code block.
       1rem = 10px (Teachable 2.0 sets html { font-size: 62.5% }).

       Colours are fixed (black #000000, orange #E8913A) so the bar
       stands out from the rest of the page. Change the hex values
       below to match your own brand.

       Position: PH_ANNOUNCE_POSITION below, 'top' or 'bottom'.
       Bottom mode pads <body> so it doesn't cover
       content.

       Countdown: set PH_ANNOUNCE_END below to the sale's end date/time,
       with a UTC offset on the end (+00:00 for Ireland/UK in winter,
       -05:00 for New York) so it ends at the same moment for everyone.
       Without one, each visitor's own clock zone is used.
       Once it passes, the bar hides itself automatically (set
       PH_ANNOUNCE_HIDE_ON_END = false to instead swap in
       PH_ANNOUNCE_ENDED_MSG and keep the bar showing).

       Coupon: PH_ANNOUNCE_COUPON below shows the code in a dashed
       "ticket" box with a Copy button (copies to the clipboard and
       shows "Copied" for a moment). Blank it out to drop the box and
       keep the countdown message-only.

       Dismiss: closing the bar remembers it in the visitor's browser
       so it won't reappear for them on this device/browser. Change
       PH_ANNOUNCE_ID whenever the sale/message changes so it
       resurfaces for everyone.

       What's stored: NO cookie is set. It's one localStorage entry
         key:   ph-announce-bf-dismissed
         value: the PH_ANNOUNCE_ID at the time (e.g. 'bf-2026')
       It's only written when someone clicks the close button, and the
       bar stays hidden while the value matches PH_ANNOUNCE_ID.
       To see or delete it: browser DevTools > Application (Chrome) or
       Storage (Firefox/Safari) > Local Storage > your school's address.
       Or paste this in the DevTools console on your school and reload:
         localStorage.removeItem('ph-announce-bf-dismissed')
       ================================================================= */

    .ph-announce--bf {
        box-sizing: border-box;
        position: relative;
        z-index: 950;
        display: flex;
        flex-wrap: wrap;
        align-items: center;
        justify-content: center;
        column-gap: 1.8rem;
        row-gap: 0.6rem;
        padding: 1.2rem clamp(1.8rem, 4vw, 3.2rem);
        background: #000000;
        color: #FFFFFF;
        font-family: "Instrument Sans", ui-sans-serif, system-ui, sans-serif;
        font-size: 1.4rem;
        font-weight: 500;
        line-height: 1.4;
        text-align: center;
    }

    /* display:flex above would beat the hidden attribute, so the close
       button and the dismissed/ended checks wouldn't hide the bar */
    .ph-announce--bf[hidden] { display: none !important; }

    .ph-announce--bf *, .ph-announce--bf *::before, .ph-announce--bf *::after { box-sizing: border-box; }

    @media (prefers-reduced-motion: reduce) {
        .ph-announce--bf *, .ph-announce--bf *::before, .ph-announce--bf *::after { transition-duration: 1ms !important; }
    }

    .ph-announce--bf[data-position="bottom"] {
        position: fixed;
        left: 0;
        right: 0;
        bottom: 0;
    }

    .ph-announce--bf__msg { margin: 0; }

    .ph-announce--bf__msg strong {
        color: #E8913A; /* --ph-sun */
        font-weight: 700;
    }

    .ph-announce--bf__msg a {
        color: inherit;
        text-decoration: underline;
        text-underline-offset: 0.2em;
    }

    /* ---- Countdown ---- */

    .ph-announce--bf__clock {
        display: flex;
        align-items: center;
        gap: 0.5rem;
    }

    .ph-announce--bf__unit {
        display: flex;
        flex-direction: column;
        align-items: center;
        min-width: 3.6rem;
        padding: 0.3rem 0.4rem;
        border-radius: 0.6rem;
        background: rgba(232, 145, 58, 0.16);
    }

    .ph-announce--bf__num {
        font-family: "Bricolage Grotesque", ui-sans-serif, system-ui, sans-serif;
        font-size: 1.7rem;
        font-weight: 700;
        line-height: 1.1;
        color: #E8913A; /* --ph-sun */
        font-variant-numeric: tabular-nums;
    }

    .ph-announce--bf__label {
        margin-top: 0.1rem;
        font-size: 0.95rem;
        font-weight: 600;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: rgba(255, 255, 255, 0.6);
    }

    .ph-announce--bf__sep {
        color: rgba(255, 255, 255, 0.35);
        font-weight: 700;
    }

    /* ---- Coupon ---- */

    .ph-announce--bf__coupon {
        flex-shrink: 0;
        display: inline-flex;
        align-items: center;
        gap: 1rem;
        padding: 0.4rem 0.4rem 0.4rem 1.2rem;
        border: 2px dashed #E8913A; /* --ph-sun */
        border-radius: 0.8rem;
        background: rgba(232, 145, 58, 0.12);
    }

    .ph-announce--bf__coupon-label {
        font-size: 0.95rem;
        font-weight: 600;
        letter-spacing: 0.04em;
        text-transform: uppercase;
        color: rgba(255, 255, 255, 0.6);
    }

    .ph-announce--bf__code {
        font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
        font-size: 1.5rem;
        font-weight: 700;
        letter-spacing: 0.08em;
        color: #E8913A; /* --ph-sun */
        user-select: all; /* one click selects the whole code */
        /* Bootstrap (Teachable) styles <code> as a pink box, so reset it */
        padding: 0;
        border: 0;
        border-radius: 0;
        background: none;
        white-space: nowrap;
    }

    @media (max-width: 400px) {
        .ph-announce--bf__coupon { gap: 0.6rem; padding-left: 0.8rem; }
        .ph-announce--bf__code { font-size: 1.3rem; letter-spacing: 0.04em; }
    }

    .ph-announce--bf__copy {
        min-width: 7.2rem;
        padding: 0.5rem 1.2rem;
        border: 0;
        border-radius: 0.5rem;
        background: #E8913A; /* --ph-sun */
        color: #1D1729;
        font: inherit;
        font-size: 1.2rem;
        font-weight: 700;
        cursor: pointer;
        transition: background-color 200ms ease, transform 120ms ease;
    }

    .ph-announce--bf__copy:hover { background: #FFA751; }
    .ph-announce--bf__copy:active { transform: scale(0.97); }

    .ph-announce--bf__copy:focus-visible {
        outline: 2px solid #E8913A;
        outline-offset: 2px;
    }

    /* ---- Close ---- */

    .ph-announce--bf__close {
        flex-shrink: 0;
        display: inline-flex;
        align-items: center;
        justify-content: center;
        width: 2.2rem;
        height: 2.2rem;
        padding: 0;
        border: 0;
        border-radius: 50%;
        background: transparent;
        color: #FFFFFF;
        opacity: 0.75;
        cursor: pointer;
        transition: opacity 200ms ease, background-color 200ms ease;
    }

    .ph-announce--bf__close:hover { opacity: 1; background: rgba(255, 255, 255, 0.14); }
    .ph-announce--bf__close:focus-visible { outline: 2px solid #E8913A; outline-offset: 2px; }
    .ph-announce--bf__close svg { width: 1.3rem; height: 1.3rem; }
    .ph-announce--bf__close { position: absolute; right: clamp(1.2rem, 3vw, 2.4rem); }
</style>

<div class="ph-announce--bf" id="ph-announce-bf" data-position="top" hidden>
    <p class="ph-announce--bf__msg" id="ph-announce-bf-msg">
        <strong>Black Friday:</strong> 30% off every course with this code.
    </p>
    <div class="ph-announce--bf__clock" id="ph-announce-bf-clock" aria-live="off">
        <div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="d">00</span><span class="ph-announce--bf__label">Days</span></div>
        <span class="ph-announce--bf__sep">:</span>
        <div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="h">00</span><span class="ph-announce--bf__label">Hrs</span></div>
        <span class="ph-announce--bf__sep">:</span>
        <div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="m">00</span><span class="ph-announce--bf__label">Min</span></div>
        <span class="ph-announce--bf__sep">:</span>
        <div class="ph-announce--bf__unit"><span class="ph-announce--bf__num" data-unit="s">00</span><span class="ph-announce--bf__label">Sec</span></div>
    </div>
    <div class="ph-announce--bf__coupon" id="ph-announce-bf-coupon">
        <span class="ph-announce--bf__coupon-label">Code</span>
        <code class="ph-announce--bf__code" id="ph-announce-bf-code">BLACKFRIDAY30</code>
        <button type="button" class="ph-announce--bf__copy" id="ph-announce-bf-copy" aria-label="Copy coupon code">Copy</button>
    </div>
    <button type="button" class="ph-announce--bf__close" id="ph-announce-bf-close" aria-label="Dismiss announcement">
        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round">
            <path d="M6 6l12 12M18 6L6 18"/>
        </svg>
    </button>
</div>

<script>
(function () {
    /* ---- settings ---- */
    var PH_ANNOUNCE_ID          = 'bf-2026';                 // change whenever the sale/message changes, to re-show it to everyone
    var PH_ANNOUNCE_POSITION    = 'top';                      // 'top' or 'bottom'
    var PH_ANNOUNCE_DISMISSIBLE = true;                       // false = no close button, always shows
    var PH_ANNOUNCE_END         = '2026-11-27T23:59:59+00:00'; // sale end; keep the UTC offset (+00:00 = Irish/UK time in winter)
    var PH_ANNOUNCE_HIDE_ON_END = true;                       // true = bar disappears once the timer hits zero
    var PH_ANNOUNCE_ENDED_MSG   = 'The sale has ended. Thanks for stopping by.'; // used only if PH_ANNOUNCE_HIDE_ON_END is false
    var PH_ANNOUNCE_COUPON      = 'BLACKFRIDAY30';            // leave blank to drop the coupon box, countdown-only

    var STORAGE_KEY = 'ph-announce-bf-dismissed';
    var bar   = document.getElementById('ph-announce-bf');
    var msg   = document.getElementById('ph-announce-bf-msg');
    var clock = document.getElementById('ph-announce-bf-clock');
    var close = document.getElementById('ph-announce-bf-close');
    var coupon = document.getElementById('ph-announce-bf-coupon');
    var code   = document.getElementById('ph-announce-bf-code');
    var copy   = document.getElementById('ph-announce-bf-copy');

    if (!bar) return;

    if (!PH_ANNOUNCE_DISMISSIBLE && close) close.remove();

    if (coupon) {
        if (PH_ANNOUNCE_COUPON) {
            code.textContent = PH_ANNOUNCE_COUPON;
            var copyTimer;
            copy.addEventListener('click', function () {
                function done() {
                    copy.textContent = 'Copied';
                    clearTimeout(copyTimer);
                    copyTimer = setTimeout(function () { copy.textContent = 'Copy'; }, 2000);
                }
                function fallback() { // older browsers / non-https: select the code so Cmd/Ctrl+C works
                    var range = document.createRange();
                    range.selectNodeContents(code);
                    var sel = window.getSelection();
                    sel.removeAllRanges();
                    sel.addRange(range);
                    try { if (document.execCommand('copy')) done(); } catch (e) {}
                }
                if (navigator.clipboard && navigator.clipboard.writeText) {
                    navigator.clipboard.writeText(PH_ANNOUNCE_COUPON).then(done, fallback);
                } else {
                    fallback();
                }
            });
        } else {
            coupon.remove();
            coupon = null;
        }
    }

    var dismissed = false;
    try { dismissed = window.localStorage.getItem(STORAGE_KEY) === PH_ANNOUNCE_ID; } catch (e) {}
    if (dismissed) return;

    var endTime = new Date(PH_ANNOUNCE_END).getTime();
    if (isNaN(endTime)) { clock.remove(); } // no valid date - just show the message, no timer

    function pad(n) { return String(n).padStart(2, '0'); }

    function tick() {
        var diff = endTime - Date.now();

        if (diff <= 0) {
            clearInterval(timer);
            if (PH_ANNOUNCE_HIDE_ON_END) {
                bar.hidden = true;
                if (PH_ANNOUNCE_POSITION === 'bottom') document.body.style.paddingBottom = '';
                return;
            }
            if (clock) clock.remove();
            if (coupon) coupon.remove();
            if (msg) msg.textContent = PH_ANNOUNCE_ENDED_MSG;
            return;
        }

        var d = Math.floor(diff / 86400000);
        var h = Math.floor((diff % 86400000) / 3600000);
        var m = Math.floor((diff % 3600000) / 60000);
        var s = Math.floor((diff % 60000) / 1000);

        var nums = clock.querySelectorAll('[data-unit]');
        nums[0].textContent = pad(d);
        nums[1].textContent = pad(h);
        nums[2].textContent = pad(m);
        nums[3].textContent = pad(s);
    }

    bar.setAttribute('data-position', PH_ANNOUNCE_POSITION);
    bar.hidden = false;

    if (PH_ANNOUNCE_POSITION === 'bottom') {
        document.body.style.paddingBottom = bar.offsetHeight + 'px';
        window.addEventListener('resize', function () {
            document.body.style.paddingBottom = bar.offsetHeight + 'px';
        });
    }

    var timer;
    if (!isNaN(endTime) && clock) {
        tick();
        timer = setInterval(tick, 1000);
    }

    if (close) {
        close.addEventListener('click', function () {
            bar.hidden = true;
            if (timer) clearInterval(timer);
            if (PH_ANNOUNCE_POSITION === 'bottom') document.body.style.paddingBottom = '';
            try { window.localStorage.setItem(STORAGE_KEY, PH_ANNOUNCE_ID); } catch (e) {}
        });
    }
})();
</script>
Rather have it built?

If you'd like a bar like this in your own colours, or something bigger built for your school, that's what Purple Hippo is for.

In short

  • Create the coupon in Teachable first, with the same end date as the countdown.
  • Paste the bar into a custom code block at the top of each page you want it on.
  • Keep the time zone on the end time, and change the ID for each new sale.
Complete and Continue