# Video gallery: free Shopify section

> A grid of video posters that open in an accessible lightbox. Shopify, YouTube or Vimeo videos; nothing third-party loads until a shopper presses play.

Video gallery is a free Shopify section that shows several videos as a grid of poster images with titles and an optional caption such as the length, and plays each one in a lightbox when it is clicked. Each video is a block: a video uploaded to Shopify or a YouTube or Vimeo link. The player is created only on click, YouTube through youtube-nocookie.com and Vimeo with Do Not Track, and removed when the lightbox closes, so no third-party player loads with the page. The lightbox is a native modal dialog that closes with Esc, its close button or a click outside and returns focus to the video's tile. It works in Online Store 2.0 themes without an app.

- **Category:** Image and video
- **Use on:** Home, About, Product, Landing pages
- **Reads:** Shopify-hosted videos or YouTube/Vimeo links
- **Size:** 22 KB in 1 file, with a small script
- **License:** MIT, © 2026 Lintel
- **Page:** https://lintelagency.com/resources/shopify-sections/video-gallery
- **Live demo:** https://lintelagency.com/resources/shopify-sections/demo/lib-video-gallery.html
- **Added:** 2026-11-02

## What it’s for

- How-to, care or styling videos for a product range on one page
- Behind-the-scenes films of the workshop on an About page
- A gallery of campaign or launch films on a landing page

## Editor settings

In the theme editor the section is called “Lib · Video gallery”. IDs, types, labels and defaults come from the section’s schema; the labels are what the theme editor shows.

### Heading

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `heading` | `inline_richtext` | Heading | Inside the 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>Short films from the wheel, the kiln and the packing table.</p>` |  |

### Grid

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `columns_desktop` | `range` | Columns on desktop | `3` | 2–4, step 1 |
| `columns_mobile` | `select` | Columns on mobile | 1 | `1`, `2` |
| `tile_ratio` | `select` | Poster shape | landscape | `landscape`, `square`, `portrait` |
| `image_radius` | `range` | Poster corner radius | `12` | 0–32 px, step 2 |

### Words for shoppers and screen readers

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `play_label` | `text` | Play label | Play video |  |
| `close_label` | `text` | Close button label | Close video |  |

### Layout

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `section_width` | `select` | Width | page | `page`, `full` |
| `text_alignment` | `text_alignment` | Heading alignment on desktop | left |  |
| `text_alignment_mobile` | `select` | Heading alignment on mobile | left | `left`, `center`, `right` |

### Colors

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `color_bg` | `color` | Background | #F4EFE8 |  |
| `color_bg_gradient` | `color_background` | Background gradient | – |  |
| `color_text` | `color` | Text | #2A2420 |  |
| `color_accent` | `color` | Accent (play button, focus ring) | #2A2420 |  |
| `color_accent_text` | `color` | Text on accent | #FFFFFF |  |

### Spacing and visibility

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `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

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `animate` | `checkbox` | Rise in on scroll | `false` |  |

## Blocks

Up to 12 blocks in total.

### Video (`video`)

| ID | Type | Label | Default | Options or range |
| --- | --- | --- | --- | --- |
| `video` | `video` | Shopify-hosted video | – |  |
| `video_url` | `video_url` | Or a YouTube or Vimeo link | – |  |
| `poster` | `image_picker` | Poster image | – |  |
| `title` | `text` | Title | Video title |  |
| `caption` | `text` | Caption | – |  |

## Install

### In Shopify admin

1. Work on a copy, not your live theme: in Shopify admin go to **Online Store > Themes**, open the **…** menu on your current theme and choose **Duplicate**.
2. On the copy, open the **…** menu and choose **Edit code**.
3. In the `sections` folder, add a new file named `lib-video-gallery.liquid` and paste the contents of `sections/lib-video-gallery.liquid` from the source below. Save.
4. Open **Customize** on the copy, choose **Add section** and pick **Lib · Video gallery**. Replace the sample content with your own and set the colors.
5. Preview the copy, then publish it when it looks right.

### With Shopify CLI

From your local theme folder, download the files and push only them to an unpublished theme (find its ID with `shopify theme list`):

