Shopify themes, liquid, logos, and UX
When reading the link below, it says Dawn will have a slider. Either I can't find it, or it hasn't been shipped in the last version. Does anyone have insight into this?
https://ux.shopify.com/next-generation-theme-design-5aae94f6d44c
Edit: I just found a slider example in a section named "featured-blog" which doesn't seem enabled. I don't know if it hasn't been completed yet or if we're supposed to figure out how to implement it on our own.
Solved! Go to the solution
This is an accepted solution.
There is a slider component that seems to be used on mobile only, in some sections.
The component CSS and JS can be found at:
/assets/component-slider.css
/assets/slider.js
It uses CSS Flex and you'd have to tweak the CSS to show it on desktop too.
New Slider is available in DAWN, check Editor @FervEngineering
Thanks
@Anthony_David_ Are you seeing it in your customizer? I see the code for it when I pull it locally, but it’s not available as a section or block.
I don't see it either. Any pointer will be helpful.
This is an accepted solution.
There is a slider component that seems to be used on mobile only, in some sections.
The component CSS and JS can be found at:
/assets/component-slider.css
/assets/slider.js
It uses CSS Flex and you'd have to tweak the CSS to show it on desktop too.
@marcoswata Thanks, I found its usage in featured-collection.liquid and is used as below-looks like they created a custom HTML element for it. I'll accept your answer as the solution.
<slider-component class="slider-mobile-gutter">
Thanks for the quick solution. Can you also explain how can we enable this product slide on the desktop version also.
I am struggling to be slider to be displayed on desktop also on the DAWN theme
It's going to be a little bit involved to get it to work. You'll need to get the products to keep the horizontal grid and unhide the slider buttons like mobile uses. All the classes are already available, but you might want to make an alternate version so it's not confusing when someone's trying to debug and they see class="mobile-xxx" when the viewport is at desktop size.
I tried everything, by playing with classes and liquid, ALAS! no workaround found!
There is a solution but no accepted! Unless, I don't copy the slider component, and duplicate it and one I call for desktop and other for mobile/tablet version, it doesn't work for desktop!
But duplicating the code is nothing but redundancy.
Does anyone has any solution yet???
+1 for this, still looking for a solution aswell. Hope this gets updated soon
I have achieved adding a slider to the product page images by using the slick slider.
Notes:
There is 4 files to edit:
Step 1: For main-product.liquid add the following and comment / remove the "<slider-component class="slider-mobile-gutter">" start and end tags. Add the below code before the <slider-component class="slider-mobile-gutter"> tag we removed or commented out.
<section class="lazy slider" data-sizes="50vw"> {%- for media in product.media -%} <div> {%- unless media.id == product.selected_or_first_available_variant.featured_media.id -%} <li class="product__media-item grid__item slider__slide{% if media.media_type != 'image' %} product__media-item--full{% endif %}{% if section.settings.hide_variants and variant_images contains media.src %} product__media-item--variant{% endif %}" data-media-id="{{ section.id }}-{{ media.id }}"> {% render 'product-thumbnail', media: media, position: forloop.index, loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true %} </li> {%- endunless -%} </div> {%- endfor -%} </section> |
Step 2: For section-main-product.css add the following to the bottom of the file
.slider { .slick-slide { .slick-slide img { .slick-prev:before,
.slick-current { |
Step 3: For header.liquid add the following just after {%- endif -%} on line 9
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.css"/> <link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick-theme.css"/> |
Step 4: For footer.liquid add the following to the end of the file
<script src="https://code.jquery.com/jquery-2.2.0.min.js" type="text/javascript"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/slick-carousel/1.8.1/slick.js" type="text/javascript" charset="utf-8"></script> <script type="text/javascript"> $(document).on('ready', function() { $(".lazy").slick({ lazyLoad: 'ondemand', // ondemand progressive anticipated infinite: true }); }); </script> |
That's like adding more code to the theme!
Since DAWN is the fastest theme, why to burden it with more code.
There should be a way to use that <slider-component> in desktop mode too!
@smj_90 for sure it's adding more but not too much. I didn't notice any impact on performance. I started to look into how to implement the built-in slider component but it would take alot of time I don't have to figure out what CSS @media quires need to be modified or disabled for force showing it on desktop. Not to mention any other code that may be altering how it looks.
For now I think this will have to do as the best free option as I did not come across any other step by step solutions. I came across the barstool dev one but for the life of me couldn't get it to work.
The slideshow works but the images still stay below it which is annoying..
we added the slick slider in dawn theme with variants swatches.
Demo Url - https://devils-jitu.myshopify.com/products/test-product
password - test
Hi there, i checked your code, it's working only for 1st product only. For the rest of the products, it's not working. also the first product pictures keep on changing rest all are static.
i guess you forgot to call 'product unique id' when making that slider, though this approach is amazing!
@shadowsfall118 Your solution has been the only one that I have seen that works to implement a slider on Dawn's product page. Very detailed instructions worked great. Thanks
Can't switch image by selecting attribute
Thank you! This was super helpful
Hi,
Fortunately, I manage to customize the Dawn code and added a product slider. I preserve the slider-component code and mostly customize the CSS. Oh I added a javascript code also so when you click the image, the large image container should show the clicked image. Not only that, my product images has different heights, so I added a code so that the slider adjust to the height of the large image. Do not worry. I don't work with any app or any website. The code is free. Just give me a thumbs up if I solve your problem.
Grab the code here with instructions https://made4uo.com/blogs/journey-to-awesome-shopify-website/product-slider-for-dawn-theme
@made4Uo nice work!
I've quickly put together a version of a simple desktop slideshow for the Dawn theme here:
https://dawn-slideshow.myshopify.com/products/example-t-shirt
Password: rohwah
It's not the best way of doing that, but it's an easy one for those who are not used to coding.
Basically:
1. sections/main-product.liquid
2. assets/component.slider.css (probably easier if you just copy the source code):
3. snippets/product-thumbnail.liquid (optional; for fixed image dimensions):
@marcoswata exactly what i was looking for! GREAT JOB!!
**EDIT: Found the code files. Thanks!**
Hi @marcoswata ! This looks super easy and I really like how it looks. Code you let me which <ul> lines to add the class to? There's a few in there. And which lines of the {%if%} statement to remove.
And do I need to replicate each instance of the slider--mobile to a new slider--desktop in the component-slider.css?
Thank you!
Hi @RachelFox sorry I got a bit lost in the conversation here, what exactly do you need done now? Feel free to send me a message. This conversation was around the first versions of Dawn, now we're on version 7 and things are probably very different.
@made4Uo I followed your guide with a base dawn theme as I like how you implemented the thumbnails but i'm not getting the large main image (only thumbnails). Any thoughts?
I also have the same problem, i have even tried deleting and copying the entire css text that is on the website, as this is probably a direct copy from your dev site - i thought it was me. The css file in the video and the one on the site are different, but only because with the cut and paste one you include all the code that has been commented out i think.
I have gone step by step by with the video and also by just following the steps on the page, it is a fresh "Dawn" install so no other customisations have taken place as yet.
I think there is a problem with the css style sheet or the script as it doesnt appear to be calling or linking up with the large image in any way, certainly the large image isnt displayed and the slider does not move.
When you do the first step you are left with giant product images on the product page, which is fine, then you implement the css file and the images shrink to what you would expect the ones along the bottom of the carousel (but with no large image showing). This at least does seem have a slide component that works, but when you change the global.js file to "3" and "4" the slider stops moving and you are left with 4 (or however many images you have on your product page) thumbnail images that just run along the bottom....
I have included a screenshot to show you what i mean, if you could take a look at your code or even try it on a fresh dawn install that would be great as others have shown how to do a slider on the main product images, which is great, but i need a carousel for a design i am working on.
Thanks
Hi,
Thank you for letting me know. The large image is the original image from Dawn, which has the modal, the zoom bottom. I edited the new Dawn theme, which is unedited.
The bold code is responsible for the big image. The red code is from Dawn. Take note too. In the video, I did not comment out the whole CSS file. Can you post your code?
This is after following step by step
main-product.liquid
{% comment %}theme-check-disable TemplateLength{% endcomment %} <link rel="stylesheet" href="{{ 'component-deferred-media.css' | asset_url }}" media="print" onload="this.media='all'"> <script src="{{ 'product-form.js' | asset_url }}" defer="defer"></script> {%- assign first_3d_model = product.media | where: "media_type", "model" | first -%} <section class="page-width"> const resizeObserver = new ResizeObserver(entries => <li class="large-image-item product__media-item grid__item slider__slide <slider-component > {%- for block in section.blocks -%} <quantity-input class="quantity"> <script type="application/json"> <noscript class="product-form__noscript-wrapper-{{ section.id }}"> {%- form 'product', product, id: product_form_id, class: 'form', novalidate: 'novalidate', data-type: 'add-to-cart-form' -%} {{ 'component-pickup-availability.css' | asset_url | stylesheet_tag }} {%- assign pick_up_availabilities = product.selected_or_first_available_variant.store_availabilities | where: 'pick_up_enabled', true -%} <pickup-availability class="product__pickup-availabilities no-js-hidden" <script src="{{ 'pickup-availability.js' | asset_url }}" defer="defer"></script> <product-modal id="ProductModal-{{ section.id }}" class="product-media-modal media-modal"> <div class="product-media-modal__content" role="document" aria-label="{{ 'products.modal.label' | t }}" tabindex="0"> {%- for media in product.media -%} unless media.id == product.selected_or_first_available_variant.featured_media.id {% assign popups = section.blocks | where: "type", "popup" %} {% javascript %} hide() { show(opener) { showActiveMedia() { const container = this.querySelector('[role="document"]'); if (activeMedia.nodeName == 'DEFERRED-MEDIA' && activeMediaContent && activeMediaContent.querySelector('.js-youtube')) customElements.define('product-modal', ProductModal); <script> return (msie > 0 || trident > 0); if (!isIE()) return; document.querySelector('#Variants-{{ section.id }}').addEventListener('change', function(event) { {%- if first_3d_model -%} <script src="{{ 'product-model.js' | asset_url }}" defer></script> <script type="application/ld+json"> {% schema %} |
component-slider.css
.slider-container { .large-image { .large-image-item { .large-image-item:not(:first-child){ ul { slider-component { .slider__slide { .product-slider-box { .product-slider { .product-slider:not(:first-child){ .slide-image {
@media screen and (max-width: 989px) {
.slider.slider--mobile .slider__slide { @media screen and (max-width: 989px) { .slider.slider--tablet .slider__slide { /* Scrollbar */ .slider { /* included in ul */ .slider::-webkit-scrollbar { /* included in ul */ .no-js .slider { .no-js .slider::-webkit-scrollbar { .slider::-webkit-scrollbar-thumb { .slider::-webkit-scrollbar-track { .slider-counter { .slider-buttons { /* @media screen and (min-width: 750px) { .slider-button { .slider-button:not([disabled]):hover { .slider-button .icon { .slider-button[disabled] .icon { .slider-button--next .icon { .slider-button--prev .icon { .slider-button--next:not([disabled]):hover .icon { .slider-button--prev:not([disabled]):hover .icon { |
global.js
function getFocusableElements(container) { const trapFocusHandlers = {}; function trapFocus(container, elementToFocus = container) { removeTrapFocus(); trapFocusHandlers.focusin = (event) => { document.addEventListener('keydown', trapFocusHandlers.keydown); trapFocusHandlers.focusout = function() { trapFocusHandlers.keydown = function(event) { // On the first focusable element and tab backward, focus the last element. document.addEventListener('focusout', trapFocusHandlers.focusout); elementToFocus.focus(); // Here run the querySelector to figure out if the browser supports :focus-visible or not and run code based on it. function focusVisiblePolyfill() { window.addEventListener('keydown', (event) => { window.addEventListener('mousedown', (event) => { window.addEventListener('focus', () => { if (mouseClick) return; currentFocusedElement = document.activeElement; }, true); function pauseAllMedia() { function removeTrapFocus(elementToFocus = null) { if (elementToFocus) elementToFocus.focus(); function onKeyUpEscape(event) { const openDetailsElement = event.target.closest('details[open]'); const summaryElement = openDetailsElement.querySelector('summary'); class QuantityInput extends HTMLElement { this.querySelectorAll('button').forEach( onButtonClick(event) { event.target.name === 'plus' ? this.input.stepUp() : this.input.stepDown(); customElements.define('quantity-input', QuantityInput); function debounce(fn, wait) { const serializeForm = form => { for (const key of formData.keys()) { if (regex.test(key)) { return JSON.stringify(obj); function fetchConfig(type = 'json') { /* Shopify.bind = function(fn, scope) { Shopify.setSelectorByValue = function(selector, value) { Shopify.addListener = function(target, eventName, callback) { Shopify.postLink = function(path, options) { var form = document.createElement("form"); for(var key in params) { Shopify.CountryProvinceSelector = function(country_domid, province_domid, options) { Shopify.addListener(this.countryEl, 'change', Shopify.bind(this.countryHandler,this)); this.initCountry(); Shopify.CountryProvinceSelector.prototype = { initProvince: function() { countryHandler: function(e) { this.clearOptions(this.provinceEl); this.provinceContainer.style.display = ""; clearOptions: function(selector) { setOptions: function(selector, values) { class MenuDrawer extends HTMLElement { this.mainDetailsToggle = this.querySelector('details'); if (navigator.platform === 'iPhone') document.documentElement.style.setProperty('--viewport-height', `${window.innerHeight}px`); this.addEventListener('keyup', this.onKeyUp.bind(this)); bindEvents() { addAccessibilityAttributes(summaryElements) { onKeyUp(event) { const openDetailsElement = event.target.closest('details[open]'); openDetailsElement === this.mainDetailsToggle ? this.closeMenuDrawer(this.mainDetailsToggle.querySelector('summary')) : this.closeSubmenu(openDetailsElement); onSummaryClick(event) { if (detailsElement === this.mainDetailsToggle) { setTimeout(() => { openMenuDrawer(summaryElement) { closeMenuDrawer(event, elementToFocus = false) { onFocusOut(event) { onCloseButtonClick(event) { closeSubmenu(detailsElement) { closeAnimation(detailsElement) { const handleAnimation = (time) => { const elapsedTime = time - animationStart; if (elapsedTime < 400) { window.requestAnimationFrame(handleAnimation); customElements.define('menu-drawer', MenuDrawer); class HeaderDrawer extends MenuDrawer { openMenuDrawer(summaryElement) { setTimeout(() => { summaryElement.setAttribute('aria-expanded', true); customElements.define('header-drawer', HeaderDrawer); class ModalDialog extends HTMLElement { show(opener) { hide() { class ModalOpener extends HTMLElement { const button = this.querySelector('button'); if (!button) return; class DeferredMedia extends HTMLElement { loadContent() { this.setAttribute('loaded', true); customElements.define('deferred-media', DeferredMedia); class SliderComponent extends HTMLElement { if (!this.slider || !this.nextButton) return; const resizeObserver = new ResizeObserver(entries => this.initPages()); this.slider.addEventListener('scroll', this.update.bind(this)); initPages() { update() { if (this.currentPage === 1) { if (this.currentPage === this.totalPages) { this.pageCount.textContent = this.currentPage; onButtonClick(event) { customElements.define('slider-component', SliderComponent); class VariantSelects extends HTMLElement { onVariantChange() { if (!this.currentVariant) { updateOptions() { updateMasterId() { updateMedia() { if (!newMedia) return; updateURL() { updateVariantInput() { updatePickupAvailability() { if (this.currentVariant && this.currentVariant.available) { removeErrorMessage() { const productForm = section.querySelector('product-form'); renderProductInfo() { if (source && destination) destination.innerHTML = source.innerHTML; const price = document.getElementById(`price-${this.dataset.section}`); if (price) price.classList.remove('visibility-hidden'); toggleAddButton(disable = true, text, modifyClass = true) { if (!addButton) return; if (disable) { if (!modifyClass) return; setUnavailable() { getVariantData() { customElements.define('variant-selects', VariantSelects); class VariantRadios extends VariantSelects { updateOptions() { customElements.define('variant-radios', VariantRadios); |
See attached after following your guide.
Looks like main image block is not being displayed and the JS is throwing an error because it can't grab the image URL of the thumb clicked.
'Uncaught TypeError: Cannot read properties of null (reading 'parentElement')'
Thank you @shadowsfall118 for also looking into this.
I have attached my files for reference as well @made4Uo - i appreciate your time.
Thanks
Stewart
{% comment %}theme-check-disable TemplateLength{% endcomment %}
{{ 'section-main-product.css' | asset_url | stylesheet_tag }}
{{ 'component-accordion.css' | asset_url | stylesheet_tag }}
{{ 'component-price.css' | asset_url | stylesheet_tag }}
{{ 'component-rte.css' | asset_url | stylesheet_tag }}
{{ 'component-slider.css' | asset_url | stylesheet_tag }}
{{ 'component-rating.css' | asset_url | stylesheet_tag }}
<link rel="stylesheet" href="{{ 'component-deferred-media.css' | asset_url }}" media="print" onload="this.media='all'">
<script src="{{ 'product-form.js' | asset_url }}" defer="defer"></script>
<script>
function newLargeImage(x) {
let clickedImage = x.dataset.thumbId;
const newImage = document.querySelector(`[data-media-id="${clickedImage}"]`);
const parentData = newImage.parentElement;
parentData.prepend(newImage);
const resizeObserver = new ResizeObserver(entries =>
x.clientHeight)
resizeObserver.observe(document.body)
const imgHt = x.clientHeight * 4.5;
document.getElementById('large-image').style.paddingBottom = imgHt + 'px';
};
</script>
{%- assign first_3d_model = product.media | where: "media_type", "model" | first -%}
{%- if first_3d_model -%}
{{ 'component-product-model.css' | asset_url | stylesheet_tag }}
<link id="ModelViewerStyle" rel="stylesheet" href="https://cdn.shopify.com/shopifycloud/model-viewer-ui/assets/v1.0/model-viewer-ui.css" media="print" onload="this.media='all'">
<link id="ModelViewerOverride" rel="stylesheet" href="{{ 'component-model-viewer-ui.css' | asset_url }}" media="print" onload="this.media='all'">
{%- endif -%}
<section class="page-width">
<div class="product grid grid--1-col {% if product.media.size > 0 %}grid--2-col-tablet{% else %}product--no-media{% endif %}">
<div class="grid__item product__media-wrapper">
<div class="slider-container" >
<a class="skip-to-content-link button visually-hidden" href="#ProductInfo-{{ section.id }}">
{{ "accessibility.skip_to_product_info" | t }}
</a>
<ul class="large-image" id="large-image">
{%- assign variant_images = product.images | where: 'attached_to_variant?', true | map: 'src' -%}
{%- if product.selected_or_first_available_variant.featured_media != null -%}
{%- assign media = product.selected_or_first_available_variant.featured_media -%}
{%- for media in product.media -%}
<li class="large-image-item product__media-item grid__item slider__slide
{% if media.media_type != 'image' %}
product__media-item--full{% endif %}"
data-media-id="{{ section.id }}-{{ media.id }}">
{% render 'product-thumbnail', media: media, position: forloop.index, loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true %}
</li>
{%- endfor -%}
{%- endif -%}
</ul>
<slider-component >
<ul class="product-slider-box slider" role="list">
{%- for media in product.media -%}
<li class="product-slider slider__slide">
<img class="slide-image"
onclick="newLargeImage(this)"
data-thumb-id="{{ section.id }}-{{ media.id }}"
src="{{ media.preview_image | img_url: 'large', scale: 4 }}"
alt="{{ thumbnailAlt }}">
</li>
{% endfor %}
</ul>
<div class="slider-buttons {% if product.media.size < 2 %}
small-hide{% endif %}">
<button type="button" class="slider-button slider-button--prev bigger-slider" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}">{% render 'icon-caret' %}</button>
<div class="slider-counter caption">
<span class="slider-counter--current">1</span>
<span aria-hidden="true"> / </span>
<span class="visually-hidden">{{ 'accessibility.of' | t }}</span>
<span class="slider-counter--total">{% if section.settings.hide_variants %}{{ product.media.size | minus: variant_images.size | plus: 1 }}{% else %}{{ product.media.size }}{% endif %}</span>
</div>
<button type="button" class="slider-button slider-button--next bigger-slider" name="next" aria-label="{{ 'accessibility.next_slide' | t }}">{% render 'icon-caret' %}</button>
</div>
</slider-component>
</div>
<!-- ORIGINAL SLIDER CODE
<slider-component class="slider-mobile-gutter">
<a class="skip-to-content-link button visually-hidden" href="#ProductInfo-{{ section.id }}">
{{ "accessibility.skip_to_product_info" | t }}
</a>
<ul class="product__media-list grid grid--peek list-unstyled slider slider--mobile" role="list">
{%- assign variant_images = product.images | where: 'attached_to_variant?', true | map: 'src' -%}
{%- if product.selected_or_first_available_variant.featured_media != null -%}
{%- assign media = product.selected_or_first_available_variant.featured_media -%}
<li class="product__media-item grid__item slider__slide{% if media.media_type != 'image' %} product__media-item--full{% endif %}{% if section.settings.hide_variants and variant_images contains media.src %} product__media-item--variant{% endif %}" data-media-id="{{ section.id }}-{{ media.id }}">
{% render 'product-thumbnail', media: media, position: 'featured', loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true %}
</li>
{%- endif -%}
{%- for media in product.media -%}
{%- unless media.id == product.selected_or_first_available_variant.featured_media.id -%}
<li class="product__media-item grid__item slider__slide{% if media.media_type != 'image' %} product__media-item--full{% endif %}{% if section.settings.hide_variants and variant_images contains media.src %} product__media-item--variant{% endif %}" data-media-id="{{ section.id }}-{{ media.id }}">
{% render 'product-thumbnail', media: media, position: forloop.index, loop: section.settings.enable_video_looping, modal_id: section.id, xr_button: true %}
</li>
{%- endunless -%}
{%- endfor -%}
</ul>
<div class="slider-buttons no-js-hidden{% if product.media.size < 2 %} small-hide{% endif %}">
<button type="button" class="slider-button slider-button--prev" name="previous" aria-label="{{ 'accessibility.previous_slide' | t }}">{% render 'icon-caret' %}</button>
<div class="slider-counter caption">
<span class="slider-counter--current">1</span>
<span aria-hidden="true"> / </span>
<span class="visually-hidden">{{ 'accessibility.of' | t }}</span>
<span class="slider-counter--total">{% if section.settings.hide_variants %}{{ product.media.size | minus: variant_images.size | plus: 1 }}{% else %}{{ product.media.size }}{% endif %}</span>
</div>
<button type="button" class="slider-button slider-button--next" name="next" aria-label="{{ 'accessibility.next_slide' | t }}">{% render 'icon-caret' %}</button>
</div>
</slider-component> -->
{%- if first_3d_model -%}
<button
class="button button--full-width product__xr-button"
type="button"
aria-label="{{ 'products.product.xr_button_label' | t }}"
data-shopify-xr
data-shopify-model3d-id="{{ first_3d_model.id }}"
data-shopify-title="{{ product.title | escape }}"
data-shopify-xr-hidden
>
{% render 'icon-3d-model' %}
{{ 'products.product.xr_button' | t }}
</button>
{%- endif -%}
</div>
<div class="product__info-wrapper grid__item">
<div id="ProductInfo-{{ section.id }}" class="product__info-container{% if section.settings.enable_sticky_info %} product__info-container--sticky{% endif %}">
{%- assign product_form_id = 'product-form-' | append: section.id -%}
{%- for block in section.blocks -%}
{%- case block.type -%}
{%- when '@app' -%}
{% render block %}
{%- when 'text' -%}
<p class="product__text{% if block.settings.text_style == 'uppercase' %} caption-with-letter-spacing{% elsif block.settings.text_style == 'subtitle' %} subtitle{% endif %}" {{ block.shopify_attributes }}>
{{- block.settings.text -}}
</p>
{%- when 'title' -%}
<h1 class="product__title" {{ block.shopify_attributes }}>
{{ product.title | escape }}
</h1>
{%- when 'price' -%}
<div class="no-js-hidden" id="price-{{ section.id }}" {{ block.shopify_attributes }}>
{%- render 'price', product: product, use_variant: true, show_badges: true, price_class: 'price--large' -%}
</div>
<div {{ block.shopify_attributes }}>
{%- form 'product', product, id: 'product-form-installment', class: 'installment caption-large' -%}
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
{{ form | payment_terms }}
{%- endform -%}
</div>
{%- when 'description' -%}
{%- if product.description != blank -%}
<div class="product__description rte">
{{ product.description }}
</div>
{%- endif -%}
{%- when 'custom_liquid' -%}
{{ block.settings.custom_liquid }}
{%- when 'collapsible_tab' -%}
<div class="product__accordion accordion" {{ block.shopify_attributes }}>
<details>
<summary>
<div class="summary__title">
{% render 'icon-accordion', icon: block.settings.icon %}
<h2 class="h4 accordion__title">
{{ block.settings.heading | default: block.settings.page.title }}
</h2>
</div>
{% render 'icon-caret' %}
</summary>
<div class="accordion__content rte">
{{ block.settings.content }}
{{ block.settings.page.content }}
</div>
</details>
</div>
{%- when 'quantity_selector' -%}
<div class="product-form__input product-form__quantity" {{ block.shopify_attributes }}>
<label class="form__label" for="Quantity-{{ section.id }}">
{{ 'products.product.quantity.label' | t }}
</label>
<quantity-input class="quantity">
<button class="quantity__button no-js-hidden" name="minus" type="button">
<span class="visually-hidden">{{ 'products.product.quantity.decrease' | t: product: product.title | escape }}</span>
{% render 'icon-minus' %}
</button>
<input class="quantity__input"
type="number"
name="quantity"
id="Quantity-{{ section.id }}"
min="1"
value="1"
form="product-form-{{ section.id }}"
>
<button class="quantity__button no-js-hidden" name="plus" type="button">
<span class="visually-hidden">{{ 'products.product.quantity.increase' | t: product: product.title | escape }}</span>
{% render 'icon-plus' %}
</button>
</quantity-input>
</div>
{%- when 'popup' -%}
<modal-opener class="product-popup-modal__opener no-js-hidden" data-modal="#PopupModal-{{ block.id }}" {{ block.shopify_attributes }}>
<button id="ProductPopup-{{ block.id }}" class="product-popup-modal__button link" type="button" aria-haspopup="dialog">{{ block.settings.text | default: block.settings.page.title }}</button>
</modal-opener>
<a href="{{ block.settings.page.url }}" class="product-popup-modal__button link no-js">{{ block.settings.text }}</a>
{%- when 'share' -%}
<share-button class="share-button" {{ block.shopify_attributes }}>
<button class="share-button__button hidden">
{% render 'icon-share' %}
{{ block.settings.share_label | escape }}
</button>
<details>
<summary class="share-button__button">
{% render 'icon-share' %}
{{ block.settings.share_label | escape }}
</summary>
<div id="Product-share-{{ section.id }}" class="share-button__fallback motion-reduce">
<div class="field">
<span id="ShareMessage-{{ section.id }}" class="share-button__message hidden" role="status">
</span>
<input type="text"
class="field__input"
id="url"
value="{{ shop.url | append: product.url }}"
placeholder="{{ 'general.share.share_url' | t }}"
onclick="this.select();"
readonly
>
<label class="field__label" for="url">{{ 'general.share.share_url' | t }}</label>
</div>
<button class="share-button__close hidden no-js-hidden">
{% render 'icon-close' %}
<span class="visually-hidden">{{ 'general.share.close' | t }}</span>
</button>
<button class="share-button__copy no-js-hidden">
{% render 'icon-clipboard' %}
<span class="visually-hidden">{{ 'general.share.copy_to_clipboard' | t }}</span>
</button>
</div>
</details>
</share-button>
<script src="{{ 'share.js' | asset_url }}" defer="defer"></script>
{%- when 'variant_picker' -%}
{%- unless product.has_only_default_variant -%}
{%- if block.settings.picker_type == 'button' -%}
<variant-radios class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
{%- for option in product.options_with_values -%}
<fieldset class="js product-form__input">
<legend class="form__label">{{ option.name }}</legend>
{%- for value in option.values -%}
<input type="radio" id="{{ section.id }}-{{ option.name }}-{{ forloop.index0 }}"
name="{{ option.name }}"
value="{{ value | escape }}"
form="product-form-{{ section.id }}"
{% if option.selected_value == value %}checked{% endif %}
>
<label for="{{ section.id }}-{{ option.name }}-{{ forloop.index0 }}">
{{ value }}
</label>
{%- endfor -%}
</fieldset>
{%- endfor -%}
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-radios>
{%- else -%}
<variant-selects class="no-js-hidden" data-section="{{ section.id }}" data-url="{{ product.url }}" {{ block.shopify_attributes }}>
{%- for option in product.options_with_values -%}
<div class="product-form__input product-form__input--dropdown">
<label class="form__label" for="Option-{{ section.id }}-{{ forloop.index0 }}">
{{ option.name }}
</label>
<div class="select">
<select id="Option-{{ section.id }}-{{ forloop.index0 }}"
class="select__select"
name="options[{{ option.name | escape }}]"
form="product-form-{{ section.id }}"
>
{%- for value in option.values -%}
<option value="{{ value | escape }}" {% if option.selected_value == value %}selected="selected"{% endif %}>
{{ value }}
</option>
{%- endfor -%}
</select>
{% render 'icon-caret' %}
</div>
</div>
{%- endfor -%}
<script type="application/json">
{{ product.variants | json }}
</script>
</variant-selects>
{%- endif -%}
{%- endunless -%}
<noscript class="product-form__noscript-wrapper-{{ section.id }}">
<div class="product-form__input{% if product.has_only_default_variant %} hidden{% endif %}">
<label class="form__label" for="Variants-{{ section.id }}">{{ 'products.product.product_variants' | t }}</label>
<div class="select">
<select name="id" id="Variants-{{ section.id }}" class="select__select" form="product-form">
{%- for variant in product.variants -%}
<option
{% if variant == product.selected_or_first_available_variant %}selected="selected"{% endif %}
{% if variant.available == false %}disabled{% endif %}
value="{{ variant.id }}"
>
{{ variant.title }}
{%- if variant.available == false %} - {{ 'products.product.sold_out' | t }}{% endif %}
- {{ variant.price | money | strip_html }}
</option>
{%- endfor -%}
</select>
{% render 'icon-caret' %}
</div>
</div>
</noscript>
{%- when 'buy_buttons' -%}
<div {{ block.shopify_attributes }}>
<product-form class="product-form">
<div class="product-form__error-message-wrapper" role="alert" hidden>
<svg aria-hidden="true" focusable="false" role="presentation" class="icon icon-error" viewBox="0 0 13 13">
<circle cx="6.5" cy="6.50049" r="5.5" stroke="white" stroke-width="2"/>
<circle cx="6.5" cy="6.5" r="5.5" fill="#EB001B" stroke="#EB001B" stroke-width="0.7"/>
<path d="M5.87413 3.52832L5.97439 7.57216H7.02713L7.12739 3.52832H5.87413ZM6.50076 9.66091C6.88091 9.66091 7.18169 9.37267 7.18169 9.00504C7.18169 8.63742 6.88091 8.34917 6.50076 8.34917C6.12061 8.34917 5.81982 8.63742 5.81982 9.00504C5.81982 9.37267 6.12061 9.66091 6.50076 9.66091Z" fill="white"/>
<path d="M5.87413 3.17832H5.51535L5.52424 3.537L5.6245 7.58083L5.63296 7.92216H5.97439H7.02713H7.36856L7.37702 7.58083L7.47728 3.537L7.48617 3.17832H7.12739H5.87413ZM6.50076 10.0109C7.06121 10.0109 7.5317 9.57872 7.5317 9.00504C7.5317 8.43137 7.06121 7.99918 6.50076 7.99918C5.94031 7.99918 5.46982 8.43137 5.46982 9.00504C5.46982 9.57872 5.94031 10.0109 6.50076 10.0109Z" fill="white" stroke="#EB001B" stroke-width="0.7">
</svg>
<span class="product-form__error-message"></span>
</div>
{%- form 'product', product, id: product_form_id, class: 'form', novalidate: 'novalidate', data-type: 'add-to-cart-form' -%}
<input type="hidden" name="id" value="{{ product.selected_or_first_available_variant.id }}">
<div class="product-form__buttons">
<button
type="submit"
name="add"
class="product-form__submit button button--full-width {% if block.settings.show_dynamic_checkout and product.selling_plan_groups == empty %}button--secondary{% else %}button--primary{% endif %}"
{% if product.selected_or_first_available_variant.available == false %}disabled{% endif %}
>
{%- if product.selected_or_first_available_variant.available -%}
{{ 'products.product.add_to_cart' | t }}
{%- else -%}
{{ 'products.product.sold_out' | t }}
{%- endif -%}
</button>
{%- if block.settings.show_dynamic_checkout -%}
{{ form | payment_button }}
{%- endif -%}
</div>
{%- endform -%}
</product-form>
{{ 'component-pickup-availability.css' | asset_url | stylesheet_tag }}
{%- assign pick_up_availabilities = product.selected_or_first_available_variant.store_availabilities | where: 'pick_up_enabled', true -%}
<pickup-availability class="product__pickup-availabilities no-js-hidden"
{% if product.selected_or_first_available_variant.available and pick_up_availabilities.size > 0 %} available{% endif %}
data-base-url="{{ shop.url }}{{ routes.root_url }}"
data-variant-id="{{ product.selected_or_first_available_variant.id }}"
data-has-only-default-variant="{{ product.has_only_default_variant }}"
>
<template>
<pickup-availability-preview class="pickup-availability-preview">
{% render 'icon-unavailable' %}
<div class="pickup-availability-info">
<p class="caption-large">{{ 'products.product.pickup_availability.unavailable' | t }}</p>
<button class="pickup-availability-button link link--text underlined-link">{{ 'products.product.pickup_availability.refresh' | t }}</button>
</div>
</pickup-availability-preview>
</template>
</pickup-availability>
</div>
<script src="{{ 'pickup-availability.js' | asset_url }}" defer="defer"></script>
{%- when 'rating' -%}
{%- if product.metafields.reviews.rating.value != blank -%}
{% liquid
assign rating_decimal = 0
assign decimal = product.metafields.reviews.rating.value.rating | modulo: 1
if decimal >= 0.3 and decimal <= 0.7
assign rating_decimal = 0.5
elsif decimal > 0.7
assign rating_decimal = 1
endif
%}
<div class="rating" role="img" aria-label="{{ 'accessibility.star_reviews_info' | t: rating_value: product.metafields.reviews.rating.value, rating_max: product.metafields.reviews.rating.value.scale_max }}">
<span aria-hidden="true" class="rating-star color-icon-{{ settings.accent_icons }}" style="--rating: {{ product.metafields.reviews.rating.value.rating | floor }}; --rating-max: {{ product.metafields.reviews.rating.value.scale_max }}; --rating-decimal: {{ rating_decimal }};"></span>
</div>
<p class="rating-text caption">
<span aria-hidden="true">{{ product.metafields.reviews.rating.value }} / {{ product.metafields.reviews.rating.value.scale_max }}</span>
</p>
<p class="rating-count caption">
<span aria-hidden="true">({{ product.metafields.reviews.rating_count }})</span>
<span class="visually-hidden">{{ product.metafields.reviews.rating_count }} {{ "accessibility.total_reviews" | t }}</span>
</p>
{%- endif -%}
{%- endcase -%}
{%- endfor -%}
</div>
</div>
</div>
<product-modal id="ProductModal-{{ section.id }}" class="product-media-modal media-modal">
<div class="product-media-modal__dialog" role="dialog" aria-label="{{ 'products.modal.label' | t }}" aria-modal="true" tabindex="-1">
<button id="ModalClose-{{ section.id }}" type="button" class="product-media-modal__toggle" aria-label="{{ 'accessibility.close' | t }}">{% render 'icon-close' %}</button>
<div class="product-media-modal__content" role="document" aria-label="{{ 'products.modal.label' | t }}" tabindex="0">
{%- liquid
if product.selected_or_first_available_variant.featured_media != null
assign media = product.selected_or_first_available_variant.featured_media
render 'product-media', media: media, loop: section.settings.enable_video_looping, variant_image: section.settings.hide_variants
endif
-%}
{%- for media in product.media -%}
{%- liquid
if section.settings.hide_variants and variant_images contains media.src
assign variant_image = true
else
assign variant_image = false
endif
unless media.id == product.selected_or_first_available_variant.featured_media.id
render 'product-media', media: media, loop: section.settings.enable_video_looping, variant_image: variant_image
endunless
-%}
{%- endfor -%}
</div>
</div>
</product-modal>
{% assign popups = section.blocks | where: "type", "popup" %}
{%- for block in popups -%}
<modal-dialog id="PopupModal-{{ block.id }}" class="product-popup-modal" {{ block.shopify_attributes }}>
<div role="dialog" aria-label="{{ block.settings.text }}" aria-modal="true" class="product-popup-modal__content" tabindex="-1">
<button id="ModalClose-{{ block.id }}" type="button" class="product-popup-modal__toggle" aria-label="{{ 'accessibility.close' | t }}">{% render 'icon-close' %}</button>
<div class="product-popup-modal__content-info">
<h1 class="h2">{{ block.settings.page.title }}</h1>
{{ block.settings.page.content }}
</div>
</div>
</modal-dialog>
{%- endfor -%}
</section>
{% javascript %}
class ProductModal extends ModalDialog {
constructor() {
super();
}
hide() {
super.hide();
window.pauseAllMedia();
}
show(opener) {
super.show(opener);
this.showActiveMedia();
}
showActiveMedia() {
this.querySelectorAll(`[data-media-id]:not([data-media-id="${this.openedBy.getAttribute("data-media-id")}"])`).forEach((element) => {
element.classList.remove('active');
}
)
const activeMedia = this.querySelector(`[data-media-id="${this.openedBy.getAttribute("data-media-id")}"]`);
const activeMediaTemplate = activeMedia.querySelector('template');
const activeMediaContent = activeMediaTemplate ? activeMediaTemplate.content : null;
activeMedia.classList.add('active');
activeMedia.scrollIntoView();
const container = this.querySelector('[role="document"]');
container.scrollLeft = (activeMedia.width - container.clientWidth) / 2;
if (activeMedia.nodeName == 'DEFERRED-MEDIA' && activeMediaContent && activeMediaContent.querySelector('.js-youtube'))
activeMedia.loadContent();
}
}
customElements.define('product-modal', ProductModal);
{% endjavascript %}
<script>
document.addEventListener('DOMContentLoaded', function() {
function isIE() {
const ua = window.navigator.userAgent;
const msie = ua.indexOf('MSIE ');
const trident = ua.indexOf('Trident/');
return (msie > 0 || trident > 0);
}
if (!isIE()) return;
const hiddenInput = document.querySelector('#{{ product_form_id }} input[name="id"]');
const noScriptInputWrapper = document.createElement('div');
const variantSwitcher = document.querySelector('variant-radios[data-section="{{ section.id }}"]') || document.querySelector('variant-selects[data-section="{{ section.id }}"]');
noScriptInputWrapper.innerHTML = document.querySelector('.product-form__noscript-wrapper-{{ section.id }}').textContent;
variantSwitcher.outerHTML = noScriptInputWrapper.outerHTML;
document.querySelector('#Variants-{{ section.id }}').addEventListener('change', function(event) {
hiddenInput.value = event.currentTarget.value;
});
});
</script>
{%- if first_3d_model -%}
<script type="application/json" id="ProductJSON-{{ product.id }}">
{{ product.media | where: 'media_type', 'model' | json }}
</script>
<script src="{{ 'product-model.js' | asset_url }}" defer></script>
{%- endif -%}
<script type="application/ld+json">
{
"@context": "http://schema.org/",
"@type": "Product",
"name": {{ product.title | json }},
"url": {{ shop.url | append: product.url | json }},
{%- if product.selected_or_first_available_variant.featured_media -%}
{%- assign media_size = product.selected_or_first_available_variant.featured_media.preview_image.width | append: 'x' -%}
"image": [
{{ product.selected_or_first_available_variant.featured_media | img_url: media_size | prepend: "https:" | json }}
],
{%- endif -%}
"description": {{ product.description | strip_html | json }},
{%- if product.selected_or_first_available_variant.sku != blank -%}
"sku": {{ product.selected_or_first_available_variant.sku | json }},
{%- endif -%}
"brand": {
"@type": "Thing",
"name": {{ product.vendor | json }}
},
"offers": [
{%- for variant in product.variants -%}
{
"@type" : "Offer",
{%- if variant.sku != blank -%}
"sku": {{ variant.sku | json }},
{%- endif -%}
"availability" : "http://schema.org/{% if variant.available %}InStock{% else %}OutOfStock{% endif %}",
"price" : {{ variant.price | divided_by: 100.00 | json }},
"priceCurrency" : {{ cart.currency.iso_code | json }},
"url" : {{ shop.url | append: variant.url | json }}
}{% unless forloop.last %},{% endunless %}
{%- endfor -%}
]
}
</script>
{% schema %}
{
"name": "t:sections.main-product.name",
"tag": "section",
"class": "product-section spaced-section",
"blocks": [
{
"type": "@app"
},
{
"type": "text",
"name": "t:sections.main-product.blocks.text.name",
"settings": [
{
"type": "text",
"id": "text",
"default": "Text block",
"label": "t:sections.main-product.blocks.text.settings.text.label"
},
{
"type": "select",
"id": "text_style",
"options": [
{
"value": "body",
"label": "t:sections.main-product.blocks.text.settings.text_style.options__1.label"
},
{
"value": "subtitle",
"label": "t:sections.main-product.blocks.text.settings.text_style.options__2.label"
},
{
"value": "uppercase",
"label": "t:sections.main-product.blocks.text.settings.text_style.options__3.label"
}
],
"default": "body",
"label": "t:sections.main-product.blocks.text.settings.text_style.label"
}
]
},
{
"type": "title",
"name": "t:sections.main-product.blocks.title.name",
"limit": 1
},
{
"type": "price",
"name": "t:sections.main-product.blocks.price.name",
"limit": 1
},
{
"type": "quantity_selector",
"name": "t:sections.main-product.blocks.quantity_selector.name",
"limit": 1
},
{
"type": "variant_picker",
"name": "t:sections.main-product.blocks.variant_picker.name",
"limit": 1,
"settings": [
{
"type": "select",
"id": "picker_type",
"options": [
{
"value": "dropdown",
"label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__1.label"
},
{
"value": "button",
"label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.options__2.label"
}
],
"default": "button",
"label": "t:sections.main-product.blocks.variant_picker.settings.picker_type.label"
}
]
},
{
"type": "buy_buttons",
"name": "t:sections.main-product.blocks.buy_buttons.name",
"limit": 1,
"settings": [
{
"type": "checkbox",
"id": "show_dynamic_checkout",
"default": true,
"label": "t:sections.main-product.blocks.buy_buttons.settings.show_dynamic_checkout.label",
"info": "t:sections.main-product.blocks.buy_buttons.settings.show_dynamic_checkout.info"
}
]
},
{
"type": "description",
"name": "t:sections.main-product.blocks.description.name",
"limit": 1
},
{
"type": "share",
"name": "t:sections.main-product.blocks.share.name",
"limit": 1,
"settings": [
{
"type": "text",
"id": "share_label",
"label": "t:sections.main-product.blocks.share.settings.text.label",
"default": "Share"
},
{
"type": "paragraph",
"content": "t:sections.main-product.blocks.share.settings.featured_image_info.content"
},
{
"type": "paragraph",
"content": "t:sections.main-product.blocks.share.settings.title_info.content"
}
]
},
{
"type": "custom_liquid",
"name": "t:sections.main-product.blocks.custom_liquid.name",
"settings": [
{
"type": "liquid",
"id": "custom_liquid",
"label": "t:sections.main-product.blocks.custom_liquid.settings.custom_liquid.label",
"info": "t:sections.main-product.blocks.custom_liquid.settings.custom_liquid.info"
}
]
},
{
"type": "collapsible_tab",
"name": "t:sections.main-product.blocks.collapsible_tab.name",
"settings": [
{
"type": "text",
"id": "heading",
"default": "Collapsible tab",
"info": "t:sections.main-product.blocks.collapsible_tab.settings.heading.info",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.heading.label"
},
{
"type": "richtext",
"id": "content",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.content.label"
},
{
"type": "page",
"id": "page",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.page.label"
},
{
"type": "select",
"id": "icon",
"options": [
{
"value": "none",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__1.label"
},
{
"value": "box",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__2.label"
},
{
"value": "chat_bubble",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__3.label"
},
{
"value": "check_mark",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__4.label"
},
{
"value": "dryer",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__5.label"
},
{
"value": "eye",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__6.label"
},
{
"value": "heart",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__7.label"
},
{
"value": "iron",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__8.label"
},
{
"value": "leaf",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__9.label"
},
{
"value": "leather",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__10.label"
},
{
"value": "lock",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__11.label"
},
{
"value": "map_pin",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__12.label"
},
{
"value": "pants",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__13.label"
},
{
"value": "plane",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__14.label"
},
{
"value": "price_tag",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__15.label"
},
{
"value": "question_mark",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__16.label"
},
{
"value": "return",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__17.label"
},
{
"value": "ruler",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__18.label"
},
{
"value": "shirt",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__19.label"
},
{
"value": "shoe",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__20.label"
},
{
"value": "silhouette",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__21.label"
},
{
"value": "star",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__22.label"
},
{
"value": "truck",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__23.label"
},
{
"value": "washing",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.options__24.label"
}
],
"default": "check_mark",
"label": "t:sections.main-product.blocks.collapsible_tab.settings.icon.label"
}
]
},
{
"type": "popup",
"name": "t:sections.main-product.blocks.popup.name",
"settings": [
{
"type": "text",
"id": "text",
"default": "Pop-up link text",
"label": "t:sections.main-product.blocks.popup.settings.link_label.label"
},
{
"id": "page",
"type": "page",
"label": "t:sections.main-product.blocks.popup.settings.page.label"
}
]
},
{
"type": "rating",
"name": "t:sections.main-product.blocks.rating.name",
"limit": 1,
"settings": [
{
"type": "paragraph",
"content": "t:sections.main-product.blocks.rating.settings.paragraph.content"
}
]
}
],
"settings": [
{
"type": "checkbox",
"id": "enable_sticky_info",
"default": true,
"label": "t:sections.main-product.settings.enable_sticky_info.label"
},
{
"type": "header",
"content": "t:sections.main-product.settings.header.content",
"info": "t:sections.main-product.settings.header.info"
},
{
"type": "checkbox",
"id": "hide_variants",
"default": false,
"label": "t:sections.main-product.settings.hide_variants.label"
},
{
"type": "checkbox",
"id": "enable_video_looping",
"default": false,
"label": "t:sections.main-product.settings.enable_video_looping.label"
}
]
}
{% endschema %}
.slider-container {
margin: 0 1rem;
width: 100%;
overflow: auto;
display: flex;
flex-flow: column wrap;
align-items: center;
position: relative;
}
.large-image {
width: 100%;
padding-bottom: 100%;
position: relative;
}
.large-image-item {
position: absolute;
max-width: 100%;
min-width: 50%;
max-height: 100%;
padding: 0;
}
.large-image-item:not(:first-child){
display: none;
}
ul {
padding-inline-start: 0px !important;
}
slider-component {
position: relative;
display: block;
width:100%;
}
.slider__slide {
scroll-snap-align: start;
flex-shrink: 0;
}
.product-slider-box {
flex-wrap: inherit;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
-webkit-overflow-scrolling: touch;
margin-bottom: 1rem;
width: 100%;
display: flex;
}
.product-slider {
height: 100%;
width: 23%;
display: inline-block;
}
.product-slider:not(:first-child){
margin-left: 1rem;
}
.slide-image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* ===ORIGINAL CODE =======
slider-component {
position: relative;
display: block;
}
@media screen and (max-width: 989px) {
.no-js slider-component .slider {
padding-bottom: 3rem;
}
}
.slider__slide {
scroll-snap-align: start;
flex-shrink: 0;
}
@media screen and (max-width: 749px) {
.slider.slider--mobile {
position: relative;
flex-wrap: inherit;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
scroll-padding-left: 1rem;
-webkit-overflow-scrolling: touch;
margin-bottom: 1rem;
}
.slider.slider--mobile .slider__slide {
margin-bottom: 0;
padding-bottom: 0;
}
}
@media screen and (max-width: 989px) {
.slider.slider--tablet {
position: relative;
flex-wrap: inherit;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
scroll-padding-left: 1rem;
-webkit-overflow-scrolling: touch;
margin-bottom: 1rem;
}
.slider.slider--tablet .slider__slide {
margin-bottom: 0;
padding-bottom: 0;
}
}
/* Scrollbar */
.slider {
scrollbar-color: rgb(var(--color-foreground)) rgba(var(--color-foreground), 0.04);
-ms-overflow-style: none;
scrollbar-width: none;
}
.slider::-webkit-scrollbar {
height: 0.4rem;
width: 0.4rem;
display: none;
}
.no-js .slider {
-ms-overflow-style: auto;
scrollbar-width: auto;
}
.no-js .slider::-webkit-scrollbar {
display: initial;
}
.slider::-webkit-scrollbar-thumb {
background-color: rgb(var(--color-foreground));
border-radius: 0.4rem;
border: 0;
}
.slider::-webkit-scrollbar-track {
background: rgba(var(--color-foreground), 0.04);
border-radius: 0.4rem;
}
.slider-counter {
margin: 0 1.2rem;
}
.slider-buttons {
display: flex;
align-items: center;
justify-content: center;
}
/* =====ORIGINAL CODE=====
@media screen and (min-width: 990px) {
.slider-buttons {
display: none;
}
}
@media screen and (min-width: 750px) {
.slider--mobile + .slider-buttons {
display: none;
}
} */
.slider-button {
color: rgba(var(--color-foreground), 0.75);
background: transparent;
border: none;
cursor: pointer;
width: 44px;
height: 44px;
}
.slider-button:not([disabled]):hover {
color: rgb(var(--color-foreground));
}
.slider-button .icon {
height: 0.6rem;
}
.slider-button[disabled] .icon {
color: rgba(var(--color-foreground), 0.3);
}
.slider-button--next .icon {
margin-right: -0.2rem;
transform: rotate(-90deg) translateX(0.15rem);
}
.slider-button--prev .icon {
margin-left: -0.2rem;
transform: rotate(90deg) translateX(-0.15rem);
}
.slider-button--next:not([disabled]):hover .icon {
transform: rotate(-90deg) translateX(0.15rem) scale(1.07);
}
.slider-button--prev:not([disabled]):hover .icon {
transform: rotate(90deg) translateX(-0.15rem) scale(1.07);
}
function getFocusableElements(container) {
return Array.from(
container.querySelectorAll(
"summary, a[href], button:enabled, [tabindex]:not([tabindex^='-']), [draggable], area, input:not([type=hidden]):enabled, select:enabled, textarea:enabled, object, iframe"
)
);
}
const trapFocusHandlers = {};
function trapFocus(container, elementToFocus = container) {
var elements = getFocusableElements(container);
var first = elements[0];
var last = elements[elements.length - 1];
removeTrapFocus();
trapFocusHandlers.focusin = (event) => {
if (
event.target !== container &&
event.target !== last &&
event.target !== first
)
return;
document.addEventListener('keydown', trapFocusHandlers.keydown);
};
trapFocusHandlers.focusout = function() {
document.removeEventListener('keydown', trapFocusHandlers.keydown);
};
trapFocusHandlers.keydown = function(event) {
if (event.code.toUpperCase() !== 'TAB') return; // If not TAB key
// On the last focusable element and tab forward, focus the first element.
if (event.target === last && !event.shiftKey) {
event.preventDefault();
first.focus();
}
// On the first focusable element and tab backward, focus the last element.
if (
(event.target === container || event.target === first) &&
event.shiftKey
) {
event.preventDefault();
last.focus();
}
};
document.addEventListener('focusout', trapFocusHandlers.focusout);
document.addEventListener('focusin', trapFocusHandlers.focusin);
elementToFocus.focus();
}
// Here run the querySelector to figure out if the browser supports :focus-visible or not and run code based on it.
try {
document.querySelector(":focus-visible");
} catch {
focusVisiblePolyfill();
}
function focusVisiblePolyfill() {
const navKeys = ['ARROWUP', 'ARROWDOWN', 'ARROWLEFT', 'ARROWRIGHT', 'TAB', 'ENTER', 'SPACE', 'ESCAPE', 'HOME', 'END', 'PAGEUP', 'PAGEDOWN']
let currentFocusedElement = null;
let mouseClick = null;
window.addEventListener('keydown', (event) => {
if(navKeys.includes(event.code.toUpperCase())) {
mouseClick = false;
}
});
window.addEventListener('mousedown', (event) => {
mouseClick = true;
});
window.addEventListener('focus', () => {
if (currentFocusedElement) currentFocusedElement.classList.remove('focused');
if (mouseClick) return;
currentFocusedElement = document.activeElement;
currentFocusedElement.classList.add('focused');
}, true);
}
function pauseAllMedia() {
document.querySelectorAll('.js-youtube').forEach((video) => {
video.contentWindow.postMessage('{"event":"command","func":"' + 'pauseVideo' + '","args":""}', '*');
});
document.querySelectorAll('.js-vimeo').forEach((video) => {
video.contentWindow.postMessage('{"method":"pause"}', '*');
});
document.querySelectorAll('video').forEach((video) => video.pause());
document.querySelectorAll('product-model').forEach((model) => {
if (model.modelViewerUI) modelViewerUI.pause();
});
}
function removeTrapFocus(elementToFocus = null) {
document.removeEventListener('focusin', trapFocusHandlers.focusin);
document.removeEventListener('focusout', trapFocusHandlers.focusout);
document.removeEventListener('keydown', trapFocusHandlers.keydown);
if (elementToFocus) elementToFocus.focus();
}
function onKeyUpEscape(event) {
if (event.code.toUpperCase() !== 'ESCAPE') return;
const openDetailsElement = event.target.closest('details[open]');
if (!openDetailsElement) return;
const summaryElement = openDetailsElement.querySelector('summary');
openDetailsElement.removeAttribute('open');
summaryElement.focus();
}
class QuantityInput extends HTMLElement {
constructor() {
super();
this.input = this.querySelector('input');
this.changeEvent = new Event('change', { bubbles: true })
this.querySelectorAll('button').forEach(
(button) => button.addEventListener('click', this.onButtonClick.bind(this))
);
}
onButtonClick(event) {
event.preventDefault();
const previousValue = this.input.value;
event.target.name === 'plus' ? this.input.stepUp() : this.input.stepDown();
if (previousValue !== this.input.value) this.input.dispatchEvent(this.changeEvent);
}
}
customElements.define('quantity-input', QuantityInput);
function debounce(fn, wait) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn.apply(this, args), wait);
};
}
const serializeForm = form => {
const obj = {};
const formData = new FormData(form);
for (const key of formData.keys()) {
const regex = /(?:^(properties\[))(.*?)(?:\]$)/;
if (regex.test(key)) {
obj.properties = obj.properties || {};
obj.properties[regex.exec(key)[2]] = formData.get(key);
} else {
obj[key] = formData.get(key);
}
}
return JSON.stringify(obj);
};
function fetchConfig(type = 'json') {
return {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Accept': `application/${type}` }
};
}
/*
* Shopify Common JS
*
*/
if ((typeof window.Shopify) == 'undefined') {
window.Shopify = {};
}
Shopify.bind = function(fn, scope) {
return function() {
return fn.apply(scope, arguments);
}
};
Shopify.setSelectorByValue = function(selector, value) {
for (var i = 0, count = selector.options.length; i < count; i++) {
var option = selector.options[i];
if (value == option.value || value == option.innerHTML) {
selector.selectedIndex = i;
return i;
}
}
};
Shopify.addListener = function(target, eventName, callback) {
target.addEventListener ? target.addEventListener(eventName, callback, false) : target.attachEvent('on'+eventName, callback);
};
Shopify.postLink = function(path, options) {
options = options || {};
var method = options['method'] || 'post';
var params = options['parameters'] || {};
var form = document.createElement("form");
form.setAttribute("method", method);
form.setAttribute("action", path);
for(var key in params) {
var hiddenField = document.createElement("input");
hiddenField.setAttribute("type", "hidden");
hiddenField.setAttribute("name", key);
hiddenField.setAttribute("value", params[key]);
form.appendChild(hiddenField);
}
document.body.appendChild(form);
form.submit();
document.body.removeChild(form);
};
Shopify.CountryProvinceSelector = function(country_domid, province_domid, options) {
this.countryEl = document.getElementById(country_domid);
this.provinceEl = document.getElementById(province_domid);
this.provinceContainer = document.getElementById(options['hideElement'] || province_domid);
Shopify.addListener(this.countryEl, 'change', Shopify.bind(this.countryHandler,this));
this.initCountry();
this.initProvince();
};
Shopify.CountryProvinceSelector.prototype = {
initCountry: function() {
var value = this.countryEl.getAttribute('data-default');
Shopify.setSelectorByValue(this.countryEl, value);
this.countryHandler();
},
initProvince: function() {
var value = this.provinceEl.getAttribute('data-default');
if (value && this.provinceEl.options.length > 0) {
Shopify.setSelectorByValue(this.provinceEl, value);
}
},
countryHandler: function(e) {
var opt = this.countryEl.options[this.countryEl.selectedIndex];
var raw = opt.getAttribute('data-provinces');
var provinces = JSON.parse(raw);
this.clearOptions(this.provinceEl);
if (provinces && provinces.length == 0) {
this.provinceContainer.style.display = 'none';
} else {
for (var i = 0; i < provinces.length; i++) {
var opt = document.createElement('option');
opt.value = provinces[i][0];
opt.innerHTML = provinces[i][1];
this.provinceEl.appendChild(opt);
}
this.provinceContainer.style.display = "";
}
},
clearOptions: function(selector) {
while (selector.firstChild) {
selector.removeChild(selector.firstChild);
}
},
setOptions: function(selector, values) {
for (var i = 0, count = values.length; i < values.length; i++) {
var opt = document.createElement('option');
opt.value = values[i];
opt.innerHTML = values[i];
selector.appendChild(opt);
}
}
};
class MenuDrawer extends HTMLElement {
constructor() {
super();
this.mainDetailsToggle = this.querySelector('details');
const summaryElements = this.querySelectorAll('summary');
this.addAccessibilityAttributes(summaryElements);
if (navigator.platform === 'iPhone') document.documentElement.style.setProperty('--viewport-height', `${window.innerHeight}px`);
this.addEventListener('keyup', this.onKeyUp.bind(this));
this.addEventListener('focusout', this.onFocusOut.bind(this));
this.bindEvents();
}
bindEvents() {
this.querySelectorAll('summary').forEach(summary => summary.addEventListener('click', this.onSummaryClick.bind(this)));
this.querySelectorAll('button').forEach(button => button.addEventListener('click', this.onCloseButtonClick.bind(this)));
}
addAccessibilityAttributes(summaryElements) {
summaryElements.forEach(element => {
element.setAttribute('role', 'button');
element.setAttribute('aria-expanded', false);
element.setAttribute('aria-controls', element.nextElementSibling.id);
});
}
onKeyUp(event) {
if(event.code.toUpperCase() !== 'ESCAPE') return;
const openDetailsElement = event.target.closest('details[open]');
if(!openDetailsElement) return;
openDetailsElement === this.mainDetailsToggle ? this.closeMenuDrawer(this.mainDetailsToggle.querySelector('summary')) : this.closeSubmenu(openDetailsElement);
}
onSummaryClick(event) {
const summaryElement = event.currentTarget;
const detailsElement = summaryElement.parentNode;
const isOpen = detailsElement.hasAttribute('open');
if (detailsElement === this.mainDetailsToggle) {
if(isOpen) event.preventDefault();
isOpen ? this.closeMenuDrawer(summaryElement) : this.openMenuDrawer(summaryElement);
} else {
trapFocus(summaryElement.nextElementSibling, detailsElement.querySelector('button'));
setTimeout(() => {
detailsElement.classList.add('menu-opening');
});
}
}
openMenuDrawer(summaryElement) {
setTimeout(() => {
this.mainDetailsToggle.classList.add('menu-opening');
});
summaryElement.setAttribute('aria-expanded', true);
trapFocus(this.mainDetailsToggle, summaryElement);
document.body.classList.add(`overflow-hidden-${this.dataset.breakpoint}`);
}
closeMenuDrawer(event, elementToFocus = false) {
if (event !== undefined) {
this.mainDetailsToggle.classList.remove('menu-opening');
this.mainDetailsToggle.querySelectorAll('details').forEach(details => {
details.removeAttribute('open');
details.classList.remove('menu-opening');
});
this.mainDetailsToggle.querySelector('summary').setAttribute('aria-expanded', false);
document.body.classList.remove(`overflow-hidden-${this.dataset.breakpoint}`);
removeTrapFocus(elementToFocus);
this.closeAnimation(this.mainDetailsToggle);
}
}
onFocusOut(event) {
setTimeout(() => {
if (this.mainDetailsToggle.hasAttribute('open') && !this.mainDetailsToggle.contains(document.activeElement)) this.closeMenuDrawer();
});
}
onCloseButtonClick(event) {
const detailsElement = event.currentTarget.closest('details');
this.closeSubmenu(detailsElement);
}
closeSubmenu(detailsElement) {
detailsElement.classList.remove('menu-opening');
removeTrapFocus();
this.closeAnimation(detailsElement);
}
closeAnimation(detailsElement) {
let animationStart;
const handleAnimation = (time) => {
if (animationStart === undefined) {
animationStart = time;
}
const elapsedTime = time - animationStart;
if (elapsedTime < 400) {
window.requestAnimationFrame(handleAnimation);
} else {
detailsElement.removeAttribute('open');
if (detailsElement.closest('details[open]')) {
trapFocus(detailsElement.closest('details[open]'), detailsElement.querySelector('summary'));
}
}
}
window.requestAnimationFrame(handleAnimation);
}
}
customElements.define('menu-drawer', MenuDrawer);
class HeaderDrawer extends MenuDrawer {
constructor() {
super();
}
openMenuDrawer(summaryElement) {
this.header = this.header || document.getElementById('shopify-section-header');
this.borderOffset = this.borderOffset || this.closest('.header-wrapper').classList.contains('header-wrapper--border-bottom') ? 1 : 0;
document.documentElement.style.setProperty('--header-bottom-position', `${parseInt(this.header.getBoundingClientRect().bottom - this.borderOffset)}px`);
setTimeout(() => {
this.mainDetailsToggle.classList.add('menu-opening');
});
summaryElement.setAttribute('aria-expanded', true);
trapFocus(this.mainDetailsToggle, summaryElement);
document.body.classList.add(`overflow-hidden-${this.dataset.breakpoint}`);
}
}
customElements.define('header-drawer', HeaderDrawer);
class ModalDialog extends HTMLElement {
constructor() {
super();
this.querySelector('[id^="ModalClose-"]').addEventListener(
'click',
this.hide.bind(this)
);
this.addEventListener('keyup', (event) => {
if (event.code.toUpperCase() === 'ESCAPE') this.hide();
});
if (this.classList.contains('media-modal')) {
this.addEventListener('pointerup', (event) => {
if (event.pointerType === 'mouse' && !event.target.closest('deferred-media, product-model')) this.hide();
});
} else {
this.addEventListener('click', (event) => {
if (event.target.nodeName === 'MODAL-DIALOG') this.hide();
});
}
}
show(opener) {
this.openedBy = opener;
const popup = this.querySelector('.template-popup');
document.body.classList.add('overflow-hidden');
this.setAttribute('open', '');
if (popup) popup.loadContent();
trapFocus(this, this.querySelector('[role="dialog"]'));
}
hide() {
document.body.classList.remove('overflow-hidden');
this.removeAttribute('open');
removeTrapFocus(this.openedBy);
window.pauseAllMedia();
}
}
customElements.define('modal-dialog', ModalDialog);
class ModalOpener extends HTMLElement {
constructor() {
super();
const button = this.querySelector('button');
if (!button) return;
button.addEventListener('click', () => {
const modal = document.querySelector(this.getAttribute('data-modal'));
if (modal) modal.show(button);
});
}
}
customElements.define('modal-opener', ModalOpener);
class DeferredMedia extends HTMLElement {
constructor() {
super();
const poster = this.querySelector('[id^="Deferred-Poster-"]');
if (!poster) return;
poster.addEventListener('click', this.loadContent.bind(this));
}
loadContent() {
window.pauseAllMedia();
if (!this.getAttribute('loaded')) {
const content = document.createElement('div');
content.appendChild(this.querySelector('template').content.firstElementChild.cloneNode(true));
this.setAttribute('loaded', true);
this.appendChild(content.querySelector('video, model-viewer, iframe')).focus();
}
}
}
customElements.define('deferred-media', DeferredMedia);
class SliderComponent extends HTMLElement {
constructor() {
super();
this.slider = this.querySelector('ul');
this.sliderItems = this.querySelectorAll('li');
this.pageCount = this.querySelector('.slider-counter--current');
this.pageTotal = this.querySelector('.slider-counter--total');
this.prevButton = this.querySelector('button[name="previous"]');
this.nextButton = this.querySelector('button[name="next"]');
if (!this.slider || !this.nextButton) return;
const resizeObserver = new ResizeObserver(entries => this.initPages());
resizeObserver.observe(this.slider);
this.slider.addEventListener('scroll', this.update.bind(this));
this.prevButton.addEventListener('click', this.onButtonClick.bind(this));
this.nextButton.addEventListener('click', this.onButtonClick.bind(this));
}
initPages() {
const sliderItemsToShow = Array.from(this.sliderItems).filter(element => element.clientWidth > 0);
this.sliderLastItem = sliderItemsToShow[sliderItemsToShow.length - 1];
if (sliderItemsToShow.length === 0) return;
this.slidesPerPage = Math.floor(this.slider.clientWidth / sliderItemsToShow[0].clientWidth);
this.totalPages = sliderItemsToShow.length - this.slidesPerPage + 3;
this.update();
}
update() {
if (!this.pageCount || !this.pageTotal) return;
this.currentPage = Math.round(this.slider.scrollLeft / this.sliderLastItem.clientWidth) + 4;
if (this.currentPage === 1) {
this.prevButton.setAttribute('disabled', true);
} else {
this.prevButton.removeAttribute('disabled');
}
if (this.currentPage === this.totalPages) {
this.nextButton.setAttribute('disabled', true);
} else {
this.nextButton.removeAttribute('disabled');
}
this.pageCount.textContent = this.currentPage;
this.pageTotal.textContent = this.totalPages;
}
onButtonClick(event) {
event.preventDefault();
const slideScrollPosition = event.currentTarget.name === 'next' ? this.slider.scrollLeft + this.sliderLastItem.clientWidth : this.slider.scrollLeft - this.sliderLastItem.clientWidth;
this.slider.scrollTo({
left: slideScrollPosition
});
}
}
customElements.define('slider-component', SliderComponent);
class VariantSelects extends HTMLElement {
constructor() {
super();
this.addEventListener('change', this.onVariantChange);
}
onVariantChange() {
this.updateOptions();
this.updateMasterId();
this.toggleAddButton(true, '', false);
this.updatePickupAvailability();
this.removeErrorMessage();
if (!this.currentVariant) {
this.toggleAddButton(true, '', true);
this.setUnavailable();
} else {
this.updateMedia();
this.updateURL();
this.updateVariantInput();
this.renderProductInfo();
}
}
updateOptions() {
this.options = Array.from(this.querySelectorAll('select'), (select) => select.value);
}
updateMasterId() {
this.currentVariant = this.getVariantData().find((variant) => {
return !variant.options.map((option, index) => {
return this.options[index] === option;
}).includes(false);
});
}
updateMedia() {
if (!this.currentVariant) return;
if (!this.currentVariant.featured_media) return;
const newMedia = document.querySelector(
`[data-media-id="${this.dataset.section}-${this.currentVariant.featured_media.id}"]`
);
if (!newMedia) return;
const modalContent = document.querySelector(`#ProductModal-${this.dataset.section} .product-media-modal__content`);
const newMediaModal = modalContent.querySelector( `[data-media-id="${this.currentVariant.featured_media.id}"]`);
const parent = newMedia.parentElement;
if (parent.firstChild == newMedia) return;
modalContent.prepend(newMediaModal);
parent.prepend(newMedia);
this.stickyHeader = this.stickyHeader || document.querySelector('sticky-header');
if(this.stickyHeader) {
this.stickyHeader.dispatchEvent(new Event('preventHeaderReveal'));
}
window.setTimeout(() => { parent.querySelector('li.product__media-item').scrollIntoView({behavior: "smooth"}); });
}
updateURL() {
if (!this.currentVariant || this.dataset.updateUrl === 'false') return;
window.history.replaceState({ }, '', `${this.dataset.url}?variant=${this.currentVariant.id}`);
}
updateVariantInput() {
const productForms = document.querySelectorAll(`#product-form-${this.dataset.section}, #product-form-installment`);
productForms.forEach((productForm) => {
const input = productForm.querySelector('input[name="id"]');
input.value = this.currentVariant.id;
input.dispatchEvent(new Event('change', { bubbles: true }));
});
}
updatePickupAvailability() {
const pickUpAvailability = document.querySelector('pickup-availability');
if (!pickUpAvailability) return;
if (this.currentVariant && this.currentVariant.available) {
pickUpAvailability.fetchAvailability(this.currentVariant.id);
} else {
pickUpAvailability.removeAttribute('available');
pickUpAvailability.innerHTML = '';
}
}
removeErrorMessage() {
const section = this.closest('section');
if (!section) return;
const productForm = section.querySelector('product-form');
if (productForm) productForm.handleErrorMessage();
}
renderProductInfo() {
fetch(`${this.dataset.url}?variant=${this.currentVariant.id}§ion_id=${this.dataset.section}`)
.then((response) => response.text())
.then((responseText) => {
const id = `price-${this.dataset.section}`;
const html = new DOMParser().parseFromString(responseText, 'text/html')
const destination = document.getElementById(id);
const source = html.getElementById(id);
if (source && destination) destination.innerHTML = source.innerHTML;
const price = document.getElementById(`price-${this.dataset.section}`);
if (price) price.classList.remove('visibility-hidden');
this.toggleAddButton(!this.currentVariant.available, window.variantStrings.soldOut);
});
}
toggleAddButton(disable = true, text, modifyClass = true) {
const productForm = document.getElementById(`product-form-${this.dataset.section}`);
if (!productForm) return;
const addButton = productForm.querySelector('[name="add"]');
if (!addButton) return;
if (disable) {
addButton.setAttribute('disabled', true);
if (text) addButton.textContent = text;
} else {
addButton.removeAttribute('disabled');
addButton.textContent = window.variantStrings.addToCart;
}
if (!modifyClass) return;
}
setUnavailable() {
const button = document.getElementById(`product-form-${this.dataset.section}`);
const addButton = button.querySelector('[name="add"]');
const price = document.getElementById(`price-${this.dataset.section}`);
if (!addButton) return;
addButton.textContent = window.variantStrings.unavailable;
if (price) price.classList.add('visibility-hidden');
}
getVariantData() {
this.variantData = this.variantData || JSON.parse(this.querySelector('[type="application/json"]').textContent);
return this.variantData;
}
}
customElements.define('variant-selects', VariantSelects);
class VariantRadios extends VariantSelects {
constructor() {
super();
}
updateOptions() {
const fieldsets = Array.from(this.querySelectorAll('fieldset'));
this.options = fieldsets.map((fieldset) => {
return Array.from(fieldset.querySelectorAll('input')).find((radio) => radio.checked).value;
});
}
}
customElements.define('variant-radios', VariantRadios);
Hi, I am building my first shop and would like to add a collection slider to the homepage. Could u tell me if this code can be adapted to Dawn 9.0? How can I achieve that?
Thank you in advance
@sapiens_sapiens this is already built into Dawn 9.0. Go to theme editor and add the Featured Collection Block and make sure to increase the maximum products to show to something like 10 and make sure to click enable carousel on desktop. Is that what you're looking for?
Thx for replying. My goal is to have a collection list slider, while featured collection displays products, not the available collections. How can I achieve that?
@sapiens_sapiens Try this.
.collection-list-wrapper > slider-component > .slider-buttons {
display: flex;
}
.collection-list-wrapper > slider-component > .slider.slider--tablet {
position: relative;
flex-wrap: inherit;
overflow-x: auto;
scroll-snap-type: x mandatory;
scroll-behavior: smooth;
scroll-padding-left: 1.5rem;
-webkit-overflow-scrolling: touch;
margin-bottom: 1rem;
}
Hope that helps!
Great, thanks. My goal is to enable desktop slider too, like in featured collection. Could you tell me how to do it?
@sapiens_sapiens If you follow my instructions this works on Dawn 9.0 theme. I tested as you can see below:
Otherwise you may have some CSS overriding it. If you install a fresh dawn theme and test it should work fine.
Thank u
Thanks @shadowsfall118 for also looking into it.
I posted my code on here but it got flagged as potential spam so that was useful - i dont know if you were able to save it before it was removed - so @made4Uo i tried to replace the code you posted with what i had but it is the same outcome. I also tried to do the walkthrough once again just in case i missed something - but still got the same result, no large image, some thumbnails and no slider activity. I have tried it over 10 times now with the same outcome sadly so i have to come to the conclusion there is something wrong 😞
Hopefully this dropbox link might work...
https://www.dropbox.com/sh/3rxwwufxg5sn8rl/AAAlY82qoOXDtsGJHUY7ZFSMa?dl=0
Thanks.
Hi @StewartM I fix it. Please check my website
I was able reproduce the problem. With my intention on mostly keeping the original Dawn code cause the elements to be hidden. Fixed code available in the website. Let me know if you still have issues
I just deleted the code following code in the large image
{%- assign variant_images = product.images | where: 'attached_to_variant?', true | map: 'src' -%}
{%- if product.selected_or_first_available_variant.featured_media != null -%}
{%- assign media = product.selected_or_first_available_variant.featured_media -%}
@shadowsfall118 Thanks for posting the file. I was able to find the problem
Thank you for the like. Yes. That is the other way
Thanks @made4Uo for the slider!!!! I can confirm it's working.
Another addition to @made4Uo code is to hide the slider buttons if there is less than 4 images.
global.js inside the INT() function right after this.update(); add the following:
const thumbCount = this.pageTotal.textContent; if (thumbCount <= 4) { document.getElementsByClassName('slider-buttons')[0].style.visibility = 'hidden'; } else { document.getElementsByClassName('slider-buttons')[0].style.visibility = 'visible'; } } |
Thank you @shadowsfall118 never think of hiding the slider 😁
Hi @shadowsfall118 . You do not need to add javascript code. Just change the product.media.size < 5 in the slider-buttons so the "small-hide" get activated. See the bold code. I updated my code in the website too. Thank you again
@made4Uo thanks for pointing this out. Although the "small-hide" css class does not seem to do anything on my vanilla (plain) shop. Maybe replace "small-hide" with "visually-hidden"?
OLD:
<div class="slider-buttons {% if product.media.size < 5 %} small-hide{% endif %}"> |
NEW:
<div class="slider-buttons {% if product.media.size < 5 %} visually-hidden{% endif %}"> |
@shadowsfall118 . If you go to the asset folder and open the base.css folder you see the visually hidden there (see below). This is not really what we want. I was able to find the small-hide and it only works in small screen. We can use 'hidden' instead. I updated my code in the website again. Code should look like this
<div class="slider-buttons {% if product.media.size < 5 %}hidden{% endif %}">
.visually-hidden {
position: absolute !important;
overflow: hidden;
width: 1px;
height: 1px;
margin: -1px;
padding: 0;
border: 0;
clip: rect(0 0 0 0);
word-wrap: normal !important;
}
@made4Uo I think this line needs to be 4 not 3.
this.totalPages = sliderItemsToShow.length - this.slidesPerPage + 4; |
I had a product with 5 images and the last was not showing / could not click the right arrow to reveal it.
You are right! Thank you
We recently spoke with Zopi developers @Zopi about how dropshipping businesses can enha...
By JasonH Oct 23, 2024A big shout out to all of the merchants who participated in our AMA with 2H Media: Holi...
By Jacqui Oct 21, 2024We want to take a moment to celebrate the incredible ways you all engage with the Shopi...
By JasonH Oct 15, 2024