Untitled

 avatar
unknown
plain_text
8 days ago
24 kB
7
Indexable
{% comment %}
  featured-color-variants.liquid — v4
  Updates:
    1. Replaced static image with a native-feeling Gallery Slider (swipeable + snap points).
    2. Added left/right navigation arrows on hover.
    3. Quick Add button updated with Shopping Bag + icon and strict theme font variables.
    4. JS updated: Clicking thumbnails now smoothly scrolls the gallery to the correct slide.
{% endcomment %}

<style>
  .fcv-section {
    width: 100%;
    background-color: {{ section.settings.background_color }};
    padding-top: {{ section.settings.padding_top }}px;
    padding-bottom: {{ section.settings.padding_bottom }}px;
    box-sizing: border-box;
  }

  .fcv-section .fcv-inner--full {
    width: 100%;
    padding-inline: 1.5rem;
    box-sizing: border-box;
  }

  .fcv-section .fcv-header {
    text-align: center;
    margin-bottom: 40px;
  }

  .fcv-section .fcv-heading {
    font-family: var(--font-heading-family, inherit);
    font-size: clamp(1.5rem, 3vw, 2.25rem);
    font-weight: 500;
    letter-spacing: 0.04em;
    margin: 0 0 12px;
    color: inherit;
  }

  .fcv-section .fcv-subheading {
    font-family: var(--font-body-family, inherit);
    font-size: 1rem;
    font-weight: 400;
    color: rgba(var(--color-foreground), 0.7);
    margin: 0;
  }

  .fcv-section .fcv-grid {
    display: grid;
    grid-template-columns: repeat(var(--fcv-cols, 3), 1fr);
    gap: 24px;
  }

  @media (max-width: 768px) {
    .fcv-section .fcv-grid {
      grid-template-columns: repeat(2, 1fr);
      gap: 16px;
    }
  }

  @media (max-width: 480px) {
    .fcv-section .fcv-grid {
      grid-template-columns: 1fr;
      gap: 16px;
    }
  }

  .fcv-section .fcv-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    position: relative;
  }

  /* --- GALLERY SLIDER CSS --- */
  .fcv-section .fcv-hero-wrap {
    position: relative;
    overflow: hidden;
    background: #f5f5f5;
  }

  .fcv-section .fcv-gallery-track {
    display: flex;
    flex-direction: row;
    overflow-x: auto;
    scroll-snap-type: x mandatory;
    scrollbar-width: none;
    -ms-overflow-style: none;
    scroll-behavior: smooth;
    width: 100%;
  }

  .fcv-section .fcv-gallery-track::-webkit-scrollbar {
    display: none;
  }

  .fcv-section .fcv-gallery-slide {
    flex: 0 0 100%;
    scroll-snap-align: start;
    position: relative;
  }

  .fcv-section .fcv-hero {
    width: 100%;
    aspect-ratio: 3 / 4;
    object-fit: cover;
    display: block;
  }

  /* Slider Navigation Arrows */
  .fcv-gallery-arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 32px;
    height: 32px;
    background: rgba(255, 255, 255, 0.9);
    border: none;
    border-radius: 50%;
    display: flex;
    align-items: center;
    justify-content: center;
    cursor: pointer;
    opacity: 0;
    transition: opacity 0.2s ease, transform 0.2s ease;
    z-index: 5;
    box-shadow: 0 2px 6px rgba(0,0,0,0.1);
    color: #000;
  }

  .fcv-gallery-arrow:hover {
    transform: translateY(-50%) scale(1.05);
  }

  .fcv-gallery-arrow svg {
    width: 16px;
    height: 16px;
    stroke-width: 2;
  }

  .fcv-gallery-arrow--prev { left: 8px; }
  .fcv-gallery-arrow--next { right: 8px; }

  @media (hover: hover) {
    .fcv-section .fcv-hero-wrap:hover .fcv-gallery-arrow {
      opacity: 1;
    }
  }

  /* --- QUICK ADD FLOATING BUTTON CSS --- */
  .fcv-quick-add {
    position: absolute;
    bottom: 12px;
    right: 12px;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    background-color: #333333; /* Matched to dark grey theme style */
    color: #ffffff;
    border: none;
    border-radius: 40px;
    height: 40px;
    min-width: 40px;
    padding: 0;
    text-decoration: none;
    overflow: hidden;
    cursor: pointer;
    z-index: 10;
    transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
    box-shadow: 0 4px 10px rgba(0,0,0,0.15);
  }

  .fcv-quick-add svg {
    width: 18px;
    height: 18px;
    fill: none;
    stroke: currentColor;
    stroke-width: 1.5;
    stroke-linecap: round;
    stroke-linejoin: round;
    flex-shrink: 0;
    margin: 0 11px;
    transition: margin 0.3s ease;
  }

  .fcv-quick-add span {
    font-family: var(--font-body-family, sans-serif);
    font-size: 10px;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    white-space: nowrap;
    opacity: 0;
    max-width: 0;
    transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
  }

  .fcv-section .fcv-hero-wrap:hover .fcv-quick-add,
  .fcv-quick-add:hover {
    background-color: #000000;
    padding: 0 16px 0 0;
  }

  .fcv-section .fcv-hero-wrap:hover .fcv-quick-add svg,
  .fcv-quick-add:hover svg {
    margin: 0 8px 0 14px;
  }

  .fcv-section .fcv-hero-wrap:hover .fcv-quick-add span,
  .fcv-quick-add:hover span {
    opacity: 1;
    max-width: 150px;
  }

  /* --- THUMBNAILS & DETAILS --- */
  .fcv-section .fcv-thumbs {
    display: flex;
    flex-direction: row;
    gap: 8px;
    overflow-x: auto;
    padding-bottom: 4px;
    scrollbar-width: none;
    -ms-overflow-style: none;
  }

  .fcv-section .fcv-thumbs::-webkit-scrollbar {
    display: none;
  }

  .fcv-section .fcv-thumb-wrap {
    position: relative;
    flex-shrink: 0;
  }

  .fcv-section .fcv-thumb {
    display: block;
    border: 2px solid transparent;
    cursor: pointer;
    padding: 0;
    background: none;
    border-radius: 2px;
    overflow: hidden;
    transition: border-color 0.2s ease, transform 0.2s ease;
    outline: none;
  }

  .fcv-section .fcv-thumb:hover {
    border-color: #ccc;
    transform: scale(1.05);
  }

  .fcv-section .fcv-thumb.fcv-thumb--active {
    border-color: #000;
  }

  {% assign swatch_px = '56px' %}
  {% if section.settings.swatch_size == 'small' %}
    {% assign swatch_px = '40px' %}
  {% elsif section.settings.swatch_size == 'large' %}
    {% assign swatch_px = '72px' %}
  {% endif %}

  .fcv-section .fcv-thumb img {
    width: {{ swatch_px }};
    height: {{ swatch_px }};
    display: block;
    object-fit: cover;
  }

  .fcv-section .fcv-color-label {
    position: absolute;
    bottom: calc(100% + 6px);
    left: 50%;
    transform: translateX(-50%);
    background: rgba(0,0,0,0.75);
    color: #fff;
    font-size: 11px;
    font-family: var(--font-body-family, inherit);
    white-space: nowrap;
    padding: 3px 7px;
    border-radius: 3px;
    pointer-events: none;
    opacity: 0;
    transition: opacity 0.2s ease;
  }

  {% if section.settings.show_color_label_on_hover %}
  .fcv-section .fcv-thumb-wrap:hover .fcv-color-label,
  .fcv-section .fcv-thumb:focus-visible ~ .fcv-color-label {
    opacity: 1;
  }
  {% endif %}

  .fcv-section .fcv-card-info {
    display: flex;
    flex-direction: column;
    gap: 6px;
    text-align: center;
  }

  .fcv-section .fcv-card-title {
    font-family: var(--font-body-family, inherit);
    font-size: 0.95rem;
    font-weight: 500;
    margin: 0;
    color: inherit;
    letter-spacing: 0.02em;
  }

  .fcv-section .fcv-card-price {
    font-family: var(--font-body-family, inherit);
    font-size: 0.9rem;
    font-weight: 400;
    color: rgba(var(--color-foreground), 0.75);
    margin: 0;
  }

  .fcv-section .fcv-card-btn {
    display: inline-block;
    margin-top: 4px;
    padding: 10px 20px;
    background: #000;
    color: #fff;
    font-family: var(--font-body-family, inherit);
    font-size: 0.85rem;
    font-weight: 500;
    letter-spacing: 0.08em;
    text-transform: uppercase;
    text-decoration: none;
    border: none;
    cursor: pointer;
    transition: background 0.2s ease;
    align-self: center;
  }

  .fcv-section .fcv-card-btn:hover {
    background: #333;
  }
