0% found this document useful (0 votes)
8 views60 pages

Header

The document outlines the structure and functionality of a custom header section for a website, including a transparent header, announcement bar, and various interactive elements such as a search bar with image search capability. It features a mobile menu, store locator, appointment booking, wishlist, account, and cart icons, along with a navigation bar for easy access to different sections. Additionally, it includes an image search modal for users to upload images and find similar products.

Uploaded by

Sam Alex
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
8 views60 pages

Header

The document outlines the structure and functionality of a custom header section for a website, including a transparent header, announcement bar, and various interactive elements such as a search bar with image search capability. It features a mobile menu, store locator, appointment booking, wishlist, account, and cart icons, along with a navigation bar for easy access to different sections. Additionally, it includes an image search modal for users to upload images and find similar products.

Uploaded by

Sam Alex
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

{% comment %}

Custom Header Section - Jaipur Rugs Style


Transparent header that overlaps hero and gets background on scroll
Search bar with image search capability
{% endcomment %}

<!-- Announcement Bar / Marquee -->


<div class="jr-marquee-section" id="jr-marquee">
<div class="jr-marquee-wrapper">
<div class="jr-marquee-slider">
{% for block in [Link] %}
{% if [Link] == 'announcement' %}
<div class="jr-marquee-slide" {{ block.shopify_attributes }}>
<p>{{ [Link] }}
{% if [Link].link_text != blank %}
<a href="{{ [Link].link_url }}">{{ [Link].link_text }}</a>
{% endif %}
</p>
</div>
{% endif %}
{% endfor %}
</div>
<button class="jr-marquee-close" onclick="closeMarquee()" aria-label="Close
announcement">
<svg width="10" height="10" viewBox="0 0 10 10" fill="none">
<path d="M1 1L9 9M9 1L1 9" stroke="currentColor" stroke-width="1.5"/>
</svg>
</button>
</div>
</div>

<!-- Main Header -->


<header class="jr-header" id="jr-header">
<div class="jr-header-inner">
<div class="jr-header-container">

<!-- Left Section: Store Icon + Logo (Desktop) -->


<div class="jr-header-left">
<!-- Mobile Menu Toggle -->
<button class="jr-mobile-toggle" id="jr-mobile-toggle" aria-label="Open menu">
<svg width="20" height="14" viewBox="0 0 20 14" fill="none">
<line x1="0" y1="1" x2="20" y2="1" stroke="currentColor" stroke-width="1.5"/>
<line x1="0" y1="7" x2="20" y2="7" stroke="currentColor" stroke-width="1.5"/>
<line x1="0" y1="13" x2="20" y2="13" stroke="currentColor" stroke-width="1.5"/>
</svg>
</button>

<!-- Store Locator Icon -->


<a href="{{ [Link].store_link | default: '/pages/stores' }}" class="jr-header-icon
jr-store-icon" aria-label="Store locator">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M21 10c0 7-9 13-9 13s-9-6-9-13a9 9 0 0118 0z"/>
<circle cx="12" cy="10" r="3"/>
</svg>
</a>

<!-- Logo (Desktop - in left section) -->


<div class="jr-logo jr-logo-desktop">
<a href="/">
{% if [Link] %}
{{ [Link] | image_url: width: 200 | image_tag: alt: [Link], class:
'jr-logo-img' }}
{% else %}
<span class="jr-logo-text">{{ [Link] }}</span>
{% endif %}
</a>
</div>
</div>

<!-- Center Section: Logo (Mobile) + Search Bar -->


<div class="jr-header-center">
<!-- Logo (Mobile - centered) -->
<div class="jr-logo jr-logo-mobile">
<a href="/">
{% if [Link] %}
{{ [Link] | image_url: width: 160 | image_tag: alt: [Link], class:
'jr-logo-img' }}
{% else %}
<span class="jr-logo-text">{{ [Link] }}</span>
{% endif %}
</a>
</div>

<!-- Search Bar -->


<div class="jr-search-bar" id="jr-search-bar">
<form action="/search" method="get" class="jr-search-form">
<input type="hidden" name="type" value="product">
<input
type="text"
name="q"
id="jr-search-input"
class="jr-search-input"
placeholder="Search"
autocomplete="off"
>
<!-- Search Icon Button -->
<button type="submit" class="jr-search-btn" aria-label="Search">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<circle cx="11" cy="11" r="8"/>
<path d="M21 21l-4.35-4.35"/>
</svg>
</button>
<!-- Camera/Image Search Icon -->
<button type="button" class="jr-camera-btn" onclick="openImageSearch()"
aria-label="Search by image">
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0
012 2z"/>
<circle cx="12" cy="13" r="4"/>
</svg>
</button>
</form>
</div>
</div>

<!-- Right Section: Book Appointment, Wishlist, Account, Cart -->


<div class="jr-header-right">
<!-- Book An Appointment -->
<a href="{{ [Link].appointment_link | default: '#' }}" class="jr-appointment-link"
target="_blank">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5">
<rect x="3" y="4" width="18" height="18" rx="2" ry="2"/>
<line x1="16" y1="2" x2="16" y2="6"/>
<line x1="8" y1="2" x2="8" y2="6"/>
<line x1="3" y1="10" x2="21" y2="10"/>
</svg>
<span>Book An Appointment</span>
</a>
<!-- Wishlist -->
<a href="/pages/wishlist" class="jr-header-icon jr-wishlist-icon" aria-label="Wishlist">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78
7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/>
</svg>
<span class="jr-badge" id="jr-wishlist-count">0</span>
</a>

<!-- Account -->


<a href="/account" class="jr-header-icon jr-account-icon" aria-label="Account">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/>
<circle cx="12" cy="7" r="4"/>
</svg>
</a>

<!-- Cart -->


<a href="/cart" class="jr-header-icon jr-cart-icon" aria-label="Cart">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M6 2L3 6v14a2 2 0 002 2h14a2 2 0 002-2V6l-3-4z"/>
<line x1="3" y1="6" x2="21" y2="6"/>
<path d="M16 10a4 4 0 01-8 0"/>
</svg>
<span class="jr-badge" id="jr-cart-count">{{ cart.item_count }}</span>
</a>
</div>
</div>
</div>

<!-- Navigation Bar -->


<nav class="jr-navigation" id="jr-navigation">
<div class="jr-nav-container">
<ul class="jr-nav-list">
{% for link in linklists[[Link]].links %}
<li class="jr-nav-item {% if [Link] > 0 %}has-dropdown{% endif %}">
<a href="{{ [Link] }}" class="jr-nav-link">{{ [Link] | upcase }}</a>
{% if [Link] > 0 %}
<div class="jr-mega-menu">
<div class="jr-mega-menu-inner">
{% for child_link in [Link] %}
<div class="jr-mega-col">
{% if child_link.[Link] > 0 %}
<span class="jr-mega-title">{{ child_link.title | upcase }}</span>
<ul class="jr-mega-links">
{% for grandchild in child_link.links %}
<li><a href="{{ [Link] }}">{{ [Link] }}</a></li>
{% endfor %}
</ul>
{% else %}
<a href="{{ child_link.url }}" class="jr-mega-single-link">{{ child_link.title }}</a>
{% endif %}
</div>
{% endfor %}
</div>
</div>
{% endif %}
</li>
{% endfor %}
</ul>
</div>
</nav>
</header>

<!-- Image Search Modal -->


<!-- Image Search Modal - Replace the existing one -->
<div class="jr-image-search-modal" id="jr-image-search-modal">
<div class="jr-image-search-overlay" onclick="closeImageSearch()"></div>
<div class="jr-image-search-content">
<button class="jr-image-search-close" onclick="closeImageSearch()" aria-label="Close">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
<h3>Search by Image</h3>
<p>Upload a rug image to find similar products</p>

<!-- Upload Area -->


<div class="jr-image-upload-area" id="jr-image-upload-area">
<input type="file" id="jr-image-upload" accept="image/*"
onchange="handleImageUpload(event)" hidden>
<label for="jr-image-upload" class="jr-image-upload-label">
<svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1">
<path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012
2z"/>
<circle cx="12" cy="13" r="4"/>
</svg>
<span>Click to upload or drag & drop</span>
<span class="jr-upload-subtitle">Supports JPG, PNG, WEBP</span>
</label>
</div>

