# Sabit kampanya çubuğu: ücretsiz Shopify bölümü

> İndirim kodunu kopyalama düğmesi, başlangıç ve bitiş saati, kapatıldığını hatırlayan kapatma düğmesiyle sabit çubuk. Altta durur ya da üste sabitlenir.

Sabit kampanya çubuğu, kısa süreli bir kampanyayı ekranda tutan ücretsiz bir Shopify bölümüdür: bir mesaj, isteğe bağlı bir indirim kodu ve kopyalama düğmesi, bir de bağlantı. Ekranın altına sabitlenir ya da müşteri üzerinden kaydırınca üste yapışır. Mağazanızın saat diliminde belirlediğiniz başlangıç ve bitiş saatleri arasında yayında kalabilir; bu aralık hem Liquid ile hem tarayıcıda kontrol edildiği için önbellekten gelen bir sayfa süresi dolmuş bir kampanyayı göstermez. Kapatma düğmesi çubuğu seçtiğiniz gün sayısı kadar gizler. Kod yalnızca sizin yazdığınız bir metindir; indirimin kendisini Shopify’da oluşturursunuz. Online Store 2.0 temalarında uygulama olmadan çalışır.

- **Kategori:** Dönüşüm ve kampanyalar
- **Kullanıldığı yerler:** Her sayfa, Ana sayfa, Koleksiyon
- **Okuduğu veri:** Yalnızca içerik
- **Boyut:** 2 dosyada 21 KB, küçük bir betikle
- **Lisans:** MIT, © 2026 Lintel
- **Sayfa:** https://lintelagency.com/tr/kaynaklar/shopify-bolumleri/sabit-kampanya-cubugu
- **Canlı demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-sticky-promo-bar.html
- **Eklenme tarihi:** 2026-11-02

## Ne işe yarar

- Müşterilerin tek dokunuşla kopyalayabildiği indirim koduyla bir hafta sonu indirimi
- Belirlenen saatlerde kendiliğinden başlayıp biten bir lansman ya da bayram mesajı
- Mobilde indirim koleksiyonuna bağlantı veren altta sabit bir çubuk

## Tema düzenleyici ayarları

Bölüm tema düzenleyicide “Lib · Sticky promo bar” adıyla görünür. Kimlikler, türler, etiketler ve varsayılan değerler bölümün şemasından gelir. Etiketler tema düzenleyicide göründüğü gibi İngilizcedir.

### Content

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `message` | `inline_richtext` | Message | `<strong>Autumn sale:</strong> 15% off everything until Sunday` |  |
| `code` | `text` | Discount code | – |  |
| `code_label` | `text` | Text before the code | Use code |  |
| `copy_label` | `text` | Copy button | Copy |  |
| `copied_label` | `text` | Copied message | Code copied |  |
| `link_label` | `text` | Link text | – |  |
| `link_url` | `url` | Link | – |  |
| `link_style` | `select` | Link style | text | `primary`, `outline`, `text` |
| `link_new_tab` | `checkbox` | Open link in a new tab | `false` |  |
| `region_label` | `text` | Bar name (screen readers) | Current offer |  |

### Position and schedule

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `position` | `select` | Position | bottom | `bottom`, `top` |
| `top_offset` | `range` | Distance from the top when pinned | `0` | 0–200 px, step 2 |
| `starts_at` | `text` | Show from | – |  |
| `ends_at` | `text` | Show until | – |  |
| `dismissible` | `checkbox` | Show a close button | `true` |  |
| `close_label` | `text` | Close button label (screen readers) | Close offer |  |
| `remember_days` | `range` | Keep closed for | `7` | 0–30 d, step 1 |

### Layout

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `section_width` | `select` | Width | page | `page`, `full` |
| `text_alignment` | `select` | Alignment on desktop | center | `left`, `center` |
| `text_alignment_mobile` | `select` | Alignment on mobile | left | `left`, `center` |

### Colors

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #2B2620 |  |
| `color_bg_gradient` | `color_background` | Background gradient | – |  |
| `color_text` | `color` | Text | #FAF6F1 |  |
| `color_accent` | `color` | Copy button | #E9C99A |  |
| `color_accent_text` | `color` | Copy button text | #2B2620 |  |

