# Mağaza bulucu: ücretsiz Shopify bölümü

> Adres, çalışma saatleri, telefon ve Google Haritalar, Apple Haritalar ya da OpenStreetMap yol tarifi bağlantısıyla mağaza kartları ve şehir filtresi. API yok.

Mağaza bulucu, mağazalarınızı, satış noktalarınızı ya da showroom’larınızı fotoğraf, adres, çalışma saatleri, telefon, e-posta, kısa bir not ve "Yol tarifi al" bağlantısıyla kartlar halinde listeleyen ücretsiz bir Shopify bölümüdür. Bağlantı, adresi Google Haritalar, Apple Haritalar ya da OpenStreetMap’te açar ya da yapıştırdığınız harita bağlantısını kullanır; ücret ödeyeceğiniz ya da sayfaya yüklenen bir harita API’si, anahtar veya gömülü harita yoktur. Mağazalarınız birden fazla şehirdeyse bir şehir filtresi belirir ve kaç mağazanın gösterildiğini bildirir. Online Store 2.0 temalarında uygulama olmadan çalışır.

- **Kategori:** Navigasyon ve yardımcı bölümler
- **Kullanıldığı yerler:** İletişim, Satış noktaları, Hakkımızda
- **Okuduğu veri:** Yalnızca içerik
- **Boyut:** 2 dosyada 24 KB, küçük bir betikle
- **Lisans:** MIT, © 2026 Lintel
- **Sayfa:** https://lintelagency.com/tr/kaynaklar/shopify-bolumleri/magaza-bulucu
- **Canlı demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-store-locator.html
- **Eklenme tarihi:** 2026-11-02

## Ne işe yarar

- Birden fazla mağazası olan markalar için Bizi ziyaret edin ya da Mağazalar sayfası
- Ürünlerinizi satan dükkânları şehre göre listeleyen bir satış noktaları sayfası
- İletişim sayfasında, çalışma saatleriyle birlikte showroom ya da mağazadan teslim alma noktaları

## Tema düzenleyici ayarları

Bölüm tema düzenleyicide “Lib · Store locator” 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 | Visit us |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | medium | `small`, `medium`, `large` |
| `text` | `richtext` | Intro text | – |  |

### City filter

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `show_filter` | `checkbox` | Show a city filter | `true` |  |
| `filter_label` | `text` | Filter label | City |  |
| `all_label` | `text` | All cities option | All cities |  |
| `count_label` | `text` | Result message | {count} of {total} stores |  |
| `show_city` | `checkbox` | Show the city on each card | `true` |  |

### Map links

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `map_provider` | `select` | Open addresses in | google | `google`, `apple`, `osm` |
| `map_label` | `text` | Map link text | Get directions |  |
| `map_new_tab` | `checkbox` | Open map links in a new tab | `true` |  |
| `new_tab_label` | `text` | New tab notice (screen readers) | opens in a new tab |  |

### Layout

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `columns` | `range` | Columns on desktop | `3` | 1–4, step 1 |
| `card_style` | `select` | Card style | card | `plain`, `card` |
| `image_ratio` | `select` | Image shape | standard | `standard`, `landscape`, `square` |
| `section_width` | `select` | Width | page | `page`, `full` |
| `text_alignment` | `select` | Heading alignment on desktop | left | `left`, `center` |
| `text_alignment_mobile` | `select` | Heading alignment on mobile | left | `left`, `center` |

### Colors

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #FFFFFF |  |
| `color_bg_gradient` | `color_background` | Background gradient | – |  |
| `color_text` | `color` | Text | #1F1B17 |  |
| `color_accent` | `color` | Accent (map links, cities) | #7A5A3A |  |
| `color_accent_text` | `color` | Text on accent | #FFFFFF |  |
| `color_card` | `color` | Card background | #FAF6F1 |  |

### Spacing and visibility

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `padding_top` | `range` | Top padding on desktop | `64` | 0–200 px, step 4 |
| `padding_bottom` | `range` | Bottom padding on desktop | `64` | 0–200 px, step 4 |
| `padding_top_mobile` | `range` | Top padding on mobile | `40` | 0–160 px, step 4 |
| `padding_bottom_mobile` | `range` | Bottom padding on mobile | `40` | 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 |
| --- | --- | --- | --- | --- |
| `animate` | `checkbox` | Fade cards in on scroll | `false` |  |