<!-- Image Preview -->


<div class="jr-image-preview" id="jr-image-preview" style="display:none;">
<img id="jr-preview-img" src="" alt="Preview">
<button onclick="clearImageUpload()" class="jr-clear-image">Remove & Upload
New</button>
</div>

<!-- Loading -->


<div class="jr-image-search-loading" id="jr-image-search-loading" style="display:none;">
<div class="jr-loading-spinner"></div>
<p>Searching for similar products...</p>
</div>

<!-- Results from store search -->


<div class="jr-image-search-results" id="jr-image-search-results" style="display:none;">
<h4 id="jr-results-title">Similar Products</h4>
<div class="jr-image-results-grid" id="jr-image-results-grid"></div>
<a href="#" id="jr-view-all-results" class="jr-view-all-results" style="display:none;">View All
Results →</a>
</div>

<!-- Action Buttons -->


<div class="jr-image-search-actions" id="jr-image-search-actions">
<button class="jr-image-search-submit" id="jr-image-search-submit"
onclick="submitImageSearch()" disabled>
Find Similar in Our Store
</button>
<button class="jr-image-search-google" id="jr-image-search-google"
onclick="searchWithGoogleLens()" style="display:none;">
<svg width="16" height="16" viewBox="0 0 533.5 544.3"><path d="M533.5
278.4c0-18.5-1.5-37.1-4.7-55.3H272.1v104.8h147c-6.1 33.8-25.7 63.7-54.4
82.7v68h87.7c51.5-47.4 81.1-117.4 81.1-200.2z" fill="#4285f4"/><path d="M272.1 544.3c73.4 0
135.3-24.1 180.4-65.7l-87.7-68c-24.4 16.6-55.9 26-92.6 26-71
0-131.2-47.9-152.8-112.3H28.9v70.1c46.2 91.9 140.3 149.9 243.2 149.9z"
fill="#34a853"/><path d="M119.3 324.3c-11.4-33.8-11.4-70.4 0-104.2V150H28.9c-38.6
76.9-38.6 167.5 0 244.4l90.4-70.1z" fill="#fbbc04"/><path d="M272.1 107.7c38.8-.6 76.3 14
104.4 40.8l77.7-77.7C405 24.6 339.7-.8 272.1 0 169.2 0 75.1 58 28.9 150l90.4 70.1c21.5-64.5
81.8-112.4 152.8-112.4z" fill="#ea4335"/></svg>
Search with Google Lens
</button>
</div>
</div>
</div>

<!-- Mobile Menu Drawer -->


<div class="jr-mobile-menu-overlay" id="jr-mobile-overlay" onclick="closeMobileMenu()"></div>
<div class="jr-mobile-menu" id="jr-mobile-menu">
<div class="jr-mobile-menu-header">
<button class="jr-mobile-back" id="jr-mobile-back" style="display:none;"
onclick="mobileMenuBack()">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M19 12H5M12 19l-7-7 7-7"/>
</svg>
</button>
<div class="jr-mobile-menu-logo">
<a href="/">
{% if [Link] %}
{{ [Link] | image_url: width: 154 | image_tag: alt: [Link] }}
{% else %}
<span>{{ [Link] }}</span>
{% endif %}
</a>
</div>
<span class="jr-mobile-menu-title" id="jr-mobile-menu-title"></span>
<button class="jr-mobile-close" onclick="closeMobileMenu()" aria-label="Close menu">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<path d="M18 6L6 18M6 6l12 12"/>
</svg>
</button>
</div>

<!-- Mobile Search -->


<div class="jr-mobile-search">
<form action="/search" method="get" class="jr-search-form">
<input type="hidden" name="type" value="product">
<input type="text" name="q" placeholder="Search" class="jr-search-input"
autocomplete="off">
<button type="submit" class="jr-search-btn" aria-label="Search">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<circle cx="11" cy="11" r="8"/>
<path d="M21 21l-4.35-4.35"/>
</svg>
</button>
<button type="button" class="jr-camera-btn" onclick="openImageSearch()"
aria-label="Search by image">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M23 19a2 2 0 01-2 2H3a2 2 0 01-2-2V8a2 2 0 012-2h4l2-3h6l2 3h4a2 2 0 012
2z"/>
<circle cx="12" cy="13" r="4"/>
</svg>
</button>
</form>
</div>

<div class="jr-mobile-menu-body" id="jr-mobile-menu-body">


<ul class="jr-mobile-nav-list" id="jr-mobile-nav-main">
{% for link in linklists[[Link]].links %}
<li class="jr-mobile-nav-item {% if [Link] > 0 %}has-children{% endif %}">
{% if [Link] > 0 %}
<a href="javascript:void(0)" class="jr-mobile-nav-link"
onclick="openMobileSubmenu(this, '{{ [Link] | escape }}')">
{{ [Link] | upcase }}
<svg width="8" height="14" viewBox="0 0 8 14" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M1 1l6 6-6 6"/>
</svg>
</a>
<ul class="jr-mobile-submenu" style="display:none;">
{% for child_link in [Link] %}
<li class="jr-mobile-nav-item {% if child_link.[Link] > 0 %}has-children{% endif
%}">
{% if child_link.[Link] > 0 %}
<a href="javascript:void(0)" class="jr-mobile-nav-link"
onclick="openMobileSubmenu(this, '{{ child_link.title | escape }}')">
{{ child_link.title | upcase }}
<svg width="8" height="14" viewBox="0 0 8 14" fill="none" stroke="currentColor"
stroke-width="1.5">
<path d="M1 1l6 6-6 6"/>
</svg>
</a>
<ul class="jr-mobile-submenu" style="display:none;">
{% for grandchild in child_link.links %}
<li><a href="{{ [Link] }}" class="jr-mobile-nav-link">{{ [Link] |
upcase }}</a></li>
{% endfor %}
</ul>
{% else %}
<a href="{{ child_link.url }}" class="jr-mobile-nav-link">{{ child_link.title | upcase
}}</a>
{% endif %}
</li>
{% endfor %}
</ul>
{% else %}
<a href="{{ [Link] }}" class="jr-mobile-nav-link">{{ [Link] | upcase }}</a>
{% endif %}
</li>
{% endfor %}
<li class="jr-mobile-nav-item">
<a href="{{ [Link].appointment_link | default: '#' }}" class="jr-mobile-nav-link
jr-mobile-appointment">BOOK AN APPOINTMENT</a>
</li>
</ul>
</div>
</div>

<style>
/* ========================================
CSS Variables
======================================== */
:root {
--jr-marquee-bg: {{ [Link].marquee_bg_color }};
--jr-marquee-text: {{ [Link].marquee_text_color }};
--jr-header-bg: {% if [Link].header_bg_color %}{{ [Link].header_bg_color
}}{% else %}transparent{% endif %};
--jr-header-text: {{ [Link].header_text_color }};
--jr-header-scrolled-bg: {{ [Link].header_scrolled_bg }};
--jr-header-scrolled-text: {{ [Link].header_scrolled_text }};
--jr-nav-bg: {{ [Link].nav_bg_color }};
--jr-nav-text: {{ [Link].nav_text_color }};
--jr-accent: #CF7B69;
--jr-search-bg: #f8f8f8;
--jr-search-border: #606060;
--jr-transition: 0.3s ease;
}

/* ========================================
CRITICAL: Remove all default body/main padding
so header overlaps hero section
======================================== */
body {
padding-top: 0 !important;
margin-top: 0 !important;
}

/* Remove any theme default header spacing */


#MainContent,
main,
.main-content,
#main,
[role="main"] {
margin-top: 0 !important;
}

/* ========================================
Announcement / Marquee Bar
======================================== */
.jr-marquee-section {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1002;
transition: transform 0.3s ease;
}

.[Link] {
transform: translateY(-100%);
pointer-events: none;
}

