{%- comment -%} MIT License · (c) 2026 Lintel · https://lintelagency.com/resources/shopify-sections {%- endcomment -%} {%- comment -%} Stats counter: up to 6 figures with label, optional description and icon. Numbers count up from zero when the section scrolls into view. The final value is in the HTML, so search engines, visitors without JavaScript and visitors who prefer reduced motion see it as typed. The leading number is animated; text before and after it ("$", "+", "%", "/5") stays as is. Layouts: row with dividers, cards, or big numbers beside the heading. {%- endcomment -%} {%- liquid assign s = section.settings assign heading_tag = s.heading_tag | default: 'h2' -%}
{%- if s.heading != blank or s.text != blank -%}
{%- if s.heading != blank -%} <{{ heading_tag }} class="lib-stats__heading lib-stats__heading--{{ s.heading_size }}"> {{- s.heading -}} {%- endif -%} {%- if s.text != blank -%}
{{ s.text }}
{%- endif -%}
{%- endif -%} {%- if section.blocks.size > 0 -%} {%- for block in section.blocks -%} {%- assign b = block.settings -%}
{%- if b.icon != blank -%} {{ b.icon | image_url: width: 160 | image_tag: class: 'lib-stats__icon', loading: 'lazy', alt: '' }} {%- endif -%}

{{ b.number | escape }}

{%- if b.label != blank -%}

{{ b.label | escape }}