## Bloklar

Toplamda en fazla 30 blok.

### Store (`store`)

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `name` | `text` | Store name | Store name |  |
| `city` | `text` | City | City |  |
| `address` | `textarea` | Address | – |  |
| `hours` | `richtext` | Opening hours | `<p>Mon–Sat 10:00–18:00</p>` |  |
| `phone` | `text` | Phone | – |  |
| `email` | `text` | Email | – |  |
| `note` | `text` | Note | – |  |
| `map_url` | `url` | Map link | – |  |
| `image` | `image_picker` | Image | – |  |
| `image_mobile` | `image_picker` | Mobile image | – |  |

## 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-store-locator.liquid` adında yeni bir dosya ekleyin, aşağıdaki kaynak koddan `sections/lib-store-locator.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 · Store locator** 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-store-locator.liquid https://lintelagency.com/resources/shopify-sections/files/lib-store-locator.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-store-locator.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-store-locator.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-store-locator.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

### Google Maps API anahtarı gerekiyor mu?

Hayır. Gömülü bir harita yoktur. Her mağazanın "Yol tarifi al" bağlantısı, adını ve adresini Google Haritalar, Apple Haritalar ya da OpenStreetMap’te aratır ya da bloğa yapıştırdığınız paylaşım bağlantısını müşterinin kendi harita uygulamasında açar.

### Şehir filtresi nasıl çalışıyor?

Şehirler, mağaza bloklarınızın Şehir alanından gelir. İki ya da daha fazla farklı şehir varsa etiketli bir açılır liste çıkar; bir şehir seçildiğinde diğer mağazalar gizlenir ve "3 mağazadan 2’si" gibi bir durum satırı duyurulur. JavaScript kapalıysa tüm mağazalar görünür.

### Kaç mağaza ekleyebilirim?

En çok 30 mağaza bloğu. Yüzlerce satış noktanız varsa mesafeye göre arama yapan özel bir mağaza bulucu uygulaması daha doğru bir seçim olur.

### Müşteriler karttan mağazayı arayabilir ya da e-posta gönderebilir mi?

Evet. Telefon numarası tel: bağlantısına, e-posta da mailto: bağlantısına dönüşür; ikisi de yalnızca doldurduğunuzda görünür.

## Kaynak kod

### sections/lib-store-locator.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Store locator: one card per shop, stockist or showroom with photo, address, opening hours, phone,
  email and a "get directions" link that opens the address in Google Maps, Apple Maps or
  OpenStreetMap. No maps API, no key, no embedded map: just links. With more than one city a small
  script adds a city filter; without it every store shows. All text is typed by the merchant.
  Depends on: snippets/lib-image.liquid
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign heading_tag = s.heading_tag | default: 'h2'
  assign item_tag = 'h3'
  if heading_tag == 'h3' or heading_tag == 'p'
    assign item_tag = 'h4'
  endif
  assign cities = section.blocks | map: 'settings' | map: 'city' | compact | uniq
  assign show_filter = false
  if s.show_filter and cities.size > 1
    assign show_filter = true
  endif
  assign cols = s.columns | plus: 0
  assign img_vw = 100 | divided_by: cols
  assign img_sizes = '(min-width: 990px) ' | append: img_vw | append: 'vw, 100vw'
  assign filter_id = 'LibStoreFilter-' | append: section.id
-%}

<lib-store-locator
  class="lib-stores lib-stores--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-style="{{ s.card_style }}"
  {% if s.animate %}
    data-animate
  {% endif %}
  style="
    --lib-bg: {{ s.color_bg }};
    {% if s.color_bg_gradient != blank %}--lib-bg-image: {{ s.color_bg_gradient }};{% endif %}
    --lib-text: {{ s.color_text }};
    --lib-accent: {{ s.color_accent }};
    --lib-accent-text: {{ s.color_accent_text }};
    --lib-card: {{ s.color_card }};
    --lib-cols: {{ cols }};
    --lib-pt: {{ s.padding_top }}px;
    --lib-pb: {{ s.padding_bottom }}px;
    --lib-pt-m: {{ s.padding_top_mobile }}px;
    --lib-pb-m: {{ s.padding_bottom_mobile }}px;
  "