.[Link]-hidden {
transform: translateY(-100%);
}
.jr-marquee-wrapper {
position: relative;
background-color: var(--jr-marquee-bg);
color: var(--jr-marquee-text);
}

.jr-marquee-slider {
text-align: center;
overflow: hidden;
position: relative;
}

.jr-marquee-slide {
padding: 8px 60px;
display: none;
}

.jr-marquee-slide:first-child {
display: block;
}

.[Link] {
display: block;
}

.jr-marquee-slide p {
margin: 0;
font-size: 12px;
line-height: 1.4;
}

.jr-marquee-slide a {
color: var(--jr-marquee-text);
text-decoration: underline;
font-weight: 500;
}

.jr-marquee-close {
position: absolute;
top: 50%;
right: 10px;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
color: var(--jr-marquee-text);
padding: 4px;
line-height: 0;
}

/* ========================================
Main Header - FIXED & OVERLAPPING
======================================== */
.jr-header {
position: fixed;
top: 0;
left: 0;
right: 0;
z-index: 1001;
background-color: transparent;
transition: background-color var(--jr-transition), box-shadow var(--jr-transition), top
var(--jr-transition);
}

/* When marquee is visible, push header down */


.[Link]-marquee {
top: var(--jr-marquee-height, 32px);
}

/* When marquee is hidden or scrolled, header at top */


.[Link]-hidden {
top: 0;
}

/* ========================================
Transparent State (at top - overlapping hero)
======================================== */
.jr-header:not(.scrolled) {
background-color: transparent;
box-shadow: none;
}

.jr-header:not(.scrolled) .jr-header-icon,
.jr-header:not(.scrolled) .jr-nav-link,
.jr-header:not(.scrolled) .jr-appointment-link,
.jr-header:not(.scrolled) .jr-appointment-link span,
.jr-header:not(.scrolled) .jr-logo-text {
color: var(--jr-header-text);
}

.jr-header:not(.scrolled) .jr-mobile-toggle {
color: var(--jr-header-text);
}

.jr-header:not(.scrolled) .jr-store-icon {
color: var(--jr-header-text);
}

/* Invert logo to white when transparent */


.jr-header:not(.scrolled) .jr-logo-img {
filter: brightness(0) invert(1);
}

/* Search bar styling when transparent */


.jr-header:not(.scrolled) .jr-search-input {
background-color: rgba(255, 255, 255, 0.1);
border-color: rgba(255, 255, 255, 0.5);
color: #fff;
}

.jr-header:not(.scrolled) .jr-search-input::placeholder {
color: rgba(255, 255, 255, 0.7);
}

.jr-header:not(.scrolled) .jr-search-btn,
.jr-header:not(.scrolled) .jr-camera-btn {
color: #fff;
}

/* Badge stays same color */


.jr-header:not(.scrolled) .jr-badge {
background-color: var(--jr-accent);
color: #fff;
}

/* ========================================
Scrolled State (solid background)
======================================== */
.[Link] {
background-color: var(--jr-header-scrolled-bg);
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
top: 0 !important;
}

.[Link] .jr-header-icon,
.[Link] .jr-nav-link,
.[Link] .jr-appointment-link,
.[Link] .jr-appointment-link span,
.[Link] .jr-logo-text {
color: var(--jr-header-scrolled-text);
}

.[Link] .jr-mobile-toggle {
color: var(--jr-header-scrolled-text);
}

.[Link] .jr-store-icon {
color: var(--jr-header-scrolled-text);
}

.[Link] .jr-logo-img {
filter: none;
}

.[Link] .jr-search-input {
background-color: var(--jr-search-bg);
border-color: var(--jr-search-border);
color: #333;
}

.[Link] .jr-search-input::placeholder {
color: #999;
}

.[Link] .jr-search-btn,
.[Link] .jr-camera-btn {
color: #333;
}

/* Navigation border changes */


.jr-header:not(.scrolled) .jr-navigation {
border-top: 1px solid rgba(255, 255, 255, 0.2);
}

.[Link] .jr-navigation {
border-top: 1px solid rgba(0, 0, 0, 0.08);
}

/* ========================================
Header Inner Layout
======================================== */
.jr-header-inner {
padding: 10px 20px;
}

.jr-header-container {
display: flex;
align-items: center;
justify-content: space-between;
max-width: 1620px;
margin: 0 auto;
}

/* ========================================
Header Left
======================================== */
.jr-header-left {
display: flex;
align-items: center;
gap: 12px;
flex: 0 0 auto;
}

.jr-mobile-toggle {
display: none;
background: none;
border: none;
cursor: pointer;
padding: 4px;
line-height: 0;
color: inherit;
}

.jr-store-icon {
display: flex;
align-items: center;
}

.jr-logo a {
display: inline-block;
line-height: 0;
text-decoration: none;
}

.jr-logo-img {
max-width: 180px;
height: auto;
transition: filter var(--jr-transition);
}

.jr-logo-text {
font-size: 22px;
font-weight: 300;
letter-spacing: 4px;
text-transform: uppercase;
text-decoration: none;
color: inherit;
}

.jr-logo-mobile {
display: none;
}

/* ========================================
Header Center - Search
======================================== */
.jr-header-center {
flex: 1;
max-width: 600px;
margin: 0 30px;
}

.jr-search-bar {
width: 100%;
}

.jr-search-form {
position: relative;
display: flex;
align-items: center;
}

.jr-search-input {
width: 100%;
padding: 10px 100px 10px 20px;
background-color: var(--jr-search-bg);
border: 1px solid var(--jr-search-border);
height: 44px;
font-size: 14px;
outline: none;
text-transform: capitalize;
box-sizing: border-box;
transition: all var(--jr-transition);
}

.jr-search-input:focus {
outline: none;
border-color: #333;
}

.jr-search-btn {
position: absolute;
right: 45px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 8px;
line-height: 0;
transition: color var(--jr-transition);
}

.jr-camera-btn {
position: absolute;
right: 8px;
top: 50%;
transform: translateY(-50%);
background: none;
border: none;
cursor: pointer;
padding: 8px;
line-height: 0;
transition: color var(--jr-transition);
}

/* ========================================
Header Right
======================================== */
.jr-header-right {
display: flex;
align-items: center;
gap: 12px;
flex: 0 0 auto;
}

.jr-appointment-link {
display: flex;
align-items: center;
gap: 8px;
font-size: 12px;
text-transform: uppercase;
text-decoration: none;
color: inherit;
white-space: nowrap;
transition: color var(--jr-transition);
}

.jr-header-icon {
position: relative;
display: flex;
align-items: center;
text-decoration: none;
color: inherit;
line-height: 0;
transition: color var(--jr-transition);
}

.jr-badge {
position: absolute;
top: -4px;
right: -10px;
background-color: var(--jr-accent);
color: #fff;
height: 14px;
width: 14px;
border-radius: 100%;
font-size: 10px;
display: flex;
align-items: center;
justify-content: center;
line-height: 1;
}

/* ========================================
Navigation
======================================== */
.jr-navigation {
transition: border-color var(--jr-transition);
}

.jr-nav-container {
max-width: 1620px;
margin: 0 auto;
padding: 0 20px;
}

.jr-nav-list {
display: flex;
align-items: center;
justify-content: space-between;
list-style: none;
margin: 0;
padding: 0;
}

.jr-nav-item {
position: relative;
}

.jr-nav-link {
display: block;
padding: 12px 8px;
font-size: 14px;
text-transform: uppercase;
text-decoration: none;
color: inherit;
letter-spacing: 0.5px;
white-space: nowrap;
transition: opacity 0.2s, color var(--jr-transition);
}

.jr-nav-link:hover {
opacity: 0.7;
}
/* ========================================
Mega Menu
======================================== */
.jr-mega-menu {
position: absolute;
top: 100%;
left: 50%;
transform: translateX(-50%);
background: #fff;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1);
min-width: 250px;
max-width: 1200px;
padding: 30px;
opacity: 0;
visibility: hidden;
transition: opacity 0.3s, visibility 0.3s;
z-index: 100;
}

.jr-nav-item:hover .jr-mega-menu {
opacity: 1;
visibility: visible;
}