```sh
curl -fsSL -o sections/lib-video-gallery.liquid https://lintelagency.com/resources/shopify-sections/files/lib-video-gallery.liquid
shopify theme push --theme <unpublished-theme-id> --only sections/lib-video-gallery.liquid
```

Never push to the live theme: always pass the ID of an unpublished theme and never use `--live` or `--allow-live`. Then add the section in the theme editor as above.

## Files

- [sections/lib-video-gallery.liquid](https://lintelagency.com/resources/shopify-sections/files/lib-video-gallery.liquid): goes in `sections/`

## Questions

### Does it slow the page down?

No player loads with the page, only the poster images, which are lazy loaded. The YouTube, Vimeo or Shopify player is added when a shopper clicks a video and removed when the lightbox closes, which also stops playback.

### Do YouTube and Vimeo set cookies?

Not before a shopper plays a video. YouTube videos then play from youtube-nocookie.com, YouTube's privacy-enhanced mode, and Vimeo videos with Do Not Track on. Whether your store needs consent for the embedded players once they play depends on your market; the section never loads them on its own.

### Is the lightbox accessible?

It is a native modal dialog labelled with the video's title. The rest of the page is inert while it is open; Esc, the close button or a click on the dark area closes it, and focus goes back to the video you opened. The play and close labels are settings, so you can change or translate them.

### What happens without JavaScript?

Each tile is a normal link: a Shopify video opens its file, a YouTube or Vimeo video opens on that site.

### Do YouTube and Vimeo videos need a poster image?

Yes, add one per block; Shopify-hosted videos use their own preview image when the poster is empty. Use a frame from the video itself so the poster matches what plays.

## Source

### sections/lib-video-gallery.liquid

```liquid
{%- comment -%}
  MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections
{%- endcomment -%}
{%- comment -%}
  Video gallery: a grid of video tiles (one block each: a Shopify-hosted video or a YouTube/Vimeo
  link, a poster image, a title and an optional caption such as the length). A tile opens the video
  in one shared lightbox (a native modal <dialog>: labelled, closes with Esc, the close button or a
  click on the backdrop, focus goes back to the tile). Nothing third-party loads with the page: the
  player is created on click (YouTube through youtube-nocookie.com, Vimeo with dnt=1) and removed
  on close. Without JavaScript each tile is a plain link to the video.
  Depends on: none
{%- endcomment -%}

{%- liquid
  assign s = section.settings
  assign heading_tag = s.heading_tag | default: 'h2'
  assign dialog_title_id = 'LibVideoGalleryTitle-' | append: section.id
  assign play_label = s.play_label | default: 'Play video' | escape
-%}

<div
  class="lib-vgallery lib-vgallery--{{ s.section_width }}"
  {% if s.anchor_id != blank %}
    id="{{ s.anchor_id | handleize }}"
  {% endif %}
  data-show="{{ s.show_on }}"
  data-ratio="{{ s.tile_ratio }}"
  {% 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-cols: {{ s.columns_desktop }};
    --lib-cols-m: {{ s.columns_mobile }};
    --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-video-gallery class="lib-vgallery__inner">
    {%- if s.heading != blank or s.text != blank -%}
      <div class="lib-vgallery__header" data-align="{{ s.text_alignment }}" data-align-m="{{ s.text_alignment_mobile }}">
        {%- if s.heading != blank -%}
          <{{ heading_tag }} class="lib-vgallery__heading lib-vgallery__heading--{{ s.heading_size }}">
            {{- s.heading -}}
          </{{ heading_tag }}>
        {%- endif -%}
        {%- if s.text != blank -%}
          <div class="lib-vgallery__text">{{ s.text }}</div>
        {%- endif -%}
      </div>
    {%- endif -%}

    <ul class="lib-vgallery__grid" role="list">
      {%- for block in section.blocks -%}
        {%- liquid
          assign b = block.settings
          assign src_type = ''
          assign src_id = ''
          assign href = ''
          assign embed = ''
          assign ratio = '16 / 9'
          if b.video != blank
            assign src_type = 'shopify'
            for source in b.video.sources
              if source.mime_type == 'video/mp4'
                assign href = source.url
                break
              endif
            endfor
            if b.video.aspect_ratio != blank
              assign ratio = b.video.aspect_ratio
            endif
          elsif b.video_url != blank
            assign src_type = b.video_url.type
            assign src_id = b.video_url.id
            if src_type == 'youtube'
              assign href = 'https://www.youtube.com/watch?v=' | append: src_id
              assign embed = 'https://www.youtube-nocookie.com/embed/' | append: src_id | append: '?autoplay=1&playsinline=1&rel=0'
            elsif src_type == 'vimeo'
              assign href = 'https://vimeo.com/' | append: src_id
              assign embed = 'https://player.vimeo.com/video/' | append: src_id | append: '?dnt=1&autoplay=1'
            endif
          endif
          assign poster = b.poster
          if poster == blank and src_type == 'shopify'
            assign poster = b.video.preview_image
          endif
          assign title = b.title | default: 'Video' | escape
        -%}
        <li class="lib-vgallery__item" {{ block.shopify_attributes }}>
          {%- capture tile_inner -%}
            <span class="lib-vgallery__poster">
              {%- if poster != blank -%}
                {{
                  poster
                  | image_url: width: 1200
                  | image_tag:
                    class: 'lib-vgallery__img',
                    loading: 'lazy',
                    alt: '',
                    sizes: '(min-width: 990px) 33vw, (min-width: 750px) 50vw, 100vw',
                    widths: '400, 600, 800, 1000, 1200'
                }}
              {%- else -%}
                {{ 'lifestyle-1' | placeholder_svg_tag: 'lib-vgallery__img' }}
              {%- endif -%}
              <span class="lib-vgallery__play" aria-hidden="true">
                <svg viewBox="0 0 24 24" width="22" height="22" focusable="false"><path d="M8 5.5v13l10.5-6.5z" fill="currentColor"/></svg>
              </span>
            </span>
            <span class="lib-vgallery__meta">
              <span class="lib-vgallery__title">{{ title }}</span>
              {%- if b.caption != blank -%}
                <span class="lib-vgallery__caption">{{ b.caption | escape }}</span>
              {%- endif -%}
            </span>
          {%- endcapture -%}

          {%- if href != blank -%}
            <a
              class="lib-vgallery__tile"
              href="{{ href }}"
              data-title="{{ title }}"
              data-ratio="{{ ratio }}"
              aria-label="{{ play_label }}: {{ title }}{% if b.caption != blank %}, {{ b.caption | escape }}{% endif %}"
            >
              {{- tile_inner -}}
            </a>
            <template class="lib-vgallery__tpl">
              {%- if src_type == 'shopify' -%}
                {{
                  b.video
                  | video_tag:
                    class: 'lib-vgallery__media',
                    autoplay: true,
                    controls: true,
                    playsinline: true,
                    preload: 'metadata'
                }}
              {%- else -%}
                <iframe
                  class="lib-vgallery__media"
                  src="{{ embed | escape }}"
                  title="{{ title }}"
                  allow="autoplay; encrypted-media; picture-in-picture; fullscreen"
                  allowfullscreen
                  referrerpolicy="strict-origin-when-cross-origin"
                ></iframe>
              {%- endif -%}
            </template>
          {%- else -%}
            <div class="lib-vgallery__tile lib-vgallery__tile--empty">{{- tile_inner -}}</div>
          {%- endif -%}
        </li>
      {%- endfor -%}
    </ul>

    <dialog class="lib-vgallery__dialog" aria-labelledby="{{ dialog_title_id }}">
      <p class="lib-vgallery__dialog-title" id="{{ dialog_title_id }}"></p>
      <button type="button" class="lib-vgallery__close" aria-label="{{ s.close_label | default: 'Close video' | escape }}">
        <svg viewBox="0 0 24 24" width="22" height="22" aria-hidden="true" focusable="false"><path d="M6 6l12 12M18 6 6 18" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg>
      </button>
      <div class="lib-vgallery__frame"></div>
    </dialog>
  </lib-video-gallery>
</div>

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

  .lib-vgallery__header { display: flex; flex-direction: column; align-items: flex-start; text-align: left; margin-bottom: clamp(1.75rem, 4vw, 2.75rem); }
  .lib-vgallery__header[data-align='center'] { align-items: center; text-align: center; }
  .lib-vgallery__header[data-align='right'] { align-items: flex-end; text-align: right; }
  @media (max-width: 749px) {
    .lib-vgallery__header[data-align-m='left'] { align-items: flex-start; text-align: left; }
    .lib-vgallery__header[data-align-m='center'] { align-items: center; text-align: center; }
    .lib-vgallery__header[data-align-m='right'] { align-items: flex-end; text-align: right; }
  }
  .lib-vgallery__heading { margin: 0; font-size: clamp(1.75rem, 3.4vw, 2.75rem); line-height: 1.1; letter-spacing: -0.02em; text-wrap: balance; }
  .lib-vgallery__heading--small { font-size: clamp(1.375rem, 2.2vw, 1.75rem); }
  .lib-vgallery__heading--medium { font-size: clamp(1.5rem, 2.8vw, 2.25rem); }
  .lib-vgallery__heading--xlarge { font-size: clamp(2.25rem, 5vw, 3.75rem); }
  .lib-vgallery__text { margin-top: 0.875rem; max-width: 40rem; font-size: 1.0625rem; line-height: 1.6; opacity: 0.82; }
  .lib-vgallery__text > :first-child { margin-top: 0; }
  .lib-vgallery__text > :last-child { margin-bottom: 0; }

  .lib-vgallery__grid {
    display: grid;
    grid-template-columns: repeat(var(--lib-cols-m), minmax(0, 1fr));
    gap: clamp(1rem, 2.5vw, 1.75rem);
    margin: 0;
    padding: 0;
    list-style: none;
  }
  @media (min-width: 750px) {
    .lib-vgallery__grid { grid-template-columns: repeat(min(var(--lib-cols), 2), minmax(0, 1fr)); }
  }
  @media (min-width: 990px) {
    .lib-vgallery__grid { grid-template-columns: repeat(var(--lib-cols), minmax(0, 1fr)); }
  }

  .lib-vgallery__tile {
    display: flex;
    flex-direction: column;
    gap: 0.875rem;
    color: inherit;
    text-decoration: none;
    border-radius: var(--lib-radius-img);
  }
  .lib-vgallery__tile:focus-visible { outline: 2px solid var(--lib-accent); outline-offset: 4px; }
  .lib-vgallery__poster {
    position: relative;
    display: block;
    overflow: hidden;
    border-radius: var(--lib-radius-img);
    aspect-ratio: 16 / 9;
    background: color-mix(in srgb, var(--lib-text) 8%, transparent);
  }
  .lib-vgallery[data-ratio='square'] .lib-vgallery__poster { aspect-ratio: 1; }
  .lib-vgallery[data-ratio='portrait'] .lib-vgallery__poster { aspect-ratio: 4 / 5; }
  .lib-vgallery__img { display: block; width: 100%; height: 100%; object-fit: cover; }
  .lib-vgallery__img:is(svg) { fill: color-mix(in srgb, var(--lib-text) 25%, transparent); }
  .lib-vgallery__play {
    position: absolute;
    inset: 50% auto auto 50%;
    display: grid;
    place-items: center;
    width: 3.5rem;
    height: 3.5rem;
    margin: -1.75rem 0 0 -1.75rem;
    border-radius: 50%;
    background: var(--lib-accent);
    color: var(--lib-accent-text);
    box-shadow: 0 10px 24px -10px rgb(0 0 0 / 0.5);
  }
  .lib-vgallery__play svg { margin-left: 3px; }
  .lib-vgallery__meta { display: grid; gap: 0.25rem; }
  .lib-vgallery__title { font-weight: 600; font-size: 1.0625rem; line-height: 1.35; }
  .lib-vgallery__caption { font-size: 0.9375rem; opacity: 0.75; }
  @media (prefers-reduced-motion: no-preference) {
    .lib-vgallery__img, .lib-vgallery__play { transition: transform 0.4s ease; }
    .lib-vgallery__tile:hover .lib-vgallery__img { transform: scale(1.03); }
    .lib-vgallery__tile:hover .lib-vgallery__play { transform: scale(1.08); }
  }

  /* Lightbox */
  .lib-vgallery__dialog {
    width: 100vw;
    max-width: none;
    height: 100vh;
    max-height: none;
    margin: 0;
    padding: 4.5rem 1rem 2rem;
    border: 0;
    background: transparent;
    color: #fff;
  }
  .lib-vgallery__dialog[open] { display: grid; grid-template-rows: auto 1fr; justify-items: center; align-items: center; }
  .lib-vgallery__dialog::backdrop { background: rgb(12 10 9 / 0.9); }
  .lib-vgallery__dialog-title {
    position: absolute;
    top: 1.25rem;
    left: 1.25rem;
    right: 4.5rem;
    margin: 0;
    font-weight: 600;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
  }
  .lib-vgallery__frame {
    grid-row: 2;
    width: min(100%, 1280px, calc((100vh - 7rem) * (var(--lib-dialog-ratio, 16 / 9))));
    aspect-ratio: var(--lib-dialog-ratio, 16 / 9);
    background: #000;
  }
  .lib-vgallery__media { display: block; width: 100%; height: 100%; border: 0; }
  .lib-vgallery__close {
    position: absolute;
    top: 0.75rem;
    right: 0.75rem;
    display: grid;
    place-items: center;
    width: 2.75rem;
    height: 2.75rem;
    border: 0;
    border-radius: 50%;
    background: rgb(255 255 255 / 0.14);
    color: #fff;
    cursor: pointer;
  }
  .lib-vgallery__close:hover { background: rgb(255 255 255 / 0.24); }
  .lib-vgallery__close:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

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

{% javascript %}
  if (!customElements.get('lib-video-gallery')) {
    customElements.define(
      'lib-video-gallery',
      class extends HTMLElement {
        connectedCallback() {
          if (this._ready) return;
          this.dialog = this.querySelector('.lib-vgallery__dialog');
          if (!this.dialog || typeof this.dialog.showModal !== 'function') return;
          this._ready = true;
          this.frame = this.dialog.querySelector('.lib-vgallery__frame');
          this.titleEl = this.dialog.querySelector('.lib-vgallery__dialog-title');
          this.closeBtn = this.dialog.querySelector('.lib-vgallery__close');
          this.tiles = Array.from(this.querySelectorAll('a.lib-vgallery__tile'));
          this.tiles.forEach((tile) => tile.setAttribute('aria-haspopup', 'dialog'));

          this.onClick = (event) => {
            const tile = event.target.closest('a.lib-vgallery__tile');
            if (!tile || !this.contains(tile)) return;
            if (event.metaKey || event.ctrlKey || event.shiftKey || event.button === 1) return;
            event.preventDefault();
            this.open(tile);
          };
          this.onClose = () => this.closed();
          this.onCloseBtn = () => this.dialog.close();
          this.onBackdrop = (event) => {
            if (event.target === this.dialog) this.dialog.close();
          };
          this.addEventListener('click', this.onClick);
          this.dialog.addEventListener('close', this.onClose);
          this.dialog.addEventListener('click', this.onBackdrop);
          this.closeBtn.addEventListener('click', this.onCloseBtn);
        }

        disconnectedCallback() {
          if (!this._ready) return;
          this._ready = false;
          this.removeEventListener('click', this.onClick);
          this.dialog.removeEventListener('close', this.onClose);
          this.dialog.removeEventListener('click', this.onBackdrop);
          this.closeBtn.removeEventListener('click', this.onCloseBtn);
          if (this.dialog.open) this.dialog.close();
        }

        // The player comes from the tile's inert template, so nothing loads before this click.
        open(tile) {
          const tpl = tile.nextElementSibling;
          if (!tpl || tpl.tagName !== 'TEMPLATE') return;
          this.opener = tile;
          this.titleEl.textContent = tile.dataset.title || '';
          this.dialog.style.setProperty('--lib-dialog-ratio', tile.dataset.ratio || '16 / 9');
          this.frame.replaceChildren(tpl.content.cloneNode(true));
          this.dialog.showModal();
          this.closeBtn.focus();
          const video = this.frame.querySelector('video');
          if (video) {
            const playing = video.play();
            if (playing && playing.catch) playing.catch(() => {});
          }
        }

        // Removing the player stops playback and any third-party connection it opened.
        closed() {
          this.frame.replaceChildren();
          if (this.opener && this.opener.isConnected) this.opener.focus();
          this.opener = null;
        }
      }
    );
  }
{% endjavascript %}

{% schema %}
{
  "name": "Lib · Video gallery",
  "tag": "section",
  "class": "lib-section",
  "max_blocks": 12,
  "settings": [
    { "type": "header", "content": "Heading" },
    { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Inside the 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>Short films from the wheel, the kiln and the packing table.</p>" },

    { "type": "header", "content": "Grid" },
    { "type": "range", "id": "columns_desktop", "label": "Columns on desktop", "min": 2, "max": 4, "step": 1, "default": 3 },
    {
      "type": "select",
      "id": "columns_mobile",
      "label": "Columns on mobile",
      "options": [
        { "value": "1", "label": "1" },
        { "value": "2", "label": "2" }
      ],
      "default": "1"
    },
    {
      "type": "select",
      "id": "tile_ratio",
      "label": "Poster shape",
      "options": [
        { "value": "landscape", "label": "Landscape (16:9)" },
        { "value": "square", "label": "Square" },
        { "value": "portrait", "label": "Portrait (4:5)" }
      ],
      "default": "landscape"
    },
    { "type": "range", "id": "image_radius", "label": "Poster corner radius", "min": 0, "max": 32, "step": 2, "unit": "px", "default": 12 },

    { "type": "header", "content": "Words for shoppers and screen readers" },
    { "type": "text", "id": "play_label", "label": "Play label", "info": "Read before each video's title.", "default": "Play video" },
    { "type": "text", "id": "close_label", "label": "Close button label", "default": "Close video" },

    { "type": "header", "content": "Layout" },
    {
      "type": "select",
      "id": "section_width",
      "label": "Width",
      "options": [
        { "value": "page", "label": "Page width" },
        { "value": "full", "label": "Full width" }
      ],
      "default": "page"
    },
    { "type": "text_alignment", "id": "text_alignment", "label": "Heading alignment on desktop", "default": "left" },
    {
      "type": "select",
      "id": "text_alignment_mobile",
      "label": "Heading alignment on mobile",
      "options": [
        { "value": "left", "label": "Left" },
        { "value": "center", "label": "Center" },
        { "value": "right", "label": "Right" }
      ],
      "default": "left"
    },

    { "type": "header", "content": "Colors" },
    { "type": "color", "id": "color_bg", "label": "Background", "default": "#F4EFE8" },
    { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." },
    { "type": "color", "id": "color_text", "label": "Text", "default": "#2A2420" },
    { "type": "color", "id": "color_accent", "label": "Accent (play button, focus ring)", "default": "#2A2420" },
    { "type": "color", "id": "color_accent_text", "label": "Text on accent", "default": "#FFFFFF" },

    { "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": "animate", "label": "Rise in on scroll", "info": "Only in browsers that support scroll-driven animations; never when reduced motion is on.", "default": false }
  ],
  "blocks": [
    {
      "type": "video",
      "name": "Video",
      "settings": [
        { "type": "video", "id": "video", "label": "Shopify-hosted video", "info": "Upload in Content > Files. Used before the link below." },
        { "type": "video_url", "id": "video_url", "label": "Or a YouTube or Vimeo link", "accept": ["youtube", "vimeo"], "info": "Loads only when a shopper plays it (youtube-nocookie.com, Vimeo with Do Not Track)." },
        { "type": "image_picker", "id": "poster", "label": "Poster image", "info": "Needed for YouTube and Vimeo. Shopify videos use their own preview image when empty." },
        { "type": "text", "id": "title", "label": "Title", "default": "Video title" },
        { "type": "text", "id": "caption", "label": "Caption", "info": "Optional, for example the length: 2 min." }
      ]
    }
  ],
  "presets": [
    {
      "name": "Lib · Video gallery",
      "blocks": [
        { "type": "video", "settings": { "title": "Throwing a vase" } },
        { "type": "video", "settings": { "title": "Glazing day" } },
        { "type": "video", "settings": { "title": "Packing an order" } }
      ]
    }
  ]
}
{% endschema %}
```

## License

MIT License, © 2026 Lintel. Free to use, change and ship in your own store or in client work; keep the license line at the top of each file.
