Direct Code Integration¶
Use this method if you want to integrate widgets directly into any website or have more control over the implementation.
Prerequisites¶
- Partner ID and Catalog ID: Obtain these credentials from the Glance AI team
- Active Glance AI Account: Ensure your account status is active
- Glance AI Application: Must be installed on your store
Implementation¶
Copy the code snippet below and customize the configuration:
<div id="glance-widget-container" class="widget-shimmer"></div>
<style>
#glance-widget-container {
display: block;
}
/* Shimmer effect */
.widget-shimmer {
background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);
background-size: 200% 100%;
animation: shimmer 1.5s infinite;
border-radius: 8px;
height: 100%;
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}
100% {
background-position: 200% 0;
}
}
</style>
<script>
(function () {
try {
// ===== CONFIGURATION - CUSTOMIZE THESE VALUES =====
const CONFIG = {
// Required: Your Glance AI credentials
// these creds will be on APP metafields for shopify
//partnerId: app.metafields.glance_ai.store_id,
//catalogId: app.metafields.glance_ai.catalog_id,
partnerId: "YOUR_STORE_ID_HERE",
catalogId: "YOUR_CATALOG_ID_HERE",
// Required: Widget configuration
widgetType: "try_on_banner", // Options: 'chat', 'try_on_banner', 'try_on_button', 'search'
// Required: Page information
pageType: "PDP", // Options: 'PDP' (Product), 'PLP' (Collection), 'HOME', 'OTHER'
pageIdentifier: "YOUR_PRODUCT_OR_PAGE_ID", // Product ID, Collection ID, or unique page identifier
storeHandle: "your-store-handle",
// Optional: Theme customization
theme: "purple", // Options: 'purple', 'white'
cornerStyle: "rounded", // Options: 'rounded', 'square'
fontStyle: "montserrat", // Options: 'montserrat', 'ttcommons'
// Optional: Widget customization
brandLogo: "", // URL to your brand logo
title: "How will this dress look on me?", // For banner/chat widgets
subtitle: "Upload your photo and virtually try it on", // For banner/chat widgets
backgroundColor:
"linear-gradient(97.16deg, rgba(157, 53, 255, 0.3) 35.24%, rgba(1, 25, 244, 0.3) 67.13%, rgba(58, 158, 174, 0.3) 99.02%), linear-gradient(0deg, rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8))", // For banner/chat widgets
// Optional: Button positioning (for try_on_button type)
buttonPosition: "absolute", // 'absolute' or 'relative'
buttonPlacement: "right", // 'left' or 'right'
buttonBottom: 20, // Bottom position in pixels
buttonSide: 20, // Side position in pixels (left or right based on buttonPlacement)
// Optional: Custom styling
customCSS: "", // Custom CSS to apply to widget container
// Optional: Animation settings
productIndex: 0, // Product position in collection (for try-on widgets)
animateTryOnButton: true, // Whether to animate try-on button
};
// ===== END CONFIGURATION =====
const containerId = "glance-widget-container";
const IFRAME_ORIGIN = "https://embed.glance.com";
let isInitialized = false;
// Function to check if conditions are met
function shouldInitializeWidget() {
return window.innerWidth < 768; // Mobile only
}
// Initialize Intersection Observer
function setupIntersectionObserver() {
const widgetContainer = document.getElementById(containerId);
if (!widgetContainer) {
console.warn("Glance AI Widget: Container not found:", containerId);
return;
}
// Create intersection observer
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting && !isInitialized) {
console.log(
"Glance AI Widget: Container in viewport, initializing...",
);
isInitialized = true;
initializeGlanceAIWidget();
observer.disconnect();
}
});
},
{
threshold: 0.1,
rootMargin: "50px",
},
);
observer.observe(widgetContainer);
console.log("Glance AI Widget: Intersection Observer setup complete");
}
function initializeGlanceAIWidget() {
// Add widget-specific styles
const style = document.createElement("style");
style.textContent = `
#glance-widget-iframe {
width: 100%;
height: 100%;
border: none;
background: transparent;
z-index: 10;
}
/* Widget type specific styles */
#glance-widget-iframe.glance-try_on_button {
width: 92px !important;
height: 0px;
}
#glance-widget-iframe.glance-try_on_banner {
width: 100% !important;
height: 0px;
}
#glance-widget-iframe.glance-chat {
width: 100% !important;
height: 0px;
}
`;
document.head.appendChild(style);
const widgetContainer = document.getElementById(containerId);
// Apply custom CSS if provided
if (CONFIG.customCSS && widgetContainer) {
widgetContainer.style.cssText += CONFIG.customCSS;
}
// Set parent element to position relative
if (widgetContainer && widgetContainer.parentElement) {
widgetContainer.parentElement.style.position = "relative";
}
// Apply positioning styles for try_on_button widget type
if (CONFIG.widgetType === "try_on_button" && widgetContainer) {
widgetContainer.style.position = CONFIG.buttonPosition;
widgetContainer.style.bottom = CONFIG.buttonBottom + "px";
// Apply side positioning based on buttonPlacement
if (CONFIG.buttonPlacement === "left") {
widgetContainer.style.left = CONFIG.buttonSide + "px";
} else {
widgetContainer.style.right = CONFIG.buttonSide + "px";
}
}
// Create iframe element for widget
const widgetIframe = document.createElement("iframe");
widgetIframe.id = "glance-widget-iframe";
widgetIframe.className = `glance-${CONFIG.widgetType}`;
// Remove shimmer effect when iframe is loaded
widgetIframe.addEventListener("load", function () {
if (widgetContainer) {
widgetContainer.classList.remove("widget-shimmer");
}
});
// Append iframe to container
if (widgetContainer) {
widgetContainer.appendChild(widgetIframe);
}
// Cookie management functions
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);
if (value) {
cookies[tokenName] = value;
}
});
return cookies;
}
// Handle iframe size updates
function handleSizeUpdateRequest(data) {
if (
data.widgetType !== CONFIG.widgetType ||
data.containerId !== containerId
) {
return;
}
console.log("Widget received size update request:", data);
if (widgetIframe) {
widgetIframe.className =
widgetIframe.className
.split(" ")
.filter((cls) => !cls.startsWith("glance-"))
.join(" ") + ` glance-${CONFIG.widgetType}`;
// Set dynamic height if provided
if (!isNaN(data.height)) {
widgetIframe.style.height = data.height + "px";
}
}
}
function getWidgetIdentifier() {
if (
CONFIG.widgetType === "try_on_button" ||
CONFIG.widgetType === "try_on_banner"
) {
return CONFIG.pageIdentifier; // Should be variant/product ID for try-on widgets
} else if (CONFIG.widgetType === "chat") {
return CONFIG.pageIdentifier; // Can be any page identifier for chat
}
}
// Initialize widget iframe
function initializeWidget() {
if (!widgetIframe) return;
const widgetUrl = `${IFRAME_ORIGIN}/build/es/latest/widgets/widget.html?widgetType=${CONFIG.widgetType}`;
widgetIframe.src = widgetUrl;
console.log("Widget iframe initialized:", widgetUrl);
}
// Send initialization data to widget
function sendInitializationData() {
if (widgetIframe && widgetIframe.contentWindow) {
const cookieData = getCurrentCookieData();
const initData = {
partnerId: CONFIG.partnerId,
catalogId: CONFIG.catalogId,
theme: CONFIG.theme,
cornerStyle: CONFIG.cornerStyle,
fontStyle: CONFIG.fontStyle,
storeHandle: CONFIG.storeHandle,
brandLogo: CONFIG.brandLogo,
pageUrl: window.location.href,
pageType: CONFIG.pageType,
pageIdentifier: CONFIG.pageIdentifier,
cookieData: cookieData,
widgetData: {
widgetType: CONFIG.widgetType,
widgetIdentifier: getWidgetIdentifier(),
containerId: containerId,
productIndex: CONFIG.productIndex,
animateTryOnButton: CONFIG.animateTryOnButton,
buttonPlacement: CONFIG.buttonPlacement,
title: CONFIG.title,
subtitle: CONFIG.subtitle,
backgroundColor: CONFIG.backgroundColor,
},
};
widgetIframe.contentWindow.postMessage(
{
type: "INIT_DATA_RESPONSE",
data: initData,
},
IFRAME_ORIGIN,
);
console.log("Sent initialization data to widget:", initData);
}
}
function hideWidget(data) {
if (
data.widgetType === CONFIG.widgetType &&
data.containerId === containerId
) {
const widgetContainer = document.getElementById(containerId);
if (widgetContainer) {
widgetContainer.innerHTML = "";
console.log("Widget hidden and content cleared");
}
}
}
// Event listeners for iframe communication
window.addEventListener("message", (event) => {
if (event.origin !== IFRAME_ORIGIN) return;
const { type, data } = event.data;
switch (type) {
case "REQUEST_INIT_DATA":
console.log("Widget received REQUEST_INIT_DATA");
sendInitializationData();
break;
case "WIDGET_SIZE_UPDATE_REQUEST":
handleSizeUpdateRequest(data);
break;
case "COOKIE_SET_REQUEST":
const { name, value, expiryDays } = data;
setCookie(name, value, expiryDays);
break;
case "COOKIE_DELETE_REQUEST":
deleteCookie(data.name);
break;
case "HIDE_WIDGET":
hideWidget(data);
break;
default:
console.log("Widget received unknown message:", type);
break;
}
});
// Initialize the widget
initializeWidget();
}
// Setup intersection observer when DOM is ready
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", () => {
if (shouldInitializeWidget()) {
setupIntersectionObserver();
}
});
} else {
if (shouldInitializeWidget()) {
setupIntersectionObserver();
}
}
} catch (error) {
console.error("Glance AI Widget: Initialization error:", error);
}
})();
</script>