.jr-mega-menu-inner {
display: flex;
gap: 40px;
}

.jr-mega-col {
min-width: 150px;
}

.jr-mega-title {
display: block;
font-size: 14px;
font-weight: 500;
text-transform: uppercase;
margin-bottom: 12px;
color: #000;
}

.jr-mega-links {
list-style: none;
padding: 0;
margin: 0;
}

.jr-mega-links li {
margin-bottom: 8px;
}

.jr-mega-links a {
font-size: 13px;
color: #555;
text-decoration: none;
text-transform: uppercase;
font-weight: 300;
transition: color 0.2s;
}

.jr-mega-links a:hover {
color: #000;
}

.jr-mega-single-link {
font-size: 13px;
color: #555;
text-decoration: none;
font-weight: 300;
display: block;
margin-bottom: 8px;
}

.jr-mega-single-link:hover {
color: #000;
}

/* ========================================
Image Search Modal
======================================== */
.jr-image-search-modal {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
z-index: 10000;
align-items: center;
justify-content: center;
}

.[Link] {
display: flex;
}

.jr-image-search-overlay {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
}

.jr-image-search-content {
position: relative;
background: #fff;
padding: 40px;
border-radius: 8px;
max-width: 500px;
width: 90%;
text-align: center;
z-index: 1;
}

.jr-image-search-close {
position: absolute;
top: 15px;
right: 15px;
background: none;
border: none;
cursor: pointer;
color: #333;
line-height: 0;
}

.jr-image-search-content h3 {
margin: 0 0 8px;
font-size: 22px;
font-weight: 500;
}

.jr-image-search-content > p {
margin: 0 0 24px;
color: #666;
font-size: 14px;
}

.jr-image-upload-area {
border: 2px dashed #ddd;
border-radius: 8px;
padding: 40px 20px;
margin-bottom: 20px;
transition: border-color 0.3s;
}

.jr-image-upload-area:hover,
.[Link] {
border-color: var(--jr-accent);
}

.jr-image-upload-label {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
cursor: pointer;
color: #888;
}

.jr-image-upload-label span {
font-size: 14px;
}

.jr-upload-subtitle {
font-size: 12px !important;
color: #aaa;
}

.jr-image-preview {
margin-bottom: 20px;
position: relative;
display: inline-block;
}
.jr-image-preview img {
max-width: 200px;
max-height: 200px;
border-radius: 4px;
object-fit: cover;
}

.jr-clear-image {
display: block;
margin: 8px auto 0;
background: none;
border: none;
color: var(--jr-accent);
cursor: pointer;
font-size: 13px;
text-decoration: underline;
}

.jr-image-search-submit {
width: 100%;
padding: 12px;
background-color: #000;
color: #fff;
border: none;
font-size: 14px;
text-transform: uppercase;
cursor: pointer;
letter-spacing: 1px;
transition: opacity 0.3s;
}

.jr-image-search-submit:disabled {
opacity: 0.5;
cursor: not-allowed;
}

.jr-image-search-submit:not(:disabled):hover {
opacity: 0.85;
}

/* ========================================
Mobile Menu
======================================== */
.jr-mobile-menu-overlay {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
z-index: 9998;
}

.[Link] {
display: block;
}

.jr-mobile-menu {
position: fixed;
top: 0;
left: -100%;
width: 320px;
max-width: 85%;
height: 100vh;
background: #fff;
z-index: 9999;
transition: left 0.3s ease;
overflow-y: auto;
display: flex;
flex-direction: column;
}

.[Link] {
left: 0;
}

.jr-mobile-menu-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 15px 20px;
border-bottom: 1px solid #eee;
min-height: 50px;
position: relative;
}
.jr-mobile-back,
.jr-mobile-close {
background: none;
border: none;
cursor: pointer;
padding: 4px;
line-height: 0;
color: #333;
z-index: 1;
}

.jr-mobile-menu-logo {
flex: 1;
text-align: center;
}

.jr-mobile-menu-logo img {
max-width: 130px;
height: auto;
}

.jr-mobile-menu-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 14px;
text-transform: uppercase;
letter-spacing: 1px;
display: none;
white-space: nowrap;
}

.jr-mobile-search {
padding: 15px 20px;
border-bottom: 1px solid #eee;
}

.jr-mobile-search .jr-search-form {
position: relative;
}

.jr-mobile-search .jr-search-input {
width: 100%;
padding: 8px 80px 8px 15px;
height: 40px;
font-size: 14px;
background-color: var(--jr-search-bg);
border: 1px solid var(--jr-search-border);
color: #333;
}

.jr-mobile-search .jr-search-btn,
.jr-mobile-search .jr-camera-btn {
color: #333;
}

.jr-mobile-menu-body {
flex: 1;
overflow-y: auto;
padding: 0;
}

.jr-mobile-nav-list {
list-style: none;
padding: 0;
margin: 0;
}

.jr-mobile-nav-item {
border-bottom: 1px solid #f0f0f0;
}

.jr-mobile-nav-link {
display: flex;
align-items: center;
justify-content: space-between;
padding: 14px 20px;
font-size: 14px;
text-transform: uppercase;
text-decoration: none;
color: #333;
letter-spacing: 0.5px;
}

.jr-mobile-nav-link:hover {
background: #f9f9f9;
}
.jr-mobile-appointment {
font-weight: 600;
}

.jr-mobile-submenu {
list-style: none;
padding: 0;
margin: 0;
}

/* ========================================
HERO SECTION OVERLAP HELPER
The hero/first section must start from top
======================================== */

/* Ensure hero section goes behind the header */


.shopify-section:first-of-type,
.shopify-section:nth-of-type(2) {
position: relative;
}

/* If your hero section has a specific class, target it */


.jr-hero-overlap {
margin-top: 0 !important;
padding-top: 0 !important;
}

/* ========================================
Responsive Styles
======================================== */
@media (max-width: 1199px) {
.jr-logo-desktop {
display: none;
}

.jr-logo-mobile {
display: block;
}

.jr-mobile-toggle {
display: block;
}

.jr-navigation {
display: none;
}

.jr-header-center {
display: flex;
justify-content: center;
margin: 0 15px;
}

.jr-search-bar {
display: none;
}

.jr-appointment-link {
display: none;
}

.jr-header-container {
position: relative;
}

.jr-header-left {
flex: 0 0 auto;
}

.jr-header-center {
position: absolute;
left: 50%;
transform: translateX(-50%);
margin: 0;
}

.jr-header-right {
flex: 0 0 auto;
}
}

@media (max-width: 767px) {


.jr-header-inner {
padding: 8px 15px;
}

.jr-logo-img {
max-width: 130px;
}

.jr-marquee-slide {
padding: 6px 30px;
}

.jr-marquee-slide a {
display: block;
}

.jr-image-search-content {
padding: 30px 20px;
}
}

@media (min-width: 1200px) {


.jr-mobile-toggle {
display: none !important;
}

.jr-logo-mobile {
display: none !important;
}

.jr-logo-desktop {
display: block !important;
}
}

@media (min-width: 1700px) {


.jr-header-container,
.jr-nav-container {
max-width: 1620px;
}
}

/* ========================================
Image Search - Loading & Results
======================================== */
.jr-image-search-loading {
padding: 30px 0;
text-align: center;
}
.jr-loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f0f0f0;
border-top: 3px solid var(--jr-accent);
border-radius: 50%;
animation: jr-spin 0.8s linear infinite;
margin: 0 auto 15px;
}

@keyframes jr-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

.jr-image-search-loading p {
color: #666;
font-size: 14px;
margin: 0;
}

.jr-image-search-results {
margin-bottom: 20px;
text-align: left;
}

.jr-image-search-results h4 {
font-size: 16px;
font-weight: 500;
margin: 0 0 15px;
text-align: center;
}

.jr-image-results-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 15px;
}

.jr-result-card {
text-decoration: none;
color: #333;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
transition: box-shadow 0.2s, transform 0.2s;
}

.jr-result-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transform: translateY(-2px);
}

.jr-result-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: #f5f5f5;
}