{%- endif -%} {%- if b.description != blank -%}
{{ b.description }}
{%- endif -%}
{%- endfor -%}
{%- endif -%}
{% stylesheet %} .lib-stats { 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-line: color-mix(in srgb, var(--lib-text) 16%, transparent); --lib-num: clamp(2.5rem, 5vw, 3.75rem); } .lib-stats[data-size='small'] { --lib-num: clamp(2rem, 3.5vw, 2.75rem); } .lib-stats[data-size='large'] { --lib-num: clamp(3rem, 6vw, 4.5rem); } .lib-stats[data-size='xlarge'] { --lib-num: clamp(3.5rem, 8vw, 6rem); } /* Dividers and padding in the row layout may reach past the section edge; never scroll sideways. */ .lib-stats { overflow-x: clip; } .lib-stats__inner { max-width: 1200px; margin: 0 auto; } .lib-stats--full .lib-stats__inner { max-width: none; } @media (max-width: 749px) { .lib-stats { padding-top: var(--lib-pt-m); padding-bottom: var(--lib-pb-m); } .lib-stats[data-show='desktop'] { display: none; } } @media (min-width: 750px) { .lib-stats[data-show='mobile'] { display: none; } } /* Alignment: desktop, then mobile override. Applies to the header and every stat. */ .lib-stats { --lib-ta: left; --lib-ai: flex-start; } .lib-stats[data-align='center'] { --lib-ta: center; --lib-ai: center; } .lib-stats[data-align='right'] { --lib-ta: right; --lib-ai: flex-end; } @media (max-width: 749px) { .lib-stats[data-align-m='left'] { --lib-ta: left; --lib-ai: flex-start; } .lib-stats[data-align-m='center'] { --lib-ta: center; --lib-ai: center; } .lib-stats[data-align-m='right'] { --lib-ta: right; --lib-ai: flex-end; } } .lib-stats__header { display: flex; flex-direction: column; align-items: var(--lib-ai); margin-bottom: clamp(2rem, 4vw, 3.5rem); text-align: var(--lib-ta); } .lib-stats__heading { max-width: 40rem; margin: 0; font-size: clamp(1.75rem, 3.5vw, 2.75rem); line-height: 1.1; letter-spacing: -0.02em; text-wrap: balance; } .lib-stats__heading--small { font-size: clamp(1.4rem, 2.5vw, 2rem); } .lib-stats__heading--large { font-size: clamp(2rem, 4.5vw, 3.5rem); } .lib-stats__heading--xlarge { font-size: clamp(2.5rem, 6vw, 4.5rem); } .lib-stats__intro { max-width: 36rem; margin-top: 1rem; font-size: 1.0625rem; line-height: 1.6; opacity: 0.75; } .lib-stats__intro > :first-child { margin-top: 0; } .lib-stats__intro > :last-child { margin-bottom: 0; } /* List: columns per device. */ .lib-stats__list { display: grid; grid-template-columns: minmax(0, 1fr); gap: 1rem; } .lib-stats[data-mobile='2'] .lib-stats__list { grid-template-columns: repeat(2, minmax(0, 1fr)); } @media (min-width: 750px) { .lib-stats .lib-stats__list, .lib-stats[data-mobile='2'] .lib-stats__list { grid-template-columns: repeat(min(var(--lib-cols), 3), minmax(0, 1fr)); } } @media (min-width: 990px) { .lib-stats .lib-stats__list, .lib-stats[data-mobile='2'] .lib-stats__list { grid-template-columns: repeat(var(--lib-cols), minmax(0, 1fr)); } } .lib-stats__item { display: flex; flex-direction: column; align-items: var(--lib-ai); min-width: 0; text-align: var(--lib-ta); container-type: inline-size; } .lib-stats__icon { display: block; width: 2.5rem; height: 2.5rem; margin-bottom: 1rem; object-fit: contain; } .lib-stats__number { margin: 0; /* Shrinks with the column so a figure like "12,000+" stays on one line. */ font-size: min(var(--lib-num), 24cqi); font-weight: 700; line-height: 1; letter-spacing: -0.03em; color: var(--lib-accent); font-variant-numeric: tabular-nums; overflow-wrap: anywhere; } .lib-stats__number [data-count] { display: inline-block; } .lib-stats__label { margin: 0.75rem 0 0; font-size: 1rem; font-weight: 600; line-height: 1.35; } .lib-stats__desc { margin-top: 0.4rem; font-size: 0.9375rem; line-height: 1.55; opacity: 0.7; } .lib-stats__desc > :first-child { margin-top: 0; } .lib-stats__desc > :last-child { margin-bottom: 0; } /* Row: hairline dividers between stats. Each stat draws a line on its start and top edge; the list clips the ones on the outer edge, so dividers only appear between stats. The list is pulled out by the stat padding, so the first column lines up with the heading. */ .lib-stats[data-layout='row'] { --lib-px: 1rem; } .lib-stats[data-layout='row'] .lib-stats__list { gap: 0; margin-inline: calc(-1 * var(--lib-px)); overflow: hidden; } .lib-stats[data-layout='row'] .lib-stats__item { padding: 1.5rem var(--lib-px); box-shadow: -1px 0 0 var(--lib-line), 0 -1px 0 var(--lib-line); } @media (min-width: 750px) { .lib-stats[data-layout='row'] { --lib-px: clamp(1rem, 2.5vw, 2rem); } .lib-stats[data-layout='row'] .lib-stats__item { padding-block: 1.75rem; } } /* Cards */ .lib-stats[data-layout='cards'] .lib-stats__item { padding: clamp(1.25rem, 3vw, 2rem); border-radius: var(--lib-radius); background: var(--lib-card); } @media (min-width: 750px) { .lib-stats[data-layout='cards'] .lib-stats__list { gap: 1.25rem; } } /* Beside: heading on the left, big numbers on the right with a rule above each. */ .lib-stats[data-layout='beside'] .lib-stats__list { gap: 2rem 1.5rem; } .lib-stats[data-layout='beside'] .lib-stats__item { padding-top: 1.25rem; border-top: 1px solid var(--lib-line); } @media (min-width: 990px) { .lib-stats[data-layout='beside'] .lib-stats__inner { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 3fr); gap: clamp(3rem, 6vw, 6rem); align-items: start; } .lib-stats[data-layout='beside'] .lib-stats__header { margin-bottom: 0; position: sticky; top: 2rem; } .lib-stats[data-layout='beside'] .lib-stats__list, .lib-stats[data-layout='beside'][data-mobile='2'] .lib-stats__list { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 2.5rem 2rem; } } {% endstylesheet %} {% javascript %} if (!customElements.get('lib-stats-counter-group')) { customElements.define( 'lib-stats-counter-group', class extends HTMLElement { connectedCallback() { if (this.started || !this.hasAttribute('data-animate')) return; if (!('IntersectionObserver' in window)) return; if (matchMedia('(prefers-reduced-motion: reduce)').matches) return; this.items = []; for (const el of this.querySelectorAll('[data-count]')) { const item = this.parse(el.textContent); if (!item) continue; item.el = el; this.items.push(item); } if (!this.items.length) return; // Keep each number's final width so the layout does not move while it counts. for (const item of this.items) { item.el.style.minWidth = `${item.el.getBoundingClientRect().width}px`; item.el.textContent = this.format(item, 0); } this.observer = new IntersectionObserver( (entries) => { if (!entries.some((e) => e.isIntersecting)) return; this.observer.disconnect(); this.run(); }, { threshold: 0.3 } ); this.observer.observe(this); } disconnectedCallback() { if (this.observer) this.observer.disconnect(); if (this.frame) cancelAnimationFrame(this.frame); // Put the typed values back if the element leaves mid-count (e.g. theme editor re-render). if (this.items) for (const item of this.items) item.el.textContent = item.text; } // "12,000+" -> prefix "", number 12000, suffix "+", 0 decimals, "," as thousands separator. parse(text) { const match = text.match(/^(\D*?)(\d(?:[\d.,'   ]*\d)?)([\s\S]*)$/); if (!match) return null; const [, prefix, raw, suffix] = match; // The last "." or "," is the decimal point when both appear, or when it appears once // and is not followed by exactly three digits ("4.8", "2,5"). Otherwise it groups thousands. const last = Math.max(raw.lastIndexOf('.'), raw.lastIndexOf(',')); let isDecimal = false; if (last >= 0) { const sep = raw[last]; const tail = raw.length - last - 1; const both = raw.includes('.') && raw.includes(','); isDecimal = both || (raw.split(sep).length === 2 && tail !== 3); } const intPart = isDecimal ? raw.slice(0, last) : raw; const fracPart = isDecimal ? raw.slice(last + 1) : ''; const groupMatch = intPart.match(/\d([.,'   ])\d/); const value = parseFloat(intPart.replace(/\D/g, '') + (fracPart ? '.' + fracPart : '')); if (!isFinite(value)) return null; return { text, prefix, suffix, value, decimals: fracPart.length, decimal: isDecimal ? raw[last] : '.', group: groupMatch ? groupMatch[1] : '', }; } format(item, n) { let [int, frac] = n.toFixed(item.decimals).split('.'); if (item.group) int = int.replace(/\B(?=(\d{3})+(?!\d))/g, item.group); return item.prefix + int + (frac ? item.decimal + frac : '') + item.suffix; } run() { this.started = true; const duration = (parseFloat(this.dataset.duration) || 2) * 1000; const start = performance.now(); const tick = (now) => { const t = Math.min(1, (now - start) / duration); const eased = 1 - Math.pow(1 - t, 3); for (const item of this.items) { item.el.textContent = t < 1 ? this.format(item, item.value * eased) : item.text; } if (t < 1) this.frame = requestAnimationFrame(tick); else this.frame = null; }; this.frame = requestAnimationFrame(tick); } } ); } {% endjavascript %} {% schema %} { "name": "Lib · Stats counter", "tag": "section", "class": "lib-section", "max_blocks": 6, "settings": [ { "type": "header", "content": "Content" }, { "type": "inline_richtext", "id": "heading", "label": "Heading", "default": "Small studio, steady hands" }, { "type": "select", "id": "heading_tag", "label": "Heading tag", "info": "Use H1 only when this is the page's main heading.", "options": [ { "value": "h1", "label": "H1" }, { "value": "h2", "label": "H2" }, { "value": "h3", "label": "H3" }, { "value": "p", "label": "Paragraph" } ], "default": "h2" }, { "type": "select", "id": "heading_size", "label": "Heading size", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" }, { "value": "xlarge", "label": "Extra large" } ], "default": "medium" }, { "type": "richtext", "id": "text", "label": "Text", "default": "

A few numbers from our workshop. Replace them with your store's own figures.

" }, { "type": "header", "content": "Layout" }, { "type": "select", "id": "layout", "label": "Layout", "options": [ { "value": "row", "label": "Row with dividers" }, { "value": "cards", "label": "Cards" }, { "value": "beside", "label": "Big numbers beside the heading" } ], "default": "row" }, { "type": "select", "id": "number_size", "label": "Number size", "options": [ { "value": "small", "label": "Small" }, { "value": "medium", "label": "Medium" }, { "value": "large", "label": "Large" }, { "value": "xlarge", "label": "Extra large" } ], "default": "large" }, { "type": "range", "id": "columns", "label": "Columns on desktop", "info": "Beside the heading, two columns on desktop.", "min": 2, "max": 6, "step": 1, "default": 4 }, { "type": "select", "id": "columns_mobile", "label": "Columns on mobile", "options": [ { "value": "1", "label": "1 column" }, { "value": "2", "label": "2 columns" } ], "default": "2" }, { "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": "Alignment on desktop", "default": "left" }, { "type": "select", "id": "text_alignment_mobile", "label": "Alignment on mobile", "options": [ { "value": "left", "label": "Left" }, { "value": "center", "label": "Center" }, { "value": "right", "label": "Right" } ], "default": "left" }, { "type": "range", "id": "card_radius", "label": "Card corner radius", "min": 0, "max": 32, "step": 2, "unit": "px", "default": 16, "visible_if": "{{ section.settings.layout == 'cards' }}" }, { "type": "header", "content": "Colors" }, { "type": "color", "id": "color_bg", "label": "Background", "default": "#1F2A24" }, { "type": "color_background", "id": "color_bg_gradient", "label": "Background gradient", "info": "Optional. Covers the background color." }, { "type": "color", "id": "color_text", "label": "Text", "default": "#F3EFE6" }, { "type": "color", "id": "color_number", "label": "Numbers", "default": "#E3B97A" }, { "type": "color", "id": "color_card", "label": "Card", "default": "#2A3830", "visible_if": "{{ section.settings.layout == 'cards' }}" }, { "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": 88 }, { "type": "range", "id": "padding_bottom", "label": "Bottom padding on desktop", "min": 0, "max": 200, "step": 4, "unit": "px", "default": 88 }, { "type": "range", "id": "padding_top_mobile", "label": "Top padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 56 }, { "type": "range", "id": "padding_bottom_mobile", "label": "Bottom padding on mobile", "min": 0, "max": 160, "step": 4, "unit": "px", "default": 56 }, { "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": "Count up when scrolled into view", "info": "Off for visitors who prefer reduced motion; they see the final numbers.", "default": true }, { "type": "range", "id": "duration", "label": "Count duration", "min": 1, "max": 4, "step": 0.5, "unit": "s", "default": 2, "visible_if": "{{ section.settings.animate }}" } ], "blocks": [ { "type": "stat", "name": "Stat", "limit": 6, "settings": [ { "type": "text", "id": "number", "label": "Number", "info": "For example 12,000+, 98%, $2.5M or 4.9/5. The first number counts up; text before and after it stays.", "default": "100+" }, { "type": "text", "id": "label", "label": "Label", "default": "Label" }, { "type": "richtext", "id": "description", "label": "Description", "info": "Optional." }, { "type": "image_picker", "id": "icon", "label": "Icon", "info": "Optional. Shown at 40px above the number." } ] } ], "presets": [ { "name": "Lib · Stats counter", "blocks": [ { "type": "stat", "settings": { "number": "12,000+", "label": "Pieces made by hand", "description": "

Thrown, glazed and fired in our studio.

" } }, { "type": "stat", "settings": { "number": "4.8/5", "label": "Average review", "description": "

From verified orders on our store.

" } }, { "type": "stat", "settings": { "number": "38", "label": "Glaze colors", "description": "

Mixed in small batches, by hand.

" } }, { "type": "stat", "settings": { "number": "9 yrs", "label": "In the same studio", "description": "

Same wheel, same kiln, same street.

" } } ] } ] } {% endschema %}