# Kombini satın al: ücretsiz Shopify bölümü

> En fazla altı ürün noktası olan bir yaşam tarzı görseli. Noktaya dokununca küçük bir ürün kartı açılır; görselin yanındaki ürün listesi aynı ürünleri gösterir.

Kombini satın al, bir yaşam tarzı görselinin üzerine tıklanabilir ürün noktaları koyan ve aynı ürünleri yanında listeleyen ücretsiz bir Shopify bölümüdür. Her nokta görsel, fiyat ve bağlantı içeren küçük bir kart açar; liste JavaScript olmadan da çalışır ve Online Store 2.0 temalarında uygulama olmadan çalışır.

- **Kategori:** Ürün ve koleksiyon keşfi
- **Kullanıldığı yerler:** Ana sayfa, Koleksiyon, Açılış sayfaları, Blog yazıları
- **Okuduğu veri:** Ürünler
- **Boyut:** 3 dosyada 43 KB, küçük bir betikle
- **Lisans:** MIT, © 2026 Lintel
- **Sayfa:** https://lintelagency.com/tr/kaynaklar/shopify-bolumleri/kombini-satin-al
- **Canlı demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-shop-the-look.html
- **Eklenme tarihi:** 2026-10-02

## Ne işe yarar

- Fotoğraftaki her ürüne doğrudan bağlanan ana sayfa oda ya da kıyafet sahnesi
- Serideki parçaların birlikte nasıl durduğunu gösteren koleksiyon sayfası bannerı
- Her sahne için bir bölüm olan lookbook ya da kampanya açılış sayfası

## Tema düzenleyici ayarları