.jr-result-image img {
width: 100%;
height: 100%;
object-fit: cover;
}

.jr-result-info {
padding: 8px;
}

.jr-result-title {
display: block;
font-size: 11px;
font-weight: 400;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.jr-result-price {
display: block;
font-size: 12px;
font-weight: 600;
margin-top: 4px;
color: var(--jr-accent);
}
.jr-view-all-results {
display: block;
text-align: center;
color: var(--jr-accent);
font-size: 14px;
text-decoration: none;
font-weight: 500;
padding: 10px;
transition: opacity 0.2s;
}

.jr-view-all-results:hover {
opacity: 0.7;
}

.jr-no-results {
text-align: center;
padding: 20px 0;
}

.jr-no-results p {
color: #666;
font-size: 14px;
margin: 0 0 12px;
}

.jr-color-suggestions {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}

.jr-color-tag {
display: inline-block;
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 13px;
color: #333;
text-decoration: none;
text-transform: capitalize;
transition: all 0.2s;
}

.jr-color-tag:hover {
background: #000;
color: #fff;
border-color: #000;
}

/* Image Search Action Buttons */


.jr-image-search-actions {
display: flex;
flex-direction: column;
gap: 10px;
}

.jr-image-search-submit {
width: 100%;
padding: 12px;
background-color: #000;
color: #fff;
border: none;
font-size: 14px;
text-transform: uppercase;
cursor: pointer;
letter-spacing: 1px;
transition: opacity 0.3s;
}

.jr-image-search-submit:disabled {
opacity: 0.4;
cursor: not-allowed;
}

.jr-image-search-submit:not(:disabled):hover {
opacity: 0.85;
}

.jr-image-search-google {
width: 100%;
padding: 10px;
background: #fff;
color: #333;
border: 1px solid #ddd;
font-size: 13px;
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.2s;
}

.jr-image-search-google:hover {
background: #f5f5f5;
border-color: #999;
}

@media (max-width: 500px) {


.jr-image-results-grid {
grid-template-columns: repeat(2, 1fr);
}
}

/* ========================================
Image Search Results - Additional Styles
======================================== */
.jr-image-search-loading {
padding: 30px 0;
text-align: center;
}

.jr-loading-spinner {
width: 40px;
height: 40px;
border: 3px solid #f0f0f0;
border-top: 3px solid var(--jr-accent);
border-radius: 50%;
animation: jr-spin 0.8s linear infinite;
margin: 0 auto 15px;
}

@keyframes jr-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}

.jr-image-search-loading p {
color: #666;
font-size: 14px;
margin: 0;
}

.jr-image-search-results {
margin-bottom: 20px;
text-align: left;
}

.jr-image-search-results h4 {
font-size: 16px;
font-weight: 500;
margin: 0 0 15px;
text-align: center;
}

.jr-image-results-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
margin-bottom: 15px;
}

.jr-result-card {
text-decoration: none;
color: #333;
border: 1px solid #eee;
border-radius: 4px;
overflow: hidden;
transition: box-shadow 0.2s, transform 0.2s;
display: block;
}

.jr-result-card:hover {
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
transform: translateY(-2px);
}

.jr-result-image {
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: #f5f5f5;
}
.jr-result-image img {
width: 100%;
height: 100%;
object-fit: cover;
}

.jr-no-image {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #ccc;
font-size: 12px;
}

.jr-result-info {
padding: 8px;
}

.jr-result-title {
display: block;
font-size: 11px;
font-weight: 400;
line-height: 1.3;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.jr-result-price {
display: block;
font-size: 12px;
font-weight: 600;
margin-top: 4px;
color: var(--jr-accent);
}

.jr-view-all-results {
display: block;
text-align: center;
color: var(--jr-accent);
font-size: 14px;
text-decoration: none;
font-weight: 500;
padding: 10px;
transition: opacity 0.2s;
}

.jr-view-all-results:hover {
opacity: 0.7;
}

.jr-no-results {
text-align: center;
padding: 20px 0;
grid-column: 1 / -1;
}

.jr-no-results p {
color: #666;
font-size: 14px;
margin: 0 0 12px;
}

.jr-color-suggestions {
display: flex;
flex-wrap: wrap;
gap: 8px;
justify-content: center;
}

.jr-color-tag {
display: inline-block;
padding: 6px 16px;
border: 1px solid #ddd;
border-radius: 20px;
font-size: 13px;
color: #333;
text-decoration: none;
text-transform: capitalize;
transition: all 0.2s;
}

.jr-color-tag:hover {
background: #000;
color: #fff;
border-color: #000;
}

.jr-image-search-actions {
display: flex;
flex-direction: column;
gap: 10px;
}

.jr-image-search-submit {
width: 100%;
padding: 12px;
background-color: #000;
color: #fff;
border: none;
font-size: 14px;
text-transform: uppercase;
cursor: pointer;
letter-spacing: 1px;
transition: opacity 0.3s;
}

.jr-image-search-submit:disabled {
opacity: 0.4;
cursor: not-allowed;
}

.jr-image-search-submit:not(:disabled):hover {
opacity: 0.85;
}

.jr-image-search-google {
width: 100%;
padding: 10px;
background: #fff;
color: #333;
border: 1px solid #ddd;
font-size: 13px;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
gap: 8px;
transition: all 0.2s;
}

.jr-image-search-google:hover {
background: #f5f5f5;
border-color: #999;
}

@media (max-width: 500px) {


.jr-image-results-grid {
grid-template-columns: repeat(2, 1fr);
}

.jr-image-search-content {
padding: 25px 15px;
max-height: 90vh;
overflow-y: auto;
}
}
/* Match percentage badge */
.jr-match-badge {
position: absolute;
top: 4px;
left: 4px;
background: rgba(0,0,0,0.7);
color: #fff;
font-size: 9px;
padding: 2px 6px;
border-radius: 10px;
font-weight: 600;
}

.jr-result-image {
position: relative;
width: 100%;
aspect-ratio: 1;
overflow: hidden;
background: #f5f5f5;
}

.jr-image-results-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-bottom: 15px;
max-height: 400px;
overflow-y: auto;
}

@media (max-width: 500px) {


.jr-image-results-grid {
grid-template-columns: repeat(2, 1fr);
}
}
</style>
<script>
[Link]('DOMContentLoaded', function() {
// ========================================
// Marquee Height
// ========================================
function updateMarqueeHeight() {
const marquee = [Link]('jr-marquee');
const header = [Link]('jr-header');
if (marquee && ![Link]('hidden')) {
const height = [Link];
[Link]('--jr-marquee-height', height + 'px');
[Link]('has-marquee');
[Link]('marquee-hidden');
} else {
[Link]('--jr-marquee-height', '0px');
if (header) {
[Link]('has-marquee');
[Link]('marquee-hidden');
}
}
}
updateMarqueeHeight();
[Link]('resize', updateMarqueeHeight);

// ========================================
// Marquee Slider
// ========================================
const marqueeSlides = [Link]('.jr-marquee-slide');
let currentSlide = 0;
if ([Link] > 1) {
setInterval(function() {
marqueeSlides[currentSlide].[Link]('active');
marqueeSlides[currentSlide].[Link] = 'none';
currentSlide = (currentSlide + 1) % [Link];
marqueeSlides[currentSlide].[Link] = 'block';
marqueeSlides[currentSlide].[Link]('active');
}, 4000);
}

// ========================================
// Scroll Handler
// ========================================
const header = [Link]('jr-header');
const marquee = [Link]('jr-marquee');
function handleScroll() {
const scrollTop = [Link] || [Link];
if (scrollTop > 50) {
[Link]('scrolled');
if (marquee && ![Link]('hidden'))
[Link]('scroll-hidden');
} else {
[Link]('scrolled');
if (marquee && ![Link]('hidden'))
[Link]('scroll-hidden');
}
}
[Link]('scroll', handleScroll, { passive: true });
handleScroll();

// ========================================
// Mobile Menu Toggle
// ========================================
const mobileToggle = [Link]('jr-mobile-toggle');
if (mobileToggle) {
[Link]('click', function() {
[Link]('jr-mobile-menu').[Link]('active');
[Link]('jr-mobile-overlay').[Link]('active');
[Link] = 'hidden';
});
}

// ========================================
// Drag and Drop for image upload
// ========================================
const uploadArea = [Link]('jr-image-upload-area');
if (uploadArea) {
['dragenter','dragover','dragleave','drop'].forEach(evt => {
[Link](evt, function(e){ [Link](); [Link](); },
false);
});
['dragenter','dragover'].forEach(evt => {
[Link](evt, () => [Link]('dragover'), false);
});
['dragleave','drop'].forEach(evt => {
[Link](evt, () => [Link]('dragover'), false);
});
[Link]('drop', function(e) {
if ([Link] > 0) handleImageFile([Link][0]);
}, false);
}

// ========================================
// Pre-load all product data for comparison
// ========================================
loadAllProductData();
});

