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

> Ana satın al düğmesi görünümden çıkınca ekranın altında ya da üstünde beliren, varyant seçimi ve adetli sabit sepete ekle çubuğu.

Sabit sepete ekle çubuğu, sayfanın kendi satın alma formu görünümden çıktığında ürün görseli, fiyat, varyant seçimi, adet ve sepete ekle düğmesiyle bir satın alma çubuğunu kaydırarak getiren ücretsiz bir Shopify bölümüdür. Yerel bir Shopify ürün formudur, tükenmiş varyantları seçilemez tutar ve hiçbir içeriği örtmemek için sayfaya boşluk ekler; Online Store 2.0 temalarında uygulama olmadan çalışır.

- **Kategori:** Ürün sayfası
- **Kullanıldığı yerler:** Ürün, Açılış sayfaları, Her sayfa
- **Okuduğu veri:** Ürünler
- **Boyut:** 1 dosyada 28 KB, küçük bir betikle
- **Lisans:** MIT, © 2026 Lintel
- **Sayfa:** https://lintelagency.com/tr/kaynaklar/shopify-bolumleri/sabit-sepete-ekle-cubugu
- **Canlı demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-sticky-add-to-cart.html
- **Eklenme tarihi:** 2026-10-02

## Ne işe yarar

- Yorumlar, sekmeler ve önerilerle uzayan ürün sayfaları; satın al düğmesi tek dokunuş uzakta kalır
- Ana sepete ekle düğmesinin hızla ekrandan çıktığı mobil ürün sayfaları
- Ürün seçiciyi kullanan, tek bir ürün için açılış ya da kampanya sayfası

## Tema düzenleyici ayarları

Bölüm tema düzenleyicide “Lib · Sticky add to cart” 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 |
| --- | --- | --- | --- | --- |
| `product` | `product` | Product | – |  |
| `show_image` | `checkbox` | Show product image | `true` |  |
| `show_compare_price` | `checkbox` | Show compare-at price | `true` |  |
| `show_quantity` | `checkbox` | Show quantity selector | `true` |  |
| `button_label` | `text` | Button label | Add to cart |  |
| `sold_out_label` | `text` | Sold out label | Sold out |  |

### When it appears

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `position` | `select` | Position | bottom | `bottom`, `top` |
| `always_visible` | `checkbox` | Always visible | `false` |  |
| `trigger_selector` | `text` | Main buy form selector | form[action*="/cart/add"] |  |
| `scroll_offset` | `range` | Fallback scroll distance | `600` | 0–1500 px, step 50 |

### Layout

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `bar_style` | `select` | Style | bar | `bar`, `floating` |
| `content_width` | `select` | Content width | page | `page`, `full` |
| `radius` | `range` | Corner radius | `8` | 0–28 px, step 2 |

### Colors

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #FFFFFF |  |
| `color_text` | `color` | Text | #1C1917 |  |
| `color_accent` | `color` | Button and sale price | #1C1917 |  |
| `color_accent_text` | `color` | Button text | #FFFFFF |  |
| `color_border` | `color` | Border | #E7E5E4 |  |
| `shadow` | `select` | Shadow | soft | `none`, `soft`, `strong` |

### Spacing and visibility

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `padding_y` | `range` | Top and bottom padding on desktop | `14` | 4–32 px, step 2 |
| `padding_y_mobile` | `range` | Top and bottom padding on mobile | `10` | 4–32 px, step 2 |
| `show_on` | `select` | Show on | all | `all`, `desktop`, `mobile` |

### Motion

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `animate` | `checkbox` | Slide in and out | `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-add-to-cart.liquid` adında yeni bir dosya ekleyin, aşağıdaki kaynak koddan `sections/lib-sticky-add-to-cart.liquid` içeriğini yapıştırıp kaydedin.
4. Kopyada **Özelleştir**’i açın, **Bölüm ekle**’yi seçin ve **Lib · Sticky add to cart** bölümünü ekleyin. Örnek içeriği kendi içeriğinizle değiştirin, renkleri ayarlayın.
5. 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-add-to-cart.liquid https://lintelagency.com/resources/shopify-sections/files/lib-sticky-add-to-cart.liquid
shopify theme push --theme <unpublished-theme-id> --only sections/lib-sticky-add-to-cart.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-add-to-cart.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-sticky-add-to-cart.liquid): `sections/` klasörüne

