# Geri sayım banner: ücretsiz Shopify bölümü

> Belirlenen tarih ve saate canlı geri sayımlı indirim ya da lansman bannerı: çubuk, banner ya da iki sütun, cihaza göre arka plan görseli, seçilen bitiş durumu.

Geri sayım banner, belirlediğiniz bitiş tarihine ve saatine bir başlık, metin ve düğmenin yanında geri sayan ücretsiz bir Shopify bölümüdür; ince bir çubuk, görselli bir banner ya da iki sütunlu bir düzen olarak gelir. Son tarih bir UTC farkına sabitlenir; böylece her ziyaretçi aynı bitiş anını görür. Online Store 2.0 temalarında uygulama olmadan çalışır.

- **Kategori:** Dönüşüm ve kampanyalar
- **Kullanıldığı yerler:** Ana sayfa, Koleksiyon, Açılış sayfaları
- **Okuduğu veri:** Yalnızca içerik
- **Boyut:** 3 dosyada 35 KB, küçük bir betikle
- **Lisans:** MIT, © 2026 Lintel
- **Sayfa:** https://lintelagency.com/tr/kaynaklar/shopify-bolumleri/geri-sayim-banner
- **Canlı demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-countdown-banner.html
- **Eklenme tarihi:** 2026-10-02

## Ne işe yarar

- Ana sayfanın en üstünde sezonluk indirim bannerı
- Koleksiyon ya da açılış sayfasında ürün lansmanı veya ön sipariş son tarihi
- Bir tatil öncesinde ücretsiz kargo son saati için ince bir çubuk

## Tema düzenleyici ayarları

Bölüm tema düzenleyicide “Lib · Countdown banner” 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 | 25% off the whole studio |  |
| `heading_tag` | `select` | Heading tag | h2 | `h1`, `h2`, `h3`, `p` |
| `heading_size` | `select` | Heading size | large | `small`, `medium`, `large`, `xlarge` |
| `text` | `richtext` | Text | `<p>Vases, candles and tableware, while the kiln cools down for the year.</p>` |  |

### Countdown

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `end_date` | `text` | End date | 2026-12-31 |  |
| `end_time` | `text` | End time | 23:59 |  |
| `utc_offset` | `select` | Time zone of the end time (UTC offset) | +00:00 | `-12:00`, `-11:00`, `-10:00`, `-09:30`, `-09:00`, `-08:00`, `-07:00`, `-06:00`, `-05:00`, `-04:00`, `-03:30`, `-03:00`, `-02:00`, `-01:00`, `+00:00`, `+01:00`, `+02:00`, `+03:00`, `+03:30`, `+04:00`, `+04:30`, `+05:00`, `+05:30`, `+05:45`, `+06:00`, `+06:30`, `+07:00`, `+08:00`, `+09:00`, `+09:30`, `+10:00`, `+10:30`, `+11:00`, `+12:00`, `+13:00`, `+14:00` |
| `show_seconds` | `checkbox` | Show seconds | `true` |  |
| `label_days` | `text` | Days label | Days |  |
| `label_hours` | `text` | Hours label | Hours |  |
| `label_minutes` | `text` | Minutes label | Minutes |  |
| `label_seconds` | `text` | Seconds label | Seconds |  |
| `ends_prefix` | `text` | End date prefix | Ends |  |
| `end_behavior` | `select` | When the timer reaches zero | message | `hide`, `message`, `zero` |
| `ended_message` | `text` | Ended message | This offer has ended. Thank you for shopping with us. |  |

### 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_position` | `select` | Image position on desktop | left | `left`, `right` |
| `overlay_opacity` | `range` | Overlay opacity | `40` | 0–90 %, step 5 |

### Button

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `button_label` | `text` | Button label | Shop the sale |  |
| `button_url` | `url` | Button link | – |  |
| `button_style` | `select` | Button style | primary | `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 |
| --- | --- | --- | --- | --- |
| `layout` | `select` | Layout | banner | `bar`, `banner`, `split` |
| `content_valign_mobile` | `select` | Content position on mobile | center | `top`, `center`, `bottom` |
| `digit_style` | `select` | Digit style | boxes | `boxes`, `plain` |
| `text_alignment` | `text_alignment` | Alignment on desktop | center |  |
| `text_alignment_mobile` | `select` | Alignment on mobile | center | `left`, `center`, `right` |

### Colors

| Kimlik | Tür | Etiket | Varsayılan | Seçenekler ya da aralık |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #1C1917 |  |
| `color_bg_gradient` | `color_background` | Background gradient | – |  |
| `color_text` | `color` | Text | #FAFAF9 |  |
| `color_accent` | `color` | Accent (button) | #E8B04A |  |
| `color_accent_text` | `color` | Text on accent | #1C1917 |  |

### 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` | Animate digits when they change | `true` |  |