// ========================================
// GLOBAL VARIABLES
// ========================================
let uploadedImageFile = null;
let uploadedImageData = null;
let allStoreProducts = [];
let productFeatureCache = new Map(); // Cache: imageUrl -> features
let productsLoaded = false;
let featuresPrecomputed = false;
let precomputeInProgress = false;

// ========================================
// MARQUEE
// ========================================
function closeMarquee() {
const marquee = [Link]('jr-marquee');
const header = [Link]('jr-header');
[Link]('hidden');
[Link]('has-marquee');
[Link]('marquee-hidden');
[Link]('--jr-marquee-height', '0px');
}

// ========================================
// MOBILE MENU
// ========================================
let mobileMenuStack = [];

function closeMobileMenu() {
[Link]('jr-mobile-menu').[Link]('active');
[Link]('jr-mobile-overlay').[Link]('active');
[Link] = '';
[Link]('.jr-mobile-submenu').forEach(s => [Link] = 'none');
[Link]('#jr-mobile-nav-main > li').forEach(l => [Link] = '');
[Link]('jr-mobile-back').[Link] = 'none';
[Link]('jr-mobile-menu-title').[Link] = 'none';
[Link]('.jr-mobile-menu-logo').[Link] = 'block';
mobileMenuStack = [];
}

function openMobileSubmenu(element, title) {


const parentLi = [Link]('.jr-mobile-nav-item');
const submenu = [Link](':scope > .jr-mobile-submenu');
if (!submenu) return;
const currentList = [Link]('ul');
[Link]({ list: currentList, title: title });
[Link]([Link]).forEach(li => [Link] = 'none');
[Link] = 'block';
[Link]([Link]).forEach(li => [Link] = '');
[Link]('jr-mobile-back').[Link] = 'block';
[Link]('jr-mobile-menu-title').textContent = title;
[Link]('jr-mobile-menu-title').[Link] = 'block';
[Link]('.jr-mobile-menu-logo').[Link] = 'none';
}

function mobileMenuBack() {
if ([Link] === 0) return;
const prev = [Link]();
[Link](':scope > li').forEach(li => {
const sub = [Link](':scope > .jr-mobile-submenu');
if (sub && [Link] === 'block') [Link] = 'none';
});
[Link]([Link]).forEach(li => [Link] = '');
if ([Link] === 0) {
[Link]('jr-mobile-back').[Link] = 'none';
[Link]('jr-mobile-menu-title').[Link] = 'none';
[Link]('.jr-mobile-menu-logo').[Link] = 'block';
} else {
[Link]('jr-mobile-menu-title').textContent =
mobileMenuStack[[Link] - 1].title;
}
}

// ========================================
// LOAD ALL PRODUCTS FROM STORE
// ========================================
async function loadAllProductData() {
try {
let page = 1;
let hasMore = true;
allStoreProducts = [];

while (hasMore && page <= 10) {


const response = await fetch(`/[Link]?limit=250&page=${page}`);
if (![Link]) break;
const data = await [Link]();
if ([Link] && [Link] > 0) {
allStoreProducts = [Link]([Link]);
page++;
if ([Link] < 250) hasMore = false;
} else {
hasMore = false;
}
}

[Link](`Loaded ${[Link]} products from store`);


productsLoaded = true;

// Start background pre-computation


precomputeAllFeatures();

} catch (e) {
[Link]('Failed to load products:', e);
try {
const response = await fetch('/collections/all/[Link]?limit=250');
if ([Link]) {
const data = await [Link]();
if ([Link]) {
allStoreProducts = [Link];
productsLoaded = true;
precomputeAllFeatures();
}
}
} catch (e2) {
[Link]('Alternate endpoint also failed:', e2);
}
}
}

// ========================================
// PRE-COMPUTE ALL PRODUCT IMAGE FEATURES IN BACKGROUND
// Uses requestIdleCallback to avoid blocking the UI
// ========================================
async function precomputeAllFeatures() {
if (precomputeInProgress) return;
precomputeInProgress = true;

const productsWithImages = [Link](p => [Link] && [Link] > 0);


let processed = 0;
const total = [Link];

[Link](`Starting background pre-computation of ${total} product images...`);

// Process in small batches during idle time


const BATCH_SIZE = 3;

for (let i = 0; i < total; i += BATCH_SIZE) {


const batch = [Link](i, i + BATCH_SIZE);

// Wait for idle time before processing next batch


await waitForIdle();

await [Link]([Link](async (product) => {


const imageUrl = [Link][0].src;
if (![Link](imageUrl)) {
try {
// Use small thumbnail for faster loading
const thumbUrl = getSizedImageUrl(imageUrl, '100x100');
const features = await extractImageFeaturesFromUrl(thumbUrl);
[Link](imageUrl, features);
} catch (e) {
// Skip failed images silently
}
}
processed++;
}));
if (processed % 50 === 0) {
[Link](`Pre-computed features: ${processed}/${total}`);
}
}

featuresPrecomputed = true;


precomputeInProgress = false;
[Link](` Pre-computation complete! ${[Link]} images cached.`);
}

// Helper: wait for browser idle time


function waitForIdle() {
return new Promise(resolve => {
if ('requestIdleCallback' in window) {
requestIdleCallback(resolve, { timeout: 100 });
} else {
setTimeout(resolve, 16);
}
});
}

// Helper: get Shopify CDN sized image URL


function getSizedImageUrl(url, size) {
if (!url) return url;
const match = [Link](/(.+)\.(jpg|jpeg|png|gif|webp)(\?.*)?$/i);
if (match) {
return `${match[1]}_${size}.${match[2]}${match[3] || ''}`;
}
return url;
}

// ========================================
// EXTRACT FEATURES FROM IMAGE URL (for product images)
// Uses a shared canvas to avoid GC pressure
// ========================================
const _sharedCanvas = [Link]('canvas');
const _sharedCtx = _sharedCanvas.getContext('2d', { willReadFrequently: true });
const FEATURE_SIZE = 32; // Smaller = faster, 32x32 is sufficient

function extractImageFeaturesFromUrl(imageUrl) {
return new Promise((resolve, reject) => {
const img = new Image();
[Link] = 'anonymous';
[Link] = function() {
try {
_sharedCanvas.width = FEATURE_SIZE;
_sharedCanvas.height = FEATURE_SIZE;
_sharedCtx.drawImage(img, 0, 0, FEATURE_SIZE, FEATURE_SIZE);
const pixels = _sharedCtx.getImageData(0, 0, FEATURE_SIZE, FEATURE_SIZE).data;
resolve(computeAllFeatures(pixels, FEATURE_SIZE, FEATURE_SIZE));
} catch(e) {
reject(e);
}
};
[Link] = () => reject(new Error('Failed to load: ' + imageUrl));
[Link] = imageUrl;
});
}

// Extract features from data URL (for uploaded image)