### Spacing and visibility

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `padding_top` | `range` | Top padding on desktop | `10` | 0–40 px, step 2 |
| `padding_bottom` | `range` | Bottom padding on desktop | `10` | 0–40 px, step 2 |
| `padding_top_mobile` | `range` | Top padding on mobile | `8` | 0–40 px, step 2 |
| `padding_bottom_mobile` | `range` | Bottom padding on mobile | `8` | 0–40 px, step 2 |
| `show_on` | `select` | Show on | all | `all`, `desktop`, `mobile` |
| `anchor_id` | `text` | Anchor ID | – |  |

### Motion

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `animate` | `checkbox` | Slide the bar in | `true` |  |

## Kurulum

### Shopify yönetim panelinde

1. Canlı temanızda değil, bir kopyasında çalışın: Shopify yönetim panelinde **Online Mağaza > Temalar** sayfasına gidin, mevcut temanızın **…** menüsünü açın ve **Kopya oluştur**’u seçin.
2. Kopyada **…** menüsünü açın ve **Kodu düzenle**’yi seçin.
3. `sections` klasörüne `lib-sticky-promo-bar.liquid` adında yeni bir dosya ekleyin, aşağıdaki kaynak koddan `sections/lib-sticky-promo-bar.liquid` içeriğini yapıştırıp kaydedin.
4. `lib-button.liquid` dosyalarını da aynı şekilde `snippets` klasörüne ekleyin. Başka bir Lib bölümünden gelen aynı adlı bir snippet zaten varsa aynı dosyadır; tek kopya yeterli.
5. Kopyada **Özelleştir**’i açın, **Bölüm ekle**’yi seçin ve **Lib · Sticky promo bar** bölümünü ekleyin. Örnek içeriği kendi içeriğinizle değiştirin, renkleri ayarlayın.
6. Kopyayı önizleyin, her şey doğru göründüğünde yayınlayın.

### Shopify CLI ile

Yerel tema klasörünüzde dosyaları indirin ve yalnızca bunları yayında olmayan bir temaya gönderin (temanın kimliğini `shopify theme list` gösterir):

```sh
curl -fsSL -o sections/lib-sticky-promo-bar.liquid https://lintelagency.com/resources/shopify-sections/files/lib-sticky-promo-bar.liquid
curl -fsSL -o snippets/lib-button.liquid https://lintelagency.com/resources/shopify-sections/files/lib-button.liquid
shopify theme push --theme <unpublished-theme-id> --only sections/lib-sticky-promo-bar.liquid --only snippets/lib-button.liquid
```

Canlı temaya asla göndermeyin: her zaman yayında olmayan bir temanın kimliğini verin, `--live` ya da `--allow-live` kullanmayın. Ardından bölümü yukarıdaki gibi tema düzenleyicide ekleyin.

## Dosyalar

- [sections/lib-sticky-promo-bar.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-sticky-promo-bar.liquid): `sections/` klasörüne
- [snippets/lib-button.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-button.liquid): `snippets/` klasörüne

## Sık sorulan sorular

### İndirim kodunu bölüm mü oluşturuyor?

Hayır. İndirimi Shopify yönetim panelinde İndirimler altında oluşturun, ardından aynı kodu bölüme yazın. Kopyalama düğmesi yazdığınız metni birebir kopyalar.

### Zamanlama nasıl çalışıyor?

Başlangıç ve bitişi mağazanın saat diliminde YYYY-AA-GG SS:DD biçiminde yazın. Liquid bu aralığın dışında çubuğu sayfaya hiç eklemez; betik de saati tarayıcıda yeniden kontrol eder; böylece önbellekten gelen bir sayfa bitişten sonra çubuğu göstermez. Her zaman göstermek için ikisini de boş bırakın.

### Müşteri kapattıktan sonra ne kadar kapalı kalıyor?

Belirlediğiniz gün sayısı (0–30) kadar; bu bilgi müşterinin tarayıcısında tutulur. 0 seçerseniz sekme kapanana kadar kapalı kalır. Mesajı ya da kodu değiştirdiğinizde yeni kampanya, eskisini kapatmış müşterilere de yeniden görünür.