</style>

<section class="fcv-section" id="fcv-{{ section.id }}" style="--fcv-cols: {{ section.settings.columns_desktop }};">
  <div class="{% if section.settings.section_width == 'full-width' %}fcv-inner--full{% else %}page-width{% endif %}">

    {% if section.settings.heading != blank or section.settings.subheading != blank %}
      <div class="fcv-header">
        {% if section.settings.heading != blank %}
          <h2 class="fcv-heading">{{ section.settings.heading | escape }}</h2>
        {% endif %}
        {% if section.settings.subheading != blank %}
          <p class="fcv-subheading">{{ section.settings.subheading | escape }}</p>
        {% endif %}
      </div>
    {% endif %}

    <div class="fcv-grid">
      {% for block in section.blocks %}
        {% assign blk = block.settings %}

        <div class="fcv-card" data-card="{{ section.id }}-{{ block.id }}" {{ block.shopify_attributes }}>

          <div class="fcv-hero-wrap">
            
            <button type="button" class="fcv-gallery-arrow fcv-gallery-arrow--prev" aria-label="Previous slide">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M15 18l-6-6 6-6" stroke-linecap="round" stroke-linejoin="round"/></svg>
            </button>
            <button type="button" class="fcv-gallery-arrow fcv-gallery-arrow--next" aria-label="Next slide">
              <svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><path d="M9 18l6-6-6-6" stroke-linecap="round" stroke-linejoin="round"/></svg>
            </button>

            <div class="fcv-gallery-track">
              {% comment %} 
                Render all available images as slides in the gallery track 
              {% endcomment %}
              
              {% if blk.hero_image != blank %}
                <div class="fcv-gallery-slide">
                  <a href="{{ blk.product.url | default: '#' }}" tabindex="-1" style="display: block;">
                    <img class="fcv-hero" src="{{ blk.hero_image | image_url: width: 800 }}" alt="{{ blk.hero_image.alt | default: blk.product.title | escape }}" loading="lazy" width="800" height="1067">
                  </a>
                </div>
              {% elsif blk.product != blank and blk.product.featured_image != blank %}
                <div class="fcv-gallery-slide">
                  <a href="{{ blk.product.url | default: '#' }}" tabindex="-1" style="display: block;">
                    <img class="fcv-hero" src="{{ blk.product.featured_image | image_url: width: 800 }}" alt="{{ blk.product.featured_image.alt | default: blk.product.title | escape }}" loading="lazy" width="800" height="1067">
                  </a>
                </div>
              {% endif %}

              {% for i in (1..6) %}
                {% if i == 1 %}{% assign c_img = blk.color_1_image %}{% assign c_name = blk.color_1_name %}
                {% elsif i == 2 %}{% assign c_img = blk.color_2_image %}{% assign c_name = blk.color_2_name %}
                {% elsif i == 3 %}{% assign c_img = blk.color_3_image %}{% assign c_name = blk.color_3_name %}
                {% elsif i == 4 %}{% assign c_img = blk.color_4_image %}{% assign c_name = blk.color_4_name %}
                {% elsif i == 5 %}{% assign c_img = blk.color_5_image %}{% assign c_name = blk.color_5_name %}
                {% elsif i == 6 %}{% assign c_img = blk.color_6_image %}{% assign c_name = blk.color_6_name %}
                {% endif %}

                {% if c_img != blank %}
                  <div class="fcv-gallery-slide" data-color-slide="{{ i }}">
                    <a href="{{ blk.product.url | default: '#' }}" tabindex="-1" style="display: block;">
                      <img class="fcv-hero" src="{{ c_img | image_url: width: 800 }}" alt="{{ c_name | default: blk.product.title | escape }}" loading="lazy" width="800" height="1067">
                    </a>
                  </div>
                {% endif %}
              {% endfor %}
            </div>

            {% comment %} 
              QUICK ADD BUTTON (Shopping Bag + Plus SVG)
              *Ensure you add your theme's modal trigger class here (e.g., js-quick-view, quick-add-btn)*
            {% endcomment %}
            {% if section.settings.show_quick_add_overlay and blk.product != blank %}
              <button class="fcv-quick-add js-quick-add" data-product-url="{{ blk.product.url }}" aria-label="Choose Options">
                <svg viewBox="0 0 24 24">
                  <path d="M8 8V6a4 4 0 018 0v2M5 8h14l1 12H4L5 8z" />
                  <path d="M12 12v6M9 15h6" />
                </svg>
                <span>CHOOSE OPTIONS</span>
              </button>
            {% endif %}
          </div>

          {% comment %} Render Thumbnails (Clicking them scrolls the gallery) {% endcomment %}
          {% assign has_colors = false %}
          {% if blk.color_1_image != blank or blk.color_2_image != blank or blk.color_3_image != blank or blk.color_4_image != blank or blk.color_5_image != blank or blk.color_6_image != blank %}
            {% assign has_colors = true %}
          {% endif %}

          {% if has_colors %}
            <div class="fcv-thumbs" role="listbox" aria-label="Select colour">
              {% assign first_color_rendered = false %}
              {% for i in (1..6) %}
                {% if i == 1 %}{% assign c_img = blk.color_1_image %}{% assign c_thumb = blk.color_1_thumb %}{% assign c_name = blk.color_1_name %}
                {% elsif i == 2 %}{% assign c_img = blk.color_2_image %}{% assign c_thumb = blk.color_2_thumb %}{% assign c_name = blk.color_2_name %}
                {% elsif i == 3 %}{% assign c_img = blk.color_3_image %}{% assign c_thumb = blk.color_3_thumb %}{% assign c_name = blk.color_3_name %}
                {% elsif i == 4 %}{% assign c_img = blk.color_4_image %}{% assign c_thumb = blk.color_4_thumb %}{% assign c_name = blk.color_4_name %}
                {% elsif i == 5 %}{% assign c_img = blk.color_5_image %}{% assign c_thumb = blk.color_5_thumb %}{% assign c_name = blk.color_5_name %}
                {% elsif i == 6 %}{% assign c_img = blk.color_6_image %}{% assign c_thumb = blk.color_6_thumb %}{% assign c_name = blk.color_6_name %}
                {% endif %}

                {% if c_img != blank %}
                  {% assign thumb_url = c_thumb | default: c_img | image_url: width: 144 %}
                  {% assign is_first = false %}
                  {% if first_color_rendered == false %}{% assign is_first = true %}{% assign first_color_rendered = true %}{% endif %}

                  <div class="fcv-thumb-wrap" role="option">
                    <button class="fcv-thumb{% if is_first %} fcv-thumb--active{% endif %}" data-target-slide="{{ i }}" aria-selected="{{ is_first }}" aria-label="{{ c_name | default: 'Colour' | escape }}" type="button">
                      <img src="{{ thumb_url }}" alt="{{ c_name | default: '' | escape }}" loading="lazy" width="144" height="144">
                    </button>
                    {% if c_name != blank %}
                      <span class="fcv-color-label" aria-hidden="true">{{ c_name | escape }}</span>
                    {% endif %}
                  </div>
                {% endif %}
              {% endfor %}
            </div>
          {% endif %}

          {% comment %} Details {% endcomment %}
          {% if blk.show_title or blk.show_price or blk.show_button %}
            <div class="fcv-card-info">
              {% if blk.show_title and blk.product != blank %}
                <p class="fcv-card-title">{{ blk.product.title }}</p>
              {% endif %}

              {% if blk.show_price and blk.product != blank %}
                <p class="fcv-card-price">
                  {{ blk.product.price_min | money }}
                  {% if blk.product.price_varies %}–{{ blk.product.price_max | money }}{% endif %}
                </p>
              {% endif %}

              {% if blk.show_button and blk.product != blank %}
                {% assign btn_label = blk.button_label | default: 'Shop Now' %}
                <a href="{{ blk.product.url }}" class="fcv-card-btn" aria-label="{{ btn_label | escape }}: {{ blk.product.title | escape }}">
                  {{ btn_label | escape }}
                </a>
              {% endif %}
            </div>
          {% endif %}

        </div>
      {% endfor %}
    </div>

  </div>
