(function() { 'use strict'; // Global Namespace window.OperationFun = window.OperationFun || {}; var origin = 'https://operation-fun.com'; // Detect origin (dev vs prod) try { var scripts = document.getElementsByTagName('script'); for (var i = 0; i < scripts.length; i++) { var src = scripts[i].src; if (src && (src.indexOf('base44.app') > -1 || src.indexOf('localhost') > -1)) { var url = new URL(src); if (url.hostname.indexOf('base44.app') > -1 || url.hostname === 'localhost') { origin = url.origin; break; } } } } catch (e) {} // Global Open Function window.OperationFun.open = function(publisherId, configId) { fetchGameData(publisherId, configId, function(data) { renderGameMenu(data, origin); }); }; function initWidget() { // Look for containers with our class or ID var containers = document.querySelectorAll('#operation-fun-widget, .operation-fun-embed'); containers.forEach(function(container) { initWidgetCore(container); }); } function initWidgetCore(container) { if (container.getAttribute('data-initialized') === 'true') return; container.setAttribute('data-initialized', 'true'); var configId = container.getAttribute('data-widget-config-id'); var pubId = container.getAttribute('data-publisher-id') || 'demo'; var type = container.getAttribute('data-embed-type') || 'banner'; // 'banner' (CTA), 'static_banner', 'direct_link' // ROUTING LOGIC - Separation of Concerns if (type === 'static_banner') { initStaticBanner(container, pubId, configId); } else if (type === 'direct_link') { initDirectLink(container, pubId, configId); } else { // Default to CTA Banner (Iframe based) initCtaBanner(container, pubId, configId, type); } } // --- TYPE 1: CTA BANNER (Iframe) --- function initCtaBanner(container, pubId, configId, type) { var embedUrl = origin + '/PublicEmbed?'; if (configId) embedUrl += 'widget_config_id=' + configId; else embedUrl += 'publisher_id=' + pubId + '&embed_type=' + type; embedUrl += '&t=' + Date.now(); var iframe = document.createElement('iframe'); iframe.id = 'operation-fun-widget-iframe'; iframe.src = embedUrl; iframe.style.cssText = 'position:fixed;bottom:0;right:0;width:450px;height:200px;border:0;z-index:999999;background:transparent;opacity:0;pointer-events:none;transition:opacity 0.4s ease;'; iframe.setAttribute('allow', 'autoplay'); iframe.setAttribute('scrolling', 'no'); iframe.setAttribute('allowtransparency', 'true'); container.innerHTML = ''; container.appendChild(iframe); // No fallback timeout - wait for explicit ready signal to prevent white flash } // --- TYPE 2: STATIC BANNER (Direct HTML) --- function initStaticBanner(container, pubId, configId) { // Fetch data first to get configuration fetchGameData(pubId, configId, function(data) { renderStaticBanner(container, data, pubId, configId); }, true); // Silent mode } function renderStaticBanner(container, data, pubId, configId) { var config = data.config || {}; // Size Dimensions var width = '100%'; var height = '90px'; if (config.static_banner_size === '728x90') { width = '728px'; height = '90px'; } else if (config.static_banner_size === '970x90') { width = '970px'; height = '90px'; } else if (config.static_banner_size === '320x50') { width = '320px'; height = '50px'; } else if (config.static_banner_size === '300x250') { width = '300px'; height = '250px'; } else if (config.static_banner_size === 'custom') { width = (config.static_banner_width || 728) + 'px'; height = (config.static_banner_height || 90) + 'px'; } var banner = document.createElement('a'); banner.href = 'javascript:void(0)'; banner.className = 'op-fun-static-banner'; // Colors var primary = config.cta_primary_color || '#6366f1'; var accent = config.cta_accent_color || '#a855f7'; banner.style.cssText = 'display:flex;width:' + width + ';height:' + height + ';max-width:100%;background:linear-gradient(135deg, ' + primary + ', ' + accent + ');align-items:center;justify-content:center;text-decoration:none;border-radius:12px;color:white;font-weight:800;font-family:system-ui, sans-serif;box-shadow:0 4px 12px rgba(0,0,0,0.15);transition:transform 0.2s;cursor:pointer;position:relative;overflow:hidden;margin:0 auto;'; var text = config.static_banner_text || 'Play Quick Games 🎮'; var subtext = config.static_banner_subtitle || 'Take a brain break! →'; banner.innerHTML = '
' + '
' + '
' + text + '
' + (parseInt(height) > 60 ? '
' + subtext + '
' : '') + '
'; banner.onmouseover = function() { this.style.transform = 'scale(1.01)'; }; banner.onmouseout = function() { this.style.transform = 'scale(1)'; }; banner.onclick = function(e) { e.preventDefault(); window.OperationFun.open(pubId, configId); }; container.innerHTML = ''; container.appendChild(banner); } // --- TYPE 3: DIRECT LINK (Click Listener) --- function initDirectLink(container, pubId, configId) { // If container is an 'a' tag or button, attach listener. // Otherwise, create a simple link inside it. if (container.tagName === 'A' || container.tagName === 'BUTTON') { container.addEventListener('click', function(e) { e.preventDefault(); window.OperationFun.open(pubId, configId); }); } else { // Check if it already has content, if so, wrap or attach to children? // For safety, we just attach a click listener to the container itself container.style.cursor = 'pointer'; container.addEventListener('click', function(e) { e.preventDefault(); // Prevent default if it wraps a link window.OperationFun.open(pubId, configId); }); } } // --- SHARED: DATA FETCHING --- var _cachedData = null; var _isFetching = false; function fetchGameData(pubId, configId, callback, silent) { if (_cachedData) { callback(_cachedData); return; } if (_isFetching) return; // Prevent double fetch _isFetching = true; // Show a global loading indicator if menu isn't open yet, unless silent if (!silent) showGlobalLoader(); var url = origin + '/functions/getPublicGameData?t=' + Date.now() + '&'; if (configId) url += 'widget_config_id=' + configId; else url += 'publisher_id=' + pubId; // Use XHR for max compatibility var xhr = new XMLHttpRequest(); xhr.open('GET', url, true); xhr.onreadystatechange = function() { if (xhr.readyState === 4) { hideGlobalLoader(); _isFetching = false; if (xhr.status === 200) { try { var data = JSON.parse(xhr.responseText); _cachedData = data; callback(data); } catch (e) { console.error('OpFun: Data parse error', e); renderError(configId, 'Parse error'); } } else { console.error('OpFun: Data fetch error', xhr.status, xhr.responseText); renderError(configId, 'Fetch error: ' + xhr.status); } } }; xhr.send(); } function showGlobalLoader() { if (document.getElementById('op-fun-global-loader')) return; var loader = document.createElement('div'); loader.id = 'op-fun-global-loader'; loader.style.cssText = 'position:fixed;top:50%;left:50%;transform:translate(-50%, -50%);padding:16px 24px;background:rgba(0,0,0,0.8);color:white;border-radius:50px;font-family:system-ui, sans-serif;font-weight:600;z-index:99999999;backdrop-filter:blur(4px);box-shadow:0 10px 25px rgba(0,0,0,0.2);display:flex;align-items:center;gap:10px;'; loader.innerHTML = '
Loading Games...'; var style = document.createElement('style'); style.innerHTML = '@keyframes opFunSpin { to { transform: rotate(360deg); } }'; loader.appendChild(style); document.body.appendChild(loader); } function hideGlobalLoader() { var loader = document.getElementById('op-fun-global-loader'); if (loader && loader.parentNode) loader.parentNode.removeChild(loader); } function renderError(configId, msg) { var containers = document.querySelectorAll('[data-widget-config-id="' + configId + '"]'); containers.forEach(function(c) { c.innerHTML = '
Widget Error: ' + msg + '
Check console for details
'; }); } // --- SHARED: EVENT LISTENER FOR IFRAME (CTA) --- window.addEventListener('message', function(event) { // CTA Widget Ready if (event.data.type === 'OPERATION_FUN_WIDGET_READY') { var iframe = document.getElementById('operation-fun-widget-iframe'); if (iframe) { iframe.style.opacity = '1'; iframe.style.pointerEvents = 'auto'; } } // CTA Open Menu if (event.data.type === 'OPERATION_FUN_OPEN_MENU') { _cachedData = event.data; // Cache data from iframe to avoid refetch renderGameMenu(event.data, origin); var iframe = document.getElementById('operation-fun-widget-iframe'); if (iframe) iframe.style.display = 'none'; } // Back to Menu logic if (event.data.type === 'OPERATION_FUN_BACK_TO_MENU') { var launcher = document.getElementById('operation-fun-game-launcher'); if (launcher && launcher.parentNode) launcher.parentNode.removeChild(launcher); // Re-open menu using cached data if (_cachedData) { renderGameMenu(_cachedData, origin); } else { // Fallback if no data (shouldn't happen) showGlobalLoader(); // or just show iframe // If it was CTA, show iframe var iframe = document.getElementById('operation-fun-widget-iframe'); if (iframe) { iframe.style.display = 'block'; setTimeout(function() { iframe.style.opacity = '1'; iframe.style.pointerEvents = 'auto'; }, 50); } hideGlobalLoader(); } } // Close Game Logic if (event.data.type === 'OPERATION_FUN_CLOSE_GAME') { var launcher = document.getElementById('operation-fun-game-launcher'); if (launcher && launcher.parentNode) launcher.parentNode.removeChild(launcher); var menu = document.getElementById('operation-fun-menu'); if (menu && menu.parentNode) menu.parentNode.removeChild(menu); // Restore CTA if exists var iframe = document.getElementById('operation-fun-widget-iframe'); if (iframe) { iframe.style.display = 'block'; setTimeout(function() { iframe.style.opacity = '1'; iframe.style.pointerEvents = 'auto'; }, 50); } } }); // --- SHARED: RENDER MENU --- function renderGameMenu(data, origin) { if (document.getElementById('operation-fun-menu')) return; var games = data.games || []; var modal = document.createElement('div'); modal.id = 'operation-fun-menu'; modal.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:9999999;display:flex;align-items:center;justify-content:center;font-family:system-ui,-apple-system,sans-serif;backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);'; var content = document.createElement('div'); content.style.cssText = 'background:white;width:95%;max-width:900px;max-height:85vh;overflow:hidden;border-radius:24px;position:relative;box-shadow:0 25px 50px -12px rgba(0,0,0,0.5);animation:opFunFadeIn 0.3s cubic-bezier(0.16, 1, 0.3, 1);display:flex;flex-direction:column;'; var style = document.createElement('style'); style.innerHTML = '@keyframes opFunFadeIn { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } } ' + '@keyframes opFunSlideUp { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } } ' + '.op-fun-scroll::-webkit-scrollbar { width: 6px; } ' + '.op-fun-scroll::-webkit-scrollbar-track { background: transparent; } ' + '.op-fun-scroll::-webkit-scrollbar-thumb { background-color: rgba(0,0,0,0.1); border-radius: 20px; } ' + '.op-game-card { transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); border: 1px solid #f1f5f9; } ' + '.op-game-card:hover { transform: translateY(-4px); box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1), 0 10px 10px -5px rgba(0,0,0,0.04); border-color: #8b5cf6; } ' + '.op-play-btn { opacity: 0; transform: scale(0.9); transition: all 0.3s; } ' + '.op-game-card:hover .op-play-btn { opacity: 1; transform: scale(1); } ' + '.op-game-img { transition: transform 0.5s; } ' + '.op-game-card:hover .op-game-img { transform: scale(1.05); }'; modal.appendChild(style); // Header var header = document.createElement('div'); header.style.cssText = 'padding:24px;border-bottom:1px solid rgba(0,0,0,0.06);background:rgba(255,255,255,0.8);backdrop-filter:blur(12px);display:flex;justify-content:space-between;align-items:center;z-index:10;'; var headerLeft = document.createElement('div'); headerLeft.style.cssText = 'display:flex;align-items:center;gap:16px;'; // Logo var logoHtml = ''; if (data.publisherLogo) { logoHtml = ''; } else { logoHtml = '
'; } headerLeft.innerHTML = logoHtml + '

Operation Fun

Select a game to play

'; header.appendChild(headerLeft); // Close Button var closeBtn = document.createElement('button'); closeBtn.innerHTML = ''; closeBtn.style.cssText = 'width:40px;height:40px;border-radius:50%;border:none;background:rgba(0,0,0,0.05);color:#64748b;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all 0.2s;'; closeBtn.onmouseover = function() { this.style.background = 'rgba(0,0,0,0.1)'; this.style.color = '#0f172a'; }; closeBtn.onmouseout = function() { this.style.background = 'rgba(0,0,0,0.05)'; this.style.color = '#64748b'; }; closeBtn.onclick = function() { if (modal.parentNode) modal.parentNode.removeChild(modal); // If we came from CTA, restore it var iframe = document.getElementById('operation-fun-widget-iframe'); if(iframe) { iframe.style.display = 'block'; iframe.style.opacity = '0'; setTimeout(function() { iframe.style.opacity = '1'; iframe.style.pointerEvents = 'auto'; }, 50); } }; header.appendChild(closeBtn); content.appendChild(header); // Grid Container var scrollArea = document.createElement('div'); scrollArea.className = 'op-fun-scroll'; scrollArea.style.cssText = 'flex:1;overflow-y:auto;padding:32px;background:#f8fafc;position:relative;'; var bgDecor = document.createElement('div'); bgDecor.style.cssText = 'position:absolute;top:0;left:0;right:0;bottom:0;background:radial-gradient(circle at 0% 0%, rgba(139, 92, 246, 0.08) 0%, transparent 50%), radial-gradient(circle at 100% 100%, rgba(217, 70, 239, 0.08) 0%, transparent 50%);pointer-events:none;'; scrollArea.appendChild(bgDecor); var grid = document.createElement('div'); grid.style.cssText = 'display:grid;grid-template-columns:repeat(auto-fill, minmax(280px, 1fr));gap:24px;position:relative;z-index:1;'; if (games.length === 0) { grid.innerHTML = '
✨

No games available yet

'; } games.forEach(function(game, idx) { var card = document.createElement('div'); card.className = 'op-game-card'; card.style.cssText = 'background:white;border-radius:20px;overflow:hidden;cursor:pointer;box-shadow:0 4px 6px -1px rgba(0,0,0,0.05);animation:opFunSlideUp 0.5s ease-out backwards;animation-delay:' + (idx * 0.05) + 's;display:flex;flex-direction:column;'; var imgUrl = 'https://placehold.co/400x225?text=' + encodeURIComponent(game.name); if (game.type === 'word') imgUrl = 'https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68ffdaa2fade107710494540/ab997fd8d_Wordspark.jpg'; else if (game.type === 'crossword') imgUrl = 'https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68ffdaa2fade107710494540/b36a242b1_Crossword.jpg'; else if (game.type === 'wordwipe') imgUrl = 'https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68ffdaa2fade107710494540/57da214ca_WordWiper.png'; else if (game.type === 'wordsearch') imgUrl = 'https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68ffdaa2fade107710494540/91669a679_image.png'; else if (game.type === 'lexiconquest') imgUrl = 'https://qtrypzzcjebvfcihiynt.supabase.co/storage/v1/object/public/base44-prod/public/68ffdaa2fade107710494540/9f46dda67_image.png'; card.innerHTML = '
' + ' ' + '
' + '
' + '
Play Now
' + '
' + '
' + '
' + '
' + '

' + game.name + '

' + ' Free' + '
' + '

' + (game.description || 'Fun and engaging game to test your skills.') + '

' + '
' + ' ' + ' ' + ' 3 min' + ' ' + ' •' + ' Casual' + '
' + '
'; card.onclick = function() { if (modal.parentNode) modal.parentNode.removeChild(modal); launchGame(game, data.publisherId, data.widgetConfigId, origin); }; grid.appendChild(card); }); scrollArea.appendChild(grid); content.appendChild(scrollArea); if (data.showBranding !== false) { var footer = document.createElement('div'); footer.style.cssText = 'border-top:1px solid rgba(0,0,0,0.06);padding:12px;text-align:center;background:white;font-size:11px;color:#94a3b8;font-weight:600;text-transform:uppercase;letter-spacing:1px;'; footer.innerHTML = 'Powered by Operation Fun'; content.appendChild(footer); } modal.appendChild(content); document.body.appendChild(modal); } function launchGame(game, publisherId, widgetConfigId, origin) { var gameUrl = origin; if (game.type === 'crossword') gameUrl += '/PublicCrossword?'; else if (game.type === 'wordsearch') gameUrl += '/WordSearchGame?'; else if (game.type === 'wordwipe') gameUrl += '/PublicWordWipe?'; else if (game.type === 'lexiconquest') gameUrl += '/LexiconQuestGame?'; else gameUrl += '/PublicGame?'; if (widgetConfigId) gameUrl += 'widget_config_id=' + widgetConfigId; else gameUrl += 'publisher_id=' + publisherId; gameUrl += '&is_embedded=true'; var launcher = document.createElement('div'); launcher.id = 'operation-fun-game-launcher'; launcher.style.cssText = 'position:fixed;inset:0;background:rgba(0,0,0,0.85);z-index:99999999;display:flex;align-items:center;justify-content:center;animation:opFunFadeIn 0.3s ease-out;'; var container = document.createElement('div'); container.style.cssText = 'position:relative;width:95vw;height:95vh;max-width:100%;max-height:100%;background:white;border-radius:16px;overflow:hidden;box-shadow:0 25px 50px rgba(0,0,0,0.5);display:flex;flex-direction:column;'; var loading = document.createElement('div'); loading.id = 'op-fun-loader'; loading.style.cssText = 'position:absolute;inset:0;background:linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:20;transition:opacity 0.4s;'; loading.innerHTML = '
Operation Fun
Loading Game...
'; container.appendChild(loading); var closeBtn = document.createElement('button'); closeBtn.innerHTML = ''; closeBtn.style.cssText = 'position:absolute;top:16px;right:16px;width:48px;height:48px;background:rgba(0,0,0,0.6);border:none;border-radius:50%;cursor:pointer;backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:30;transition:transform 0.2s;'; closeBtn.onmouseover = function() { this.style.transform = 'scale(1.1)'; }; closeBtn.onmouseout = function() { this.style.transform = 'scale(1)'; }; closeBtn.onclick = function() { // Treat manual X button as BACK to menu if (launcher.parentNode) launcher.parentNode.removeChild(launcher); // We can just re-render menu with cached data if (_cachedData) { renderGameMenu(_cachedData, origin); } else { // Fallback var iframe = document.getElementById('operation-fun-widget-iframe'); if(iframe) { iframe.style.display = 'block'; iframe.style.opacity = '1'; iframe.style.pointerEvents = 'auto'; } } }; container.appendChild(closeBtn); var iframe = document.createElement('iframe'); iframe.src = gameUrl; iframe.style.cssText = 'flex:1;border:none;width:100%;height:100%;background:white;'; iframe.onload = function() { setTimeout(function() { loading.style.opacity = '0'; setTimeout(function() { if(loading.parentNode) loading.parentNode.removeChild(loading); }, 400); }, 500); }; container.appendChild(iframe); var footer = document.createElement('div'); footer.style.cssText = 'position:absolute;bottom:0;left:0;right:0;background:rgba(0,0,0,0.6);backdrop-filter:blur(8px);padding:8px;text-align:center;z-index:25;color:rgba(255,255,255,0.9);font-size:12px;pointer-events:none;'; footer.innerHTML = 'Powered by Operation Fun'; container.appendChild(footer); launcher.appendChild(container); document.body.appendChild(launcher); } initWidget(); })();