### Sabit başlığımın üstünü kapatır mı?

Üste sabitlendiğinde çubuk, üstten belirlediğiniz mesafede durur; bu mesafeye sabit başlığınızın yüksekliğini girin. Alta sabitlendiğinde bölüm sayfanın sonuna çubukla aynı yükseklikte boşluk ekler; böylece alt bilgi hiçbir zaman örtülmez.

### Erişilebilir mi?

Çubuk etiketli bir bölgedir; kopyalama ve kapatma düğmeleri görünür odaklı, en az 32 piksel yüksekliğinde gerçek düğmelerdir; "Kod kopyalandı" mesajı ekran okuyuculara bildirilir; ziyaretçi hareketin azaltılmasını istediğinde kayarak gelme efekti kapanır.

## Kaynak kod

### sections/lib-sticky-promo-bar.liquid

```liquid
{%- 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 -%}
  <lib-sticky-promo-bar
    class="lib-promo lib-promo--{{ s.section_width }}"
    {% if s.anchor_id != blank %}
      id="{{ s.anchor_id | handleize }}"
    {% endif %}
    data-show="{{ s.show_on }}"
    data-position="{{ s.position }}"
    data-starts="{{ starts | escape }}"
    data-ends="{{ ends | escape }}"
    data-tz="{{ tz }}"
    data-remember="{{ s.remember_days }}"
    {% if request.design_mode %}
      data-editor
    {% endif %}
    {% if s.animate %}
      data-animate
    {% endif %}
    style="
      --lib-bg: {{ s.color_bg }};
      {% if s.color_bg_gradient != blank %}--lib-bg-image: {{ s.color_bg_gradient }};{% endif %}
      --lib-text: {{ s.color_text }};
      --lib-accent: {{ s.color_accent }};
      --lib-accent-text: {{ s.color_accent_text }};
      --lib-offset: {{ s.top_offset }}px;
      --lib-pt: {{ s.padding_top }}px;
      --lib-pb: {{ s.padding_bottom }}px;
      --lib-pt-m: {{ s.padding_top_mobile }}px;
      --lib-pb-m: {{ s.padding_bottom_mobile }}px;
    "
  >
    <div class="lib-promo__spacer" data-spacer></div>
    <aside class="lib-promo__bar" aria-label="{{ s.region_label | escape }}" data-bar>
      <div class="lib-promo__inner" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
        <div class="lib-promo__content">
          {%- if s.message != blank -%}
            <p class="lib-promo__message">{{ s.message }}</p>
          {%- endif -%}
          {%- if s.code != blank -%}
            <span class="lib-promo__code-wrap">
              <span class="lib-promo__code-label">{{ s.code_label | escape }}</span>
              <code class="lib-promo__code" id="{{ code_id }}">{{ s.code | escape }}</code>
              {%- if s.copy_label != blank -%}
                <button type="button" class="lib-promo__copy" aria-describedby="{{ code_id }}" data-copy>
                  {{- s.copy_label | escape -}}
                </button>
              {%- endif -%}
            </span>
          {%- endif -%}
          {%- if s.link_label != blank -%}
            <span class="lib-promo__link">
              {%- render 'lib-button', label: s.link_label, url: s.link_url, style: s.link_style, new_tab: s.link_new_tab, arrow: false -%}
            </span>
          {%- endif -%}
        </div>
        <span class="lib-promo__status" role="status" data-status></span>
        {%- if s.dismissible -%}
          <button type="button" class="lib-promo__close" aria-label="{{ s.close_label | escape }}" data-close>
            <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M4 4l8 8M12 4l-8 8" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
          </button>
        {%- endif -%}
      </div>
    </aside>
    <template data-copied>{{ s.copied_label | escape }}</template>
  </lib-sticky-promo-bar>
{%- 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": "<strong>Autumn sale:</strong> 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 %}
```