Bölüm tema düzenleyicide “Lib · Shop the look” 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>living room</em>` |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | medium | `small`, `medium`, `large`, `xlarge` |
| `text` | `richtext` | Text | `<p>Soft textures, warm light and a few handmade pieces. Tap a dot to see what is in the room.</p>` |  |
| `list_title` | `text` | Product list title | In this room |  |
| `card_link_label` | `text` | Product card link label | View product |  |

### Image

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `image` | `image_picker` | Desktop image | – |  |
| `image_mobile` | `image_picker` | Mobile image | – |  |
| `image_ratio` | `select` | Image shape on desktop | adapt | `adapt`, `wide`, `landscape`, `square`, `portrait` |
| `image_ratio_mobile` | `select` | Image shape on mobile | adapt | `adapt`, `wide`, `landscape`, `square`, `portrait` |
| `image_radius` | `range` | Image corner radius | `16` | 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 |  |

### Product card and list

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `color_card` | `color` | Card background | #FFFFFF |  |
| `color_card_text` | `color` | Card text | #1C1917 |  |

### Button

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `button_label` | `text` | Button label | Shop the collection |  |
| `button_url` | `url` | Button link | – |  |
| `button_style` | `select` | Button style | outline | `primary`, `outline`, `text` |
| `button_new_tab` | `checkbox` | Open link in a new tab | `false` |  |

### Layout

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `section_width` | `select` | Width | page | `page`, `full` |
| `list_position` | `select` | Product list on desktop | side | `side`, `below` |
| `image_position` | `select` | Image position | left | `left`, `right` |
| `image_width` | `select` | Image width | default | `half`, `default`, `wide` |
| `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 (active product, solid button) | #1C1917 |  |
| `color_accent_text` | `color` | Text on accent | #F5F2EC |  |
| `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 | – |  |

### Motion

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `pulse` | `checkbox` | Pulse the hotspots | `true` |  |

## Bloklar

Toplamda en fazla 6 blok.

### Product hotspot (`hotspot`, en fazla 6)

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `product` | `product` | Product | – |  |
| `label` | `text` | Label | – |  |
| `x` | `range` | Horizontal position | `50` | 0–100 %, step 1 |
| `y` | `range` | Vertical position | `50` | 0–100 %, step 1 |
| `mobile_custom` | `checkbox` | Different position on mobile | `false` |  |
| `x_mobile` | `range` | Horizontal position on mobile | `50` | 0–100 %, step 1 |
| `y_mobile` | `range` | Vertical position on mobile | `50` | 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-shop-the-look.liquid` adında yeni bir dosya ekleyin, aşağıdaki kaynak koddan `sections/lib-shop-the-look.liquid` içeriğini yapıştırıp kaydedin.
4. `lib-image.liquid` ve `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 · Shop the look** 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-shop-the-look.liquid https://lintelagency.com/resources/shopify-sections/files/lib-shop-the-look.liquid
curl -fsSL -o snippets/lib-image.liquid https://lintelagency.com/resources/shopify-sections/files/lib-image.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-shop-the-look.liquid --only snippets/lib-image.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-shop-the-look.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-shop-the-look.liquid): `sections/` klasörüne
- [snippets/lib-image.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-image.liquid): `snippets/` 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

### Bir görselde kaç ürün olabilir?

En fazla altı. Her nokta; bir ürün, isteğe bağlı bir etiket ve görselin yüzdesi cinsinden yatay ve dikey konumu olan bir bloktur.

### Noktalar mobilde farklı yerlerde durabilir mi?

Evet. Ayrı bir mobil görsel yükleyebilirsiniz (750px altında kullanılır) ve her noktanın isteğe bağlı bir mobil konumu vardır; böylece mobil kırpma farklı olsa da noktalar nesnelerinin üzerinde kalır.

### Biri bir noktaya dokunduğunda ne olur?

Noktanın yanında, yer olan tarafta küçük bir ürün kartı açılır ve ürün listede vurgulanır. Aynı anda yalnızca bir kart açık olur; Escape, kapat düğmesi ya da dışarıya tıklamak onu kapatır. Noktalar aria-expanded özellikli ve "Show Speckled bud vase" gibi erişilebilir bir adı olan düğmelerdir.

### JavaScript olmadan çalışır mı?

Ürün listesi her ürün için bağlantı, görsel ve fiyat içeren düz HTML’dir; script çalışmasa bile müşteriler ve arama motorları her ürüne ulaşabilir. Yalnızca açılır kartlar JavaScript gerektirir; bu da kütüphane içermeyen küçük bir custom element’tir.

### Bir noktada ürün seçilmemişse ne olur?

Tema düzenleyicide konumlandırabilmeniz için örnek başlık ve fiyatla yer tutucu bir kart gösterir. Canlı mağazada ürünü olmayan bir nokta gösterilmez.

## Kaynak kod

### sections/lib-shop-the-look.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Shop the look: a lifestyle image with up to six product hotspots and a product list beside it
  (below on mobile). Clicking a hotspot opens a small product card next to the dot and highlights
  the product in the list. The list is plain HTML, so every product stays reachable without JS.
  Separate desktop and mobile image, and optional separate hotspot positions on mobile.
  Depends on: snippets/lib-image.liquid, snippets/lib-button.liquid
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign heading_tag = s.heading_tag | default: 'h2'
  assign eager = false
  if section.index <= 2
    assign eager = true
  endif
  assign uid = 'LibStl-' | append: section.id
-%}

<div
  class="lib-shop-the-look lib-shop-the-look--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-list="{{ s.list_position }}"
  data-image="{{ s.image_position }}"
  data-image-width="{{ s.image_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-accent-text: {{ s.color_accent_text }};
    --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-shop-the-look-hotspots class="lib-shop-the-look__inner">
    {%- if s.heading != blank or s.text != blank -%}
      <header class="lib-shop-the-look__header" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
        {%- if s.heading != blank -%}
          <{{ heading_tag }} class="lib-shop-the-look__heading lib-shop-the-look__heading--{{ s.heading_size }}">
            {{- s.heading -}}
          </{{ heading_tag }}>
        {%- endif -%}
        {%- if s.text != blank -%}
          <div class="lib-shop-the-look__text">{{ s.text }}</div>
        {%- endif -%}
      </header>
    {%- endif -%}

    <div class="lib-shop-the-look__media">
      <div class="lib-shop-the-look__frame" data-ratio="{{ s.image_ratio }}" data-ratio-m="{{ s.image_ratio_mobile }}">
        {%- render 'lib-image',
          image: s.image,
          image_mobile: s.image_mobile,
          class: 'lib-shop-the-look__img',
          sizes: '(min-width: 990px) 66vw, 100vw',
          eager: eager
        -%}
      </div>

      {%- assign n = 0 -%}
      {%- for block in section.blocks -%}
        {%- liquid
          assign b = block.settings
          assign p = b.product
          if p == blank and request.design_mode == false
            continue
          endif
          assign n = n | plus: 1
          assign card_id = uid | append: '-card-' | append: block.id
          assign title = p.title | default: 'Example product'
          assign xm = b.x
          assign ym = b.y
          if b.mobile_custom
            assign xm = b.x_mobile
            assign ym = b.y_mobile
          endif
        -%}
        {%- capture price_html -%}
          <span class="lib-shop-the-look__price">
            {%- if p != blank -%}
              {%- if p.compare_at_price > p.price -%}
                <span class="lib-shop-the-look__sr">Sale price</span>
                <span class="lib-shop-the-look__price-now lib-shop-the-look__price-now--sale">{{ p.price | money }}</span>
                <span class="lib-shop-the-look__sr">Regular price</span>
                <s class="lib-shop-the-look__price-was">{{ p.compare_at_price | money }}</s>
              {%- else -%}
                <span class="lib-shop-the-look__price-now">{{ p.price | money }}</span>
              {%- endif -%}
            {%- else -%}
              <span class="lib-shop-the-look__price-now">{{ 4800 | money }}</span>
            {%- endif -%}
          </span>
        {%- endcapture -%}
        <button
          type="button"
          class="lib-shop-the-look__dot"
          style="--x: {{ b.x }}%; --y: {{ b.y }}%; --xm: {{ xm }}%; --ym: {{ ym }}%;"
          aria-expanded="false"
          aria-controls="{{ card_id }}"
          aria-label="Show {{ title | escape }}"
          {{ block.shopify_attributes }}
        >
          {%- case s.hotspot_style -%}
            {%- when 'number' -%}
              <span class="lib-shop-the-look__dot-num" aria-hidden="true">{{ n }}</span>
            {%- when 'plus' -%}
              <svg class="lib-shop-the-look__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-shop-the-look__dot-core" aria-hidden="true"></span>
          {%- endcase -%}
        </button>

        <div class="lib-shop-the-look__card" id="{{ card_id }}" role="group" aria-label="{{ title | escape }}" hidden>
          <div class="lib-shop-the-look__card-media">
            {%- if p.featured_image != blank -%}
              {{
                p.featured_image
                | image_url: width: 320
                | image_tag: class: 'lib-shop-the-look__thumb', loading: 'lazy', alt: p.featured_image.alt, widths: '160, 320'
              }}
            {%- else -%}
              {{ 'product-1' | placeholder_svg_tag: 'lib-shop-the-look__thumb lib-shop-the-look__thumb--ph' }}
            {%- endif -%}
          </div>
          <div class="lib-shop-the-look__card-body">
            {%- if b.label != blank -%}
              <p class="lib-shop-the-look__label">{{ b.label | escape }}</p>
            {%- endif -%}
            <p class="lib-shop-the-look__card-title">{{ title | escape }}</p>
            {{ price_html }}
            {%- if p != blank -%}
              <a class="lib-shop-the-look__card-link" href="{{ p.url }}">
                {{- s.card_link_label | default: 'View product' | escape -}}
                <span aria-hidden="true"> →</span>
              </a>
            {%- endif -%}
          </div>
          <button type="button" class="lib-shop-the-look__close" data-close aria-label="Close">
            <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>

    <div class="lib-shop-the-look__aside">
      {%- if s.list_title != blank -%}
        <p class="lib-shop-the-look__list-title">{{ s.list_title | escape }}</p>
      {%- endif -%}
      <ul class="lib-shop-the-look__list" role="list">
        {%- assign n = 0 -%}
        {%- for block in section.blocks -%}
          {%- liquid
            assign b = block.settings
            assign p = b.product
            if p == blank and request.design_mode == false
              continue
            endif
            assign n = n | plus: 1
            assign card_id = uid | append: '-card-' | append: block.id
            assign title = p.title | default: 'Example product'
            assign tag = 'div'
            if p != blank
              assign tag = 'a'
            endif
          -%}
          {%- capture price_html -%}
          <span class="lib-shop-the-look__price">
            {%- if p != blank -%}
              {%- if p.compare_at_price > p.price -%}
                <span class="lib-shop-the-look__sr">Sale price</span>
                <span class="lib-shop-the-look__price-now lib-shop-the-look__price-now--sale">{{ p.price | money }}</span>
                <span class="lib-shop-the-look__sr">Regular price</span>
                <s class="lib-shop-the-look__price-was">{{ p.compare_at_price | money }}</s>
              {%- else -%}
                <span class="lib-shop-the-look__price-now">{{ p.price | money }}</span>
              {%- endif -%}
            {%- else -%}
              <span class="lib-shop-the-look__price-now">{{ 4800 | money }}</span>
            {%- endif -%}
          </span>
        {%- endcapture -%}
          <li class="lib-shop-the-look__item" data-for="{{ card_id }}">
            <{{ tag }} class="lib-shop-the-look__item-link" {% if tag == 'a' %}href="{{ p.url }}"{% endif %}>
              {%- if s.hotspot_style == 'number' -%}
                <span class="lib-shop-the-look__item-num" aria-hidden="true">{{ n }}</span>
              {%- endif -%}
              <span class="lib-shop-the-look__item-media">
                  {%- if p.featured_image != blank -%}
                    {{
                      p.featured_image
                      | image_url: width: 192
                      | image_tag: class: 'lib-shop-the-look__thumb', loading: 'lazy', alt: p.featured_image.alt, widths: '96, 192'
                    }}
                  {%- else -%}
                    {{ 'product-1' | placeholder_svg_tag: 'lib-shop-the-look__thumb lib-shop-the-look__thumb--ph' }}
                  {%- endif -%}
                </span>
              <span class="lib-shop-the-look__item-body">
                {%- if b.label != blank -%}
                  <span class="lib-shop-the-look__label">{{ b.label | escape }}</span>
                {%- endif -%}
                <span class="lib-shop-the-look__item-title">{{ title | escape }}</span>
                {{ price_html }}
              </span>
              <svg class="lib-shop-the-look__item-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>
            </{{ tag }}>
          </li>
        {%- endfor -%}
      </ul>

      {%- if s.button_label != blank -%}
        <div class="lib-shop-the-look__actions">
          {%- render 'lib-button',
            label: s.button_label,
            url: s.button_url,
            style: s.button_style,
            new_tab: s.button_new_tab
          -%}
        </div>
      {%- endif -%}
    </div>
  </lib-shop-the-look-hotspots>
</div>

{% stylesheet %}
  .lib-shop-the-look {
    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-shop-the-look__inner {
    display: grid;
    gap: 1.75rem;
    max-width: 1200px;
    margin: 0 auto;
    grid-template-areas: 'head' 'media' 'aside';
    grid-template-columns: minmax(0, 1fr);
  }
  .lib-shop-the-look--full .lib-shop-the-look__inner { max-width: none; }
  @media (max-width: 749px) {
    .lib-shop-the-look { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-shop-the-look[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-shop-the-look[data-show='mobile'] { display: none; }
  }
  @media (min-width: 990px) {
    .lib-shop-the-look[data-list='side'] .lib-shop-the-look__inner {
      --lib-media-col: 3fr;
      column-gap: clamp(2rem, 4vw, 4rem);
      row-gap: 2.5rem;
      grid-template-columns: minmax(0, var(--lib-media-col)) minmax(0, 2fr);
      grid-template-areas: 'head head' 'media aside';
      align-items: center;
    }
    .lib-shop-the-look[data-list='side'][data-image-width='half'] .lib-shop-the-look__inner { --lib-media-col: 2fr; }
    .lib-shop-the-look[data-list='side'][data-image-width='wide'] .lib-shop-the-look__inner { --lib-media-col: 4fr; }
    .lib-shop-the-look[data-list='side'][data-image='right'] .lib-shop-the-look__inner {
      grid-template-columns: minmax(0, 2fr) minmax(0, var(--lib-media-col));
      grid-template-areas: 'head head' 'aside media';
    }
  }
  .lib-shop-the-look__header { grid-area: head; }
  .lib-shop-the-look__media { grid-area: media; }
  .lib-shop-the-look__aside { grid-area: aside; min-width: 0; }

  /* Header alignment: desktop, then mobile override. */
  .lib-shop-the-look__header { text-align: left; }
  .lib-shop-the-look__header[data-align='center'] { text-align: center; }
  .lib-shop-the-look__header[data-align='right'] { text-align: right; }
  @media (max-width: 749px) {
    .lib-shop-the-look__header[data-align-m='left'] { text-align: left; }
    .lib-shop-the-look__header[data-align-m='center'] { text-align: center; }
    .lib-shop-the-look__header[data-align-m='right'] { text-align: right; }
  }
  .lib-shop-the-look__heading {
    margin: 0;
    font-size: clamp(1.75rem, 3.4vw, 2.75rem);
    line-height: 1.1;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .lib-shop-the-look__heading--small { font-size: clamp(1.5rem, 2.4vw, 2rem); }
  .lib-shop-the-look__heading--large { font-size: clamp(2rem, 4.4vw, 3.5rem); }
  .lib-shop-the-look__heading--xlarge { font-size: clamp(2.5rem, 6vw, 4.5rem); }
  .lib-shop-the-look__text { max-width: 40rem; margin-top: 0.85rem; font-size: 1.0625rem; line-height: 1.6; opacity: 0.75; }
  .lib-shop-the-look__header[data-align='center'] .lib-shop-the-look__text { margin-inline: auto; }
  .lib-shop-the-look__header[data-align='right'] .lib-shop-the-look__text { margin-inline-start: auto; }
  .lib-shop-the-look__text > :first-child { margin-top: 0; }
  .lib-shop-the-look__text > :last-child { margin-bottom: 0; }

  /* Image with hotspots. Dots and cards sit outside the clipped frame so cards are never cut off. */
  .lib-shop-the-look__media { position: relative; }
  .lib-shop-the-look__frame {
    position: relative;
    overflow: hidden;
    border-radius: var(--lib-radius-img);
    background: color-mix(in srgb, var(--lib-text) 6%, transparent);
  }
  .lib-shop-the-look__frame[data-ratio='square'] { aspect-ratio: 1; }
  .lib-shop-the-look__frame[data-ratio='portrait'] { aspect-ratio: 4 / 5; }
  .lib-shop-the-look__frame[data-ratio='landscape'] { aspect-ratio: 3 / 2; }
  .lib-shop-the-look__frame[data-ratio='wide'] { aspect-ratio: 16 / 9; }
  .lib-shop-the-look__img { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-shop-the-look__frame[data-ratio='adapt'] .lib-shop-the-look__img { height: auto; }
  @media (max-width: 749px) {
    .lib-shop-the-look__frame[data-ratio-m='adapt'] { aspect-ratio: auto; }
    .lib-shop-the-look__frame[data-ratio-m='square'] { aspect-ratio: 1; }
    .lib-shop-the-look__frame[data-ratio-m='portrait'] { aspect-ratio: 4 / 5; }
    .lib-shop-the-look__frame[data-ratio-m='landscape'] { aspect-ratio: 3 / 2; }
    .lib-shop-the-look__frame[data-ratio-m='wide'] { aspect-ratio: 16 / 9; }
    .lib-shop-the-look__frame[data-ratio-m='adapt'] .lib-shop-the-look__img { height: auto; }
    .lib-shop-the-look__frame:not([data-ratio-m='adapt']) .lib-shop-the-look__img { height: 100%; }
  }
  .lib-shop-the-look__frame svg.lib-shop-the-look__img {
    aspect-ratio: 3 / 2;
    fill: color-mix(in srgb, var(--lib-text) 22%, transparent);
  }

  .lib-shop-the-look__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;
  }
  @media (max-width: 749px) {
    .lib-shop-the-look__dot { left: var(--xm); top: var(--ym); }
  }
  /* Larger invisible hit area for touch. */
  .lib-shop-the-look__dot::after { content: ''; position: absolute; inset: -8px; border-radius: 50%; }
  .lib-shop-the-look[data-pulse] .lib-shop-the-look__dot::before {
    content: '';
    position: absolute;
    inset: 0;
    border-radius: 50%;
    animation: lib-shop-the-look-pulse 2.2s infinite;
  }
  @keyframes lib-shop-the-look-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-shop-the-look__dot:hover,
  .lib-shop-the-look__dot.is-hint,
  .lib-shop-the-look__dot[aria-expanded='true'] { transform: scale(1.12); }
  .lib-shop-the-look__dot[aria-expanded='true'] { z-index: 3; }
  .lib-shop-the-look__dot:focus-visible {
    outline: 2px solid var(--lib-hotspot-icon);
    outline-offset: 2px;
    box-shadow: 0 0 0 6px var(--lib-hotspot);
  }
  .lib-shop-the-look__dot-core { width: 34%; height: 34%; border-radius: 50%; background: currentColor; }
  .lib-shop-the-look__dot-plus { transition: transform 0.2s ease; }
  .lib-shop-the-look__dot[aria-expanded='true'] .lib-shop-the-look__dot-plus { transform: rotate(45deg); }

  .lib-shop-the-look__card {
    position: absolute;
    z-index: 4;
    left: 0;
    top: 0;
    display: grid;
    grid-template-columns: 5rem minmax(0, 1fr);
    gap: 0.85rem;
    align-items: center;
    width: 17rem;
    max-width: calc(100% - 16px);
    padding: 0.75rem 2rem 0.75rem 0.75rem;
    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-shop-the-look-in 0.18s ease-out;
  }
  .lib-shop-the-look__card[hidden] { display: none; }
  @media (max-width: 749px) {
    .lib-shop-the-look__card { grid-template-columns: 4rem minmax(0, 1fr); gap: 0.7rem; width: 16rem; padding: 0.6rem 2rem 0.6rem 0.6rem; }
  }
  @keyframes lib-shop-the-look-in {
    from { opacity: 0; transform: translateY(4px) scale(0.98); }
  }
  .lib-shop-the-look__card-media {
    aspect-ratio: 1;
    border-radius: 8px;
    overflow: hidden;
    background: color-mix(in srgb, var(--lib-card-text) 6%, transparent);
  }
  .lib-shop-the-look__thumb { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-shop-the-look__thumb--ph { fill: color-mix(in srgb, currentColor 25%, transparent); }
  .lib-shop-the-look__card-body { display: flex; flex-direction: column; gap: 0.2rem; min-width: 0; }
  .lib-shop-the-look__card-title { margin: 0; font-weight: 600; line-height: 1.3; }
  .lib-shop-the-look__card-link {
    margin-top: 0.35rem;
    color: inherit;
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: underline;
    text-underline-offset: 0.25em;
  }
  .lib-shop-the-look__card-link:focus-visible,
  .lib-shop-the-look__close:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; border-radius: 4px; }
  .lib-shop-the-look__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-shop-the-look__close:hover { opacity: 1; background: color-mix(in srgb, currentColor 8%, transparent); }

  .lib-shop-the-look__label {
    margin: 0;
    font-size: 0.75rem;
    font-weight: 600;
    letter-spacing: 0.06em;
    text-transform: uppercase;
    opacity: 0.6;
  }
  .lib-shop-the-look__price {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: 0.25rem 0.5rem;
    font-size: 0.9375rem;
  }
  .lib-shop-the-look__price-now--sale { color: var(--lib-sale); font-weight: 600; }
  .lib-shop-the-look__price-was { opacity: 0.55; font-size: 0.875em; }
  .lib-shop-the-look__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* Product list: plain HTML, so it works without JavaScript. */
  .lib-shop-the-look__list-title {
    margin: 0 0 0.75rem;
    font-size: 0.8125rem;
    font-weight: 600;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    opacity: 0.6;
  }
  .lib-shop-the-look__list { display: grid; gap: 0.5rem; margin: 0; padding: 0; list-style: none; }
  @media (min-width: 750px) {
    .lib-shop-the-look[data-list='below'] .lib-shop-the-look__list {
      grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    }
  }
  @media (min-width: 750px) and (max-width: 989px) {
    .lib-shop-the-look[data-list='side'] .lib-shop-the-look__list {
      grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    }
  }
  .lib-shop-the-look__item-link {
    display: flex;
    align-items: center;
    gap: 0.9rem;
    padding: 0.6rem 0.9rem 0.6rem 0.6rem;
    border-radius: 12px;
    color: inherit;
    text-decoration: none;
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lib-text) 12%, transparent);
    transition: background-color 0.2s ease, box-shadow 0.2s ease;
  }
  .lib-shop-the-look__item-link:hover,
  .lib-shop-the-look__item.is-active .lib-shop-the-look__item-link {
    background: color-mix(in srgb, var(--lib-accent) 7%, transparent);
    box-shadow: inset 0 0 0 1.5px var(--lib-accent);
  }
  .lib-shop-the-look__item-link:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 2px; }
  .lib-shop-the-look__item-num {
    flex: none;
    display: grid;
    place-items: center;
    width: 1.6rem;
    height: 1.6rem;
    border-radius: 50%;
    background: var(--lib-hotspot);
    color: var(--lib-hotspot-icon);
    font-size: 0.75rem;
    font-weight: 700;
    box-shadow: 0 0 0 1px color-mix(in srgb, var(--lib-text) 15%, transparent);
  }
  .lib-shop-the-look__item-media {
    flex: none;
    width: 4rem;
    aspect-ratio: 1;
    border-radius: 8px;
    overflow: hidden;
    background: color-mix(in srgb, var(--lib-text) 6%, transparent);
  }
  .lib-shop-the-look__item-body { display: flex; flex: 1; flex-direction: column; gap: 0.15rem; min-width: 0; }
  .lib-shop-the-look__item-title { font-weight: 600; line-height: 1.3; }
  .lib-shop-the-look__item-arrow { flex: none; opacity: 0.5; transition: transform 0.2s ease, opacity 0.2s ease; }
  .lib-shop-the-look__item-link:hover .lib-shop-the-look__item-arrow { opacity: 1; transform: translateX(3px); }
  .lib-shop-the-look__actions { margin-top: 1.5rem; }

  @media (prefers-reduced-motion: reduce) {
    .lib-shop-the-look[data-pulse] .lib-shop-the-look__dot::before,
    .lib-shop-the-look__card { animation: none; }
    .lib-shop-the-look__dot,
    .lib-shop-the-look__dot-plus,
    .lib-shop-the-look__item-link,
    .lib-shop-the-look__item-arrow { transition: none; }
    .lib-shop-the-look__item-link:hover .lib-shop-the-look__item-arrow { transform: none; }
  }
{% endstylesheet %}

{% javascript %}
  if (!customElements.get('lib-shop-the-look-hotspots')) {
    customElements.define(
      'lib-shop-the-look-hotspots',
      class extends HTMLElement {
        connectedCallback() {
          this.media = this.querySelector('.lib-shop-the-look__media');
          this.dots = [...this.querySelectorAll('.lib-shop-the-look__dot')];
          this.items = [...this.querySelectorAll('.lib-shop-the-look__item')];
          if (!this.media || !this.dots.length || this.onDocClick) return;
          this.current = null;

          this.dots.forEach((dot) => dot.addEventListener('click', () => this.toggle(dot)));
          this.querySelectorAll('.lib-shop-the-look__close').forEach((btn) =>
            btn.addEventListener('click', () => this.close(true))
          );
          // Hovering or focusing a list item hints at its dot.
          this.items.forEach((item) => {
            const dot = this.dots.find((d) => d.getAttribute('aria-controls') === item.dataset.for);
            if (!dot) return;
            const on = () => dot.classList.add('is-hint');
            const off = () => dot.classList.remove('is-hint');
            item.addEventListener('pointerenter', on);
            item.addEventListener('pointerleave', off);
            item.addEventListener('focusin', on);
            item.addEventListener('focusout', off);
          });

          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 = () => {
            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);
        }

        disconnectedCallback() {
          if (!this.onDocClick) return;
          document.removeEventListener('click', this.onDocClick);
          document.removeEventListener('keydown', this.onKey);
          window.removeEventListener('resize', this.onResize);
          this.onDocClick = null;
        }

        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.items.forEach((item) => item.classList.toggle('is-active', item.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.items.forEach((item) => item.classList.remove('is-active'));
          if (returnFocus) dot.focus();
        }

        // Puts the card beside the dot on the side with room (right first when the dot is in the
        // left half). When neither side fits, it drops below or above the dot. Always kept inside the image.
        place(dot, card) {
          const m = this.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 if (cy - gap - h >= pad) {
            x = cx - w / 2;
            y = cy - gap - h;
          } else {
            // Small image (usually mobile): hang the card below the dot, past the image edge,
            // rather than covering the dot.
            x = cx - w / 2;
            y = cy + gap;
          }
          card.style.left = `${clamp(x, m.width - w - pad)}px`;
          card.style.top = `${Math.round(y)}px`;
        }
      }
    );
  }
{% endjavascript %}

{% schema %}
{
  "name": "Lib · Shop the look",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 6,
  "settings": [
    { "type": "header", "content": "Content" },
    { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Shop the <em>living room</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" },
        { "value": "xlarge", "label": "Extra large" }
      ],
      "default": "medium"
    },
    { "type": "richtext", "id": "text", "label": "Text", "default": "<p>Soft textures, warm light and a few handmade pieces. Tap a dot to see what is in the room.</p>" },
    { "type": "text", "id": "list_title", "label": "Product list title", "default": "In this room" },
    { "type": "text", "id": "card_link_label", "label": "Product card link label", "default": "View product" },

    { "type": "header", "content": "Image" },
    { "type": "image_picker", "id": "image", "label": "Desktop image" },
    {
      "type": "image_picker",
      "id": "image_mobile",
      "label": "Mobile image",
      "info": "Optional. Shown below 750px. If it is cropped differently, set mobile positions on each hotspot."
    },
    {
      "type": "select",
      "id": "image_ratio",
      "label": "Image shape on desktop",
      "info": "Hotspot positions are measured on the image as shown. Adapt keeps the whole image, so dots stay on their objects.",
      "options": [
        { "value": "adapt", "label": "Adapt to image" },
        { "value": "wide", "label": "Wide (16:9)" },
        { "value": "landscape", "label": "Landscape (3:2)" },
        { "value": "square", "label": "Square" },
        { "value": "portrait", "label": "Portrait (4:5)" }
      ],
      "default": "adapt"
    },
    {
      "type": "select",
      "id": "image_ratio_mobile",
      "label": "Image shape on mobile",
      "options": [
        { "value": "adapt", "label": "Adapt to image" },
        { "value": "wide", "label": "Wide (16:9)" },
        { "value": "landscape", "label": "Landscape (3:2)" },
        { "value": "square", "label": "Square" },
        { "value": "portrait", "label": "Portrait (4:5)" }
      ],
      "default": "adapt"
    },
    { "type": "range", "id": "image_radius", "label": "Image corner radius", "min": 0, "max": 40, "step": 2, "unit": "px", "default": 16 },

    { "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 list)" }
      ],
      "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": "header", "content": "Product card and list" },
    { "type": "color", "id": "color_card", "label": "Card background", "default": "#FFFFFF" },
    { "type": "color", "id": "color_card_text", "label": "Card text", "default": "#1C1917" },

    { "type": "header", "content": "Button" },
    { "type": "text", "id": "button_label", "label": "Button label", "info": "Optional. Shown under the product list.", "default": "Shop the collection" },
    { "type": "url", "id": "button_url", "label": "Button link" },
    {
      "type": "select",
      "id": "button_style",
      "label": "Button style",
      "options": [
        { "value": "primary", "label": "Solid" },
        { "value": "outline", "label": "Outline" },
        { "value": "text", "label": "Text link" }
      ],
      "default": "outline"
    },
    { "type": "checkbox", "id": "button_new_tab", "label": "Open link in a new tab", "default": false },

    { "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": "list_position",
      "label": "Product list on desktop",
      "info": "On mobile the list always sits below the image.",
      "options": [
        { "value": "side", "label": "Next to the image" },
        { "value": "below", "label": "Below the image" }
      ],
      "default": "side"
    },
    {
      "type": "select",
      "id": "image_position",
      "label": "Image position",
      "visible_if": "{{ section.settings.list_position == 'side' }}",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "right", "label": "Right" }
      ],
      "default": "left"
    },
    {
      "type": "select",
      "id": "image_width",
      "label": "Image width",
      "visible_if": "{{ section.settings.list_position == 'side' }}",
      "options": [
        { "value": "half", "label": "Half" },
        { "value": "default", "label": "Three fifths" },
        { "value": "wide", "label": "Two thirds" }
      ],
      "default": "default"
    },
    { "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 (active product, solid button)", "default": "#1C1917" },
    { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#F5F2EC" },
    { "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." },

    { "type": "header", "content": "Motion" },
    { "type": "checkbox", "id": "pulse", "label": "Pulse the hotspots", "default": true }
  ],
  "blocks": [
    {
      "type": "hotspot",
      "name": "Product hotspot",
      "limit": 6,
      "settings": [
        { "type": "product", "id": "product", "label": "Product" },
        { "type": "text", "id": "label", "label": "Label", "info": "Optional. A short word above the title, e.g. Throw." },
        { "type": "range", "id": "x", "label": "Horizontal position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 50 },
        { "type": "range", "id": "y", "label": "Vertical position", "min": 0, "max": 100, "step": 1, "unit": "%", "default": 50 },
        { "type": "checkbox", "id": "mobile_custom", "label": "Different position on mobile", "default": false },
        {
          "type": "range",
          "id": "x_mobile",
          "label": "Horizontal position on mobile",
          "min": 0,
          "max": 100,
          "step": 1,
          "unit": "%",
          "default": 50,
          "visible_if": "{{ block.settings.mobile_custom }}"
        },
        {
          "type": "range",
          "id": "y_mobile",
          "label": "Vertical position on mobile",
          "min": 0,
          "max": 100,
          "step": 1,
          "unit": "%",
          "default": 50,
          "visible_if": "{{ block.settings.mobile_custom }}"
        }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Shop the look",
      "blocks": [
        { "type": "hotspot", "settings": { "label": "Throw", "x": 34, "y": 62 } },
        { "type": "hotspot", "settings": { "label": "Table lamp", "x": 80, "y": 50 } },
        { "type": "hotspot", "settings": { "label": "Vase", "x": 89, "y": 64 } }
      ]
    }
  ]
}
{% 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 %}
```

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