function extractImageFeaturesFromDataUrl(dataUrl) {
return new Promise((resolve, reject) => {
const img = new Image();
[Link] = function() {
try {
_sharedCanvas.width = FEATURE_SIZE;
_sharedCanvas.height = FEATURE_SIZE;
_sharedCtx.drawImage(img, 0, 0, FEATURE_SIZE, FEATURE_SIZE);
const pixels = _sharedCtx.getImageData(0, 0, FEATURE_SIZE, FEATURE_SIZE).data;
resolve(computeAllFeatures(pixels, FEATURE_SIZE, FEATURE_SIZE));
} catch(e) {
reject(e);
}
};
[Link] = () => reject(new Error('Failed to load uploaded image'));
[Link] = dataUrl;
});
}

// ========================================
// COMPUTE ALL FEATURES IN ONE PASS (optimized)
// Single loop through pixels extracts everything at once
// ========================================
function computeAllFeatures(pixels, w, h) {
const hBins = 12, sBins = 3, lBins = 3;
const colorHist = new Float32Array(hBins * sBins * lBins);
// Color moments accumulators
let rSum = 0, gSum = 0, bSum = 0;
let rSq = 0, gSq = 0, bSq = 0;

// Spatial quadrants
const quads = [
{r:0,g:0,b:0,n:0}, {r:0,g:0,b:0,n:0},
{r:0,g:0,b:0,n:0}, {r:0,g:0,b:0,n:0}
];
const hw = w >> 1, hh = h >> 1;
const n = w * h;

// Grayscale buffer for edge detection


const gray = new Float32Array(n);

// SINGLE PASS: color histogram + moments + spatial + grayscale


for (let y = 0; y < h; y++) {
for (let x = 0; x < w; x++) {
const idx = (y * w + x) * 4;
const ri = pixels[idx], gi = pixels[idx+1], bi = pixels[idx+2];
const r = ri / 255, g = gi / 255, b = bi / 255;

// Grayscale
const grayVal = r * 0.299 + g * 0.587 + b * 0.114;
gray[y * w + x] = grayVal;

// Color moments
rSum += r; gSum += g; bSum += b;
rSq += r*r; gSq += g*g; bSq += b*b;

// Spatial quadrant
const qi = (y < hh ? 0 : 2) + (x < hw ? 0 : 1);
quads[qi].r += r; quads[qi].g += g; quads[qi].b += b; quads[qi].n++;

// HSL color histogram


const max = [Link](r, g, b), min = [Link](r, g, b);
const l = (max + min) * 0.5;
const d = max - min;
let hue = 0, sat = 0;
if (d > 0.001) {
sat = l > 0.5 ? d / (2 - max - min) : d / (max + min);
if (max === r) hue = ((g - b) / d + (g < b ? 6 : 0)) / 6;
else if (max === g) hue = ((b - r) / d + 2) / 6;
else hue = ((r - g) / d + 4) / 6;
}
const hi = [Link]((hue * hBins) | 0, hBins - 1);
const si = [Link]((sat * sBins) | 0, sBins - 1);
const li = [Link]((l * lBins) | 0, lBins - 1);
colorHist[hi * sBins * lBins + si * lBins + li]++;
}
}

// Normalize color histogram


for (let i = 0; i < [Link]; i++) colorHist[i] /= n;

// Edge histogram (8 bins) - computed from grayscale buffer


const edgeBins = 8;
const edgeHist = new Float32Array(edgeBins);
let edgeTotal = 0;

for (let y = 1; y < h - 1; y += 2) {


for (let x = 1; x < w - 1; x += 2) {
const gx = -gray[(y-1)*w+(x-1)] - 2*gray[y*w+(x-1)] - gray[(y+1)*w+(x-1)]
+ gray[(y-1)*w+(x+1)] + 2*gray[y*w+(x+1)] + gray[(y+1)*w+(x+1)];
const gy = -gray[(y-1)*w+(x-1)] - 2*gray[(y-1)*w+x] - gray[(y-1)*w+(x+1)]
+ gray[(y+1)*w+(x-1)] + 2*gray[(y+1)*w+x] + gray[(y+1)*w+(x+1)];
const mag = [Link](gx*gx + gy*gy);
if (mag > 0.05) {
let angle = Math.atan2(gy, gx);
if (angle < 0) angle += 6.283185307;
const bin = [Link]((angle / 6.283185307 * edgeBins) | 0, edgeBins - 1);
edgeHist[bin] += mag;
edgeTotal += mag;
}
}
}
if (edgeTotal > 0) {
for (let i = 0; i < edgeBins; i++) edgeHist[i] /= edgeTotal;
}

// Color moments
const colorMoments = new Float32Array(6);
colorMoments[0] = rSum / n;
colorMoments[1] = gSum / n;
colorMoments[2] = bSum / n;
colorMoments[3] = [Link]([Link](rSq/n - (rSum/n)**2));
colorMoments[4] = [Link]([Link](gSq/n - (gSum/n)**2));
colorMoments[5] = [Link]([Link](bSq/n - (bSum/n)**2));
// Spatial colors (flattened)
const spatialColors = new Float32Array(12);
for (let q = 0; q < 4; q++) {
const qn = quads[q].n || 1;
spatialColors[q*3] = quads[q].r / qn;
spatialColors[q*3+1] = quads[q].g / qn;
spatialColors[q*3+2] = quads[q].b / qn;
}

return { colorHist, edgeHist, colorMoments, spatialColors };


}

// ========================================
// FAST SIMILARITY CALCULATION
// Uses typed arrays for speed
// ========================================
function calculateSimilarity(f1, f2) {
let score = 0;

// 1. Color histogram intersection (weight: 0.35)


let intersection = 0;
const ch1 = [Link], ch2 = [Link];
for (let i = 0; i < [Link]; i++) {
intersection += ch1[i] < ch2[i] ? ch1[i] : ch2[i];
}
score += 0.35 * intersection;

// 2. Edge histogram cosine similarity (weight: 0.25)


let dot = 0, m1 = 0, m2 = 0;
const eh1 = [Link], eh2 = [Link];
for (let i = 0; i < [Link]; i++) {
dot += eh1[i] * eh2[i];
m1 += eh1[i] * eh1[i];
m2 += eh2[i] * eh2[i];
}
m1 = [Link](m1); m2 = [Link](m2);
score += 0.25 * ((m1 > 0 && m2 > 0) ? dot / (m1 * m2) : 0);

// 3. Color moments distance (weight: 0.20)


const cm1 = [Link], cm2 = [Link];
let cmDist = 0;
for (let i = 0; i < 6; i++) {
const d = cm1[i] - cm2[i];
cmDist += d * d;
}
score += 0.20 * [Link](0, 1 - [Link](cmDist));

// 4. Spatial color distance (weight: 0.20)


const sc1 = [Link], sc2 = [Link];
let spDist = 0;
for (let q = 0; q < 4; q++) {
let qd = 0;
for (let c = 0; c < 3; c++) {
const d = sc1[q*3+c] - sc2[q*3+c];
qd += d * d;
}
spDist += [Link](qd);
}
score += 0.20 * [Link](0, 1 - spDist / 4);

return score;
}

// ========================================
// IMAGE UPLOAD FUNCTIONS
// ========================================
function openImageSearch() {
[Link]('jr-image-search-modal').[Link]('active');
[Link] = 'hidden';
}

function closeImageSearch() {
[Link]('jr-image-search-modal').[Link]('active');
[Link] = '';
clearImageUpload();
[Link]('jr-image-search-results').[Link] = 'none';
[Link]('jr-image-search-loading').[Link] = 'none';
[Link]('jr-image-search-actions').[Link] = 'block';
}

function handleImageUpload(event) {
const file = [Link][0];
if (file) handleImageFile(file);
}

function handleImageFile(file) {
if (![Link]('image/')) {
alert('Please upload an image file (JPG, PNG, or WEBP)');
return;
}
if ([Link] > 10 * 1024 * 1024) {
alert('Image is too large. Please upload under 10MB.');
return;
}
uploadedImageFile = file;
const reader = new FileReader();
[Link] = function(e) {
uploadedImageData = [Link];
[Link]('jr-preview-img').src = uploadedImageData;
[Link]('jr-image-preview').[Link] = 'inline-block';
[Link]('jr-image-upload-area').[Link] = 'none';
[Link]('jr-image-search-submit').disabled = false;
[Link]('jr-image-search-google').[Link] = 'inline-flex';
[Link]('jr-image-search-results').[Link] = 'none';
};
[Link](file);
}

