Skip to content

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>