# Ürün etiketli karusel: ücretsiz Shopify bölümü

> Kaydırılabilir bir sırada yaşam tarzı fotoğrafları: her birinde ürün kartı açan en çok üç işaret, her fotoğrafın altında da düz ürün bağlantıları.

Ürün etiketli karusel, birkaç yaşam tarzı fotoğrafını kaydırılabilir bir sırada gösteren ve her fotoğrafa en çok üç ürün işareti (hotspot) koyan ücretsiz bir Shopify bölümüdür. Bir işaret, ürünün görseli, fiyatı ve bağlantısıyla küçük bir kart açar; her fotoğraftaki ürünler ayrıca altında bağlantı olarak listelenir, bu yüzden JavaScript olmadan da çalışır. Sıra parmakla, dokunmatik yüzeyle, klavyeyle ya da önceki ve sonraki düğmeleriyle kaydırılır; kendiliğinden hiç hareket etmez. Online Store 2.0 temalarında uygulama olmadan çalışır.

- **Kategori:** Görsel ve video
- **Kullanıldığı yerler:** Ana sayfa, Koleksiyon, Açılış sayfaları, Lookbook
- **Okuduğu veri:** Her fotoğrafta işaretlediğiniz ürünler
- **Boyut:** 2 dosyada 38 KB, küçük bir betikle
- **Lisans:** MIT, © 2026 Lintel
- **Sayfa:** https://lintelagency.com/tr/kaynaklar/shopify-bolumleri/urun-etiketli-karusel
- **Canlı demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-shoppable-carousel.html
- **Eklenme tarihi:** 2026-11-02

## Ne işe yarar

- Ana sayfada, her parçası satın alınabilen oda oda bir lookbook
- Koleksiyon ya da açılış sayfasında, içindeki ürünlerle işaretlenmiş sezon kampanyası fotoğrafları
- Doğrudan ürün sayfalarına götüren kombin ya da sofra düzeni fotoğrafları

## Tema düzenleyici ayarları

Bölüm tema düzenleyicide “Lib · Shoppable carousel” 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 |
| --- | --- | --- | --- | --- |
| `heading` | `inline_richtext` | Heading | `Shop the <em>rooms</em>` |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | medium | `small`, `medium`, `large` |
| `text` | `richtext` | Text | `<p>Tap a dot to see what is in each photo.</p>` |  |

### Words

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `card_link_label` | `text` | Product card link | View product |  |
| `show_label` | `text` | Screen reader: before a hotspot's product | Show |  |
| `close_label` | `text` | Screen reader: close card | Close |  |
| `prev_label` | `text` | Screen reader: previous | Previous photo |  |
| `next_label` | `text` | Screen reader: next | Next photo |  |
| `carousel_label` | `text` | Screen reader: name of the row | Shoppable photos |  |
| `sale_label` | `text` | Screen reader: sale price | Sale price |  |
| `regular_label` | `text` | Screen reader: regular price | Regular price |  |

### Photos

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `slide_width` | `select` | Photo width on desktop | medium | `large`, `medium`, `small` |
| `image_ratio` | `select` | Photo shape on desktop | landscape | `landscape`, `wide`, `square`, `portrait` |
| `image_ratio_mobile` | `select` | Photo shape on mobile | portrait | `landscape`, `square`, `portrait` |
| `image_radius` | `range` | Photo corner radius | `14` | 0–40 px, step 2 |

### Hotspots

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `hotspot_style` | `select` | Hotspot style | plus | `dot`, `plus`, `number` |
| `hotspot_size` | `range` | Hotspot size | `32` | 24–48 px, step 2 |
| `color_hotspot` | `color` | Hotspot color | #FFFFFF |  |
| `color_hotspot_icon` | `color` | Hotspot icon color | #1C1917 |  |
| `pulse` | `checkbox` | Pulse the hotspots | `true` |  |

### Layout

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

### Colors

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #F5F2EC |  |
| `color_bg_gradient` | `color_background` | Background gradient | – |  |
| `color_text` | `color` | Text | #1C1917 |  |
| `color_accent` | `color` | Accent (arrows, active product) | #1C1917 |  |
| `color_card` | `color` | Product card background | #FFFFFF |  |
| `color_card_text` | `color` | Product card text | #1C1917 |  |
| `color_sale` | `color` | Sale price | #B4432F |  |

### Spacing and visibility

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

## Bloklar

Toplamda en fazla 8 blok.

### Photo (`slide`)

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `image` | `image_picker` | Desktop image | – |  |
| `image_mobile` | `image_picker` | Mobile image | – |  |
| `caption` | `text` | Caption | – |  |
| `product_1` | `product` | Product | – |  |
| `x_1` | `range` | Horizontal position | `30` | 0–100 %, step 1 |
| `y_1` | `range` | Vertical position | `60` | 0–100 %, step 1 |
| `product_2` | `product` | Product | – |  |
| `x_2` | `range` | Horizontal position | `55` | 0–100 %, step 1 |
| `y_2` | `range` | Vertical position | `45` | 0–100 %, step 1 |
| `product_3` | `product` | Product | – |  |
| `x_3` | `range` | Horizontal position | `78` | 0–100 %, step 1 |
| `y_3` | `range` | Vertical position | `65` | 0–100 %, step 1 |
| `mobile_custom` | `checkbox` | Different hotspot positions on mobile | `false` |  |
| `xm_1` | `range` | Hotspot 1 horizontal | `30` | 0–100 %, step 1 |
| `ym_1` | `range` | Hotspot 1 vertical | `60` | 0–100 %, step 1 |
| `xm_2` | `range` | Hotspot 2 horizontal | `55` | 0–100 %, step 1 |
| `ym_2` | `range` | Hotspot 2 vertical | `45` | 0–100 %, step 1 |
| `xm_3` | `range` | Hotspot 3 horizontal | `78` | 0–100 %, step 1 |
| `ym_3` | `range` | Hotspot 3 vertical | `65` | 0–100 %, step 1 |