## Sık sorulan sorular

### Çubuk ne zaman görünür?

"Main buy form selector" ayarıyla eşleşen öğeyi izler; varsayılan olarak bu, sayfada çubuğun kendisine ait olmayan ilk ürün formudur. Çubuk, bu form tamamen ekranın üstüne kaydırıldığında görünür ve forma geri kaydırınca yeniden gizlenir. Seçiciyle hiçbir şey eşleşmezse bunun yerine yedek kaydırma mesafesinden sonra görünür (varsayılan 600px). "Always visible" onu en baştan gösterir.

### Hangi ürünü kullanır?

Bölüm ayarlarında bir ürün seçildiyse onu, seçilmediyse bulunduğu ürün sayfasının ürününü. Tema düzenleyici dışında ikisi de yoksa bölüm hiçbir şey çıkarmaz; düzenleyicide yer tutucu bir çubuk gösterir.

### Varyantlar ve tükenmiş ürünler nasıl ele alınır?

Ürünün birden fazla varyantı varsa çubuk tüm varyantları içeren bir seçim kutusu gösterir. Tükenmiş varyantlar devre dışıdır ve tükendi metninizle etiketlenir. Bir varyant seçmek fiyatı ve karşılaştırma fiyatını günceller; sayfanın kendi satın alma formu varyantını değiştirirse çubuk da onu izler. Seçili varyant tükenmişse düğme devre dışı kalır ve tükendi etiketini gösterir.

### Temamın sepet çekmecesini kullanır mı?

Hayır. Sepete gönderen standart bir Shopify ürün formudur; JavaScript olmadan çalışır ve müşteriyi sepet sayfasına götüren Shopify varsayılan davranışını izler. Temanın sepet çekmecesini açmaz.

### Alt bilgiyi ya da başka içerikleri örter mi?

Altta konumlandığında görünür olduğu sürece kendi yüksekliğini sayfanın alt boşluğuna ekler, gizlenince kaldırır; böylece sayfanın sonu her zaman kaydırılarak görülebilir. Gizliyken inert ve aria-hidden olur; klavye ve ekran okuyucu kullanıcıları asla içine düşmez.

### Yalnızca mobilde gösterebilir miyim?

Evet. "Show on" ayarı onu tüm cihazlar, yalnızca masaüstü (750px ve üzeri) ve yalnızca mobil arasında değiştirir. 750px altında ürün bilgisi varyant seçimi, adet ve düğmenin üstünde durur.

## Kaynak kod

### sections/lib-sticky-add-to-cart.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Sticky add to cart: a bar fixed to the bottom (or top) of the screen that appears once the
  page's own buy button has scrolled out of view. Product image, title, price with compare-at
  price, variant select, optional quantity and an add to cart button in a native product form.
  Uses the product picked in the settings, or the product of the product template.
  While visible it pads the bottom of the page so it never covers the last content.
  No snippets needed.
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign p = s.product
  if p == blank
    assign p = product
  endif
  assign render_bar = true
  if p == blank and request.design_mode == false
    assign render_bar = false
  endif

  assign title = p.title | default: 'Example product'
  assign current = p.selected_or_first_available_variant
  assign has_select = false
  if p != blank and p.has_only_default_variant == false and p.variants.size > 1
    assign has_select = true
  endif
  assign option_names = p.options_with_values | map: 'name' | join: ' / '
  if option_names == blank
    assign option_names = 'Options'
  endif
  assign uid = 'lib-sticky-atc-' | append: section.id
  assign hidden_on_load = true
  if s.always_visible
    assign hidden_on_load = false
  endif
-%}

{%- if render_bar -%}
<lib-sticky-add-to-cart-bar
  class="lib-sticky-add-to-cart"
  data-show="{{ s.show_on }}"
  data-position="{{ s.position }}"
  data-style="{{ s.bar_style }}"
  data-width="{{ s.content_width }}"
  data-shadow="{{ s.shadow }}"
  data-selector="{{ s.trigger_selector | escape }}"
  data-offset="{{ s.scroll_offset }}"
  {% if s.always_visible %}
    data-always
  {% endif %}
  {% if s.animate %}
    data-animate
  {% endif %}
  style="
    --lib-bg: {{ s.color_bg }};
    --lib-text: {{ s.color_text }};
    --lib-accent: {{ s.color_accent }};
    --lib-accent-text: {{ s.color_accent_text }};
    --lib-border: {{ s.color_border }};
    --lib-py: {{ s.padding_y }}px;
    --lib-py-m: {{ s.padding_y_mobile }}px;
    --lib-radius: {{ s.radius }}px;
  "