</section>

<script>
(function () {
  'use strict';

  function initFCV(sectionEl) {
    const cards = sectionEl.querySelectorAll('[data-card]');

    cards.forEach(card => {
      const track = card.querySelector('.fcv-gallery-track');
      const prevBtn = card.querySelector('.fcv-gallery-arrow--prev');
      const nextBtn = card.querySelector('.fcv-gallery-arrow--next');
      const thumbs = card.querySelectorAll('.fcv-thumb');
      const slides = card.querySelectorAll('.fcv-gallery-slide');

      if (!track || slides.length === 0) return;

      // Gallery Left/Right Navigation
      if (prevBtn && nextBtn) {
        prevBtn.addEventListener('click', () => {
          track.scrollBy({ left: -track.clientWidth, behavior: 'smooth' });
        });
        nextBtn.addEventListener('click', () => {
          track.scrollBy({ left: track.clientWidth, behavior: 'smooth' });
        });
      }

      // Thumbnail Click -> Scroll to specific slide
      thumbs.forEach(thumb => {
        thumb.addEventListener('click', () => {
          const targetIndex = thumb.getAttribute('data-target-slide');
          const targetSlide = card.querySelector(`.fcv-gallery-slide[data-color-slide="${targetIndex}"]`);
          
          if (targetSlide) {
            // Scroll to the slide
            const scrollPos = targetSlide.offsetLeft - track.offsetLeft;
            track.scrollTo({ left: scrollPos, behavior: 'smooth' });

            // Update active state
            thumbs.forEach(t => {
              t.classList.remove('fcv-thumb--active');
              t.setAttribute('aria-selected', 'false');
            });
            thumb.classList.add('fcv-thumb--active');
            thumb.setAttribute('aria-selected', 'true');
          }
        });
      });
      
      // Update active thumbnail on manual swipe
      track.addEventListener('scroll', () => {
        const scrollPosition = track.scrollLeft;
        const slideWidth = track.clientWidth;
        const activeIndex = Math.round(scrollPosition / slideWidth);
        
        if (slides[activeIndex]) {
          const activeDataSlide = slides[activeIndex].getAttribute('data-color-slide');
          if (activeDataSlide) {
            thumbs.forEach(t => t.classList.remove('fcv-thumb--active'));
            const activeThumb = card.querySelector(`.fcv-thumb[data-target-slide="${activeDataSlide}"]`);
            if (activeThumb) activeThumb.classList.add('fcv-thumb--active');
          }
        }
      }, { passive: true });
    });
  }

  document.addEventListener('DOMContentLoaded', function () {
    document.querySelectorAll('.fcv-section').forEach(initFCV);
  });

  document.addEventListener('shopify:section:load', function (e) {
    const s = e.target.querySelector('.fcv-section');
    if (s) initFCV(s);
  });

})();
</script>