## Kurulum

### Shopify yönetim panelinde

1. Canlı temanızda değil, bir kopyasında çalışın: Shopify yönetim panelinde **Online Mağaza > Temalar** sayfasına gidin, mevcut temanızın **…** menüsünü açın ve **Kopya oluştur**’u seçin.
2. Kopyada **…** menüsünü açın ve **Kodu düzenle**’yi seçin.
3. `sections` klasörüne `lib-countdown-banner.liquid` adında yeni bir dosya ekleyin, aşağıdaki kaynak koddan `sections/lib-countdown-banner.liquid` içeriğini yapıştırıp kaydedin.
4. `lib-button.liquid` ve `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 · Countdown banner** 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-countdown-banner.liquid https://lintelagency.com/resources/shopify-sections/files/lib-countdown-banner.liquid
curl -fsSL -o snippets/lib-button.liquid https://lintelagency.com/resources/shopify-sections/files/lib-button.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-countdown-banner.liquid --only snippets/lib-button.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-countdown-banner.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-countdown-banner.liquid): `sections/` klasörüne
- [snippets/lib-button.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-button.liquid): `snippets/` 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

### Geri sayım hangi saat dilimini kullanır?

Bitiş tarihini (YYYY-MM-DD) ve saatini (HH:MM) girer, ait oldukları UTC farkını seçersiniz. Sayaç, nerede olurlarsa olsunlar her ziyaretçi için aynı anda biter. Fark yaz saatiyle değişmez; bu yüzden bitiş tarihinde geçerli olanı seçin.

### Sayaç sıfıra ulaşınca ne olur?

Siz seçersiniz: bölümü gizle, bir bitti mesajı göster ya da sıfırları göstermeye devam et. Bölüm ayrıca bitiş tarihini sayfanın oluşturulduğu zamanla karşılaştırır; böylece zaten bitmiş bir kampanya kaybolmadan önce bir sayaç gösterip söndürmez. Tema düzenleyicide gizlenmiş bir geri sayım, düzenlemeye devam edebilmeniz için bir notla görünür kalır.

### JavaScript olmadan ziyaretçiler ne görür?

Başlığı, metni, düğmeyi ve bitiş tarihini metin olarak, örneğin "Ends 31 December", haneler yerine tirelerle. Küçük bir custom element sayacı başlatır ve her saniye, saniyeler gizliyse her dakika günceller.

### Geri sayım erişilebilir mi?

Değişen haneler ekran okuyuculardan gizlenir. Bunun yerine ekran okuyucular bitiş tarihini ve kalan gün, saat ve dakikayı içeren bir metin satırı okur; sayaç bu satırı her saniye değil dakikada bir yeniden yazar. Hane animasyonu ziyaretçi azaltılmış hareket tercih ettiğinde ya da siz kapattığınızda kapalıdır.

## Kaynak kod

### sections/lib-countdown-banner.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Countdown banner: heading, text, button and a timer to an end date and time.
  Three layouts: inline bar, banner over an image or color, split image and text.
  The server renders the end date as text ("Ends 31 December"); a small custom element
  turns the timer on and updates it every second (or every minute without seconds).
  The end time is read in the UTC offset chosen in the settings, so every visitor sees the same deadline.
  Depends on: snippets/lib-button.liquid, snippets/lib-image.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 end_date = s.end_date | strip
  assign end_time = s.end_time | strip | default: '23:59'
  if end_time.size == 4
    assign end_time = '0' | append: end_time
  endif
  assign end_iso = ''
  if end_date.size == 10
    assign end_iso = end_date | append: 'T' | append: end_time | append: ':00' | append: s.utc_offset
  endif

  comment
    Server-side state: compare the deadline with the render time. Cached pages can be a little stale,
    so the custom element re-checks in the browser.
  endcomment
  assign ended = false
  if end_iso != blank
    assign end_epoch = end_iso | date: '%s' | plus: 0
    assign now_epoch = 'now' | date: '%s' | plus: 0
    if end_epoch > 0 and end_epoch <= now_epoch
      assign ended = true
    endif
    comment
      Midday keeps the printed day stable whatever the shop's time zone is.
    endcomment
    assign end_date_text = end_date | append: ' 12:00' | date: '%-d %B'
  endif

  assign hide_section = false
  if ended and s.end_behavior == 'hide' and request.design_mode != true
    assign hide_section = true
  endif

  assign has_image = false
  if s.image != blank or s.image_mobile != blank
    assign has_image = true
  endif
-%}

{%- unless hide_section -%}
  <div
    class="lib-countdown lib-countdown--{{ s.layout }}"
    {% if s.anchor_id != blank %}
      id="{{ s.anchor_id | handleize }}"
    {% endif %}
    data-show="{{ s.show_on }}"
    data-digits="{{ s.digit_style }}"
    data-align="{{ s.text_alignment }}"
    data-align-m="{{ s.text_alignment_mobile }}"
    data-image-pos="{{ s.image_position }}"
    data-valign-m="{{ s.content_valign_mobile }}"
    {% if has_image and s.layout != 'split' %}
      data-has-bg
    {% endif %}
    {% if ended %}
      data-ended
    {% endif %}
    {% if s.animate %}
      data-animate
    {% endif %}
    style="
      --lib-bg: {{ s.color_bg }};
      {% if s.color_bg_gradient != blank %}--lib-bg-image: {{ s.color_bg_gradient }};{% endif %}
      --lib-text: {{ s.color_text }};
      --lib-accent: {{ s.color_accent }};
      --lib-accent-text: {{ s.color_accent_text }};
      --lib-overlay-opacity: {{ s.overlay_opacity | divided_by: 100.0 }};
      --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;
    "
  >
    {%- if s.layout != 'split' and has_image -%}
      <div class="lib-countdown__bg" aria-hidden="true">
        {%- render 'lib-image',
          image: s.image,
          image_mobile: s.image_mobile,
          class: 'lib-countdown__img',
          sizes: '100vw',
          eager: eager
        -%}
      </div>
    {%- endif -%}

    <div class="lib-countdown__inner">
      {%- if s.layout == 'split' -%}
        <div class="lib-countdown__media">
          {%- render 'lib-image',
            image: s.image,
            image_mobile: s.image_mobile,
            class: 'lib-countdown__img',
            sizes: '(min-width: 990px) 50vw, 100vw',
            eager: eager,
            placeholder: 'lifestyle-2'
          -%}
        </div>
      {%- endif -%}

      <div class="lib-countdown__copy">
        {%- if s.heading != blank -%}
          <{{ heading_tag }} class="lib-countdown__heading lib-countdown__heading--{{ s.heading_size }}">
            {{- s.heading -}}
          </{{ heading_tag }}>
        {%- endif -%}
        {%- if s.text != blank -%}
          <div class="lib-countdown__text">{{ s.text }}</div>
        {%- endif -%}
      </div>

      {%- if end_iso != blank -%}
        <lib-countdown-banner-timer
          class="lib-countdown__timer"
          data-end="{{ end_iso | escape }}"
          data-end-behavior="{{ s.end_behavior }}"
          {% if s.show_seconds %}
            data-seconds
          {% endif %}
          data-label-days="{{ s.label_days | escape }}"
          data-label-hours="{{ s.label_hours | escape }}"
          data-label-minutes="{{ s.label_minutes | escape }}"
          data-label-seconds="{{ s.label_seconds | escape }}"
        >
          {%- liquid
            assign show_clock = true
            assign start_digits = '--'
            if ended
              assign show_clock = false
              if s.end_behavior == 'zero'
                assign show_clock = true
                assign start_digits = '00'
              endif
            endif
          -%}
          {%- if show_clock -%}
            <div class="lib-countdown__clock" aria-hidden="true" data-clock>
              {%- assign units = 'days,hours,minutes,seconds' | split: ',' -%}
              {%- for unit in units -%}
                {%- if unit == 'seconds' and s.show_seconds == false -%}{%- continue -%}{%- endif -%}
                {%- liquid
                  case unit
                    when 'days'
                      assign unit_label = s.label_days
                    when 'hours'
                      assign unit_label = s.label_hours
                    when 'minutes'
                      assign unit_label = s.label_minutes
                    else
                      assign unit_label = s.label_seconds
                  endcase
                -%}
                {%- unless forloop.first -%}
                  <span class="lib-countdown__sep">:</span>
                {%- endunless -%}
                <span class="lib-countdown__unit">
                  <span class="lib-countdown__num" data-unit="{{ unit }}" aria-live="off">{{ start_digits }}</span>
                  <span class="lib-countdown__label">{{ unit_label | escape }}</span>
                </span>
              {%- endfor -%}
            </div>
          {%- endif -%}
          {%- comment -%} Read by screen readers instead of the ticking digits; the timer rewrites it once a minute. {%- endcomment -%}
          <p class="lib-countdown__date" data-summary {% if ended and s.end_behavior != 'zero' %}hidden{% endif %}>
            <span data-summary-date>
              {%- if s.ends_prefix != blank %}{{ s.ends_prefix | escape }} {% endif -%}
              {{- end_date_text -}}
            </span>
            <span data-summary-text></span>
          </p>
          {%- if s.end_behavior != 'zero' -%}
            <p class="lib-countdown__ended" data-ended-message {% unless ended %}hidden{% endunless %}>
              {%- if s.end_behavior == 'hide' -%}
                This countdown has ended, so the section is hidden on your live store.
              {%- else -%}
                {{- s.ended_message | escape -}}
              {%- endif -%}
            </p>
          {%- endif -%}
        </lib-countdown-banner-timer>
      {%- elsif request.design_mode -%}
        <p class="lib-countdown__ended">Set an end date as YYYY-MM-DD to show the countdown.</p>
      {%- endif -%}

      {%- if s.button_label != blank -%}
        <div class="lib-countdown__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>
  </div>
{%- endunless -%}

{% stylesheet %}
  .lib-countdown {
    position: relative;
    isolation: isolate;
    overflow: hidden;
    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-countdown[hidden], .lib-countdown [hidden] { display: none !important; }
  @media (max-width: 749px) {
    .lib-countdown { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); }
    .lib-countdown[data-show='desktop'] { display: none; }
  }
  @media (min-width: 750px) {
    .lib-countdown[data-show='mobile'] { display: none; }
  }

  /* Background image with overlay (bar and banner layouts). */
  .lib-countdown__bg { position: absolute; inset: 0; z-index: -1; }
  .lib-countdown__bg::after {
    content: '';
    position: absolute;
    inset: 0;
    background: var(--lib-bg);
    opacity: var(--lib-overlay-opacity);
  }
  .lib-countdown__img { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-countdown__img:is(svg) { fill: color-mix(in srgb, var(--lib-text) 25%, transparent); background: color-mix(in srgb, var(--lib-text) 8%, transparent); }

  .lib-countdown__inner {
    position: relative;
    max-width: 1200px;
    margin: 0 auto;
    display: flex;
    flex-direction: column;
    gap: 1.5rem;
  }

  /* Alignment (banner and split): desktop, then mobile override. */
  .lib-countdown__inner { align-items: flex-start; text-align: left; }
  .lib-countdown[data-align='center'] .lib-countdown__inner { align-items: center; text-align: center; }
  .lib-countdown[data-align='right'] .lib-countdown__inner { align-items: flex-end; text-align: right; }
  @media (max-width: 749px) {
    .lib-countdown[data-align-m='left'] .lib-countdown__inner { align-items: flex-start; text-align: left; }
    .lib-countdown[data-align-m='center'] .lib-countdown__inner { align-items: center; text-align: center; }
    .lib-countdown[data-align-m='right'] .lib-countdown__inner { align-items: flex-end; text-align: right; }
  }

  /* Banner */
  .lib-countdown--banner .lib-countdown__inner { justify-content: center; }
  .lib-countdown--banner .lib-countdown__copy { max-width: 42rem; }
  .lib-countdown--banner[data-has-bg] .lib-countdown__inner { min-height: 26rem; }
  @media (max-width: 749px) {
    .lib-countdown--banner[data-has-bg] .lib-countdown__inner { min-height: 32rem; }
    .lib-countdown--banner[data-valign-m='top'] .lib-countdown__inner { justify-content: flex-start; }
    .lib-countdown--banner[data-valign-m='center'] .lib-countdown__inner { justify-content: center; }
    .lib-countdown--banner[data-valign-m='bottom'] .lib-countdown__inner { justify-content: flex-end; }
    .lib-countdown__clock { gap: 0.75rem; }
    .lib-countdown__label { font-size: 0.6875rem; letter-spacing: 0.04em; }
  }

  /* Inline bar: copy, timer and button in one row from 990px. */
  .lib-countdown--bar .lib-countdown__inner { gap: 1rem; }
  @media (min-width: 990px) {
    .lib-countdown--bar[data-align] .lib-countdown__inner {
      flex-direction: row;
      flex-wrap: wrap;
      align-items: center;
      justify-content: space-between;
      text-align: left;
      gap: 1rem 2.5rem;
    }
    .lib-countdown--bar .lib-countdown__copy { flex: 1 1 18rem; }
    .lib-countdown--bar[data-align] .lib-countdown__copy { align-items: flex-start; }
    .lib-countdown--bar .lib-countdown__text { margin-top: 0.25rem; }
  }
  .lib-countdown--bar .lib-countdown__heading { font-size: clamp(1.25rem, 2vw, 1.5rem); }
  .lib-countdown--bar .lib-countdown__actions { margin-top: 0; }

  /* Split */
  .lib-countdown__media {
    position: relative;
    width: 100%;
    border-radius: 12px;
    overflow: hidden;
    aspect-ratio: 3 / 2;
  }
  @media (min-width: 990px) {
    .lib-countdown--split .lib-countdown__inner {
      display: grid;
      grid-template-columns: 1fr 1fr;
      grid-template-rows: auto auto auto;
      column-gap: clamp(2.5rem, 5vw, 4.5rem);
      row-gap: 1.75rem;
      align-content: center;
    }
    .lib-countdown--split .lib-countdown__media { grid-column: 1; grid-row: 1 / span 3; align-self: center; }
    .lib-countdown--split .lib-countdown__inner > :not(.lib-countdown__media) { grid-column: 2; }
    .lib-countdown--split .lib-countdown__copy { align-self: end; }
    .lib-countdown--split .lib-countdown__inner > :last-child:not(.lib-countdown__media) { align-self: start; }
    .lib-countdown--split[data-image-pos='right'] .lib-countdown__media { grid-column: 2; }
    .lib-countdown--split[data-image-pos='right'] .lib-countdown__inner > :not(.lib-countdown__media) { grid-column: 1; }
    .lib-countdown--split[data-align='center'] .lib-countdown__inner > :not(.lib-countdown__media) { justify-self: center; }
    .lib-countdown--split[data-align='right'] .lib-countdown__inner > :not(.lib-countdown__media) { justify-self: end; }
  }

  /* Copy */
  .lib-countdown__copy { display: flex; flex-direction: column; align-items: inherit; }
  .lib-countdown__heading {
    margin: 0;
    font-size: clamp(2rem, 4.5vw, 3.5rem);
    line-height: 1.05;
    letter-spacing: -0.02em;
    text-wrap: balance;
  }
  .lib-countdown__heading--small { font-size: clamp(1.5rem, 2.5vw, 2rem); }
  .lib-countdown__heading--medium { font-size: clamp(1.75rem, 3.5vw, 2.75rem); }
  .lib-countdown__heading--xlarge { font-size: clamp(2.5rem, 6.5vw, 5rem); }
  .lib-countdown__text { margin-top: 1rem; max-width: 36rem; font-size: 1.0625rem; line-height: 1.6; opacity: 0.85; }
  .lib-countdown__text > :first-child { margin-top: 0; }
  .lib-countdown__text > :last-child { margin-bottom: 0; }

  /* Timer */
  .lib-countdown__timer { display: flex; flex-direction: column; align-items: inherit; gap: 0.6rem; }
  .lib-countdown__clock {
    display: flex;
    align-items: flex-start;
    gap: 0.5rem;
    font-variant-numeric: tabular-nums;
  }
  .lib-countdown__unit { display: flex; flex-direction: column; align-items: center; gap: 0.4rem; min-width: 0; }
  .lib-countdown__num {
    display: block;
    min-width: 2ch;
    font-size: clamp(1.75rem, 4vw, 2.75rem);
    font-weight: 700;
    line-height: 1;
    letter-spacing: -0.01em;
    text-align: center;
  }
  .lib-countdown__label { font-size: 0.75rem; letter-spacing: 0.08em; text-transform: uppercase; opacity: 0.75; }
  .lib-countdown__sep { font-size: clamp(1.5rem, 3.5vw, 2.25rem); font-weight: 600; line-height: 1.1; opacity: 0.5; }
  .lib-countdown[data-digits='boxes'] .lib-countdown__sep { display: none; }
  .lib-countdown[data-digits='boxes'] .lib-countdown__num {
    min-width: calc(2ch + 1.5rem);
    padding: 0.7rem 0.75rem;
    border-radius: 8px;
    background: color-mix(in srgb, var(--lib-text) 12%, transparent);
    box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--lib-text) 18%, transparent);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
  }
  .lib-countdown--bar .lib-countdown__num { font-size: clamp(1.5rem, 2.5vw, 2rem); }
  .lib-countdown--bar[data-digits='boxes'] .lib-countdown__num { padding: 0.55rem 0.6rem; }
  .lib-countdown__date, .lib-countdown__ended { margin: 0; font-size: 0.9375rem; opacity: 0.85; }
  .lib-countdown__ended { font-size: 1.125rem; font-weight: 600; opacity: 1; }
  .lib-countdown__timer.is-live .lib-countdown__date {
    position: absolute;
    width: 1px;
    height: 1px;
    margin: -1px;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
  }
  .lib-countdown__actions { margin-top: 0.5rem; }

  /* Motion: a short drop when a digit changes; none under reduced motion. */
  @media (prefers-reduced-motion: no-preference) {
    .lib-countdown__num.is-ticking { animation: lib-countdown-tick 0.35s cubic-bezier(0.2, 0.7, 0.2, 1); }
  }
  @keyframes lib-countdown-tick {
    from { opacity: 0.2; transform: translateY(-0.2em); }
  }
{% endstylesheet %}

{% javascript %}
  if (!customElements.get('lib-countdown-banner-timer')) {
    customElements.define(
      'lib-countdown-banner-timer',
      class extends HTMLElement {
        connectedCallback() {
          this.end = Date.parse(this.dataset.end || '');
          this.clock = this.querySelector('[data-clock]');
          if (Number.isNaN(this.end) || !this.clock) return;
          this.seconds = this.hasAttribute('data-seconds');
          this.animate = this.closest('[data-animate]') !== null;
          this.nums = {};
          for (const el of this.querySelectorAll('[data-unit]')) this.nums[el.dataset.unit] = el;
          this.summary = this.querySelector('[data-summary-text]');
          this.lastMinute = -1;
          this.classList.add('is-live');
          this.tick();
        }

        disconnectedCallback() {
          clearTimeout(this.timer);
        }

        tick() {
          const left = this.end - Date.now();
          if (left <= 0) return this.finish();
          const total = Math.floor(left / 1000);
          const parts = {
            days: Math.floor(total / 86400),
            hours: Math.floor((total % 86400) / 3600),
            minutes: Math.floor((total % 3600) / 60),
            seconds: total % 60,
          };
          for (const [unit, value] of Object.entries(parts)) this.set(unit, value);
          const minute = Math.floor(total / 60);
          if (minute !== this.lastMinute) {
            this.lastMinute = minute;
            this.speak(parts);
          }
          // Wake up on the next second (or minute) boundary of the countdown.
          const step = this.seconds ? 1000 : 60000;
          this.timer = setTimeout(() => this.tick(), (left % step) || step);
        }

        set(unit, value) {
          const el = this.nums[unit];
          if (!el) return;
          const text = String(value).padStart(2, '0');
          if (el.textContent === text) return;
          el.textContent = text;
          if (this.animate) {
            el.classList.remove('is-ticking');
            void el.offsetWidth;
            el.classList.add('is-ticking');
          }
        }

        speak(p) {
          if (!this.summary) return;
          const d = this.dataset;
          const bits = [];
          if (p.days) bits.push(`${p.days} ${d.labelDays}`);
          if (p.days || p.hours) bits.push(`${p.hours} ${d.labelHours}`);
          bits.push(`${p.minutes} ${d.labelMinutes}`);
          this.summary.textContent = `: ${bits.join(' ')}`;
        }

        finish() {
          clearTimeout(this.timer);
          const behavior = this.dataset.endBehavior;
          const root = this.closest('.lib-countdown');
          const message = this.querySelector('[data-ended-message]');
          const designMode = window.Shopify && window.Shopify.designMode;
          if (behavior === 'hide' && root && !designMode) {
            root.hidden = true;
            return;
          }
          if (behavior === 'zero') {
            for (const unit of Object.keys(this.nums)) this.set(unit, 0);
            if (this.summary) this.summary.textContent = this.summary.textContent.replace(/\d+/g, '0');
            return;
          }
          this.clock.hidden = true;
          if (this.summary) this.summary.parentElement.hidden = true;
          if (message) message.hidden = false;
          if (root) root.setAttribute('data-ended', '');
        }
      }
    );
  }
{% endjavascript %}

{% schema %}
{
  "name": "Lib · Countdown banner",
  "tag": "section",
  "class": "lib-section",
  "settings": [
    {"type": "header", "content": "Content"},
    {"type": "inline_richtext", "id": "heading", "label": "Heading", "default": "25% off the whole studio"},
    {
      "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": "large"
    },
    {"type": "richtext", "id": "text", "label": "Text", "default": "<p>Vases, candles and tableware, while the kiln cools down for the year.</p>"},

    {"type": "header", "content": "Countdown"},
    {"type": "text", "id": "end_date", "label": "End date", "info": "Format YYYY-MM-DD, for example 2026-12-31.", "default": "2026-12-31"},
    {"type": "text", "id": "end_time", "label": "End time", "info": "24-hour format HH:MM, for example 23:59. Leave blank for 23:59.", "default": "23:59"},
    {
      "type": "select",
      "id": "utc_offset",
      "label": "Time zone of the end time (UTC offset)",
      "info": "The date and time above are read in this offset, so the timer ends at the same moment for every visitor wherever they are. Pick your shop's offset, and remember daylight saving time: for example UTC+01:00 is winter time in Berlin, UTC+02:00 summer time.",
      "options": [
        {"value": "-12:00", "label": "UTC−12:00"},
        {"value": "-11:00", "label": "UTC−11:00"},
        {"value": "-10:00", "label": "UTC−10:00 (Hawaii)"},
        {"value": "-09:30", "label": "UTC−09:30"},
        {"value": "-09:00", "label": "UTC−09:00 (Alaska)"},
        {"value": "-08:00", "label": "UTC−08:00 (Pacific standard)"},
        {"value": "-07:00", "label": "UTC−07:00 (Mountain standard, Pacific daylight)"},
        {"value": "-06:00", "label": "UTC−06:00 (Central standard)"},
        {"value": "-05:00", "label": "UTC−05:00 (Eastern standard, Central daylight)"},
        {"value": "-04:00", "label": "UTC−04:00 (Atlantic, Eastern daylight)"},
        {"value": "-03:30", "label": "UTC−03:30"},
        {"value": "-03:00", "label": "UTC−03:00"},
        {"value": "-02:00", "label": "UTC−02:00"},
        {"value": "-01:00", "label": "UTC−01:00"},
        {"value": "+00:00", "label": "UTC±00:00 (London winter)"},
        {"value": "+01:00", "label": "UTC+01:00 (Central Europe winter, London summer)"},
        {"value": "+02:00", "label": "UTC+02:00 (Central Europe summer)"},
        {"value": "+03:00", "label": "UTC+03:00"},
        {"value": "+03:30", "label": "UTC+03:30"},
        {"value": "+04:00", "label": "UTC+04:00"},
        {"value": "+04:30", "label": "UTC+04:30"},
        {"value": "+05:00", "label": "UTC+05:00"},
        {"value": "+05:30", "label": "UTC+05:30 (India)"},
        {"value": "+05:45", "label": "UTC+05:45"},
        {"value": "+06:00", "label": "UTC+06:00"},
        {"value": "+06:30", "label": "UTC+06:30"},
        {"value": "+07:00", "label": "UTC+07:00"},
        {"value": "+08:00", "label": "UTC+08:00"},
        {"value": "+09:00", "label": "UTC+09:00"},
        {"value": "+09:30", "label": "UTC+09:30"},
        {"value": "+10:00", "label": "UTC+10:00"},
        {"value": "+10:30", "label": "UTC+10:30"},
        {"value": "+11:00", "label": "UTC+11:00"},
        {"value": "+12:00", "label": "UTC+12:00"},
        {"value": "+13:00", "label": "UTC+13:00"},
        {"value": "+14:00", "label": "UTC+14:00"}
      ],
      "default": "+00:00"
    },
    {"type": "checkbox", "id": "show_seconds", "label": "Show seconds", "info": "Without seconds the timer updates once a minute.", "default": true},
    {"type": "text", "id": "label_days", "label": "Days label", "default": "Days"},
    {"type": "text", "id": "label_hours", "label": "Hours label", "default": "Hours"},
    {"type": "text", "id": "label_minutes", "label": "Minutes label", "default": "Minutes"},
    {"type": "text", "id": "label_seconds", "label": "Seconds label", "default": "Seconds", "visible_if": "{{ section.settings.show_seconds }}"},
    {"type": "text", "id": "ends_prefix", "label": "End date prefix", "info": "Shown before the end date, e.g. \"Ends 31 December\". Visible before the timer starts; screen readers hear it with the time left, updated once a minute.", "default": "Ends"},
    {
      "type": "select",
      "id": "end_behavior",
      "label": "When the timer reaches zero",
      "info": "The section also checks the end date when the page is built, so an ended offer does not flash on load.",
      "options": [
        {"value": "hide", "label": "Hide the section"},
        {"value": "message", "label": "Show a message"},
        {"value": "zero", "label": "Keep showing 00"}
      ],
      "default": "message"
    },
    {"type": "text", "id": "ended_message", "label": "Ended message", "default": "This offer has ended. Thank you for shopping with us.", "visible_if": "{{ section.settings.end_behavior == 'message' }}"},

    {"type": "header", "content": "Image"},
    {"type": "image_picker", "id": "image", "label": "Desktop image", "info": "Optional in the bar and banner layouts (shown behind the text); the split layout shows it beside the text."},
    {"type": "image_picker", "id": "image_mobile", "label": "Mobile image", "info": "Optional. Shown below 750px; a portrait crop works best."},
    {
      "type": "select",
      "id": "image_position",
      "label": "Image position on desktop",
      "options": [
        {"value": "left", "label": "Left"},
        {"value": "right", "label": "Right"}
      ],
      "default": "left",
      "visible_if": "{{ section.settings.layout == 'split' }}"
    },
    {"type": "range", "id": "overlay_opacity", "label": "Overlay opacity", "min": 0, "max": 90, "step": 5, "unit": "%", "default": 40, "visible_if": "{{ section.settings.layout != 'split' }}", "info": "The overlay uses the background color."},

    {"type": "header", "content": "Button"},
    {"type": "text", "id": "button_label", "label": "Button label", "default": "Shop the sale"},
    {"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": "primary"
    },
    {"type": "checkbox", "id": "button_new_tab", "label": "Open link in a new tab", "default": false},

    {"type": "header", "content": "Layout"},
    {
      "type": "select",
      "id": "layout",
      "label": "Layout",
      "options": [
        {"value": "bar", "label": "Inline bar"},
        {"value": "banner", "label": "Banner"},
        {"value": "split", "label": "Split image and text"}
      ],
      "default": "banner"
    },
    {
      "type": "select",
      "id": "content_valign_mobile",
      "label": "Content position on mobile",
      "info": "Place the text where the mobile image has space.",
      "options": [
        {"value": "top", "label": "Top"},
        {"value": "center", "label": "Middle"},
        {"value": "bottom", "label": "Bottom"}
      ],
      "default": "center",
      "visible_if": "{{ section.settings.layout == 'banner' }}"
    },
    {
      "type": "select",
      "id": "digit_style",
      "label": "Digit style",
      "options": [
        {"value": "boxes", "label": "Boxes"},
        {"value": "plain", "label": "Plain with colons"}
      ],
      "default": "boxes"
    },
    {"type": "text_alignment", "id": "text_alignment", "label": "Alignment on desktop", "info": "Banner and split layouts.", "default": "center"},
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "Alignment on mobile",
      "options": [
        {"value": "left", "label": "Left"},
        {"value": "center", "label": "Center"},
        {"value": "right", "label": "Right"}
      ],
      "default": "center"
    },

    {"type": "header", "content": "Colors"},
    {"type": "color", "id": "color_bg", "label": "Background", "default": "#1C1917"},
    {"type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color."},
    {"type": "color", "id": "color_text", "label": "Text", "default": "#FAFAF9"},
    {"type": "color", "id": "color_accent", "label": "Accent (button)", "default": "#E8B04A"},
    {"type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#1C1917"},

    {"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": "Animate digits when they change", "info": "Always off when the visitor prefers reduced motion.", "default": true}
  ],
  "presets": [
    {
      "name": "Lib · Countdown banner"
    }
  ]
}
{% endschema %}
```

### snippets/lib-button.liquid

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

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

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

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

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

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

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

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