>
  <div
    class="lib-sticky-add-to-cart__bar{% unless hidden_on_load %} is-visible{% endunless %}"
    role="region"
    aria-label="Add {{ title | escape }} to cart"
    {% if hidden_on_load %}
      aria-hidden="true" inert
    {% endif %}
  >
    <div class="lib-sticky-add-to-cart__inner">
      <div class="lib-sticky-add-to-cart__product">
        {%- if s.show_image -%}
          <div class="lib-sticky-add-to-cart__media">
            {%- if p.featured_image != blank -%}
              {{
                p.featured_image
                | image_url: width: 160
                | image_tag:
                  class: 'lib-sticky-add-to-cart__img',
                  loading: 'lazy',
                  alt: p.featured_image.alt,
                  widths: '80, 160',
                  sizes: '56px'
              }}
            {%- else -%}
              {{ 'product-1' | placeholder_svg_tag: 'lib-sticky-add-to-cart__img lib-sticky-add-to-cart__img--ph' }}
            {%- endif -%}
          </div>
        {%- endif -%}
        <div class="lib-sticky-add-to-cart__info">
          <p class="lib-sticky-add-to-cart__title">{{ title | escape }}</p>
          <p class="lib-sticky-add-to-cart__price">
            {%- if p != blank -%}
              {%- liquid
                assign price = current.price | default: p.price
                assign compare = current.compare_at_price | default: 0
                assign on_sale = false
                if s.show_compare_price and compare > price
                  assign on_sale = true
                endif
              -%}
              <span class="lib-sticky-add-to-cart__sr" data-sale-label{% unless on_sale %} hidden{% endunless %}>Sale price</span>
              <span class="lib-sticky-add-to-cart__now{% if on_sale %} is-sale{% endif %}" data-price>{{ price | money }}</span>
              {%- if s.show_compare_price -%}
                <span class="lib-sticky-add-to-cart__sr" data-was-label{% unless on_sale %} hidden{% endunless %}>Regular price</span>
                <s class="lib-sticky-add-to-cart__was" data-compare{% unless on_sale %} hidden{% endunless %}>
                  {%- if on_sale -%}{{ compare | money }}{%- endif -%}
                </s>
              {%- endif -%}
            {%- else -%}
              <span class="lib-sticky-add-to-cart__now">{{ 4800 | money }}</span>
            {%- endif -%}
          </p>
        </div>
      </div>

      {%- if p != blank -%}
        {%- form 'product', p, class: 'lib-sticky-add-to-cart__form' -%}
          <div class="lib-sticky-add-to-cart__controls">
            {%- if has_select -%}
              <label class="lib-sticky-add-to-cart__sr" for="{{ uid }}-variant">{{ option_names | escape }}</label>
              <div class="lib-sticky-add-to-cart__select-wrap">
                <select
                  id="{{ uid }}-variant"
                  class="lib-sticky-add-to-cart__select"
                  name="id"
                  data-variant-select
                >
                  {%- for v in p.variants -%}
                    {%- liquid
                      assign v_compare = ''
                      if v.compare_at_price > v.price
                        assign v_compare = v.compare_at_price | money
                      endif
                    -%}
                    <option
                      value="{{ v.id }}"
                      data-price="{{ v.price | money | escape }}"
                      data-compare="{{ v_compare | escape }}"
                      {% if v.id == current.id %}
                        selected
                      {% endif %}
                      {% unless v.available %}
                        disabled
                      {% endunless %}
                    >
                      {{- v.title | escape -}}
                      {%- unless v.available %} · {{ s.sold_out_label | escape }}{% endunless -%}
                    </option>
                  {%- endfor -%}
                </select>
                <svg class="lib-sticky-add-to-cart__chevron" viewBox="0 0 12 12" width="12" height="12" aria-hidden="true" focusable="false">
                  <path d="m2.5 4.5 3.5 3.5 3.5-3.5" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
                </svg>
              </div>
            {%- else -%}
              <input type="hidden" name="id" value="{{ current.id }}">
            {%- endif -%}

            {%- if s.show_quantity -%}
              <div class="lib-sticky-add-to-cart__qty">
                <button type="button" class="lib-sticky-add-to-cart__step" data-step="-1" aria-label="Decrease quantity">
                  <svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true" focusable="false"><path d="M2.5 6h7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
                </button>
                <label class="lib-sticky-add-to-cart__sr" for="{{ uid }}-qty">Quantity</label>
                <input
                  id="{{ uid }}-qty"
                  class="lib-sticky-add-to-cart__qty-input"
                  type="number"
                  name="quantity"
                  value="1"
                  min="1"
                  step="1"
                  inputmode="numeric"
                >
                <button type="button" class="lib-sticky-add-to-cart__step" data-step="1" aria-label="Increase quantity">
                  <svg viewBox="0 0 12 12" width="12" height="12" aria-hidden="true" focusable="false"><path d="M2.5 6h7M6 2.5v7" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"/></svg>
                </button>
              </div>
            {%- endif -%}

            <button
              type="submit"
              name="add"
              class="lib-sticky-add-to-cart__button"
              data-add
              data-label="{{ s.button_label | escape }}"
              data-sold-out="{{ s.sold_out_label | escape }}"
              {% unless current.available %}
                disabled
              {% endunless %}
            >
              {%- if current.available -%}
                {{- s.button_label | escape -}}
              {%- else -%}
                {{- s.sold_out_label | escape -}}
              {%- endif -%}
            </button>
          </div>
        {%- endform -%}
      {%- else -%}
        <div class="lib-sticky-add-to-cart__controls">
          <button type="button" class="lib-sticky-add-to-cart__button" disabled>{{ s.button_label | escape }}</button>
        </div>
      {%- endif -%}
    </div>
  </div>