{% schema %}
{
  "name": "Featured Color Variants",
  "tag": "div",
  "class": "fcv-section-wrapper",
  "settings": [
    {
      "type": "text",
      "id": "heading",
      "label": "Heading",
      "default": "Featured Collection"
    },
    {
      "type": "text",
      "id": "subheading",
      "label": "Subheading"
    },
    {
      "type": "select",
      "id": "columns_desktop",
      "label": "Columns (desktop)",
      "options": [
        { "value": "2", "label": "2" },
        { "value": "3", "label": "3" },
        { "value": "4", "label": "4" }
      ],
      "default": "4"
    },
    {
      "type": "select",
      "id": "section_width",
      "label": "Section width",
      "options": [
        { "value": "page-width", "label": "Page width" },
        { "value": "full-width", "label": "Full width" }
      ],
      "default": "page-width"
    },
    {
      "type": "checkbox",
      "id": "show_quick_add_overlay",
      "label": "Show quick add hovering icon over image",
      "default": true
    },
    {
      "type": "select",
      "id": "swatch_size",
      "label": "Thumbnail size",
      "options": [
        { "value": "small",  "label": "Small (40px)"  },
        { "value": "medium", "label": "Medium (56px)" },
        { "value": "large",  "label": "Large (72px)"  }
      ],
      "default": "medium"
    },
    {
      "type": "checkbox",
      "id": "show_color_label_on_hover",
      "label": "Show colour name on hover",
      "default": true
    },
    {
      "type": "range",
      "id": "padding_top",
      "label": "Padding top",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "default": 36
    },
    {
      "type": "range",
      "id": "padding_bottom",
      "label": "Padding bottom",
      "min": 0,
      "max": 100,
      "step": 4,
      "unit": "px",
      "default": 36
    },
    {
      "type": "color",
      "id": "background_color",
      "label": "Background colour",
      "default": "#ffffff"
    }
  ],
  "blocks": [
    {
      "type": "product_card",
      "name": "Product Card",
      "settings": [
        {
          "type": "product",
          "id": "product",
          "label": "Product"
        },
        {
          "type": "image_picker",
          "id": "hero_image",
          "label": "Hero image (overrides product image)"
        },
        {
          "type": "header",
          "content": "Card Details Options"
        },
        {
          "type": "checkbox",
          "id": "show_title",
          "label": "Show product title",
          "default": false
        },
        {
          "type": "checkbox",
          "id": "show_price",
          "label": "Show price",
          "default": false
        },
        {
          "type": "checkbox",
          "id": "show_button",
          "label": "Show standard text button",
          "default": false
        },
        {
          "type": "text",
          "id": "button_label",
          "label": "Button label",
          "default": "Shop Now"
        },
        { "type": "header", "content": "Colour 1" },
        { "type": "text",         "id": "color_1_name",  "label": "Colour 1 name"            },
        { "type": "image_picker", "id": "color_1_image", "label": "Colour 1 full image"       },
        { "type": "image_picker", "id": "color_1_thumb", "label": "Colour 1 thumbnail (opt.)" },
        { "type": "header", "content": "Colour 2" },
        { "type": "text",         "id": "color_2_name",  "label": "Colour 2 name"            },
        { "type": "image_picker", "id": "color_2_image", "label": "Colour 2 full image"       },
        { "type": "image_picker", "id": "color_2_thumb", "label": "Colour 2 thumbnail (opt.)" },
        { "type": "header", "content": "Colour 3" },
        { "type": "text",         "id": "color_3_name",  "label": "Colour 3 name"            },
        { "type": "image_picker", "id": "color_3_image", "label": "Colour 3 full image"       },
        { "type": "image_picker", "id": "color_3_thumb", "label": "Colour 3 thumbnail (opt.)" },
        { "type": "header", "content": "Colour 4" },
        { "type": "text",         "id": "color_4_name",  "label": "Colour 4 name"            },
        { "type": "image_picker", "id": "color_4_image", "label": "Colour 4 full image"       },
        { "type": "image_picker", "id": "color_4_thumb", "label": "Colour 4 thumbnail (opt.)" },
        { "type": "header", "content": "Colour 5" },
        { "type": "text",         "id": "color_5_name",  "label": "Colour 5 name"            },
        { "type": "image_picker", "id": "color_5_image", "label": "Colour 5 full image"       },
        { "type": "image_picker", "id": "color_5_thumb", "label": "Colour 5 thumbnail (opt.)" },
        { "type": "header", "content": "Colour 6" },
        { "type": "text",         "id": "color_6_name",  "label": "Colour 6 name"            },
        { "type": "image_picker", "id": "color_6_image", "label": "Colour 6 full image"       },
        { "type": "image_picker", "id": "color_6_thumb", "label": "Colour 6 thumbnail (opt.)" }
      ]
    }
  ],
  "max_blocks": 6,
  "presets": [
    {
      "name": "Featured Color Variants",
      "blocks": [
        { "type": "product_card" },
        { "type": "product_card" },
        { "type": "product_card" }
      ]
    }
  ]
}
{% endschema %}
Editor is loading...
Leave a Comment