### snippets/lib-button.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{% doc %}
  Animated call-to-action link. Light sweep on hover, arrow slides right.
  Colors come from the parent section's --lib-accent / --lib-accent-text / --lib-text vars.
  Inspired by Uiverse.io button patterns; rewritten for themes, no dependencies.

  @param {string} label - Button text
  @param {string} [url] - Link target; renders a disabled-looking span when blank
  @param {string} [style] - 'primary' (default), 'outline' or 'text'
  @param {boolean} [new_tab] - Open the link in a new tab
  @param {boolean} [arrow] - Show the trailing arrow (default true)

  @example
  {% render 'lib-button', label: section.settings.button_label, url: section.settings.button_url, style: 'outline' %}
{% enddoc %}

{%- liquid
  assign btn_style = style | default: 'primary'
  assign show_arrow = true
  if arrow == false
    assign show_arrow = false
  endif
-%}

{%- unless label == blank -%}
{%- capture inner -%}
  <span class="lib-btn__label">{{ label | escape }}</span>
  {%- if show_arrow -%}
    <svg class="lib-btn__arrow" viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false">
      <path d="M3 8h9M8.5 4.5 12 8l-3.5 3.5" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/>
    </svg>
  {%- endif -%}
{%- endcapture -%}

{%- if url != blank -%}
  <a
    class="lib-btn lib-btn--{{ btn_style }}"
    href="{{ url }}"
    {% if new_tab %}
      target="_blank" rel="noopener"
    {% endif %}
  >
    {{- inner -}}
  </a>
{%- else -%}
  <span class="lib-btn lib-btn--{{ btn_style }}" aria-disabled="true">{{ inner }}</span>
{%- endif -%}
{%- endunless -%}

{% stylesheet %}
  .lib-btn {
    position: relative;
    display: inline-flex;
    align-items: center;
    gap: 0.5em;
    padding: 0.85em 1.5em;
    border-radius: var(--lib-radius, 999px);
    font: inherit;
    font-weight: 600;
    line-height: 1;
    text-decoration: none;
    overflow: hidden;
    isolation: isolate;
    transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
  }
  .lib-btn--primary {
    background: var(--lib-accent, #111);
    color: var(--lib-accent-text, #fff);
    box-shadow: 0 1px 2px rgb(0 0 0 / 0.12);
  }
  .lib-btn--outline {
    background: transparent;
    color: var(--lib-text, #111);
    box-shadow: inset 0 0 0 1.5px currentColor;
  }
  .lib-btn--text {
    padding-inline: 0;
    background: none;
    color: var(--lib-text, #111);
    text-decoration: underline;
    text-underline-offset: 0.3em;
    overflow: visible;
  }
  .lib-btn--text::before { display: none; }
  .lib-btn--text:hover { transform: none; box-shadow: none; }
  .lib-btn::before {
    content: '';
    position: absolute;
    inset: 0;
    z-index: -1;
    background: linear-gradient(110deg, transparent 30%, rgb(255 255 255 / 0.35) 50%, transparent 70%);
    transform: translateX(-120%);
    transition: transform 0.6s ease;
  }
  .lib-btn--outline::before {
    background: linear-gradient(110deg, transparent 30%, rgb(127 127 127 / 0.18) 50%, transparent 70%);
  }
  .lib-btn:hover {
    transform: translateY(-2px);
    box-shadow: 0 8px 20px -8px rgb(0 0 0 / 0.35);
  }
  .lib-btn:hover::before { transform: translateX(120%); }
  .lib-btn__arrow { transition: transform 0.2s ease; }
  .lib-btn:hover .lib-btn__arrow { transform: translateX(3px); }
  .lib-btn:focus-visible {
    outline: 2px solid var(--lib-accent, #111);
    outline-offset: 3px;
  }
  .lib-btn[aria-disabled='true'] { pointer-events: none; }
  @media (prefers-reduced-motion: reduce) {
    .lib-btn, .lib-btn::before, .lib-btn__arrow { transition: none; }
    .lib-btn:hover { transform: none; }
  }
{% endstylesheet %}
```

## Lisans

MIT Lisansı, © 2026 Lintel. Kendi mağazanızda ya da müşteri işlerinde ücretsiz kullanabilir, değiştirebilir ve yayınlayabilirsiniz; her dosyanın başındaki lisans satırını koruyun.