## 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-shoppable-carousel.liquid` adında yeni bir dosya ekleyin, aşağıdaki kaynak koddan `sections/lib-shoppable-carousel.liquid` içeriğini yapıştırıp kaydedin.
4. `lib-image.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 · Shoppable carousel** 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-shoppable-carousel.liquid https://lintelagency.com/resources/shopify-sections/files/lib-shoppable-carousel.liquid
curl -fsSL -o snippets/lib-image.liquid https://lintelagency.com/resources/shopify-sections/files/lib-image.liquid
shopify theme push --theme <unpublished-theme-id> --only sections/lib-shoppable-carousel.liquid --only snippets/lib-image.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-shoppable-carousel.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-shoppable-carousel.liquid): `sections/` klasörüne
- [snippets/lib-image.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-image.liquid): `snippets/` klasörüne

## Sık sorulan sorular

### Kombini satın al bölümünden farkı ne?

Kombini satın al, en çok altı işaretli tek büyük bir fotoğraf ve yanında bir ürün listesidir. Bu bölüm ise her birinde en çok üç işaret bulunan bir fotoğraf sırasıdır; göstermek istediğiniz birkaç sahne olduğunda idealdir.

### İşaretleri nasıl yerleştiririm?

Her fotoğraf, üç ürün ve her biri için yüzde cinsinden yatay ve dikey konum içeren bir bloktur. Mobil kırpım farklıysa mobil konumları açıp ayrı olarak ayarlayın.

### Kendiliğinden kayıyor mu?

Hayır. Sıra yalnızca müşteri kaydırdığında ya da düğmeleri kullandığında hareket eder. İşaretlerin nabız animasyonu kapatılabilir ve hareketin azaltılmasını isteyen ziyaretçiler için her zaman kapalıdır.

### Erişilebilir mi?

İşaretler, ürün adını etiket olarak taşıyan gerçek düğmelerdir; kart Esc ile kapanır ve odağı geri verir, sıra klavyeyle kaydırılabilir ve her ürün, fotoğrafın altında düz bir bağlantı olarak da yer alır. Ekran okuyucuların duyduğu her kelime bir ayardır.

## Kaynak kod

### sections/lib-shoppable-carousel.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Shoppable image carousel: a swipeable row of lifestyle photos, each with up to three product
  hotspots (the Shop the look pattern). A hotspot opens a small product card beside the dot; the
  products in each photo are also listed as plain links under it, so they stay reachable without
  JavaScript. Scroll-snap row with previous and next buttons; it never moves on its own.
  Separate desktop and mobile image per slide, optional mobile hotspot positions.
  Depends on: snippets/lib-image.liquid
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign heading_tag = s.heading_tag | default: 'h2'
  assign uid = 'LibShc-' | append: section.id
  assign slide_total = section.blocks.size
-%}

<div
  class="lib-shoppable lib-shoppable--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-width="{{ s.slide_width }}"
  data-hotspot="{{ s.hotspot_style }}"
  {% if s.pulse %}
    data-pulse
  {% 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-sale: {{ s.color_sale }};
    --lib-hotspot: {{ s.color_hotspot }};
    --lib-hotspot-icon: {{ s.color_hotspot_icon }};
    --lib-card: {{ s.color_card }};
    --lib-card-text: {{ s.color_card_text }};
    --lib-dot: {{ s.hotspot_size }}px;
    --lib-radius-img: {{ s.image_radius }}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;
  "
>
  <lib-shoppable-carousel class="lib-shoppable__inner">
    <div class="lib-shoppable__top">
      {%- if s.heading != blank or s.text != blank -%}
        <header class="lib-shoppable__header" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
          {%- if s.heading != blank -%}
            <{{ heading_tag }} class="lib-shoppable__heading lib-shoppable__heading--{{ s.heading_size }}">
              {{- s.heading -}}
            </{{ heading_tag }}>
          {%- endif -%}
          {%- if s.text != blank -%}
            <div class="lib-shoppable__text">{{ s.text }}</div>
          {%- endif -%}
        </header>
      {%- endif -%}
      <div class="lib-shoppable__nav" hidden data-nav>
        <button type="button" class="lib-shoppable__arrow" data-prev aria-controls="{{ uid }}-track" aria-label="{{ s.prev_label | escape }}">
          <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M10 3.5 5.5 8l4.5 4.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </button>
        <button type="button" class="lib-shoppable__arrow" data-next aria-controls="{{ uid }}-track" aria-label="{{ s.next_label | escape }}">
          <svg viewBox="0 0 16 16" width="16" height="16" aria-hidden="true" focusable="false"><path d="M6 3.5 10.5 8 6 12.5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/></svg>
        </button>
      </div>
    </div>

    <ul
      class="lib-shoppable__track"
      id="{{ uid }}-track"
      role="list"
      aria-label="{{ s.carousel_label | escape }}"
      tabindex="0"
    >
      {%- for block in section.blocks -%}
        {%- assign b = block.settings -%}
        <li
          class="lib-shoppable__slide"
          aria-label="{{ forloop.index }} / {{ slide_total }}"
          data-slide
          {{ block.shopify_attributes }}
        >
          <div class="lib-shoppable__media">
            <div class="lib-shoppable__frame" data-ratio="{{ s.image_ratio }}" data-ratio-m="{{ s.image_ratio_mobile }}">
              {%- render 'lib-image',
                image: b.image,
                image_mobile: b.image_mobile,
                class: 'lib-shoppable__img',
                sizes: '(min-width: 990px) 70vw, 90vw',
                eager: false
              -%}
            </div>

            {%- assign n = 0 -%}
            {%- for i in (1..3) -%}
              {%- liquid
                assign key = 'product_' | append: i
                assign p = b[key]
                if p == blank
                  continue
                endif
                assign n = n | plus: 1
                assign kx = 'x_' | append: i
                assign ky = 'y_' | append: i
                assign x = b[kx]
                assign y = b[ky]
                assign xm = x
                assign ym = y
                if b.mobile_custom
                  assign kx = 'xm_' | append: i
                  assign ky = 'ym_' | append: i
                  assign xm = b[kx]
                  assign ym = b[ky]
                endif
                assign card_id = uid | append: '-' | append: block.id | append: '-' | append: i
              -%}
              <button
                type="button"
                class="lib-shoppable__dot"
                style="--x: {{ x }}%; --y: {{ y }}%; --xm: {{ xm }}%; --ym: {{ ym }}%;"
                aria-expanded="false"
                aria-controls="{{ card_id }}"
                aria-label="{{ s.show_label | escape }} {{ p.title | escape }}"
                hidden
                data-dot
              >
                {%- case s.hotspot_style -%}
                  {%- when 'number' -%}
                    <span class="lib-shoppable__dot-num" aria-hidden="true">{{ n }}</span>
                  {%- when 'plus' -%}
                    <svg class="lib-shoppable__dot-plus" viewBox="0 0 16 16" width="14" height="14" aria-hidden="true" focusable="false">
                      <path d="M8 3v10M3 8h10" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/>
                    </svg>
                  {%- else -%}
                    <span class="lib-shoppable__dot-core" aria-hidden="true"></span>
                {%- endcase -%}
              </button>

              <div class="lib-shoppable__card" id="{{ card_id }}" role="group" aria-label="{{ p.title | escape }}" hidden>
                <div class="lib-shoppable__card-media">
                  {%- if p.featured_image != blank -%}
                    {{
                      p.featured_image
                      | image_url: width: 320
                      | image_tag: class: 'lib-shoppable__thumb', loading: 'lazy', alt: p.featured_image.alt, widths: '160, 320'
                    }}
                  {%- else -%}
                    {{ 'product-1' | placeholder_svg_tag: 'lib-shoppable__thumb' }}
                  {%- endif -%}
                </div>
                <div class="lib-shoppable__card-body">
                  <p class="lib-shoppable__card-title">{{ p.title | escape }}</p>
                  <p class="lib-shoppable__price">
                    {%- if p.compare_at_price > p.price -%}
                      <span class="lib-shoppable__sr">{{ s.sale_label | escape }}</span>
                      <span class="lib-shoppable__now is-sale">{{ p.price | money }}</span>
                      <span class="lib-shoppable__sr">{{ s.regular_label | escape }}</span>
                      <s class="lib-shoppable__was">{{ p.compare_at_price | money }}</s>
                    {%- else -%}
                      <span class="lib-shoppable__now">{{ p.price | money }}</span>
                    {%- endif -%}
                  </p>
                  <a class="lib-shoppable__card-link" href="{{ p.url }}">
                    {{- s.card_link_label | escape -}}
                    <span aria-hidden="true"> →</span>
                  </a>
                </div>
                <button type="button" class="lib-shoppable__close" data-close aria-label="{{ s.close_label | escape }}">
                  <svg viewBox="0 0 16 16" width="12" height="12" 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>
              </div>
            {%- endfor -%}
          </div>

          {%- if b.caption != blank or n > 0 -%}
            <div class="lib-shoppable__below">
              {%- if b.caption != blank -%}
                <p class="lib-shoppable__caption">{{ b.caption | escape }}</p>
              {%- endif -%}
              {%- if n > 0 -%}
                <ul class="lib-shoppable__products" role="list">
                  {%- assign m = 0 -%}
                  {%- for i in (1..3) -%}
                    {%- assign key = 'product_' | append: i -%}
                    {%- assign p = b[key] -%}
                    {%- if p == blank -%}{%- continue -%}{%- endif -%}
                    {%- assign m = m | plus: 1 -%}
                    <li>
                      <a href="{{ p.url }}" data-for="{{ uid }}-{{ block.id }}-{{ i }}">
                        {%- if s.hotspot_style == 'number' -%}<span class="lib-shoppable__chip-num" aria-hidden="true">{{ m }}</span>{%- endif -%}
                        <span>{{ p.title | escape }}</span>
                        <span class="lib-shoppable__chip-price">{{ p.price | money }}</span>
                      </a>
                    </li>
                  {%- endfor -%}
                </ul>
              {%- endif -%}
            </div>
          {%- endif -%}
        </li>
      {%- endfor -%}
    </ul>
  </lib-shoppable-carousel>
</div>

{% stylesheet %}
  .lib-shoppable {
    background-color: var(--lib-bg);
    background-image: var(--lib-bg-image, none);
    color: var(--lib-text);
    padding: var(--lib-pt) 0 var(--lib-pb);
    overflow: hidden;
  }
  .lib-shoppable__inner { display: block; max-width: 1200px; margin: 0 auto; padding: 0 1rem; }
  .lib-shoppable--full .lib-shoppable__inner { max-width: none; }
  @media (max-width: 749px) {
    .lib-shoppable { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-shoppable[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-shoppable[data-show='mobile'] { display: none; }
  }

  .lib-shoppable__top { display: flex; align-items: flex-end; justify-content: space-between; gap: 1.5rem; margin-bottom: clamp(1.25rem, 3vw, 2rem); }
  .lib-shoppable__header { flex: 1; min-width: 0; text-align: left; }
  .lib-shoppable__header[data-align='center'] { text-align: center; }
  .lib-shoppable__header[data-align='right'] { text-align: right; }
  @media (max-width: 749px) {
    .lib-shoppable__header[data-align-m='left'] { text-align: left; }
    .lib-shoppable__header[data-align-m='center'] { text-align: center; }
    .lib-shoppable__header[data-align-m='right'] { text-align: right; }
  }
  .lib-shoppable__heading { margin: 0; font-size: clamp(1.75rem, 3.4vw, 2.75rem); line-height: 1.1; letter-spacing: -0.02em; text-wrap: balance; }
  .lib-shoppable__heading--small { font-size: clamp(1.5rem, 2.4vw, 2rem); }
  .lib-shoppable__heading--large { font-size: clamp(2rem, 4.4vw, 3.5rem); }
  .lib-shoppable__text { max-width: 40rem; margin-top: 0.75rem; font-size: 1.0625rem; line-height: 1.6; opacity: 0.78; }
  .lib-shoppable__header[data-align='center'] .lib-shoppable__text { margin-inline: auto; }
  .lib-shoppable__text > :first-child { margin-top: 0; }
  .lib-shoppable__text > :last-child { margin-bottom: 0; }

  .lib-shoppable__nav { display: flex; gap: 0.5rem; flex: none; }
  .lib-shoppable__nav[hidden] { display: none; }
  .lib-shoppable__arrow {
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    padding: 0;
    border: 1px solid color-mix(in srgb, var(--lib-text) 30%, transparent);
    border-radius: 50%;
    background: transparent;
    color: var(--lib-accent);
    cursor: pointer;
  }
  .lib-shoppable__arrow:hover:not(:disabled) { background: color-mix(in srgb, var(--lib-text) 8%, transparent); }
  .lib-shoppable__arrow:disabled { opacity: 0.35; cursor: default; }
  .lib-shoppable__arrow:focus-visible { outline: 2px solid var(--lib-text); outline-offset: 2px; }

  /* The row: native scrolling with snap points, so touch, trackpad and keyboard all work. */
  .lib-shoppable__track {
    --lib-slide: 72%;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: var(--lib-slide);
    gap: clamp(0.75rem, 2vw, 1.5rem);
    margin: 0;
    padding: 0 0 0.5rem;
    list-style: none;
    overflow-x: auto;
    overscroll-behavior-x: contain;
    scroll-snap-type: x mandatory;
    scroll-behavior: smooth;
    scrollbar-width: thin;
  }
  .lib-shoppable[data-width='large'] .lib-shoppable__track { --lib-slide: 86%; }
  .lib-shoppable[data-width='small'] .lib-shoppable__track { --lib-slide: calc((100% - 2 * clamp(0.75rem, 2vw, 1.5rem)) / 2.6); }
  @media (max-width: 749px) {
    .lib-shoppable .lib-shoppable__track,
    .lib-shoppable[data-width] .lib-shoppable__track { --lib-slide: 88%; }
  }
  @media (prefers-reduced-motion: reduce) {
    .lib-shoppable__track { scroll-behavior: auto; }
  }
  .lib-shoppable__track:focus-visible { outline: 2px solid var(--lib-text); outline-offset: 4px; border-radius: 4px; }
  .lib-shoppable__slide { scroll-snap-align: start; min-width: 0; }

  .lib-shoppable__media { position: relative; }
  .lib-shoppable__frame {
    position: relative;
    overflow: hidden;
    border-radius: var(--lib-radius-img);
    background: color-mix(in srgb, var(--lib-text) 6%, transparent);
    aspect-ratio: 4 / 3;
  }
  .lib-shoppable__frame[data-ratio='square'] { aspect-ratio: 1; }
  .lib-shoppable__frame[data-ratio='portrait'] { aspect-ratio: 4 / 5; }
  .lib-shoppable__frame[data-ratio='wide'] { aspect-ratio: 16 / 9; }
  @media (max-width: 749px) {
    .lib-shoppable__frame[data-ratio-m='landscape'] { aspect-ratio: 4 / 3; }
    .lib-shoppable__frame[data-ratio-m='square'] { aspect-ratio: 1; }
    .lib-shoppable__frame[data-ratio-m='portrait'] { aspect-ratio: 4 / 5; }
  }
  .lib-shoppable__img { display: block; width: 100%; height: 100%; object-fit: cover; }
  svg.lib-shoppable__img { fill: color-mix(in srgb, var(--lib-text) 22%, transparent); }

  .lib-shoppable__dot {
    position: absolute;
    z-index: 1;
    left: var(--x);
    top: var(--y);
    translate: -50% -50%;
    display: grid;
    place-items: center;
    width: var(--lib-dot);
    height: var(--lib-dot);
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: var(--lib-hotspot);
    color: var(--lib-hotspot-icon);
    font: inherit;
    font-size: calc(var(--lib-dot) * 0.42);
    font-weight: 700;
    line-height: 1;
    cursor: pointer;
    box-shadow: 0 2px 10px rgb(0 0 0 / 0.28);
    transition: transform 0.2s ease;
  }
  .lib-shoppable__dot[hidden] { display: none; }
  @media (max-width: 749px) {
    .lib-shoppable__dot { left: var(--xm); top: var(--ym); }
  }
  .lib-shoppable__dot::after { content: ''; position: absolute; inset: -8px; border-radius: 50%; }
  .lib-shoppable[data-pulse] .lib-shoppable__dot::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    animation: lib-shoppable-pulse 2.2s infinite;
  }
  @media (prefers-reduced-motion: reduce) {
    .lib-shoppable[data-pulse] .lib-shoppable__dot::before { animation: none; }
    .lib-shoppable__card { animation: none; }
  }
  @keyframes lib-shoppable-pulse {
    0% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--lib-hotspot) 70%, transparent); }
    70%, 100% { box-shadow: 0 0 0 12px transparent; }
  }
  .lib-shoppable__dot:hover,
  .lib-shoppable__dot.is-hint,
  .lib-shoppable__dot[aria-expanded='true'] { transform: scale(1.12); }
  .lib-shoppable__dot[aria-expanded='true'] { z-index: 3; }
  .lib-shoppable__dot:focus-visible { outline: 2px solid var(--lib-hotspot-icon); outline-offset: 2px; box-shadow: 0 0 0 6px var(--lib-hotspot); }
  .lib-shoppable__dot-core { width: 34%; height: 34%; border-radius: 50%; background: currentColor; }
  .lib-shoppable__dot-plus { transition: transform 0.2s ease; }
  .lib-shoppable__dot[aria-expanded='true'] .lib-shoppable__dot-plus { transform: rotate(45deg); }

  .lib-shoppable__card {
    position: absolute;
    z-index: 4;
    left: 0;
    top: 0;
    display: grid;
    grid-template-columns: 4.5rem minmax(0, 1fr);
    gap: 0.8rem;
    align-items: center;
    width: 16rem;
    max-width: calc(100% - 16px);
    padding: 0.65rem 2rem 0.65rem 0.65rem;
    border-radius: 12px;
    background: var(--lib-card);
    color: var(--lib-card-text);
    box-shadow: 0 12px 32px -8px rgb(0 0 0 / 0.35), 0 0 0 1px rgb(0 0 0 / 0.04);
    animation: lib-shoppable-in 0.18s ease-out;
  }
  .lib-shoppable__card[hidden] { display: none; }
  @keyframes lib-shoppable-in { from { opacity: 0; transform: translateY(4px) scale(0.98); } }
  .lib-shoppable__card-media { aspect-ratio: 1; border-radius: 8px; overflow: hidden; background: color-mix(in srgb, var(--lib-card-text) 6%, transparent); }
  .lib-shoppable__thumb { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-shoppable__card-body { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
  .lib-shoppable__card-title { margin: 0; font-weight: 600; line-height: 1.3; }
  .lib-shoppable__price { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.5rem; margin: 0; font-size: 0.9375rem; }
  .lib-shoppable__now.is-sale { color: var(--lib-sale); font-weight: 600; }
  .lib-shoppable__was { opacity: 0.55; font-size: 0.875em; }
  .lib-shoppable__card-link { margin-top: 0.3rem; color: inherit; font-size: 0.875rem; font-weight: 600; text-decoration: underline; text-underline-offset: 0.25em; }
  .lib-shoppable__card-link:focus-visible,
  .lib-shoppable__close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; border-radius: 4px; }
  .lib-shoppable__close {
    position: absolute;
    top: 0.4rem;
    right: 0.4rem;
    display: grid;
    place-items: center;
    width: 1.75rem;
    height: 1.75rem;
    padding: 0;
    border: 0;
    border-radius: 50%;
    background: transparent;
    color: inherit;
    cursor: pointer;
    opacity: 0.6;
  }
  .lib-shoppable__close:hover { opacity: 1; background: color-mix(in srgb, currentColor 8%, transparent); }

  .lib-shoppable__below { display: grid; gap: 0.5rem; margin-top: 0.85rem; }
  .lib-shoppable__caption { margin: 0; font-weight: 600; }
  .lib-shoppable__products { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0; padding: 0; list-style: none; }
  .lib-shoppable__products a {
    display: inline-flex;
    align-items: center;
    gap: 0.45rem;
    min-height: 2rem;
    padding: 0.3rem 0.75rem;
    border: 1px solid color-mix(in srgb, var(--lib-text) 22%, transparent);
    border-radius: 999px;
    color: inherit;
    font-size: 0.875rem;
    text-decoration: none;
  }
  .lib-shoppable__products a:hover,
  .lib-shoppable__products a.is-active { border-color: var(--lib-accent); background: color-mix(in srgb, var(--lib-accent) 8%, transparent); }
  .lib-shoppable__products a:focus-visible { outline: 2px solid var(--lib-text); outline-offset: 2px; }
  .lib-shoppable__chip-num {
    display: grid;
    place-items: center;
    width: 1.25rem;
    height: 1.25rem;
    border-radius: 50%;
    background: var(--lib-accent);
    color: var(--lib-bg);
    font-size: 0.7rem;
    font-weight: 700;
  }
  .lib-shoppable__chip-price { opacity: 0.7; }
  .lib-shoppable__sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }
{% endstylesheet %}

{% javascript %}
  if (!customElements.get('lib-shoppable-carousel')) {
    customElements.define(
      'lib-shoppable-carousel',
      class extends HTMLElement {
        connectedCallback() {
          if (this.ready) return;
          this.ready = true;
          this.track = this.querySelector('.lib-shoppable__track');
          this.slides = [...this.querySelectorAll('[data-slide]')];
          this.dots = [...this.querySelectorAll('[data-dot]')];
          this.prev = this.querySelector('[data-prev]');
          this.next = this.querySelector('[data-next]');
          this.current = null;
          if (!this.track) return;

          // Hotspots need JavaScript; without it the product links under each photo do the job.
          this.dots.forEach((dot) => {
            dot.hidden = false;
            dot.addEventListener('click', () => this.toggle(dot));
          });
          this.querySelectorAll('[data-close]').forEach((btn) => btn.addEventListener('click', () => this.close(true)));
          this.querySelectorAll('[data-for]').forEach((link) => {
            const dot = this.dots.find((d) => d.getAttribute('aria-controls') === link.dataset.for);
            if (!dot) return;
            const on = () => dot.classList.add('is-hint');
            const off = () => dot.classList.remove('is-hint');
            link.addEventListener('pointerenter', on);
            link.addEventListener('pointerleave', off);
            link.addEventListener('focus', on);
            link.addEventListener('blur', off);
          });

          this.prev?.addEventListener('click', () => this.step(-1));
          this.next?.addEventListener('click', () => this.step(1));
          this.onScroll = () => {
            window.cancelAnimationFrame(this.frame);
            this.frame = window.requestAnimationFrame(() => this.updateNav());
          };
          this.track.addEventListener('scroll', this.onScroll, {passive: true});
          this.onDocClick = (e) => {
            if (!this.current) return;
            const {dot, card} = this.current;
            if (!card.contains(e.target) && !dot.contains(e.target)) this.close(false);
          };
          this.onKey = (e) => {
            if (e.key === 'Escape' && this.current) this.close(true);
          };
          this.onResize = () => {
            this.updateNav();
            if (this.current) this.place(this.current.dot, this.current.card);
          };
          document.addEventListener('click', this.onDocClick);
          document.addEventListener('keydown', this.onKey);
          window.addEventListener('resize', this.onResize);
          // In the theme editor, selecting a slide block scrolls it into view.
          this.addEventListener('shopify:block:select', (e) => e.target.scrollIntoView?.({block: 'nearest', inline: 'start'}));
          this.updateNav();
        }

        disconnectedCallback() {
          document.removeEventListener('click', this.onDocClick);
          document.removeEventListener('keydown', this.onKey);
          window.removeEventListener('resize', this.onResize);
          this.ready = false;
        }

        step(direction) {
          const slide = this.slides[0];
          if (!slide) return;
          const gap = parseFloat(getComputedStyle(this.track).columnGap) || 0;
          this.close(false);
          // 'auto' follows the row's CSS scroll-behavior: smooth, or instant under reduced motion.
          this.track.scrollBy({left: direction * (slide.offsetWidth + gap), behavior: 'auto'});
        }

        updateNav() {
          const nav = this.querySelector('[data-nav]');
          const max = this.track.scrollWidth - this.track.clientWidth;
          if (nav) nav.hidden = max <= 2;
          if (this.prev) this.prev.disabled = this.track.scrollLeft <= 2;
          if (this.next) this.next.disabled = this.track.scrollLeft >= max - 2;
          // A card left open on a slide that scrolled away would float over the next one.
          if (this.current) {
            const t = this.track.getBoundingClientRect();
            const d = this.current.dot.getBoundingClientRect();
            if (d.right < t.left || d.left > t.right) this.close(false);
          }
        }

        toggle(dot) {
          const wasOpen = this.current && this.current.dot === dot;
          this.close(false);
          if (!wasOpen) this.open(dot);
        }

        open(dot) {
          const id = dot.getAttribute('aria-controls');
          const card = id ? document.getElementById(id) : null;
          if (!card) return;
          card.hidden = false;
          dot.setAttribute('aria-expanded', 'true');
          this.current = {dot, card};
          this.place(dot, card);
          this.querySelectorAll('[data-for]').forEach((link) => link.classList.toggle('is-active', link.dataset.for === id));
        }

        close(returnFocus) {
          if (!this.current) return;
          const {dot, card} = this.current;
          card.hidden = true;
          dot.setAttribute('aria-expanded', 'false');
          this.current = null;
          this.querySelectorAll('[data-for].is-active').forEach((link) => link.classList.remove('is-active'));
          if (returnFocus) dot.focus();
        }

        // Same placement as Shop the look: beside the dot on the side with room, else below or
        // above it, always inside the photo.
        place(dot, card) {
          const media = dot.closest('.lib-shoppable__media');
          const m = media.getBoundingClientRect();
          const d = dot.getBoundingClientRect();
          const pad = 8;
          const gap = d.width / 2 + 10;
          const cx = d.left + d.width / 2 - m.left;
          const cy = d.top + d.height / 2 - m.top;
          const w = card.offsetWidth;
          const h = card.offsetHeight;
          const fitsRight = cx + gap + w <= m.width - pad;
          const fitsLeft = cx - gap - w >= pad;
          const clamp = (v, max) => Math.max(pad, Math.min(v, Math.max(pad, max)));
          let x;
          let y;
          if (fitsRight || fitsLeft) {
            x = fitsRight && (cx <= m.width / 2 || !fitsLeft) ? cx + gap : cx - gap - w;
            y = clamp(cy - h / 2, m.height - h - pad);
          } else if (cy + gap + h <= m.height - pad) {
            x = cx - w / 2;
            y = cy + gap;
          } else {
            x = cx - w / 2;
            y = clamp(cy - gap - h, m.height - h - pad);
          }
          card.style.left = `${clamp(x, m.width - w - pad)}px`;
          card.style.top = `${Math.round(y)}px`;
        }
      }
    );
  }
{% endjavascript %}

{% schema %}
{
  "name": "Lib · Shoppable carousel",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 8,
  "settings": [
    { "type": "header", "content": "Content" },
    { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Shop the <em>rooms</em>" },
    {
      "type": "select",
      "id": "heading_tag",
      "label": "Heading tag",
      "info": "Use H1 only when this is the page's main heading.",
      "options": [
        { "value": "h1", "label": "H1" },
        { "value": "h2", "label": "H2" },
        { "value": "h3", "label": "H3" },
        { "value": "p", "label": "Paragraph" }
      ],
      "default": "h2"
    },
    {
      "type": "select",
      "id": "heading_size",
      "label": "Heading size",
      "options": [
        { "value": "small", "label": "Small" },
        { "value": "medium", "label": "Medium" },
        { "value": "large", "label": "Large" }
      ],
      "default": "medium"
    },
    { "type": "richtext", "id": "text", "label": "Text", "default": "<p>Tap a dot to see what is in each photo.</p>" },

    { "type": "header", "content": "Words" },
    { "type": "text", "id": "card_link_label", "label": "Product card link", "default": "View product" },
    { "type": "text", "id": "show_label", "label": "Screen reader: before a hotspot's product", "default": "Show" },
    { "type": "text", "id": "close_label", "label": "Screen reader: close card", "default": "Close" },
    { "type": "text", "id": "prev_label", "label": "Screen reader: previous", "default": "Previous photo" },
    { "type": "text", "id": "next_label", "label": "Screen reader: next", "default": "Next photo" },
    { "type": "text", "id": "carousel_label", "label": "Screen reader: name of the row", "default": "Shoppable photos" },
    { "type": "text", "id": "sale_label", "label": "Screen reader: sale price", "default": "Sale price" },
    { "type": "text", "id": "regular_label", "label": "Screen reader: regular price", "default": "Regular price" },

    { "type": "header", "content": "Photos" },
    {
      "type": "select",
      "id": "slide_width",
      "label": "Photo width on desktop",
      "info": "On mobile each photo takes most of the screen, with the next one peeking in.",
      "options": [
        { "value": "large", "label": "Large (one photo)" },
        { "value": "medium", "label": "Medium (next one peeks in)" },
        { "value": "small", "label": "Small (two and a half)" }
      ],
      "default": "medium"
    },
    {
      "type": "select",
      "id": "image_ratio",
      "label": "Photo shape on desktop",
      "info": "Hotspot positions are measured on the photo as shown.",
      "options": [
        { "value": "landscape", "label": "Landscape (4:3)" },
        { "value": "wide", "label": "Wide (16:9)" },
        { "value": "square", "label": "Square" },
        { "value": "portrait", "label": "Portrait (4:5)" }
      ],
      "default": "landscape"
    },
    {
      "type": "select",
      "id": "image_ratio_mobile",
      "label": "Photo shape on mobile",
      "options": [
        { "value": "landscape", "label": "Landscape (4:3)" },
        { "value": "square", "label": "Square" },
        { "value": "portrait", "label": "Portrait (4:5)" }
      ],
      "default": "portrait"
    },
    { "type": "range", "id": "image_radius", "label": "Photo corner radius", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 14 },

    { "type": "header", "content": "Hotspots" },
    {
      "type": "select",
      "id": "hotspot_style",
      "label": "Hotspot style",
      "options": [
        { "value": "dot", "label": "Dot" },
        { "value": "plus", "label": "Plus" },
        { "value": "number", "label": "Number (also shown in the product links)" }
      ],
      "default": "plus"
    },
    { "type": "range", "id": "hotspot_size", "label": "Hotspot size", "min": 24, "max": 48, "step": 2, "unit": "px", "default": 32 },
    { "type": "color", "id": "color_hotspot", "label": "Hotspot color", "default": "#FFFFFF" },
    { "type": "color", "id": "color_hotspot_icon", "label": "Hotspot icon color", "default": "#1C1917" },
    { "type": "checkbox", "id": "pulse", "label": "Pulse the hotspots", "info": "Always off for visitors who prefer reduced motion.", "default": true },

    { "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": "text_alignment", "id": "text_alignment", "label": "Heading alignment on desktop", "default": "left" },
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "Heading alignment on mobile",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" },
        { "value": "right", "label": "Right" }
      ],
      "default": "left"
    },

    { "type": "header", "content": "Colors" },
    { "type": "color", "id": "color_bg", "label": "Background", "default": "#F5F2EC" },
    { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." },
    { "type": "color", "id": "color_text", "label": "Text", "default": "#1C1917" },
    { "type": "color", "id": "color_accent", "label": "Accent (arrows, active product)", "default": "#1C1917" },
    { "type": "color", "id": "color_card", "label": "Product card background", "default": "#FFFFFF" },
    { "type": "color", "id": "color_card_text", "label": "Product card text", "default": "#1C1917" },
    { "type": "color", "id": "color_sale", "label": "Sale price", "default": "#B4432F" },

    { "type": "header", "content": "Spacing and visibility" },
    { "type": "range", "id": "padding_top", "label": "Top padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 80 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 80 },
    { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 48 },
    { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 48 },
    {
      "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." }
  ],
  "blocks": [
    {
      "type": "slide",
      "name": "Photo",
      "settings": [
        { "type": "image_picker", "id": "image", "label": "Desktop image" },
        { "type": "image_picker", "id": "image_mobile", "label": "Mobile image", "info": "Optional. If it is cropped differently, set mobile hotspot positions below." },
        { "type": "text", "id": "caption", "label": "Caption", "info": "Optional. Shown under the photo." },
        { "type": "header", "content": "Hotspot 1" },
        { "type": "product", "id": "product_1", "label": "Product" },
        { "type": "range", "id": "x_1", "label": "Horizontal position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 30 },
        { "type": "range", "id": "y_1", "label": "Vertical position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 60 },
        { "type": "header", "content": "Hotspot 2" },
        { "type": "product", "id": "product_2", "label": "Product" },
        { "type": "range", "id": "x_2", "label": "Horizontal position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 55 },
        { "type": "range", "id": "y_2", "label": "Vertical position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 45 },
        { "type": "header", "content": "Hotspot 3" },
        { "type": "product", "id": "product_3", "label": "Product" },
        { "type": "range", "id": "x_3", "label": "Horizontal position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 78 },
        { "type": "range", "id": "y_3", "label": "Vertical position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 65 },
        { "type": "header", "content": "Mobile" },
        { "type": "checkbox", "id": "mobile_custom", "label": "Different hotspot positions on mobile", "default": false },
        { "type": "range", "id": "xm_1", "label": "Hotspot 1 horizontal", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 30, "visible_if": "{{ block.settings.mobile_custom }}" },
        { "type": "range", "id": "ym_1", "label": "Hotspot 1 vertical", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 60, "visible_if": "{{ block.settings.mobile_custom }}" },
        { "type": "range", "id": "xm_2", "label": "Hotspot 2 horizontal", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 55, "visible_if": "{{ block.settings.mobile_custom }}" },
        { "type": "range", "id": "ym_2", "label": "Hotspot 2 vertical", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 45, "visible_if": "{{ block.settings.mobile_custom }}" },
        { "type": "range", "id": "xm_3", "label": "Hotspot 3 horizontal", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 78, "visible_if": "{{ block.settings.mobile_custom }}" },
        { "type": "range", "id": "ym_3", "label": "Hotspot 3 vertical", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 65, "visible_if": "{{ block.settings.mobile_custom }}" }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Shoppable carousel",
      "blocks": [
        { "type": "slide" },
        { "type": "slide" },
        { "type": "slide" }
      ]
    }
  ]
}
{% endschema %}
```