>
  <div class="lib-stores__inner">
    <header class="lib-stores__header" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
      <div class="lib-stores__copy">
        {%- if s.heading != blank -%}
          <{{ heading_tag }} class="lib-stores__heading lib-stores__heading--{{ s.heading_size }}">
            {{- s.heading -}}
          </{{ heading_tag }}>
        {%- endif -%}
        {%- if s.text != blank -%}
          <div class="lib-stores__text">{{ s.text }}</div>
        {%- endif -%}
      </div>
      {%- if show_filter -%}
        <div class="lib-stores__filter" hidden data-filter>
          <label class="lib-stores__filter-label" for="{{ filter_id }}">{{ s.filter_label | escape }}</label>
          <select class="lib-stores__select" id="{{ filter_id }}">
            <option value="">{{ s.all_label | escape }}</option>
            {%- for city in cities -%}
              <option value="{{ city | handleize }}">{{ city | escape }}</option>
            {%- endfor -%}
          </select>
        </div>
      {%- endif -%}
    </header>
    <p class="lib-stores__count" role="status" data-count data-template="{{ s.count_label | escape }}"></p>

    {%- if section.blocks.size > 0 -%}
      <ul class="lib-stores__list" role="list">
        {%- for block in section.blocks -%}
          {%- liquid
            assign b = block.settings
            assign query = b.name | append: ', ' | append: b.address | newline_to_br | replace: '<br />', ', ' | strip_newlines | url_encode
            assign map_url = b.map_url
            if map_url == blank and b.address != blank
              case s.map_provider
                when 'apple'
                  assign map_url = 'https://maps.apple.com/?q=' | append: query
                when 'osm'
                  assign map_url = 'https://www.openstreetmap.org/search?query=' | append: query
                else
                  assign map_url = 'https://www.google.com/maps/search/?api=1&query=' | append: query
              endcase
            endif
            assign phone_digits = b.phone | remove: ' ' | remove: '(' | remove: ')' | remove: '-' | remove: '.'
          -%}
          <li class="lib-stores__item" data-city="{{ b.city | handleize }}" {{ block.shopify_attributes }}>
            <article class="lib-stores__card">
              {%- if b.image != blank or b.image_mobile != blank -%}
                <div class="lib-stores__media" data-ratio="{{ s.image_ratio }}">
                  {%- render 'lib-image', image: b.image, image_mobile: b.image_mobile, class: 'lib-stores__img', sizes: img_sizes -%}
                </div>
              {%- endif -%}
              <div class="lib-stores__body">
                {%- if b.city != blank and s.show_city -%}
                  <p class="lib-stores__city">{{ b.city | escape }}</p>
                {%- endif -%}
                <{{ item_tag }} class="lib-stores__name">{{ b.name | escape }}</{{ item_tag }}>
                {%- if b.address != blank -%}
                  <address class="lib-stores__address">{{ b.address | escape | newline_to_br }}</address>
                {%- endif -%}
                {%- if b.hours != blank -%}
                  <div class="lib-stores__hours">{{ b.hours }}</div>
                {%- endif -%}
                {%- if b.phone != blank or b.email != blank -%}
                  <ul class="lib-stores__contact" role="list">
                    {%- if b.phone != blank -%}
                      <li><a href="tel:{{ phone_digits }}">{{ b.phone | escape }}</a></li>
                    {%- endif -%}
                    {%- if b.email != blank -%}
                      <li><a href="mailto:{{ b.email | strip }}">{{ b.email | escape }}</a></li>
                    {%- endif -%}
                  </ul>
                {%- endif -%}
                {%- if b.note != blank -%}
                  <p class="lib-stores__note">{{ b.note | escape }}</p>
                {%- endif -%}
                {%- if map_url != blank and s.map_label != blank -%}
                  <a
                    class="lib-stores__map"
                    href="{{ map_url }}"
                    {% if s.map_new_tab %}
                      target="_blank" rel="noopener"
                    {% endif %}
                  >
                    <svg viewBox="0 0 24 24" width="18" height="18" aria-hidden="true" focusable="false" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 21s-6.5-5.6-6.5-11a6.5 6.5 0 0 1 13 0c0 5.4-6.5 11-6.5 11z"/><circle cx="12" cy="10" r="2.4"/></svg>
                    <span>{{ s.map_label | escape }}</span>
                    <span class="lib-stores__sr">{{ b.name | escape }}{% if s.map_new_tab and s.new_tab_label != blank %} ({{ s.new_tab_label | escape }}){% endif %}</span>
                  </a>
                {%- endif -%}
              </div>
            </article>
          </li>
        {%- endfor -%}
      </ul>
    {%- endif -%}
  </div>
