{%- comment -%} MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections {%- endcomment -%} {%- comment -%} Sticky promo bar: a short offer with an optional discount code and copy button, fixed to the bottom of the screen or pinned to the top once the shopper scrolls past it. It can run on a schedule (start and end in the store's time zone, checked by Liquid and again in the browser, so a cached page doesn't show an ended offer) and can be dismissed; the dismissal is remembered in the browser for the days you choose. The code is text you type: create the discount itself in Shopify. Depends on: snippets/lib-button.liquid {%- endcomment -%} {%- liquid assign s = section.settings assign now = 'now' | date: '%Y-%m-%d %H:%M' assign starts = s.starts_at | strip assign ends = s.ends_at | strip assign in_window = true if starts != blank and now < starts assign in_window = false endif if ends != blank and now >= ends assign in_window = false endif assign tz = 'now' | date: '%z' assign code_id = 'LibPromoCode-' | append: section.id -%} {%- if in_window or request.design_mode -%}
{%- endif -%} {% stylesheet %} .lib-promo { display: block; } .lib-promo[hidden] { display: none; } .lib-promo__bar { z-index: 40; background-color: var(--lib-bg); background-image: var(--lib-bg-image, none); color: var(--lib-text); padding: var(--lib-pt) 1rem var(--lib-pb); } .lib-promo[data-position='bottom'] .lib-promo__bar { position: fixed; inset: auto 0 0; box-shadow: 0 -4px 18px rgb(0 0 0 / 0.08); } .lib-promo[data-position='top'].is-stuck .lib-promo__bar { position: fixed; inset: var(--lib-offset) 0 auto; box-shadow: 0 4px 18px rgb(0 0 0 / 0.08); } .lib-promo__inner { position: relative; display: flex; align-items: center; justify-content: space-between; gap: 0.75rem; max-width: 1200px; margin: 0 auto; } .lib-promo--full .lib-promo__inner { max-width: none; } @media (max-width: 749px) { .lib-promo__bar { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); } .lib-promo[data-show='desktop'] { display: none; } } @media (min-width: 750px) { .lib-promo[data-show='mobile'] { display: none; } } .lib-promo__content { display: flex; flex: 1; flex-wrap: wrap; align-items: center; gap: 0.5rem 1rem; min-width: 0; font-size: 0.9375rem; line-height: 1.4; } .lib-promo__inner[data-align='center'] .lib-promo__content { justify-content: center; text-align: center; } @media (max-width: 749px) { .lib-promo__inner[data-align-m='left'] .lib-promo__content { justify-content: flex-start; text-align: left; } .lib-promo__inner[data-align-m='center'] .lib-promo__content { justify-content: center; text-align: center; } } .lib-promo__message { margin: 0; } .lib-promo__message a { color: inherit; } .lib-promo__code-wrap { display: inline-flex; flex-wrap: wrap; align-items: center; gap: 0.4rem; } .lib-promo__code-label { font-size: 0.875rem; } .lib-promo__code { padding: 0.2rem 0.55rem; border: 1.5px dashed currentColor; border-radius: 6px; font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-size: 0.875rem; font-weight: 700; letter-spacing: 0.06em; user-select: all; } .lib-promo__copy { min-height: 2rem; min-width: 2.75rem; padding: 0.35rem 0.8rem; border: 0; border-radius: 999px; background: var(--lib-accent); color: var(--lib-accent-text); font: inherit; font-size: 0.8125rem; font-weight: 600; cursor: pointer; } .lib-promo__copy:focus-visible, .lib-promo__close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; } .lib-promo__link { font-size: 0.875rem; } .lib-promo__link .lib-btn { padding: 0.55em 1.1em; } .lib-promo__link .lib-btn--text { padding-inline: 0; color: inherit; } .lib-promo__status { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; } .lib-promo__close { flex: none; display: grid; place-items: center; width: 2.25rem; height: 2.25rem; border: 0; border-radius: 50%; background: transparent; color: inherit; cursor: pointer; } .lib-promo__close:hover { background: color-mix(in srgb, currentColor 12%, transparent); } @media (prefers-reduced-motion: no-preference) { .lib-promo[data-animate][data-position='bottom'] .lib-promo__bar { animation: lib-promo-up 0.45s cubic-bezier(0.2, 0.7, 0.2, 1) both; } .lib-promo[data-animate].is-stuck .lib-promo__bar { animation: lib-promo-down 0.35s cubic-bezier(0.2, 0.7, 0.2, 1) both; } } @keyframes lib-promo-up { from { transform: translateY(100%); } } @keyframes lib-promo-down { from { transform: translateY(-100%); } } {% endstylesheet %} {% javascript %} (() => { if (customElements.get('lib-sticky-promo-bar')) return; const DAY_MS = 86400000; const COPIED_RESET_MS = 2500; // A short, stable key per offer: a new message or code shows again to shoppers who dismissed the old one. const hash = (text) => { let h = 5381; for (let i = 0; i < text.length; i += 1) h = ((h << 5) + h + text.charCodeAt(i)) | 0; return (h >>> 0).toString(36); }; // "2026-11-27 09:00" in the store's time zone ("+0300") -> a Date. const parse = (value, tz) => { if (!value) return null; const offset = /^[+-]\d{4}$/.test(tz) ? `${tz.slice(0, 3)}:${tz.slice(3)}` : 'Z'; const date = new Date(`${value.trim().replace(' ', 'T')}:00${offset}`); return Number.isNaN(date.getTime()) ? null : date; }; class LibStickyPromoBar extends HTMLElement { connectedCallback() { this.bar = this.querySelector('[data-bar]'); this.key = `lib-promo-dismissed:${hash(this.bar.textContent.replace(/\s+/g, ' '))}`; const editor = this.hasAttribute('data-editor'); if (!editor && (!this.inWindow() || this.wasDismissed())) { this.hidden = true; return; } this.querySelector('[data-close]')?.addEventListener('click', () => this.dismiss()); this.querySelector('[data-copy]')?.addEventListener('click', (event) => this.copy(event.currentTarget)); if (this.dataset.position === 'top') this.pinOnScroll(); if (this.dataset.position === 'bottom') this.reserveSpace(); } disconnectedCallback() { this.observer?.disconnect(); this.resizer?.disconnect(); document.body.style.removeProperty('padding-bottom'); } inWindow() { const now = Date.now(); const starts = parse(this.dataset.starts, this.dataset.tz); const ends = parse(this.dataset.ends, this.dataset.tz); return (!starts || now >= starts.getTime()) && (!ends || now < ends.getTime()); } storage() { return Number(this.dataset.remember) > 0 ? localStorage : sessionStorage; } wasDismissed() { try { const until = Number(this.storage().getItem(this.key)); return until > Date.now(); } catch (error) { // Storage blocked: show the bar; dismissing still works for this page view. return false; } } dismiss() { const days = Number(this.dataset.remember); const until = Date.now() + (days > 0 ? days * DAY_MS : DAY_MS); try { this.storage().setItem(this.key, String(until)); } catch (error) { console.warn('Promo bar: the dismissal could not be remembered.', error); } this.hidden = true; this.disconnectedCallback(); } async copy(button) { const code = this.querySelector('.lib-promo__code'); const status = this.querySelector('[data-status]'); const copied = this.querySelector('[data-copied]').content.textContent.trim(); try { await navigator.clipboard.writeText(code.textContent.trim()); status.textContent = copied; const label = button.textContent; button.textContent = copied; setTimeout(() => { button.textContent = label; status.textContent = ''; }, COPIED_RESET_MS); } catch (error) { // No clipboard access (old browser, insecure page): select the code so it can be copied by hand. const range = document.createRange(); range.selectNodeContents(code); const selection = window.getSelection(); selection.removeAllRanges(); selection.addRange(range); } } // Top: the bar sits in its place and stays at the top once its place scrolls out of view. pinOnScroll() { const spacer = this.querySelector('[data-spacer]'); this.observer = new IntersectionObserver(([entry]) => { const stuck = !entry.isIntersecting && entry.boundingClientRect.top < 0; spacer.style.height = stuck ? `${this.bar.offsetHeight}px` : ''; this.classList.toggle('is-stuck', stuck); }); this.observer.observe(spacer); } // Bottom: keep the page's last lines clear of the fixed bar. reserveSpace() { this.resizer = new ResizeObserver(() => { document.body.style.paddingBottom = `${this.bar.offsetHeight}px`; }); this.resizer.observe(this.bar); } } customElements.define('lib-sticky-promo-bar', LibStickyPromoBar); })(); {% endjavascript %} {% schema %} { "name": "Lib · Sticky promo bar", "tag": "section", "class": "lib-section", "settings": [ { "type": "header", "content": "Content" }, { "type": "inline_richtext", "id": "message", "label": "Message", "default": "Autumn sale: 15% off everything until Sunday" }, { "type": "text", "id": "code", "label": "Discount code", "info": "Optional. Create the discount itself in Shopify under Discounts." }, { "type": "text", "id": "code_label", "label": "Text before the code", "default": "Use code" }, { "type": "text", "id": "copy_label", "label": "Copy button", "info": "Leave empty to hide the button.", "default": "Copy" }, { "type": "text", "id": "copied_label", "label": "Copied message", "default": "Code copied" }, { "type": "text", "id": "link_label", "label": "Link text" }, { "type": "url", "id": "link_url", "label": "Link" }, { "type": "select", "id": "link_style", "label": "Link style", "options": [ { "value": "primary", "label": "Solid" }, { "value": "outline", "label": "Outline" }, { "value": "text", "label": "Text link" } ], "default": "text" }, { "type": "checkbox", "id": "link_new_tab", "label": "Open link in a new tab", "default": false }, { "type": "text", "id": "region_label", "label": "Bar name (screen readers)", "default": "Current offer" }, { "type": "header", "content": "Position and schedule" }, { "type": "select", "id": "position", "label": "Position", "options": [ { "value": "bottom", "label": "Fixed to the bottom of the screen" }, { "value": "top", "label": "In place, then pinned to the top on scroll" } ], "default": "bottom" }, { "type": "range", "id": "top_offset", "label": "Distance from the top when pinned", "info": "Use your sticky header's height so the bar sits below it.", "min": 0, "max": 200, "step": 2, "unit": "px", "default": 0, "visible_if": "{{ section.settings.position == 'top' }}" }, { "type": "text", "id": "starts_at", "label": "Show from", "info": "Optional. YYYY-MM-DD HH:MM in the store's time zone, for example 2026-11-27 09:00." }, { "type": "text", "id": "ends_at", "label": "Show until", "info": "Optional. Same format. The bar disappears at this time." }, { "type": "checkbox", "id": "dismissible", "label": "Show a close button", "default": true }, { "type": "text", "id": "close_label", "label": "Close button label (screen readers)", "default": "Close offer" }, { "type": "range", "id": "remember_days", "label": "Keep closed for", "info": "0 keeps it closed until the browser tab is closed.", "min": 0, "max": 30, "step": 1, "unit": "d", "default": 7 }, { "type": "header", "content": "Layout" }, { "type": "select", "id": "section_width", "label": "Width", "options": [ { "value": "page", "label": "Page width" }, { "value": "full", "label": "Full width" } ], "default": "page" }, { "type": "select", "id": "text_alignment", "label": "Alignment on desktop", "options": [ { "value": "left", "label": "Left" }, { "value": "center", "label": "Center" } ], "default": "center" }, { "type": "select", "id": "text_alignment_mobile", "label": "Alignment on mobile", "options": [ { "value": "left", "label": "Left" }, { "value": "center", "label": "Center" } ], "default": "left" }, { "type": "header", "content": "Colors" }, { "type": "color", "id": "color_bg", "label": "Background", "default": "#2B2620" }, { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." }, { "type": "color", "id": "color_text", "label": "Text", "default": "#FAF6F1" }, { "type": "color", "id": "color_accent", "label": "Copy button", "default": "#E9C99A" }, { "type": "color", "id": "color_accent_text", "label": "Copy button text", "default": "#2B2620" }, { "type": "header", "content": "Spacing and visibility" }, { "type": "range", "id": "padding_top", "label": "Top padding on desktop", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 10 }, { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 10 }, { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 8 }, { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 8 }, { "type": "select", "id": "show_on", "label": "Show on", "options": [ { "value": "all", "label": "All devices" }, { "value": "desktop", "label": "Desktop only" }, { "value": "mobile", "label": "Mobile only" } ], "default": "all" }, { "type": "text", "id": "anchor_id", "label": "Anchor ID", "info": "Optional. Link to this section with #your-id." }, { "type": "header", "content": "Motion" }, { "type": "checkbox", "id": "animate", "label": "Slide the bar in", "info": "Never when reduced motion is on.", "default": true } ], "presets": [{ "name": "Lib · Sticky promo bar", "settings": { "code": "AUTUMN15" } }] } {% endschema %}