</lib-sticky-add-to-cart-bar>
{%- endif -%}

{% stylesheet %}
  .lib-sticky-add-to-cart { display: block; }
  .lib-sticky-add-to-cart__bar {
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 40;
    padding: 0;
    color: var(--lib-text);
    visibility: hidden;
    transform: translateY(calc(100% + 24px));
  }
  .lib-sticky-add-to-cart[data-position='top'] .lib-sticky-add-to-cart__bar {
    top: 0;
    bottom: auto;
    transform: translateY(calc(-100% - 24px));
  }
  .lib-sticky-add-to-cart__bar.is-visible { visibility: visible; transform: none; }
  .lib-sticky-add-to-cart[data-position='top'] .lib-sticky-add-to-cart__bar.is-visible { transform: none; }
  .lib-sticky-add-to-cart[data-animate] .lib-sticky-add-to-cart__bar {
    transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1), visibility 0s linear 0.35s;
  }
  .lib-sticky-add-to-cart[data-animate] .lib-sticky-add-to-cart__bar.is-visible {
    transition: transform 0.35s cubic-bezier(0.2, 0.7, 0.2, 1), visibility 0s;
  }
  @media (max-width: 749px) {
    .lib-sticky-add-to-cart[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-sticky-add-to-cart[data-show='mobile'] { display: none; }
  }

  /* The visible surface: an edge-to-edge bar, or a floating card inset from the edges. */
  .lib-sticky-add-to-cart__inner {
    display: grid;
    gap: 0.75rem;
    align-items: center;
    margin: 0 auto;
    padding: var(--lib-py-m) 1rem calc(var(--lib-py-m) + env(safe-area-inset-bottom, 0px));
    background: var(--lib-bg);
    border-top: 1px solid var(--lib-border);
  }
  .lib-sticky-add-to-cart[data-position='top'] .lib-sticky-add-to-cart__inner {
    padding-bottom: var(--lib-py-m);
    border-top: 0;
    border-bottom: 1px solid var(--lib-border);
  }
  .lib-sticky-add-to-cart[data-shadow='soft'] .lib-sticky-add-to-cart__inner { box-shadow: 0 -6px 24px rgb(0 0 0 / 0.08); }
  .lib-sticky-add-to-cart[data-shadow='strong'] .lib-sticky-add-to-cart__inner { box-shadow: 0 -10px 40px rgb(0 0 0 / 0.18); }
  .lib-sticky-add-to-cart[data-position='top'][data-shadow='soft'] .lib-sticky-add-to-cart__inner { box-shadow: 0 6px 24px rgb(0 0 0 / 0.08); }
  .lib-sticky-add-to-cart[data-position='top'][data-shadow='strong'] .lib-sticky-add-to-cart__inner { box-shadow: 0 10px 40px rgb(0 0 0 / 0.18); }

  .lib-sticky-add-to-cart[data-style='floating'] .lib-sticky-add-to-cart__bar { padding: 0.5rem 0.5rem calc(0.5rem + env(safe-area-inset-bottom, 0px)); }
  .lib-sticky-add-to-cart[data-style='floating'][data-position='top'] .lib-sticky-add-to-cart__bar { padding: 0.5rem; }
  .lib-sticky-add-to-cart[data-style='floating'] .lib-sticky-add-to-cart__inner {
    max-width: 1200px;
    padding: var(--lib-py-m) 0.75rem;
    border: 1px solid var(--lib-border);
    border-radius: var(--lib-radius);
  }
  .lib-sticky-add-to-cart[data-style='floating'][data-width='full'] .lib-sticky-add-to-cart__inner { max-width: none; }

  @media (min-width: 750px) {
    .lib-sticky-add-to-cart__inner {
      grid-template-columns: minmax(0, 1fr) auto;
      gap: 1.5rem;
      padding: var(--lib-py) max(1.5rem, calc((100% - 1200px) / 2)) calc(var(--lib-py) + env(safe-area-inset-bottom, 0px));
    }
    .lib-sticky-add-to-cart[data-position='top'] .lib-sticky-add-to-cart__inner { padding-bottom: var(--lib-py); }
    .lib-sticky-add-to-cart[data-width='full'] .lib-sticky-add-to-cart__inner { padding-inline: 1.5rem; }
    .lib-sticky-add-to-cart[data-style='floating'] .lib-sticky-add-to-cart__bar { padding: 0.75rem 1.5rem calc(0.75rem + env(safe-area-inset-bottom, 0px)); }
    .lib-sticky-add-to-cart[data-style='floating'][data-position='top'] .lib-sticky-add-to-cart__bar { padding: 0.75rem 1.5rem; }
    .lib-sticky-add-to-cart[data-style='floating'] .lib-sticky-add-to-cart__inner { padding: var(--lib-py) 1rem var(--lib-py) var(--lib-py); }
  }

  .lib-sticky-add-to-cart__product { display: flex; align-items: center; gap: 0.75rem; min-width: 0; }
  .lib-sticky-add-to-cart__media {
    flex: none;
    width: 3rem;
    aspect-ratio: 1;
    border-radius: min(var(--lib-radius), 10px);
    overflow: hidden;
    background: color-mix(in srgb, var(--lib-text) 6%, transparent);
  }
  .lib-sticky-add-to-cart__img { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-sticky-add-to-cart__img--ph { fill: color-mix(in srgb, var(--lib-text) 25%, transparent); }
  .lib-sticky-add-to-cart__info { min-width: 0; }
  .lib-sticky-add-to-cart__title {
    margin: 0;
    font-size: 0.95rem;
    font-weight: 600;
    line-height: 1.3;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .lib-sticky-add-to-cart__price {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.5rem;
    margin: 0.15rem 0 0;
    font-size: 0.9rem;
    line-height: 1.3;
  }
  .lib-sticky-add-to-cart__now.is-sale { color: var(--lib-accent); font-weight: 600; }
  .lib-sticky-add-to-cart__was { opacity: 0.6; }
  .lib-sticky-add-to-cart__was[hidden], .lib-sticky-add-to-cart__sr[hidden] { display: none; }

  .lib-sticky-add-to-cart__form { margin: 0; min-width: 0; }
  .lib-sticky-add-to-cart__controls { display: flex; align-items: stretch; gap: 0.5rem; min-width: 0; }
  .lib-sticky-add-to-cart__select-wrap { position: relative; flex: 1 1 8rem; min-width: 0; }
  .lib-sticky-add-to-cart__select,
  .lib-sticky-add-to-cart__qty,
  .lib-sticky-add-to-cart__button {
    min-height: 2.75rem;
    border-radius: min(var(--lib-radius), 999px);
    font: inherit;
    font-size: 0.9rem;
  }
  .lib-sticky-add-to-cart__select {
    width: 100%;
    height: 100%;
    padding: 0 2.25rem 0 0.9rem;
    color: inherit;
    background: transparent;
    border: 1px solid color-mix(in srgb, var(--lib-text) 22%, transparent);
    appearance: none;
    -webkit-appearance: none;
    text-overflow: ellipsis;
    cursor: pointer;
  }
  .lib-sticky-add-to-cart__select option { color: #111; background: #fff; }
  .lib-sticky-add-to-cart__chevron {
    position: absolute;
    inset-inline-end: 0.85rem;
    top: 50%;
    translate: 0 -50%;
    pointer-events: none;
  }
  .lib-sticky-add-to-cart__qty {
    display: flex;
    flex: none;
    align-items: center;
    border: 1px solid color-mix(in srgb, var(--lib-text) 22%, transparent);
  }
  .lib-sticky-add-to-cart__step {
    display: grid;
    place-items: center;
    width: 2.25rem;
    height: 100%;
    padding: 0;
    color: inherit;
    background: none;
    border: 0;
    border-radius: inherit;
    cursor: pointer;
  }
  .lib-sticky-add-to-cart__qty-input {
    width: 2rem;
    padding: 0;
    color: inherit;
    background: none;
    border: 0;
    font: inherit;
    text-align: center;
    -moz-appearance: textfield;
  }
  .lib-sticky-add-to-cart__qty-input::-webkit-outer-spin-button,
  .lib-sticky-add-to-cart__qty-input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
  .lib-sticky-add-to-cart__button {
    flex: 1 1 auto;
    padding: 0 1.5rem;
    color: var(--lib-accent-text);
    background: var(--lib-accent);
    border: 1px solid var(--lib-accent);
    font-weight: 600;
    white-space: nowrap;
    cursor: pointer;
    transition: filter 0.2s;
  }
  .lib-sticky-add-to-cart__button:hover:not(:disabled) { filter: brightness(1.12); }
  .lib-sticky-add-to-cart__button:disabled { opacity: 0.5; cursor: not-allowed; }
  .lib-sticky-add-to-cart__select:focus-visible,
  .lib-sticky-add-to-cart__step:focus-visible,
  .lib-sticky-add-to-cart__qty-input:focus-visible,
  .lib-sticky-add-to-cart__button:focus-visible {
    outline: 2px solid var(--lib-accent);
    outline-offset: 2px;
  }
  @media (min-width: 750px) {
    .lib-sticky-add-to-cart__title { font-size: 1rem; }
    .lib-sticky-add-to-cart__select-wrap { flex: 0 1 15rem; }
    .lib-sticky-add-to-cart__button { flex: none; min-width: 11rem; }
  }
  .lib-sticky-add-to-cart__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
    border: 0;
  }
  @media (prefers-reduced-motion: reduce) {
    .lib-sticky-add-to-cart[data-animate] .lib-sticky-add-to-cart__bar,
    .lib-sticky-add-to-cart[data-animate] .lib-sticky-add-to-cart__bar.is-visible,
    .lib-sticky-add-to-cart__button { transition: none; }
  }
{% endstylesheet %}

{% javascript %}
  if (!customElements.get('lib-sticky-add-to-cart-bar')) {
    customElements.define(
      'lib-sticky-add-to-cart-bar',
      class extends HTMLElement {
        connectedCallback() {
          this.bar = this.querySelector('.lib-sticky-add-to-cart__bar');
          if (!this.bar) return;
          this.bottom = this.dataset.position !== 'top';
          this.forced = false;
          this.shown = this.bar.classList.contains('is-visible');
          this.cleanup = [];

          this.setupForm();
          this.setupSpace();

          if (this.hasAttribute('data-always')) {
            this.setShown(true);
          } else {
            this.setupTrigger();
          }

          // Theme editor: show the bar while its section is selected.
          const onSelect = (e) => {
            if (!this.closest('.shopify-section') || e.target !== this.closest('.shopify-section')) return;
            this.forced = e.type === 'shopify:section:select';
            this.update();
          };
          document.addEventListener('shopify:section:select', onSelect);
          document.addEventListener('shopify:section:deselect', onSelect);
          this.cleanup.push(() => {
            document.removeEventListener('shopify:section:select', onSelect);
            document.removeEventListener('shopify:section:deselect', onSelect);
          });
        }

        disconnectedCallback() {
          (this.cleanup || []).forEach((fn) => fn());
          this.cleanup = [];
          this.setSpace(0);
        }

        /* Show when the page's own buy form is above the viewport; otherwise after a scroll distance. */
        setupTrigger() {
          this.wanted = false;
          let target = null;
          try {
            const selector = this.dataset.selector || '';
            if (selector) target = [...document.querySelectorAll(selector)].find((el) => !this.contains(el)) || null;
          } catch (err) {
            target = null;
          }
          this.target = target;

          if (target && 'IntersectionObserver' in window) {
            const io = new IntersectionObserver((entries) => {
              const entry = entries[entries.length - 1];
              this.wanted = !entry.isIntersecting && entry.boundingClientRect.bottom < 0;
              this.update();
            });
            io.observe(target);
            this.cleanup.push(() => io.disconnect());
            return;
          }

          const offset = parseInt(this.dataset.offset, 10) || 0;
          let ticking = false;
          const check = () => {
            ticking = false;
            this.wanted = window.scrollY > offset;
            this.update();
          };
          const onScroll = () => {
            if (!ticking) {
              ticking = true;
              requestAnimationFrame(check);
            }
          };
          window.addEventListener('scroll', onScroll, { passive: true });
          this.cleanup.push(() => window.removeEventListener('scroll', onScroll));
          check();
        }

        update() {
          if (this.hasAttribute('data-always')) return;
          this.setShown(Boolean(this.wanted || this.forced));
        }

        setShown(show) {
          this.shown = show;
          this.bar.classList.toggle('is-visible', show);
          this.bar.toggleAttribute('inert', !show);
          if (show) this.bar.removeAttribute('aria-hidden');
          else this.bar.setAttribute('aria-hidden', 'true');
          this.setSpace(show ? this.bar.offsetHeight : 0);
        }

        /* Pad the page by the bar's height while it is visible, so it never covers the last content. */
        setupSpace() {
          if (!this.bottom) return;
          const body = document.body;
          if (!body) return;
          const before = body.style.paddingBottom;
          const base = getComputedStyle(body).paddingBottom || '0px';
          body.style.paddingBottom = `calc(${base} + var(--lib-sticky-atc-space, 0px))`;
          this.cleanup.push(() => {
            body.style.paddingBottom = before;
            body.style.removeProperty('--lib-sticky-atc-space');
          });
          if ('ResizeObserver' in window) {
            const ro = new ResizeObserver(() => this.shown && this.setSpace(this.bar.offsetHeight));
            ro.observe(this.bar);
            this.cleanup.push(() => ro.disconnect());
          }
        }

        setSpace(px) {
          if (!this.bottom || !document.body) return;
          document.body.style.setProperty('--lib-sticky-atc-space', `${px}px`);
        }

        setupForm() {
          const select = this.querySelector('[data-variant-select]');
          const button = this.querySelector('[data-add]');
          const qty = this.querySelector('.lib-sticky-add-to-cart__qty-input');

          if (qty) {
            this.querySelectorAll('[data-step]').forEach((btn) => {
              btn.addEventListener('click', () => {
                const next = (parseInt(qty.value, 10) || 1) + parseInt(btn.dataset.step, 10);
                qty.value = String(Math.max(1, next));
                qty.dispatchEvent(new Event('change', { bubbles: true }));
              });
            });
          }

          if (!select || !button) return;
          const price = this.querySelector('[data-price]');
          const compare = this.querySelector('[data-compare]');
          const saleLabel = this.querySelector('[data-sale-label]');
          const wasLabel = this.querySelector('[data-was-label]');

          const render = () => {
            const option = select.options[select.selectedIndex];
            if (!option) return;
            const available = !option.disabled;
            button.disabled = !available;
            button.textContent = available ? button.dataset.label : button.dataset.soldOut;
            if (price) price.textContent = option.dataset.price || price.textContent;
            const was = compare ? option.dataset.compare || '' : '';
            if (compare) {
              compare.textContent = was;
              compare.hidden = !was;
            }
            if (price) price.classList.toggle('is-sale', Boolean(was));
            if (saleLabel) saleLabel.hidden = !was;
            if (wasLabel) wasLabel.hidden = !was;
          };
          select.addEventListener('change', render);

          // Follow the variant chosen in the page's own buy form.
          const onMainChange = (e) => {
            const field = e.target;
            if (!field || field.name !== 'id' || this.contains(field)) return;
            const match = [...select.options].find((o) => o.value === String(field.value));
            if (match && !match.disabled && select.value !== match.value) {
              select.value = match.value;
              render();
            }
          };
          document.addEventListener('change', onMainChange);
          this.cleanup.push(() => document.removeEventListener('change', onMainChange));
        }
      }
    );
  }
{% endjavascript %}

{% schema %}
{
  "name": "Lib · Sticky add to cart",
  "tag": "section",
  "class": "lib-section",
  "settings": [
    { "type": "header", "content": "Content" },
    {
      "type": "product",
      "id": "product",
      "label": "Product",
      "info": "Optional on product pages, where the page's own product is used. Pick one to use the bar on any other page."
    },
    { "type": "checkbox", "id": "show_image", "label": "Show product image", "default": true },
    { "type": "checkbox", "id": "show_compare_price", "label": "Show compare-at price", "default": true },
    { "type": "checkbox", "id": "show_quantity", "label": "Show quantity selector", "default": true },
    { "type": "text", "id": "button_label", "label": "Button label", "default": "Add to cart" },
    { "type": "text", "id": "sold_out_label", "label": "Sold out label", "info": "Shown on the button and next to sold-out variants.", "default": "Sold out" },

    { "type": "header", "content": "When it appears" },
    {
      "type": "select",
      "id": "position",
      "label": "Position",
      "options": [
        { "value": "bottom", "label": "Bottom of the screen" },
        { "value": "top", "label": "Top of the screen" }
      ],
      "default": "bottom"
    },
    {
      "type": "checkbox",
      "id": "always_visible",
      "label": "Always visible",
      "info": "Shows the bar from the start instead of waiting for the buy button to scroll away.",
      "default": false
    },
    {
      "type": "text",
      "id": "trigger_selector",
      "label": "Main buy form selector",
      "info": "CSS selector of the page's own buy form. The bar appears once it has scrolled above the screen.",
      "default": "form[action*=\"/cart/add\"]",
      "visible_if": "{{ section.settings.always_visible == false }}"
    },
    {
      "type": "range",
      "id": "scroll_offset",
      "label": "Fallback scroll distance",
      "info": "Used when no buy form matches the selector.",
      "min": 0,
      "max": 1500,
      "step": 50,
      "unit": "px",
      "default": 600,
      "visible_if": "{{ section.settings.always_visible == false }}"
    },

    { "type": "header", "content": "Layout" },
    {
      "type": "select",
      "id": "bar_style",
      "label": "Style",
      "options": [
        { "value": "bar", "label": "Edge-to-edge bar" },
        { "value": "floating", "label": "Floating card" }
      ],
      "default": "bar"
    },
    {
      "type": "select",
      "id": "content_width",
      "label": "Content width",
      "options": [
        { "value": "page", "label": "Page width" },
        { "value": "full", "label": "Full width" }
      ],
      "default": "page"
    },
    { "type": "range", "id": "radius", "label": "Corner radius", "min": 0, "max": 28, "step": 2, "unit": "px", "default": 8 },

    { "type": "header", "content": "Colors" },
    { "type": "color", "id": "color_bg", "label": "Background", "default": "#FFFFFF" },
    { "type": "color", "id": "color_text", "label": "Text", "default": "#1C1917" },
    { "type": "color", "id": "color_accent", "label": "Button and sale price", "default": "#1C1917" },
    { "type": "color", "id": "color_accent_text", "label": "Button text", "default": "#FFFFFF" },
    { "type": "color", "id": "color_border", "label": "Border", "default": "#E7E5E4" },
    {
      "type": "select",
      "id": "shadow",
      "label": "Shadow",
      "options": [
        { "value": "none", "label": "None" },
        { "value": "soft", "label": "Soft" },
        { "value": "strong", "label": "Strong" }
      ],
      "default": "soft"
    },

    { "type": "header", "content": "Spacing and visibility" },
    { "type": "range", "id": "padding_y", "label": "Top and bottom padding on desktop", "min": 4, "max": 32, "step": 2, "unit": "px", "default": 14 },
    { "type": "range", "id": "padding_y_mobile", "label": "Top and bottom padding on mobile", "min": 4, "max": 32, "step": 2, "unit": "px", "default": 10 },
    {
      "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": "header", "content": "Motion" },
    { "type": "checkbox", "id": "animate", "label": "Slide in and out", "default": true }
  ],
  "presets": [
    {
      "name": "Lib · Sticky add to cart"
    }
  ]
}
{% endschema %}
```

## 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.