</lib-store-locator>

{% stylesheet %}
  .lib-stores {
    display: block;
    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-stores__inner { max-width: 1200px; margin: 0 auto; }
  .lib-stores--full .lib-stores__inner { max-width: none; }
  @media (max-width: 749px) {
    .lib-stores { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-stores[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-stores[data-show='mobile'] { display: none; }
  }

  .lib-stores__header {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem 2rem;
    margin-bottom: clamp(1.25rem, 3vw, 2rem);
  }
  .lib-stores__copy { min-width: 0; max-width: 40rem; }
  .lib-stores__heading {
    margin: 0;
    font-size: clamp(1.75rem, 3vw, 2.5rem);
    line-height: 1.15;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .lib-stores__heading--small { font-size: clamp(1.25rem, 1.8vw, 1.5rem); }
  .lib-stores__heading--large { font-size: clamp(2rem, 4vw, 3.25rem); }
  .lib-stores__text { margin-top: 0.6rem; font-size: 1rem; line-height: 1.6; opacity: 0.85; }
  .lib-stores__text > :first-child { margin-top: 0; }
  .lib-stores__text > :last-child { margin-bottom: 0; }
  .lib-stores__header[data-align='center'] { flex-direction: column; align-items: center; text-align: center; }
  @media (max-width: 749px) {
    .lib-stores__header[data-align-m='left'] { flex-direction: column; align-items: flex-start; text-align: left; }
    .lib-stores__header[data-align-m='center'] { flex-direction: column; align-items: center; text-align: center; }
  }

  .lib-stores__filter { display: flex; flex-direction: column; gap: 0.35rem; min-width: min(16rem, 100%); }
  .lib-stores__filter[hidden] { display: none; }
  .lib-stores__filter-label { font-size: 0.875rem; font-weight: 600; text-align: left; }
  .lib-stores__select {
    min-height: 2.75rem;
    padding: 0.5rem 2.25rem 0.5rem 0.875rem;
    border: 1.5px solid color-mix(in srgb, var(--lib-text) 35%, transparent);
    border-radius: 10px;
    background: var(--lib-card)
      url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath d='M4 6l4 4 4-4' fill='none' stroke='%23777' stroke-width='1.8' stroke-linecap='round'/%3E%3C/svg%3E")
      no-repeat right 0.75rem center / 1rem;
    color: var(--lib-text);
    font: inherit;
    font-size: 0.9375rem;
    appearance: none;
  }
  .lib-stores__select:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 2px; }
  .lib-stores__count { margin: 0 0 1rem; font-size: 0.875rem; opacity: 0.8; }
  .lib-stores__count:empty { display: none; }

  .lib-stores__list {
    display: grid;
    grid-template-columns: repeat(var(--lib-cols), minmax(0, 1fr));
    gap: clamp(1rem, 2.5vw, 1.75rem);
    margin: 0;
    padding: 0;
    list-style: none;
  }
  @media (max-width: 989px) {
    .lib-stores__list { grid-template-columns: repeat(min(var(--lib-cols), 2), minmax(0, 1fr)); }
  }
  @media (max-width: 749px) {
    .lib-stores__list { grid-template-columns: 1fr; }
  }
  .lib-stores__item { display: flex; min-width: 0; }
  .lib-stores__item[hidden] { display: none; }
  .lib-stores__card { display: flex; flex: 1; flex-direction: column; min-width: 0; border-radius: 14px; overflow: hidden; }
  .lib-stores[data-style='card'] .lib-stores__card {
    background: var(--lib-card);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lib-text) 10%, transparent);
  }
  .lib-stores__media { aspect-ratio: 4 / 3; overflow: hidden; background: color-mix(in srgb, var(--lib-text) 6%, transparent); }
  .lib-stores[data-style='plain'] .lib-stores__media { border-radius: 14px; }
  .lib-stores__media[data-ratio='landscape'] { aspect-ratio: 16 / 9; }
  .lib-stores__media[data-ratio='square'] { aspect-ratio: 1; }
  .lib-stores__img { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-stores__body { display: flex; flex: 1; flex-direction: column; gap: 0.5rem; padding: 1rem 0 0; }
  .lib-stores[data-style='card'] .lib-stores__body { padding: 1.125rem 1.25rem 1.25rem; }
  .lib-stores__city { margin: 0; font-size: 0.75rem; font-weight: 600; letter-spacing: 0.08em; text-transform: uppercase; color: var(--lib-accent); }
  .lib-stores__name { margin: 0; font-size: 1.1875rem; line-height: 1.3; }
  .lib-stores__address { font-style: normal; font-size: 0.9375rem; line-height: 1.55; }
  .lib-stores__hours { font-size: 0.875rem; line-height: 1.55; opacity: 0.85; }
  .lib-stores__hours > * { margin: 0; }
  .lib-stores__contact { display: flex; flex-wrap: wrap; gap: 0.25rem 1rem; margin: 0; padding: 0; list-style: none; font-size: 0.9375rem; }
  .lib-stores__contact a { display: inline-block; padding-block: 0.2rem; color: inherit; text-underline-offset: 0.2em; }
  .lib-stores__note { margin: 0; font-size: 0.875rem; opacity: 0.8; }
  .lib-stores__map {
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
    align-self: flex-start;
    min-height: 2.75rem;
    margin-top: auto;
    padding: 0.5rem 1rem;
    border-radius: 999px;
    background: var(--lib-accent);
    color: var(--lib-accent-text);
    font-size: 0.875rem;
    font-weight: 600;
    text-decoration: none;
  }
  .lib-stores__map:hover { filter: brightness(1.08); }
  .lib-stores__map:focus-visible,
  .lib-stores__contact a:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 2px; }
  .lib-stores__sr {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  @supports (animation-timeline: view()) {
    @media (prefers-reduced-motion: no-preference) {
      .lib-stores[data-animate] .lib-stores__card {
        animation: lib-stores-rise linear both;
        animation-timeline: view();
        animation-range: entry 0% entry 50%;
      }
    }
  }
  @keyframes lib-stores-rise {
    from { opacity: 0; transform: translateY(18px); }
  }
{% endstylesheet %}

{% javascript %}
  (() => {
    if (customElements.get('lib-store-locator')) return;

    class LibStoreLocator extends HTMLElement {
      connectedCallback() {
        const filter = this.querySelector('[data-filter]');
        if (!filter) return;
        this.items = [...this.querySelectorAll('[data-city]')];
        this.count = this.querySelector('[data-count]');
        filter.hidden = false;
        filter.querySelector('select').addEventListener('change', (event) => this.apply(event.target.value));
      }

      apply(city) {
        let shown = 0;
        this.items.forEach((item) => {
          item.hidden = Boolean(city) && item.dataset.city !== city;
          if (!item.hidden) shown += 1;
        });
        const template = this.count.dataset.template || '';
        this.count.textContent = city ? template.replace('{count}', shown).replace('{total}', this.items.length) : '';
      }
    }
    customElements.define('lib-store-locator', LibStoreLocator);
  })();
{% endjavascript %}

{% schema %}
{
  "name": "Lib · Store locator",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 30,
  "settings": [
    { "type": "header", "content": "Content" },
    { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Visit us" },
    {
      "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": "Intro text" },

    { "type": "header", "content": "City filter" },
    { "type": "checkbox", "id": "show_filter", "label": "Show a city filter", "info": "Only when the stores are in more than one city.", "default": true },
    { "type": "text", "id": "filter_label", "label": "Filter label", "default": "City" },
    { "type": "text", "id": "all_label", "label": "All cities option", "default": "All cities" },
    { "type": "text", "id": "count_label", "label": "Result message", "info": "{count} stores shown, {total} in all. Read out by screen readers too.", "default": "{count} of {total} stores" },
    { "type": "checkbox", "id": "show_city", "label": "Show the city on each card", "default": true },

    { "type": "header", "content": "Map links" },
    {
      "type": "select",
      "id": "map_provider",
      "label": "Open addresses in",
      "info": "A link built from the store's name and address; a block's own map link wins.",
      "options": [
        { "value": "google", "label": "Google Maps" },
        { "value": "apple", "label": "Apple Maps" },
        { "value": "osm", "label": "OpenStreetMap" }
      ],
      "default": "google"
    },
    { "type": "text", "id": "map_label", "label": "Map link text", "info": "Leave empty to hide map links.", "default": "Get directions" },
    { "type": "checkbox", "id": "map_new_tab", "label": "Open map links in a new tab", "default": true },
    { "type": "text", "id": "new_tab_label", "label": "New tab notice (screen readers)", "default": "opens in a new tab" },

    { "type": "header", "content": "Layout" },
    { "type": "range", "id": "columns", "label": "Columns on desktop", "info": "Mobile shows one column.", "min": 1, "max": 4, "step": 1, "default": 3 },
    {
      "type": "select",
      "id": "card_style",
      "label": "Card style",
      "options": [
        { "value": "plain", "label": "Plain" },
        { "value": "card", "label": "Card" }
      ],
      "default": "card"
    },
    {
      "type": "select",
      "id": "image_ratio",
      "label": "Image shape",
      "options": [
        { "value": "standard", "label": "4:3" },
        { "value": "landscape", "label": "16:9" },
        { "value": "square", "label": "Square" }
      ],
      "default": "standard"
    },
    {
      "type": "select",
      "id": "section_width",
      "label": "Width",
      "options": [
        { "value": "page", "label": "Page width" },
        { "value": "full", "label": "Full width" }
      ],
      "default": "page"
    },
    {
      "type": "select",
      "id": "text_alignment",
      "label": "Heading alignment on desktop",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" }
      ],
      "default": "left"
    },
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "Heading alignment on mobile",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" }
      ],
      "default": "left"
    },

    { "type": "header", "content": "Colors" },
    { "type": "color", "id": "color_bg", "label": "Background", "default": "#FFFFFF" },
    { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." },
    { "type": "color", "id": "color_text", "label": "Text", "default": "#1F1B17" },
    { "type": "color", "id": "color_accent", "label": "Accent (map links, cities)", "default": "#7A5A3A" },
    { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FFFFFF" },
    { "type": "color", "id": "color_card", "label": "Card background", "default": "#FAF6F1" },

    { "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": 64 },
    { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 64 },
    { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 40 },
    { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 40 },
    {
      "type": "select",
      "id": "show_on",
      "label": "Show on",
      "options": [
        { "value": "all", "label": "All devices" },
        { "value": "desktop", "label": "Desktop only" },
        { "value": "mobile", "label": "Mobile only" }
      ],
      "default": "all"
    },
    { "type": "text", "id": "anchor_id", "label": "Anchor ID", "info": "Optional. Link to this section with #your-id." },

    { "type": "header", "content": "Motion" },
    { "type": "checkbox", "id": "animate", "label": "Fade cards in on scroll", "info": "Only in browsers that support scroll-driven animations; never when reduced motion is on.", "default": false }
  ],
  "blocks": [
    {
      "type": "store",
      "name": "Store",
      "settings": [
        { "type": "text", "id": "name", "label": "Store name", "default": "Store name" },
        { "type": "text", "id": "city", "label": "City", "info": "Used by the city filter.", "default": "City" },
        { "type": "textarea", "id": "address", "label": "Address", "info": "One line per address line." },
        { "type": "richtext", "id": "hours", "label": "Opening hours", "default": "<p>Mon–Sat 10:00–18:00</p>" },
        { "type": "text", "id": "phone", "label": "Phone" },
        { "type": "text", "id": "email", "label": "Email" },
        { "type": "text", "id": "note", "label": "Note", "info": "Optional, for example parking or step-free access." },
        { "type": "url", "id": "map_url", "label": "Map link", "info": "Optional. Paste a share link from your maps app to use it instead." },
        { "type": "image_picker", "id": "image", "label": "Image" },
        { "type": "image_picker", "id": "image_mobile", "label": "Mobile image", "info": "Optional. Below 750px." }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Store locator",
      "blocks": [{ "type": "store" }, { "type": "store" }]
    }
  ]
}
{% 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.