function clearImageUpload() {
const inp = [Link]('jr-image-upload');
if (inp) [Link] = '';
[Link]('jr-preview-img').src = '';
[Link]('jr-image-preview').[Link] = 'none';
[Link]('jr-image-upload-area').[Link] = 'block';
[Link]('jr-image-search-submit').disabled = true;
[Link]('jr-image-search-google').[Link] = 'none';
[Link]('jr-image-search-results').[Link] = 'none';
[Link]('jr-image-search-loading').[Link] = 'none';
uploadedImageFile = null;
uploadedImageData = null;
}

// ========================================
// MAIN IMAGE SEARCH - OPTIMIZED
// If features are pre-computed, comparison is instant
// If not, loads only uncached images
// ========================================
async function submitImageSearch() {
if (!uploadedImageData) {
alert('Please upload an image first');
return;
}

// Show loading
const loadingEl = [Link]('jr-image-search-loading');
const loadingText = [Link]('p');
[Link] = 'block';
[Link]('jr-image-search-actions').[Link] = 'none';
[Link]('jr-image-preview').[Link] = 'none';
[Link]('jr-image-search-results').[Link] = 'none';

try {
// Step 1: Extract features from uploaded image (instant - it's local)
[Link] = 'Analyzing your image...';
const uploadedFeatures = await extractImageFeaturesFromDataUrl(uploadedImageData);

// Step 2: Compare against all products


const productsWithImages = [Link](p => [Link] && [Link] > 0);
const results = [];
let uncachedCount = 0;

// Count how many need loading


for (const product of productsWithImages) {
if (![Link]([Link][0].src)) uncachedCount++;
}

if (uncachedCount === 0) {
// ALL CACHED - instant comparison!
[Link] = 'Finding similar products...';
for (const product of productsWithImages) {
const features = [Link]([Link][0].src);
if (features) {
[Link]({
product: product,
score: calculateSimilarity(uploadedFeatures, features)
});
}
}
} else {
// Some uncached - process them
let processed = 0;
const total = [Link];
const BATCH = 10;

for (let i = 0; i < total; i += BATCH) {


const batch = [Link](i, i + BATCH);

await [Link]([Link](async (product) => {


const imageUrl = [Link][0].src;
let features = [Link](imageUrl);

if (!features) {
try {
const thumbUrl = getSizedImageUrl(imageUrl, '100x100');
features = await extractImageFeaturesFromUrl(thumbUrl);
[Link](imageUrl, features);
} catch(e) {
return;
}
}

if (features) {
[Link]({
product: product,
score: calculateSimilarity(uploadedFeatures, features)
});
}
processed++;
}));

const progress = [Link](100, [Link](processed / total * 100));


[Link] = `Comparing products... ${progress}%`;

// Yield to UI
await new Promise(r => setTimeout(r, 0));
}
}

// Sort by similarity
[Link]((a, b) => [Link] - [Link]);

// Display results
displayVisualResults(results);

} catch (error) {
[Link]('Image search error:', error);
[Link] = 'none';
[Link]('jr-image-search-actions').[Link] = 'block';
[Link]('jr-image-preview').[Link] = 'inline-block';
alert('Something went wrong. Please try again.');
}
}

// ========================================
// DISPLAY VISUAL SEARCH RESULTS
// ========================================
function displayVisualResults(results) {
const loading = [Link]('jr-image-search-loading');
const resultsDiv = [Link]('jr-image-search-results');
const grid = [Link]('jr-image-results-grid');
const viewAll = [Link]('jr-view-all-results');
const actions = [Link]('jr-image-search-actions');
const preview = [Link]('jr-image-preview');
const title = [Link]('jr-results-title');

[Link] = 'none';
[Link] = 'inline-block';
[Link] = 'block';

const goodResults = [Link](r => [Link] > 0.15);


const displayResults = [Link] > 0 ? [Link](0, 8) : [Link](0, 8);

[Link] = '';

if ([Link] > 0) {
[Link] = `Visually Similar Products (${[Link]([Link], 8)})`;

[Link]((result) => {
const product = [Link];
const imageUrl = [Link] && [Link] > 0 ? [Link][0].src : '';
const productTitle = [Link] || 'Product';
const price = [Link] && [Link] > 0 ? [Link][0].price : '';
const url = `/products/${[Link]}`;
const matchPercent = [Link]([Link] * 100);

const card = [Link]('a');


[Link] = url;
[Link] = 'jr-result-card';
[Link] = function() { closeImageSearch(); };
[Link] = `
<div class="jr-result-image">
${imageUrl ? `<img src="${imageUrl}" alt="${productTitle}" loading="lazy">` : '<div
class="jr-no-image">No Image</div>'}
<span class="jr-match-badge">${matchPercent}% match</span>
</div>
<div class="jr-result-info">
<span class="jr-result-title">${productTitle}</span>
${price ? `<span class="jr-result-price">₹${parseFloat(price).toLocaleString()}</span>` : ''}
</div>
`;
[Link](card);
});

[Link] = 'block';
[Link] = 'none';

} else {
[Link] = 'No Similar Products Found';
[Link] = `
<div class="jr-no-results">
<p>Try uploading a clearer image or browse our collection</p>
<div class="jr-color-suggestions">
<a href="/collections/all" class="jr-color-tag" onclick="closeImageSearch()">Browse All
Products</a>
</div>
</div>
`;
[Link] = 'block';
}
}

// ========================================
// GOOGLE LENS SEARCH
// ========================================
function searchWithGoogleLens() {
if (!uploadedImageFile) {
alert('Please upload an image first');
return;
}
try {
[Link]('[Link] '_blank');
} catch(e) {
[Link]('[Link] '_blank');
}
}
</script>
{% schema %}
{
"name": "Custom Header (JR Style)",
"settings": [
{
"type": "image_picker",
"id": "logo",
"label": "Logo"
},
{
"type": "link_list",
"id": "menu",
"label": "Main Menu",
"default": "main-menu"
},
{
"type": "url",
"id": "store_link",
"label": "Store Locator Link"
},
{
"type": "url",
"id": "appointment_link",
"label": "Book Appointment Link"
},
{
"type": "header",
"content": "Marquee / Announcement Bar"
},
{
"type": "color",
"id": "marquee_bg_color",
"label": "Marquee Background",
"default": "#EDD8C2"
},
{
"type": "color",
"id": "marquee_text_color",
"label": "Marquee Text Color",
"default": "#000000"
},
{
"type": "header",
"content": "Header Colors (Transparent State)"
},
{
"type": "color",
"id": "header_text_color",
"label": "Header Text Color (Top)",
"default": "#ffffff"
},
{
"type": "header",
"content": "Header Colors (Scrolled State)"
},
{
"type": "color",
"id": "header_scrolled_bg",
"label": "Header Background (Scrolled)",
"default": "#ffffff"
},
{
"type": "color",
"id": "header_scrolled_text",
"label": "Header Text Color (Scrolled)",
"default": "#000000"
},
{
"type": "header",
"content": "Navigation Colors"
},
{
"type": "color",
"id": "nav_bg_color",
"label": "Navigation Background",
"default": "#ffffff"
},
{
"type": "color",
"id": "nav_text_color",
"label": "Navigation Text Color",
"default": "#000000"
}
],
"blocks": [
{
"type": "announcement",
"name": "Announcement",
"settings": [
{
"type": "text",
"id": "text",
"label": "Announcement Text",
"default": "Send us an"
},
{
"type": "text",
"id": "link_text",
"label": "Link Text",
"default": "Email"
},
{
"type": "url",
"id": "link_url",
"label": "Link URL"
}
]
}
],
"presets": [
{
"name": "Custom Header (JR Style)",
"blocks": [
{
"type": "announcement",
"settings": {
"text": "Send us an",
"link_text": "Email"
}
},
{
"type": "announcement",
"settings": {
"text": "Get Free",
"link_text": "Virtual Consultation"
}
}
]
}
]
}
{% endschema %}

You might also like