### snippets/lib-image.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{% doc %}
  Responsive image with optional art direction: a separate mobile image (below 750px),
  each honouring its own focal point from the theme editor. Falls back to a placeholder.

  @param {image} [image] - Desktop image (also used on mobile when no mobile image is set)
  @param {image} [image_mobile] - Mobile image, below 750px
  @param {string} [class] - Class for the img element
  @param {string} [sizes] - sizes attribute for the desktop image (default 100vw)
  @param {boolean} [eager] - Load immediately with high fetch priority (use for the first sections on a page)
  @param {string} [placeholder] - placeholder_svg_tag name when no image is set (default 'lifestyle-1')

  @example
  {% render 'lib-image', image: section.settings.image, image_mobile: section.settings.image_mobile, class: 'lib-hero__img', sizes: '(min-width: 990px) 50vw, 100vw', eager: true %}
{% enddoc %}

{%- liquid
  assign desktop = image
  if desktop == blank
    assign desktop = image_mobile
  endif
  assign img_sizes = sizes | default: '100vw'
  assign img_loading = 'lazy'
  assign img_priority = 'auto'
  if eager
    assign img_loading = 'eager'
    assign img_priority = 'high'
  endif
  assign art = false
  if image != blank and image_mobile != blank
    assign art = true
  endif
-%}

