Skip to content
Customers also bought
ULMUS-RD
Red Oversized Fit Crew Neck T-Shirt with Tonal Bear Print
₹2,299
ZARF-OW
Off white Oversized Fit Crew Neck T-Shirt with Retro Car Print & Bear Logo
₹1,999
JUNO-NY
Navy Solid Regular Fit Polo with Contrast Tipping
₹1,699
STRUT-DSBL
Dusky Blue Horizontal Striped Regular Fit Polo
₹1,899
TORRA-BK
Black Solid Regular Fit Casual Trouser
₹2,599
AURYN-BK
Black Solid Pleated Regular Fit Semi Casual Trousers
₹1,699
FRAGE-LB
Light Blue Mid-Rise Straight Fit Jeans with Whiskered Fade Effect
₹2,599
SAVOUR-BK
Black Mid-Rise Straight Fit Jeans
₹2,599
VYBE-BK
Round-Oval Full-Rim Sunglasses with Dual-Tone Frame and Smoke Grey Gradient Lenses
₹1,799
SOLIZ-OL
Square Full-Rim Sunglasses with Smoke Olive Transparent Frame and Olive Gradient Lenses
₹1,799
" would otherwise terminate this inline script for every
// storefront visitor. '\/' is a valid JS escape for '/', so the runtime value is unchanged.
const chatCustomCss = "";
// Add styles for iframe and mobile optimizations
const style = document.createElement('style');
style.textContent = `
#embed-iframe {
position: fixed;
border: none;
transition: bottom 0.15s ease-out;
}
/* Mobile styles (≤768px) */
@media (max-width: 768px) {
#embed-iframe {
z-index: 1000000000;
}
#embed-iframe.glance-fab {
width: 68px;
height: 68px;
top: auto;
${fabPlacement === "left" ? 'left: 20px;' : 'right: 20px;'}
bottom: var(--bottom-offset, 20px);
}
#embed-iframe.glance-nudge {
width: 255px;
height: 180px;
${fabPlacement === "left" ? 'left: 20px;' : 'right: 20px;'}
bottom: var(--bottom-offset, 20px);
}
#embed-iframe.glance-gen-timer {
width: 90vw;
height: 100px;
${fabPlacement === "left" ? 'left: 20px; right: auto;' : 'right: 20px; left: auto;'}
bottom: var(--bottom-offset, 20px);
}
#embed-iframe.glance-fullscreen {
width: 100%;
height: 100%;
top: 0;
left: 0;
bottom: 0;
right: 0;
}
body.glance-fullscreen-active {
overflow: hidden;
position: fixed;
width: 100%;
height: 100%;
}
}
/* Desktop styles (>768px) */
@media (min-width: 769px) {
#embed-iframe {
z-index: 1000000000;
}
#embed-iframe.glance-fab {
width: 68px;
height: 68px;
top: auto;
${fabPlacementDesktop === "left" ? 'left: 6px;' : 'right: 6px;'}
bottom: var(--bottom-offset-desktop, 20px);
}
#embed-iframe.glance-gen-timer {
width: 30vw;
height: 100px;
${fabPlacementDesktop === "left" ? 'left: 6px; right: auto;' : 'right: 6px; left: auto;'}
bottom: var(--bottom-offset-desktop, 20px);
}
#embed-iframe.glance-nudge {
width: 255px;
height: 180px;
${fabPlacementDesktop === "left" ? 'left: 6px;' : 'right: 6px;'}
bottom: var(--bottom-offset-desktop, 20px);
}
#embed-iframe.glance-fullscreen {
max-width: 450px;
width: 100%;
border-radius: 24px;
margin: 16px;
height: calc(100% - 24px);
top: 0;
bottom: 0;
${fabPlacementDesktop === "left" ? 'left: 0px;' : 'right: 0px;'}
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
}
#timer-iframe {
position: fixed;
top: -60px;
left: 50%;
transform: translateX(-50%);
width: 320px;
max-width: 90vw;
height: 60px;
z-index: 1000000100;
border: none;
transition: top 0.6s ease;
}
#timer-iframe.visible {
top: var(--timer-top-offset, 8%);
}
`;
document.head.appendChild(style);
// Constants for iframe communication
const IFRAME_ORIGIN = 'https://embed.glance.com';
// Glance OAuth constants for iOS login relay
const GLANCE_ACCOUNTS_URL = 'https://accounts.glance.com';
const GLANCE_OAUTH_CALLBACK_URI = IFRAME_ORIGIN + '/build/es/latest/oauth-callback.html';
// Google Pay environment: 'TEST' or 'PRODUCTION'. Substituted by switch-env.js
// from GPAY_ENV in the active .env file. Default is 'TEST' for safety —
// PRODUCTION requires the page origin to match the merchant's registered domain.
const GPAY_ENV = 'PRODUCTION';
// Comma-separated UCP payment-handler keys, in preferred order. The first
// handler whose config exists in the merchant's /.well-known/ucp.json AND
// is usable on this device wins. Substituted by switch-env.js from the active
// .env file (PAYMENT_HANDLER_PRIORITY). Set to a card-first order on
// merchants where Google Pay isn't reliably configured.
const PAYMENT_HANDLER_PRIORITY = 'dev.shopify.card,com.google.pay';
const QUERY_PARAMS = {
CHAT: 'g_chat',
SCREEN: 'g_screen',
COLLECTION_ID: 'g_collection_id',
SEARCH_KEY: 'g_search_key'
};
// Create iframe element
const embedIframe = document.createElement('iframe');
embedIframe.id = 'embed-iframe';
embedIframe.className = 'glance-fab'; // Default to FAB state
embedIframe.allow = `camera ${IFRAME_ORIGIN}; web-share ${IFRAME_ORIGIN}; clipboard-write ${IFRAME_ORIGIN};`; // Camera, native share, clipboard
document.body.appendChild(embedIframe);
// Apply merchant custom CSS inline — beats the media-queried z-index in the
// stylesheet above without !important, and survives the glance-* class swaps
// (handleSizeUpdateRequest only mutates className). Re-applied after soft
// navigation too, whose Object.assign can overwrite the same properties.
function applyChatCustomCss() {
if (chatCustomCss && chatCustomCss.trim()) {
embedIframe.style.cssText += ';' + chatCustomCss;
}
}
applyChatCustomCss();
// Create timer widget iframe (only if gen timer widget is enabled)
let timerIframe = null;
const timerWidgetType = 'gen_timer';
const timerContainerId = 'timer-iframe';
if (genTimerEnabled) {
timerIframe = document.createElement('iframe');
timerIframe.id = timerContainerId;
document.body.appendChild(timerIframe);
}
// Scroll detection variables for timer visibility
let lastScrollY = 0;
let scrollTimeout = null;
let isScrollHidden = false;
// Simple throttle utility function
function throttle(func, delay) {
let timeoutId = null;
return function(...args) {
if (!timeoutId) {
timeoutId = setTimeout(() => {
func.apply(this, args);
timeoutId = null;
}, delay);
}
};
}
// Throttled scroll handler for timer visibility
function handleTimerScroll() {
const currentScrollY = window.scrollY;
const scrollingDown = currentScrollY > lastScrollY;
const nearTop = currentScrollY <= 10;
// Clear existing timeout
if (scrollTimeout) {
clearTimeout(scrollTimeout);
}
// Determine if timer should be hidden due to scroll
const shouldHideForScroll = scrollingDown && !nearTop;
// Only update if scroll state actually changed
if (isScrollHidden !== shouldHideForScroll) {
isScrollHidden = shouldHideForScroll;
// Apply current timer state with scroll consideration
if (timerIframe && currentTimerData) {
applyTimerVisibility(currentTimerData);
}
}
lastScrollY = currentScrollY;
scrollTimeout = null;
}
// Create throttled version of scroll handler
const throttledTimerScroll = throttle(handleTimerScroll, 100); // 10fps
// Store current timer data for scroll-based updates
let currentTimerData = null;
// Apply timer visibility considering both timer state and scroll state
function applyTimerVisibility(data) {
if (!timerIframe) return;
// Only apply scroll hiding during GENERATING phase
// COUNTER phase should always be visible (important completion notification)
const shouldApplyScrollHiding = data.phase === 'GENERATING' && isScrollHidden;
// Timer should be visible if:
// 1. shouldRenderTimer is true AND
// 2. NOT animating out from generation logic AND
// 3. NOT hidden due to scroll (only during GENERATING phase)
const shouldBeVisible = data.shouldRenderTimer && !data.isAnimatingOut && !shouldApplyScrollHiding;
if (shouldBeVisible) {
timerIframe.classList.add('visible');
} else {
timerIframe.classList.remove('visible');
}
// Always relay the original state to the timer widget
// (scroll state is handled only at the parent level)
if (timerIframe.contentWindow) {
timerIframe.contentWindow.postMessage({
type: 'TIMER_STATE_SYNC',
data: data
}, IFRAME_ORIGIN);
}
}
// Cookie management for iframe auth tokens
function setCookie(name, value, expiryDays) {
const date = new Date();
date.setTime(date.getTime() + expiryDays * 24 * 60 * 60 * 1000);
const expires = `expires=${date.toUTCString()}`;
document.cookie = `${name}=${value};${expires};path=/;SameSite=Strict;Secure`;
}
function getCookie(name) {
const nameEQ = name + '=';
const ca = document.cookie.split(';');
for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) === ' ') c = c.substring(1, c.length);
if (c.indexOf(nameEQ) === 0) return c.substring(nameEQ.length, c.length);
}
return null;
}
function deleteCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
function getCurrentCookieData() {
const cookies = {};
const authTokens = ['anonAccessToken', 'accessToken', 'refreshToken'];
authTokens.forEach(tokenName => {
const value = getCookie(tokenName);
cookies[tokenName] = value;
});
return cookies;
}
const checkoutContext = {
// Inline checkout (GPay/Shopify) is disabled on cart-popup stores
// (e.g. Shiprocket) — their popup owns checkout. Keep in sync with
// CART_POPUP_CHECKOUT_STORE_HANDLES in glance-cart-checkout.js.
ready: window.Shopify.shop?.split('.')?.[0] !== 'rijac2021',
currency: window.Shopify?.currency?.active || '',
country: window.Shopify?.country || '',
shopOrigin: window.Shopify?.shop || ''
};
(function loadAndInitCartCheckout() {
function doInit() {
window.GlanceCartCheckout.init({
embedIframe: embedIframe,
IFRAME_ORIGIN: IFRAME_ORIGIN,
checkoutContext: checkoutContext,
GPAY_ENV: GPAY_ENV,
PAYMENT_HANDLER_PRIORITY: PAYMENT_HANDLER_PRIORITY
});
}
// Already loaded (e.g. re-run in this page context) — no need to fetch again.
if (window.GlanceCartCheckout && typeof window.GlanceCartCheckout.init === 'function') {
doInit();
return;
}
// Create the element and attach load/error listeners BEFORE setting src/appending,
// so the load can never fire before we're listening for it.
const scriptEl = document.createElement('script');
scriptEl.async = true;
scriptEl.addEventListener('load', function () {
if (window.GlanceCartCheckout && typeof window.GlanceCartCheckout.init === 'function') {
doInit();
} else {
console.error('GlanceAI: GlanceCartCheckout module missing after script load; cart/checkout will be unavailable');
}
});
scriptEl.addEventListener('error', function () {
console.error('GlanceAI: glance-cart-checkout.js failed to load; cart/checkout will be unavailable');
});
scriptEl.src = "https:\/\/cdn.shopify.com\/extensions\/01a0ee58-5bb1-7779-8488-901f4c604044\/glance-ai-prod-154\/assets\/glance-cart-checkout.js";
document.body.appendChild(scriptEl);
})();
/**
* Handle navigation request from iframe (iframe has already updated its URL and UI)
*/
function handleSoftNavigationRequest(data) {
const { navigationState, replace, css } = data;
// Update iframe CSS based on navigation state
if (embedIframe && css) {
Object.assign(embedIframe.style, css);
// Re-apply merchant custom CSS last so its declarations win over
// anything the navigation payload set (later inline declarations win).
applyChatCustomCss();
}
// Update host URL to match iframe's navigation state
updateUrl(navigationState, replace);
// No need to notify iframe - it has already updated itself
}
/**
* Handle hard navigation request from iframe to navigate to a specific URL
*/
function handleHardNavigationRequest(data) {
const { url } = data;
if (url) {
console.log('Navigating to:', url);
window.location.href = url;
} else {
console.warn('Hard navigation request received without URL');
}
}
/**
* Handle back navigation request from iframe
*/
function handleBackNavigation() {
console.log('Handling back navigation');
window.history.back();
}
function handleOpenGoogleAuth() {
const POPUP_NAME = 'glance-auth-popup';
const POLL_INTERVAL_MS = 200;
const SAFETY_TIMEOUT_MS = 5 * 60 * 1000;
const state = (window.crypto && window.crypto.randomUUID)
? window.crypto.randomUUID()
: ('oauth-' + Date.now() + '-' + Math.random().toString(36).slice(2));
const authorizeUrl = GLANCE_ACCOUNTS_URL + '/api/v0/oauth2/authorize?' +
new URLSearchParams({
response_type: 'code',
client_id: 'ai-looks-embed',
redirect_uri: GLANCE_OAUTH_CALLBACK_URI,
state,
provider: 'GOOGLE'
}).toString();
const authPopup = window.open(authorizeUrl, POPUP_NAME);
if (!authPopup) {
console.error('[iOS-OAuth] window.open blocked');
postCancelToIframe();
return;
}
let popupDone = false;
let closedPoller;
let safetyTimeout;
function cleanup() {
clearInterval(closedPoller);
clearTimeout(safetyTimeout);
window.removeEventListener('message', onOAuthMessage);
}
function postCancelToIframe() {
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({ type: 'GLANCE_OAUTH_CANCELLED' }, IFRAME_ORIGIN);
}
}
function onOAuthMessage(e) {
if (e.source !== authPopup) return;
if (!e.data || e.data.type !== 'GLANCE_OAUTH_CODE') return;
if (e.data.state !== state) return;
popupDone = true;
cleanup();
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage(
{ type: 'GLANCE_OAUTH_CODE', data: { code: e.data.code, error: e.data.error || null, state: e.data.state, redirectUri: GLANCE_OAUTH_CALLBACK_URI } },
IFRAME_ORIGIN
);
}
}
window.addEventListener('message', onOAuthMessage);
closedPoller = setInterval(() => {
if (!authPopup.closed) return;
cleanup();
if (!popupDone) postCancelToIframe();
}, POLL_INTERVAL_MS);
safetyTimeout = setTimeout(() => {
cleanup();
if (!popupDone) {
try { authPopup.close(); } catch (_e) { /* popup already closed */ }
postCancelToIframe();
}
}, SAFETY_TIMEOUT_MS);
}
/**
* Update the host URL based on navigation state
*/
function updateUrl(navigationState, replace = false) {
// Construct query string from NavigationState object
const params = new URLSearchParams();
if (navigationState.isChatOpen) {
params.set(QUERY_PARAMS.CHAT, 'true');
}
if (navigationState.currentScreen) {
params.set(QUERY_PARAMS.SCREEN, navigationState.currentScreen);
}
if (navigationState.collectionId) {
params.set(QUERY_PARAMS.COLLECTION_ID, navigationState.collectionId);
}
if (navigationState.searchKey) {
params.set(QUERY_PARAMS.SEARCH_KEY, navigationState.searchKey);
}
const queryString = params.toString();
const newUrl = queryString ? `${window.location.pathname}?${queryString}` : window.location.pathname;
// Preserve history state
const historyState = history.state || {};
const updatedState = {
...historyState,
glance: navigationState
};
// Update the host URL without reloading.
// Prefer the pre-captured refs (bypass converters installed after
// init); fall back to the live history methods if unavailable.
const doReplace = (window.glanceNav && window.glanceNav.replaceState) || window.history.replaceState.bind(window.history);
const doPush = (window.glanceNav && window.glanceNav.pushState) || window.history.pushState.bind(window.history);
if (replace) {
doReplace(updatedState, '', newUrl);
} else {
doPush(updatedState, '', newUrl);
}
console.log('Host URL updated:', newUrl);
}
/**
* Parse current URL and return navigation state
*/
function getCurrentNavigationState() {
const params = new URLSearchParams(window.location.search);
return {
isChatOpen: params.get(QUERY_PARAMS.CHAT) === 'true',
currentScreen: params.get(QUERY_PARAMS.SCREEN) || null,
isPopupOpen: params.get(QUERY_PARAMS.SCREEN) !== null,
collectionId: params.get(QUERY_PARAMS.COLLECTION_ID) || undefined,
searchKey: params.get(QUERY_PARAMS.SEARCH_KEY) || undefined
};
}
/**
* Notify iframe of URL update via postMessage
*/
function notifyIframeOfUrlUpdate(navigationState, replace = false) {
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({
type: 'URL_UPDATE',
data: {
navigationState,
replace
}
}, IFRAME_ORIGIN);
}
}
/**
* Open Chat flow iframe of URL update via postMessage
*/
function openChat(data) {
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({
type: 'OPEN_CHAT',
data: data || {}
}, IFRAME_ORIGIN);
}
}
/**
* Open Chat with Image via postMessage
*/
function openChatWithImage(data) {
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({
type: 'OPEN_IMAGE_SEARCH',
data: data || {}
}, IFRAME_ORIGIN);
}
}
/**
* Try On flow iframe with data via postMessage
*/
function tryOn(data) {
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({
type: 'OPEN_TRYON',
data
}, IFRAME_ORIGIN);
}
}
/**
* Open Your Looks flow via postMessage
*/
function openYourLooks() {
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({
type: 'OPEN_YOUR_LOOKS'
}, IFRAME_ORIGIN);
}
}
/**
* Handle widget analytics flow iframe with data via postMessage
*/
function handleWidgetAnalytics(data) {
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({
type: 'WIDGET_ANALYTICS',
data,
}, IFRAME_ORIGIN);
}
}
/**
* Initialize iframe with current parent URL state
* Initialize iframe with clean URL (no query params)
*/
function initializeIframe() {
if (!embedIframe) return;
// Always use clean base URL without any query parameters
// Navigation state will be sent via postMessage after initialization
const baseIframeUrl = `${IFRAME_ORIGIN}/build/es/latest/iframe.html`;
// Set iframe src with clean URL
embedIframe.src = baseIframeUrl;
// Conditionally initialize timer iframe if enabled
if (genTimerEnabled && timerIframe) {
const widgetUrl = `${IFRAME_ORIGIN}/build/es/latest/widgets/widget.html?widgetType=${timerWidgetType}&containerId=${timerContainerId}`;
timerIframe.src = widgetUrl;
}
}
// Page type and identifier functions
function getPageType() {
return 'PDP';
}
function getPageIdentifier() {
return String(46142192845051);
}
/**
* Send initialization data to iframe including cookie data and current navigation state
* @param {Window} targetWindow - The iframe's contentWindow to send data to (defaults to embedIframe)
*/
function sendInitializationData(targetWindow) {
// Default to embedIframe if no targetWindow provided
const target = targetWindow || (embedIframe && embedIframe.contentWindow);
if (target) {
const cookieData = getCurrentCookieData();
// Get pixel ID from localStorage
let pixelId = '';
let shouldListenForPixelId = false;
try {
const iDspCookie = localStorage.getItem('iDSP_Cookie');
if (iDspCookie) {
const extractedId = iDspCookie.split('*')[0];
if (extractedId) {
pixelId = extractedId;
console.log('======Pixel ID from localStorage iDSP_Cookie:', pixelId);
} else {
shouldListenForPixelId = true;
console.log('======Pixel ID not found in localStorage iDSP_Cookie, will start polling');
}
} else {
// Cookie not found - we should listen for it
shouldListenForPixelId = true;
console.log('======iDSP_Cookie not found, will start polling');
}
} catch (e) {
console.warn('Failed to retrieve pixelId from localStorage:', e);
pixelId = '';
shouldListenForPixelId = true;
}
// Determine if chat nudge should be shown on current page
let showChatNudge = false;
showChatNudge = true;
const initData = {
partnerId: "e7c2e013-aea8-450b-af5a-d9c2558e2de8",
catalogId: "bearhouseindia",
region: null,
theme: "purple",
cornerStyle: "rounded",
fontStyle: "montserrat",
chatFontStyle: "montserrat",
storeHandle: window.Shopify.shop?.split('.')?.[0] || '',
bottomOffset: (isDesktop ? bottomOffsetDesktop : bottomOffset) + 'px',
fabPlacement: isDesktop ? fabPlacementDesktop : fabPlacement,
brandLogo: "https:\/\/thebearhouse.com\/cdn\/shop\/files\/Frame_78_616c6580-bd5c-4281-8b26-8b0107aa9953.webp?v=1772082159\u0026width=200",
pageUrl: window.location.href,
pageType: getPageType(),
pageIdentifier: getPageIdentifier(),
productId: "8676986749179",
productData: {
"merchantVariantId": "46142192845051",
"productName": "Kimoc",
"productImageUrl": "\/\/thebearhouse.com\/cdn\/shop\/files\/1_d5691036-18c3-400c-984d-0dad1d85e865.jpg?v=1757341390\u0026width=800",
"productImages": ["\/\/thebearhouse.com\/cdn\/shop\/files\/1_d5691036-18c3-400c-984d-0dad1d85e865.jpg?v=1757341390\u0026width=800","\/\/thebearhouse.com\/cdn\/shop\/files\/2_4d72dcb5-0037-4b6b-a8dc-71ae155e3903.jpg?v=1757341391\u0026width=800","\/\/thebearhouse.com\/cdn\/shop\/files\/3_eea8c39c-0305-40c0-95be-d2347628ac32.jpg?v=1757341391\u0026width=800"],
"deeplinkUrl": "https:\/\/thebearhouse.com\/products\/kimoc",
"price": {
"currency": "₹",
"price": 1499.0,"discountPrice": 1199.0,
"discount": 20
},
"sizes": [{"id": "46142192845051","value": "OneSize","inStock": true}]
},
cookieData,
pixelId,
pageTitle: document.title,
referrer: document.referrer,
showChatNudge: showChatNudge,
isDesktop: desktopEnabled && isDesktop,
genTimerEnabled: genTimerEnabled,
checkout: checkoutContext
};
// Only send navigation state and setup pixel polling for embedIframe
const isEmbedIframe = target === embedIframe?.contentWindow;
if (isEmbedIframe) {
// Send initialization data
target.postMessage({
type: 'INIT_DATA_RESPONSE',
data: initData
}, IFRAME_ORIGIN);
// Scrape parent DOM for enrichment (coupons/offers) — PDP only,
// since the embed iframe cannot access the parent document itself.
if (getPageType() === 'PDP') {
try {
const rawText = (document.body.innerText || '')
.replace(/\s+/g, ' ').trim().slice(0, 50000);
const rawSchemaJson = Array.from(
document.querySelectorAll('script[type="application/ld+json"]')
).map(tag => tag.textContent || '').filter(Boolean).join('\n').slice(0, 8000);
target.postMessage({
type: 'PAGE_CONTEXT_DATA',
data: { rawText, rawSchemaJson }
}, IFRAME_ORIGIN);
} catch (e) {
console.warn('GlanceAI: PAGE_CONTEXT_DATA scrape failed', e);
}
}
// Also send current navigation state from parent URL
const navigationState = getCurrentNavigationState();
notifyIframeOfUrlUpdate(navigationState, true); // Use replace=true for initial state
// If pixelId was not found, set up polling for when external script sets it
if (shouldListenForPixelId) {
setupPixelIdListener();
}
}
//update genTimer initData
const isTimerIframe = target === timerIframe?.contentWindow;
if(isTimerIframe && genTimerEnabled) {
const wInitData = {...initData};
wInitData.widgetData = {
widgetType: timerWidgetType,
containerId: timerContainerId
}
// Send initialization data
timerIframe.contentWindow.postMessage({
type: 'INIT_WIDGET_DATA_RESPONSE',
data: wInitData
}, IFRAME_ORIGIN);
}
}
}
// Polling configuration for pixelId
const PIXEL_ID_POLLING_INTERVAL = 1000; // Check every 1 second
const PIXEL_ID_POLLING_MAX_ATTEMPTS = 30; // Max 30 attempts (30 seconds)
/**
* Notify iframe of pixelId update via postMessage
*/
function notifyIframeOfPixelIdUpdate(pixelId) {
if (embedIframe && embedIframe.contentWindow && pixelId) {
embedIframe.contentWindow.postMessage({
type: 'PIXEL_ID_UPDATE',
data: { pixelId }
}, IFRAME_ORIGIN);
console.log('Sent PIXEL_ID_UPDATE to iframe:', pixelId);
}
}
/**
* Set up a listener (polling) for the pixelId in localStorage
*/
function setupPixelIdListener() {
let attempts = 0;
const intervalId = setInterval(() => {
attempts++;
if (attempts > PIXEL_ID_POLLING_MAX_ATTEMPTS) {
console.warn('Max attempts reached for pixelId polling. Pixel ID not found.');
clearInterval(intervalId);
return;
}
try {
const iDspCookie = localStorage.getItem('iDSP_Cookie');
if (iDspCookie) {
const extractedId = iDspCookie.split('*')[0];
if (extractedId) {
console.log('======Pixel ID found via polling:', extractedId);
notifyIframeOfPixelIdUpdate(extractedId);
clearInterval(intervalId);
}
}
} catch (e) {
console.warn('Error during pixelId polling:', e);
}
}, PIXEL_ID_POLLING_INTERVAL);
console.log('Started polling for pixelId...');
}
/**
* Handle iframe size update request
*/
function handleSizeUpdateRequest(data) {
const { widgetType } = data;
console.log('Host received SIZE_UPDATE_REQUEST:', widgetType);
// Remove existing glance classes and apply new one
if (embedIframe) {
embedIframe.className = embedIframe.className
.split(' ')
.filter(cls => !cls.startsWith('glance-'))
.join(' ') + ` glance-${widgetType}`;
}
// Handle body scroll based on iframe state
if (widgetType === 'fullscreen') {
// Disable body scrolling when going fullscreen
document.body.classList.add('glance-fullscreen-active');
} else {
// Re-enable body scrolling when leaving fullscreen (fab or nudge)
document.body.classList.remove('glance-fullscreen-active');
}
}
// Add event listeners
// Listener for receiving messages from iframe
window.addEventListener('message', (event) => {
// Security check: ensure message is from expected origin
if (event.origin !== IFRAME_ORIGIN) return;
const { type, data } = event.data;
switch (type) {
case 'REQUEST_INIT_DATA':
console.log('Host received REQUEST_INIT_DATA:');
sendInitializationData(event.source);
break;
case 'REQUEST_WIDGET_INIT_DATA':
if(data.widgetType !== timerWidgetType || data.containerId !== timerContainerId) {
break;
}
sendInitializationData(event.source);
break;
case 'IFRAME_READY':
console.log('Host received IFRAME_READY signal');
window.GlanceIframeReady = true;
// If widget manager is already loaded, notify it
if (window.GlanceWidgetManager && typeof window.GlanceWidgetManager.setIframeReady === 'function') {
window.GlanceWidgetManager.setIframeReady();
}
break;
case 'SOFT_NAVIGATE_REQUEST':
console.log('Host received SOFT_NAVIGATE_REQUEST:', data);
handleSoftNavigationRequest(data);
break;
case 'HARD_NAVIGATE_REQUEST':
console.log('Host received HARD_NAVIGATE_REQUEST:', data);
handleHardNavigationRequest(data);
break;
case 'NAVIGATE_BACK_REQUEST':
console.log('Host received NAVIGATE_BACK_REQUEST');
handleBackNavigation();
break;
case 'OPEN_GOOGLE_AUTH':
handleOpenGoogleAuth();
break;
case 'SIZE_UPDATE_REQUEST':
handleSizeUpdateRequest(data);
break;
case 'COOKIE_SET_REQUEST':
console.log('Host received COOKIE_SET_REQUEST:', data);
const { name, value, expiryDays } = data;
setCookie(name, value, expiryDays);
break;
case 'COOKIE_DELETE_REQUEST':
console.log('Host received COOKIE_DELETE_REQUEST:', data);
deleteCookie(data.name);
break;
case 'OPEN_CHAT':
openChat(data);
break;
case 'OPEN_IMAGE_SEARCH':
console.log('Request to Open Chat with Image', data);
openChatWithImage(data);
break;
case 'OPEN_TRYON':
console.log('Request to Try On with data:', data);
tryOn(data);
break;
case 'OPEN_YOUR_LOOKS':
console.log('Request to open Your Looks');
openYourLooks();
break;
case 'WIDGET_ANALYTICS':
console.log('Request to handle widget analytics with data:', data);
handleWidgetAnalytics(data);
break;
case 'TIMER_STATE_UPDATE':
// Only handle timer state updates if gen timer widget is enabled
if (genTimerEnabled) {
console.log('Host received TIMER_STATE_UPDATE:', data);
// Store current timer data for scroll-based updates
currentTimerData = data;
// Apply timer visibility with scroll consideration
applyTimerVisibility(data);
// Manage scroll listener based on timer state
if (data.shouldRenderTimer) {
// Add scroll listener when timer should be active
window.addEventListener('scroll', throttledTimerScroll, { passive: true });
} else {
// Remove scroll listener when timer is not active
window.removeEventListener('scroll', throttledTimerScroll, { passive: true });
// Reset scroll state
isScrollHidden = false;
lastScrollY = 0;
if (scrollTimeout) {
clearTimeout(scrollTimeout);
scrollTimeout = null;
}
}
}
break;
case 'REQUEST_TIMER_STATE':
// Only handle timer state requests if gen timer widget is enabled
if (genTimerEnabled) {
console.log('Host received REQUEST_TIMER_STATE, relaying to embed-iframe');
if (embedIframe && embedIframe.contentWindow) {
embedIframe.contentWindow.postMessage({
type: 'REQUEST_TIMER_STATE'
}, IFRAME_ORIGIN);
}
}
break;
case 'CART_GET_REQUEST':
console.log('Host received CART_GET_REQUEST');
if (window.GlanceCartCheckout) window.GlanceCartCheckout.handleCartGetRequest();
break;
case 'CART_ADD_REQUEST':
console.log('Host received CART_ADD_REQUEST:', data);
if (window.GlanceCartCheckout) window.GlanceCartCheckout.handleCartAddRequest(data);
break;
case 'CART_UPDATE_REQUEST':
console.log('Host received CART_UPDATE_REQUEST:', data);
if (window.GlanceCartCheckout) window.GlanceCartCheckout.handleCartUpdateRequest(data);
break;
case 'OPEN_CART':
console.log('Host received OPEN_CART');
if (window.GlanceCartCheckout) window.GlanceCartCheckout.handleOpenCart();
break;
case 'OPEN_CHECKOUT':
console.log('Host received OPEN_CHECKOUT');
if (window.GlanceCartCheckout) window.GlanceCartCheckout.handleCheckoutRedirect();
break;
case 'CHECKOUT_BEGIN_REQUEST':
console.log('Host received CHECKOUT_BEGIN_REQUEST:', data);
if (window.GlanceCartCheckout) window.GlanceCartCheckout.handleCheckoutBegin(data);
break;
case 'CHECKOUT_COMPLETE_RESPONSE': {
if (window.GlanceCartCheckout) window.GlanceCartCheckout.resolveCheckoutComplete(data);
break;
}
default:
console.log('Host received unknown message type:', type);
break;
}
});
// Listener for popstate (browser back/forward buttons)
window.addEventListener('popstate', (event) => {
console.log('Host popstate event:', window.location.href);
// Get current navigation state from URL
const navigationState = getCurrentNavigationState();
// Notify iframe to update its URL without reloading
notifyIframeOfUrlUpdate(navigationState, true); // Use replace=true for back/forward
});
// Initialize iframe with current navigation state
initializeIframe();
// Widget configuration based on page type and settings
if (!isAllowedPage) {
console.log('Widgets enabled but GlanceAI not allowed on this page');
return;
}
console.log('Native widgets enabled and page allowed, checking widget configuration...');
// Determine which widgets are enabled for current page
const enabledWidgets = {};
// Widget-specific rollout checks
const widgetRollouts = {
try_on_button: 100,
try_on_banner: 100,
search: 100
};
// Check rollout for each widget using session storage
const isInRollout = (widgetType) => {
const rolloutKey = `glance_widget_rollout_${widgetType}`;
// Check if already determined in this session
const storedValue = sessionStorage.getItem(rolloutKey);
if (storedValue !== null) {
return storedValue === 'true';
}
// Calculate and store for this session
const rolloutPercentage = widgetRollouts[widgetType] || 100;
const isIncluded = Math.random() * 100 < rolloutPercentage;
sessionStorage.setItem(rolloutKey, isIncluded.toString());
return isIncluded;
};
enabledWidgets.try_on_button = true && true && isInRollout('try_on_button');
enabledWidgets.try_on_banner = false && false && isInRollout('try_on_banner');
enabledWidgets.search = false && false && isInRollout('search');
// Generic gating for Svelte custom-element widgets. The list is
// auto-generated by the embed build (window.GLANCE_SVELTE_WIDGETS), so
// adding a widget directory + rebuilding wires it up here automatically —
// no per-widget edits in this file.
const svelteWidgets = window.GLANCE_SVELTE_WIDGETS || [];
const currentPageType = getPageType();
svelteWidgets.forEach(w => {
const pageAllowed =
currentPageType === 'PDP' ? w.productPage :
currentPageType === 'PLP' ? w.collectionPage :
currentPageType === 'HOME' ? w.homePage :
false;
// Expose this widget's rollout % so the shared isInRollout() can use it.
widgetRollouts[w.key] = w.rollout ?? 100;
// && short-circuits exactly like the iframe widgets above: isInRollout
// (which reads/writes sessionStorage) only runs when enabled + page-allowed.
enabledWidgets[w.key] = w.enabled && pageAllowed && isInRollout(w.key);
});
// Check if any widget is enabled for this page
const hasEnabledWidgets = Object.values(enabledWidgets).some(enabled => enabled);
// Svelte widgets need the runtime + config bundles loaded.
const enabledSvelteWidgets = svelteWidgets.filter(w => enabledWidgets[w.key]);
const hasSvelteWidgets = enabledSvelteWidgets.length > 0;
if (hasEnabledWidgets) {
// Pass configuration to widget manager
window.GlanceWidgetConfig = enabledWidgets;
// Expose theme values for Svelte widgets
window.GLANCE_WIDGET_THEME = "purple";
window.GLANCE_WIDGET_CORNER_STYLE = "rounded";
window.GLANCE_WIDGET_FONT = "montserrat";
// Pass isDesktop flag globally
window.GlanceIsDesktop = isDesktop;
// Track iframe ready state globally
window.GlanceIframeReady = false;
// Load widget infrastructure
// First, set the IFRAME_ORIGIN for widgets to use
window.GLANCE_IFRAME_ORIGIN = IFRAME_ORIGIN;
// Set up asset URLs for widgets to use
window.GLANCE_WIDGET_ASSETS = {
glanceGradientLogo: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/glance-gradient-logo.svg',
yourPhoto: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/your-photo.png',
blackShirt: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/black-shirt.png',
blackShirtModel: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/black-shirt-model.png',
blueShirt: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/blue-shirt.png',
blueShirtModel: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/blue-shirt-model.png',
greenShirt: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/green-shirt.png',
greenShirtModel: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/green-shirt-model.png',
redShirt: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/red-shirt.png',
redShirtModel: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/red-shirt-model.png',
tryOnConvertArrow: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/try-on-convert-arrow.svg',
tryOnWhiteStar: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/try-on-white-star.svg',
glanceAiLogo: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/glance-ai.svg',
chatWidgetImg: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/chat-widget-img.png',
blackStar: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/black-star.svg',
starIconWhite: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/star-icon-white.svg',
mixMatchVector: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/mix-match-vector.svg'
};
// Define widget configurations with their asset URLs
const widgetAssets = [];
widgetAssets.push({
key: 'try_on_button',
url: 'https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/widget-try-on-button.js',
name: 'Try On Button'
});
// Register every auto-generated Svelte widget enabled for this page. Their
// asset URLs come from the build-generated GLANCE_SVELTE_WIDGETS list, so no
// per-widget entry is needed here.
enabledSvelteWidgets.forEach(w => {
widgetAssets.push({
key: w.key,
url: w.url,
name: w.key,
isModule: true
});
});
// Helper to load a script
const loadScript = (src, name, isModule = false) => {
return new Promise((resolve, reject) => {
const script = document.createElement('script');
script.src = src;
if (isModule) {
script.type = 'module';
}
script.onload = () => resolve();
script.onerror = () => {
console.error(`Failed to load ${name}`);
reject();
};
document.head.appendChild(script);
});
};
// Load core first, then config, then runtime for Svelte widgets if needed, then widgets, then initialize
loadScript('https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/glance-widgets-core.js', 'Core widget infrastructure')
.then(() => {
if (hasSvelteWidgets) {
return loadScript('https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/glance-widgets-config.js', 'Svelte widget configs');
}
return Promise.resolve();
})
.then(() => {
if (hasSvelteWidgets) {
return loadScript('https://cdn.shopify.com/extensions/01a0ee58-5bb1-7779-8488-901f4c604044/glance-ai-prod-154/assets/build-svelte-runtime.js', 'Svelte runtime', true);
}
return Promise.resolve();
})
.then(() => {
// Load all enabled widget components
const promises = [];
widgetAssets.forEach(widget => {
if (enabledWidgets[widget.key]) {
promises.push(loadScript(widget.url, widget.name, widget.isModule ?? false));
}
});
return Promise.all(promises);
})
.then(() => {
if (hasSvelteWidgets) {
const timeout = new Promise(resolve => setTimeout(resolve, 2000));
const defined = Promise.all(
enabledSvelteWidgets.map(w => customElements.whenDefined(w.tag))
);
return Promise.race([defined, timeout]);
}
})
.then(() => {
if (window.GlanceWidgetManager) {
window.GlanceWidgetManager.init();
}
})
.catch(error => {
console.error('Error loading widget system:', error);
});
}
// Auto-detect and track sticky bar (.yt-sticky-bar-container) for dynamic FAB positioning (PDP only)
(function setupBottomOffsetTracking() {
// Only run on product pages for specific store
const storeHandle = window.Shopify.shop?.split('.')?.[0] || '';
if (getPageType() !== 'PDP' || storeHandle !== 'thebearhouseindia') return;
// Use mobile bottom offset as base (sticky bar tracking is for mobile)
const baseBottom = bottomOffset;
const iframeElem = document.getElementById('embed-iframe');
// Helper function to extract translateY from transform matrix
function getTranslateY(element) {
const transform = window.getComputedStyle(element).transform;
if (transform === 'none') return 0;
const matrix = transform.match(/matrix.*\((.+)\)/);
if (matrix) {
const values = matrix[1].split(', ');
return parseFloat(values[5]) || 0;
}
return 0;
}
// Helper function to calculate and set iframe height based on sticky bar
function updateIframeHeightForStickyBar() {
const stickyBar = document.querySelector('#yt001-widget-reward .yt-sticky-bar-container');
if (stickyBar) {
const translateY = getTranslateY(stickyBar);
const stickyBarHeight = stickyBar.offsetHeight;
const visibleAmount = Math.max(0, stickyBarHeight - translateY);
const newBottom = baseBottom + visibleAmount - 75;
iframeElem.style.height = `calc(100% - 24px - ${newBottom}px)`;
}
}
// Watch for class changes on iframe (desktop only)
if (isDesktop && iframeElem) {
const classObserver = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'attributes' && mutation.attributeName === 'class') {
if (iframeElem.classList.contains('glance-fullscreen')) {
// Fullscreen added - adjust height based on sticky bar position
updateIframeHeightForStickyBar();
} else {
// Fullscreen removed - reset height
iframeElem.style.height = null;
}
}
});
});
classObserver.observe(iframeElem, { attributes: true });
}
// Function to set up requestAnimationFrame tracking for sticky bar transform
function startStickyBarTracking(stickyBar) {
console.log('GlanceAI Embed: Sticky bar detected, enabling dynamic FAB positioning with rAF');
let lastTranslateY = null;
function trackStickyBar() {
const translateY = getTranslateY(stickyBar);
// Only update if translateY changed
if (translateY !== lastTranslateY) {
lastTranslateY = translateY;
const stickyBarHeight = stickyBar.offsetHeight;
// translateY = 0 means fully visible, translateY = height means fully hidden
const visibleAmount = Math.max(0, stickyBarHeight - translateY);
const newBottom = baseBottom + visibleAmount - 45; // 45px margin
document.documentElement.style.setProperty('--bottom-offset', newBottom + 'px');
document.documentElement.style.setProperty('--bottom-offset-desktop', newBottom + 'px');
// Adjust fullscreen iframe height (desktop only)
if (isDesktop && iframeElem.classList.contains('glance-fullscreen')) {
iframeElem.style.height = `calc(100% - ${newBottom}px)`;
}
}
requestAnimationFrame(trackStickyBar);
}
// Start tracking
requestAnimationFrame(trackStickyBar);
console.log('GlanceAI Embed: Started tracking sticky bar with requestAnimationFrame');
}
// Check if sticky bar already exists in DOM
const existingStickyBar = document.querySelector('#yt001-widget-reward .yt-sticky-bar-container');
if (existingStickyBar) {
startStickyBarTracking(existingStickyBar);
return;
}
// Sticky bar not found - set up MutationObserver to watch for it
console.log('GlanceAI Embed: Sticky bar not found yet, watching for DOM changes...');
const mutationObserver = new MutationObserver((mutations) => {
// Check if sticky bar has been added to DOM
const stickyBar = document.querySelector('#yt001-widget-reward .yt-sticky-bar-container');
if (stickyBar) {
// Found it! Start tracking and disconnect mutation observer
mutationObserver.disconnect();
startStickyBarTracking(stickyBar);
}
});
// Watch entire document for child additions (subtree includes all descendants)
mutationObserver.observe(document.body, {
childList: true,
subtree: true
});
})();
}
} catch (error) {
console.error('GlanceAI Embed: Unexpected error during initialization:', error);
}
Get the app now
Enjoy exclusive offers on app
Looks like you don't have the app installed
Download from Play Store
DOWNLOAD APP
Get our app now!
Scan the QR code below!