(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 =
'' +
'

' +
'
' +
'
' +
'
' +
'' +
'
' +
'
' + 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();
})();