{%- if desktop != blank -%}
  <picture
    class="lib-picture{% if art %} lib-picture--art{% endif %}"
    {% if art %}
      style="--lib-pos-m: {{ image_mobile.presentation.focal_point | default: '50% 50%' }};"
    {% endif %}
  >
    {%- if art -%}
      <source
        media="(max-width: 749px)"
        srcset="
          {{ image_mobile | image_url: width: 375 }} 375w,
          {{ image_mobile | image_url: width: 750 }} 750w,
          {{ image_mobile | image_url: width: 1100 }} 1100w,
          {{ image_mobile | image_url: width: 1500 }} 1500w
        "
        sizes="100vw"
        width="{{ image_mobile.width }}"
        height="{{ image_mobile.height }}"
      >
    {%- endif -%}
    {{
      desktop
      | image_url: width: 2400
      | image_tag:
        class: class,
        loading: img_loading,
        fetchpriority: img_priority,
        sizes: img_sizes,
        widths: '600, 900, 1200, 1600, 2000, 2400'
    }}
  </picture>
{%- else -%}
  {{ placeholder | default: 'lifestyle-1' | placeholder_svg_tag: class }}
{%- endif -%}

{% stylesheet %}
  .lib-picture { display: contents; }
  @media (max-width: 749px) {
    /* image_tag sets the desktop focal point inline; the mobile image needs its own. */
    .lib-picture--art img { object-position: var(--lib-pos-m) !important; }
  }
{% 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.
