import '../lib/presets.js';
// ELLY PREMIUM THEME - ENHANCED INTERACTIONS WITH FUNCTIONALITY
const APP_SHORTNAME = 'Elly';
/**
* Redirects to Access PIN setup/unlock and closes this window.
* @param {boolean} configured
* @returns {Promise<void>}
*/
async function redirectToAccessPin(configured) {
const path = configured ? 'pin/pin.html' : 'pin/pin.html?mode=setup';
await chrome.windows.create({
url: chrome.runtime.getURL(path),
type: 'popup',
width: 560,
height: 900,
focused: true
});
window.close();
}
/**
* Requires configured + verified Access PIN before showing popup controls.
* @returns {Promise<boolean>}
*/
async function requireAccessGate() {
try {
const status = await chrome.runtime.sendMessage({ action: 'getAppStatus' });
const configured = !!(status?.configured ?? status?.pinSet);
const verified = !!status?.verified;
if (!configured || !verified) {
await redirectToAccessPin(configured);
return false;
}
return true;
} catch (err) {
console.error('Elly popup access gate:', err);
await redirectToAccessPin(false);
return false;
}
}
/**
* Creates the Tabler-style prompt dialog once (minimal popup HTML may omit it).
* @returns {HTMLDialogElement}
*/
function ensureEllyPopupPromptDialog() {
let dlg = document.getElementById('ellyPopupPromptModal');
if (dlg) {
return /** @type {HTMLDialogElement} */ (dlg);
}
dlg = document.createElement('dialog');
dlg.id = 'ellyPopupPromptModal';
dlg.className = 'ellyp-modal';
dlg.setAttribute('aria-labelledby', 'ellyPopupPromptTitle');
const inner = document.createElement('div');
inner.className = 'ellyp-modal-inner';
inner.innerHTML = `
<h3 id="ellyPopupPromptTitle" class="ellyp-modal-title"></h3>
<p id="ellyPopupPromptMessage" class="ellyp-modal-message"></p>
<input type="number" id="ellyPopupPromptInput" class="ellyp-modal-input" min="1" step="1" />
<div class="ellyp-modal-actions">
<button type="button" id="ellyPopupPromptCancel" class="ellyp-btn ellyp-btn-secondary">Cancel</button>
<button type="button" id="ellyPopupPromptOk" class="ellyp-btn ellyp-btn-primary"><i class="ti ti-check"></i> OK</button>
</div>
`;
dlg.appendChild(inner);
document.body.appendChild(dlg);
return /** @type {HTMLDialogElement} */ (dlg);
}
/**
* Tabler-style prompt (replaces window.prompt).
* @param {{ title: string, message: string, defaultValue?: string, min?: number }} opts
* @returns {Promise<string|null>}
*/
function openEllyPopupPromptModal(opts) {
const dlg = ensureEllyPopupPromptDialog();
const titleEl = document.getElementById('ellyPopupPromptTitle');
const msgEl = document.getElementById('ellyPopupPromptMessage');
const input = document.getElementById('ellyPopupPromptInput');
const cancelBtn = document.getElementById('ellyPopupPromptCancel');
const okBtn = document.getElementById('ellyPopupPromptOk');
if (!titleEl || !msgEl || !input || !cancelBtn || !okBtn) {
return Promise.resolve(null);
}
titleEl.replaceChildren();
const icon = document.createElement('i');
icon.className = 'ti ti-clock';
titleEl.appendChild(icon);
titleEl.appendChild(document.createTextNode(` ${opts.title}`));
msgEl.textContent = opts.message;
input.value = opts.defaultValue ?? '60';
input.min = String(opts.min ?? 1);
return new Promise((resolve) => {
let done = false;
const cleanup = () => {
okBtn.removeEventListener('click', onOk);
cancelBtn.removeEventListener('click', onCancel);
dlg.removeEventListener('cancel', onEsc);
input.removeEventListener('keydown', onKey);
};
const finish = (/** @type {string|null} */ v) => {
if (done) {
return;
}
done = true;
dlg.close();
cleanup();
resolve(v);
};
const onOk = () => finish(input.value.trim());
const onCancel = () => finish(null);
const onEsc = (e) => {
e.preventDefault();
finish(null);
};
const onKey = (e) => {
if (e.key === 'Enter') {
e.preventDefault();
onOk();
}
};
okBtn.addEventListener('click', onOk);
cancelBtn.addEventListener('click', onCancel);
dlg.addEventListener('cancel', onEsc);
input.addEventListener('keydown', onKey);
dlg.showModal();
input.select();
input.focus();
});
}
// Global state
let currentTab = null;
let isExtensionEnabled = true;
/**
* Boot UI when the popup document is ready. ES modules + import() can run after DOMContentLoaded
* in edge cases; always run if the document is already interactive.
*/
async function bootPopupUi() {
const ok = await requireAccessGate();
if (!ok) {
return;
}
addRequiredStyles();
initializePopup();
initializePremiumEffects();
initializeButtonEffects();
initializeCardAnimations();
initializeParticleSystem();
}
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
bootPopupUi().catch((err) => console.error(err));
});
} else {
bootPopupUi().catch((err) => console.error(err));
}
// Initialize popup functionality
async function initializePopup() {
console.log('Initializing popup...');
try {
// Check if extension context is valid
if (!chrome.runtime?.id) {
showError('Extension context invalid. Please refresh the page.');
return;
}
// Get current tab
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
currentTab = tabs[0];
const currentUrlEl = document.getElementById('currentUrl');
if (currentTab && currentTab.url) {
try {
const url = new URL(currentTab.url);
if (currentUrlEl) {
currentUrlEl.textContent = url.hostname || currentTab.url;
}
await checkSiteStatus(url.hostname);
} catch (parseErr) {
if (currentUrlEl) {
currentUrlEl.textContent = currentTab.url;
}
console.warn('Elly popup: could not parse tab URL', parseErr);
}
} else if (currentUrlEl) {
currentUrlEl.textContent = 'No active tab';
}
await loadStatistics();
await checkActiveTimeLimits();
await loadExtensionState();
} catch (error) {
console.error('Error initializing popup:', error);
const msg = error && error.message ? String(error.message) : '';
if (msg.includes('Extension context invalidated')) {
showError('Extension reloaded. Please refresh the page.');
} else {
showError('Error loading popup. Please try again.');
}
} finally {
setupEventListeners();
}
}
// Check if current site is blocked or limited
async function checkSiteStatus(domain) {
try {
const data = await chrome.storage.sync.get(['blockedSites', 'limitedSites']);
const blockedSites = data.blockedSites || [];
const limitedSites = data.limitedSites || {};
// Check if domain is in blocked list
const isBlocked = blockedSites.some(site =>
domain.includes(site.replace('*.', '')) || site.includes(domain)
);
// Check if domain has time limit
const hasTimeLimit = limitedSites[domain] ||
Object.keys(limitedSites).some(key => domain.includes(key));
const blockBtn = document.getElementById('blockCurrent');
const limitBtn = document.getElementById('limitCurrent');
if (!blockBtn || !limitBtn) {
return;
}
if (isBlocked) {
blockBtn.innerHTML = '<i class="ti ti-ban premium"></i> Unblock This Site';
blockBtn.classList.add('blocked');
} else {
blockBtn.innerHTML = '<i class="ti ti-ban premium"></i> Block This Site';
blockBtn.classList.remove('blocked');
}
if (hasTimeLimit) {
limitBtn.innerHTML = '<i class="ti ti-clock premium"></i> Edit Time Limit';
} else {
limitBtn.innerHTML = '<i class="ti ti-clock premium"></i> Set Time Limit';
}
} catch (error) {
console.error('Error checking site status:', error);
}
}
// Load statistics from storage
async function loadStatistics() {
try {
const data = await chrome.storage.sync.get(['blockedSites', 'statistics', 'limitedSites']);
const blockedCount = data.blockedSites?.length || 0;
const blockedCountEl = document.getElementById('blockedCount');
if (blockedCountEl) {
blockedCountEl.textContent = blockedCount;
}
const stats = data.statistics || {};
const totalTimeSaved = Object.values(stats).reduce((sum, day) => sum + (day.timeSaved || 0), 0);
const timeSavedEl = document.getElementById('timeSaved');
if (timeSavedEl) {
timeSavedEl.textContent = `${Math.floor(totalTimeSaved / 3600)}h`;
}
const activeLimits = Object.keys(data.limitedSites || {}).length;
const activeLimitsEl = document.getElementById('activeLimits');
if (activeLimitsEl) {
activeLimitsEl.textContent = activeLimits;
}
} catch (error) {
console.error('Error loading statistics:', error);
}
}
// Check for active time limits
async function checkActiveTimeLimits() {
try {
const data = await chrome.storage.sync.get(['limitedSites']);
const limitedSites = data.limitedSites || {};
if (currentTab && currentTab.url) {
let domain = '';
try {
domain = new URL(currentTab.url).hostname;
} catch (e) {
return;
}
const limit = limitedSites[domain];
if (limit) {
const widget = document.getElementById('timeLimitWidget');
const timerSite = document.getElementById('timerSite');
if (widget) {
widget.style.display = 'block';
}
if (timerSite) {
timerSite.textContent = domain;
}
}
}
} catch (error) {
console.error('Error checking time limits:', error);
}
}
// Load extension enabled state
async function loadExtensionState() {
try {
const data = await chrome.storage.sync.get(['settings']);
isExtensionEnabled = data.settings?.extensionEnabled !== false;
const toggle = document.getElementById('toggleEnabled');
if (toggle) {
toggle.checked = isExtensionEnabled;
}
} catch (error) {
console.error('Error loading extension state:', error);
}
}
let popupListenersBound = false;
// Setup event listeners for all buttons
function setupEventListeners() {
if (popupListenersBound) {
return;
}
popupListenersBound = true;
document.getElementById('blockCurrent')?.addEventListener('click', () => {
toggleBlockSite();
});
document.getElementById('limitCurrent')?.addEventListener('click', () => {
setTimeLimit();
});
document.getElementById('btnDashboard')?.addEventListener('click', () => {
chrome.tabs.create({ url: chrome.runtime.getURL('options/options.html#stats') });
});
document.getElementById('btnSettings')?.addEventListener('click', () => {
chrome.tabs.create({ url: chrome.runtime.getURL('options/options.html') });
});
document.getElementById('btnFocus')?.addEventListener('click', () => {
startFocusSession();
});
document.getElementById('btnQuickAdd')?.addEventListener('click', () => {
quickAddUrl();
});
document.getElementById('quickUrl')?.addEventListener('keypress', (e) => {
if (e.key === 'Enter') quickAddUrl();
});
// Preset buttons
document.querySelectorAll('.preset-btn').forEach(btn => {
btn.addEventListener('click', (e) => {
const preset = e.currentTarget?.dataset?.preset;
if (preset) {
addPreset(preset);
}
});
});
document.getElementById('toggleEnabled')?.addEventListener('change', (e) => {
toggleExtension(e.target.checked);
});
}
// Toggle block/unblock current site
async function toggleBlockSite() {
if (!currentTab || !currentTab.url) {
showError('No active tab found');
return;
}
try {
const url = new URL(currentTab.url);
const domain = url.hostname;
const data = await chrome.storage.sync.get(['blockedSites', 'settings']);
let blockedSites = data.blockedSites || [];
const settings = data.settings || {};
const pinLock = !!(settings.blocklistPinLock || settings.strictMode);
// Check if already blocked
const isBlocked = blockedSites.some(site =>
domain.includes(site.replace('*.', '')) || site.includes(domain)
);
if (isBlocked) {
let pin = null;
if (pinLock) {
pin = await promptParentPinForPopup(
'Enter your Parent PIN to unblock this site.'
);
if (pin == null) {
return;
}
}
const sitesToRemove = blockedSites.filter(site =>
domain.includes(site.replace('*.', '')) || site.includes(domain)
);
const response = await chrome.runtime.sendMessage({
action: 'removeFromBlocklist',
sites: sitesToRemove,
pin
});
if (!response?.success) {
showError(response?.error || 'Failed to unblock site');
return;
}
blockedSites = response.blockedSites || [];
showSuccessNotification('Site unblocked!');
} else {
// Add to blocklist
if (!blockedSites.includes(domain)) {
blockedSites.push(domain);
await chrome.storage.sync.set({ blockedSites });
showSuccessNotification('Site blocked!');
}
}
await checkSiteStatus(domain);
await loadStatistics();
// Notify background script to update blocking
chrome.runtime.sendMessage({
action: 'updateBlocklist',
blockedSites: blockedSites
});
} catch (error) {
console.error('Error toggling block:', error);
showError('Failed to update blocklist');
}
}
/**
* Minimal Parent PIN prompt for the popup (8–12 digits; verified by background).
* @param {string} message
* @returns {Promise<string|null>}
*/
async function promptParentPinForPopup(message) {
const entered = window.prompt(`${message}\n(8–12 digits)`, '');
if (entered == null) {
return null;
}
const trimmed = String(entered).trim();
if (!/^\d{8,12}$/.test(trimmed)) {
showError('PIN must be 8–12 digits');
return null;
}
return trimmed;
}
// Set time limit for current site
async function setTimeLimit() {
if (!currentTab || !currentTab.url) {
showError('No active tab found');
return;
}
try {
const url = new URL(currentTab.url);
const domain = url.hostname;
const rawMinutes = await openEllyPopupPromptModal({
title: `${APP_SHORTNAME}: Time limit`,
message: `Enter daily time limit for ${domain} (in minutes).`,
defaultValue: '60',
min: 1
});
if (rawMinutes === null) {
return;
}
const minutes = parseInt(rawMinutes, 10);
if (!rawMinutes || isNaN(minutes) || minutes <= 0) {
return;
}
const data = await chrome.storage.sync.get(['limitedSites', 'settings']);
const settings = data.settings || {};
const pinLock = !!(settings.blocklistPinLock || settings.strictMode);
if (pinLock) {
const pin = await promptParentPinForPopup(
'Enter your Parent PIN to set a time limit.'
);
if (pin == null) {
return;
}
const verified = await chrome.runtime.sendMessage({
action: 'verifyParentPinForBlocklist',
pin
});
if (!verified?.success) {
showError(verified?.error || 'Incorrect Parent PIN');
return;
}
}
const limitedSites = data.limitedSites || {};
limitedSites[domain] = {
limitMinutes: parseInt(minutes),
added: new Date().toISOString()
};
await chrome.storage.sync.set({ limitedSites });
showSuccessNotification(`${APP_SHORTNAME}: Time limit set: ${minutes} minutes/day`);
await checkSiteStatus(domain);
await loadStatistics();
} catch (error) {
console.error('Error setting time limit:', error);
showError(`${APP_SHORTNAME}: Failed to set time limit`);
}
}
// Quick add URL
async function quickAddUrl() {
const input = document.getElementById('quickUrl');
const url = input.value.trim();
if (!url) {
showError('Please enter a URL');
return;
}
try {
const data = await chrome.storage.sync.get(['blockedSites']);
const blockedSites = data.blockedSites || [];
if (!blockedSites.includes(url)) {
blockedSites.push(url);
await chrome.storage.sync.set({ blockedSites });
showSuccessNotification('Site added to blocklist!');
input.value = '';
await loadStatistics();
// Notify background script
chrome.runtime.sendMessage({
action: 'updateBlocklist',
blockedSites: blockedSites
});
} else {
showError('Site already in blocklist');
}
} catch (error) {
console.error('Error adding site:', error);
showError('Failed to add site');
}
}
// Add preset sites
async function addPreset(preset) {
const presets = typeof window !== 'undefined' && window.ELLY_PRESETS
? window.ELLY_PRESETS
: {
social: [
'*.facebook.com', '*.twitter.com', '*.x.com', '*.instagram.com', '*.tiktok.com', '*.reddit.com',
'*.linkedin.com', '*.discord.com', 'discord.com', 'vrilchat.com', '*.vrilchat.com', 'vrilone.com', '*.vrilone.com',
],
entertainment: ['*.youtube.com', '*.netflix.com', '*.twitch.tv', '*.hulu.com', '*.disneyplus.com'],
shopping: ['*.amazon.com', '*.ebay.com', '*.aliexpress.com', '*.etsy.com', '*.bestbuy.com'],
prohibited: [],
};
const presetUrls = presets[preset];
if (!presetUrls) return;
try {
const data = await chrome.storage.sync.get(['blockedSites']);
const blockedSites = data.blockedSites || [];
const newUrls = presetUrls.filter(url => !blockedSites.includes(url));
if (newUrls.length > 0) {
blockedSites.push(...newUrls);
await chrome.storage.sync.set({ blockedSites });
showSuccessNotification(`Added ${newUrls.length} ${preset} sites!`);
await loadStatistics();
// Notify background script
chrome.runtime.sendMessage({
action: 'updateBlocklist',
blockedSites: blockedSites
});
} else {
showError(`All ${preset} sites are already blocked`);
}
} catch (error) {
console.error('Error adding preset:', error);
showError('Failed to add preset');
}
}
// Start focus session
async function startFocusSession() {
try {
const rawDuration = await openEllyPopupPromptModal({
title: 'Focus session',
message: 'Enter focus session duration (minutes).',
defaultValue: '25',
min: 1
});
if (rawDuration === null) {
return;
}
const duration = parseInt(rawDuration, 10);
if (!rawDuration || isNaN(duration) || duration <= 0) {
return;
}
await chrome.runtime.sendMessage({
action: 'startFocusSession',
duration
});
showSuccessNotification(`Focus session started for ${duration} minutes!`);
} catch (error) {
console.error('Error starting focus session:', error);
showError('Failed to start focus session');
}
}
// Toggle extension on/off — Parent PIN always required to disable
async function toggleExtension(enabled) {
try {
if (!enabled) {
const pin = await promptParentPinForPopup(
'Enter your Parent PIN to disable Elly.'
);
if (pin == null) {
const toggle = document.getElementById('toggleEnabled');
if (toggle) {
toggle.checked = true;
}
return;
}
const verified = await chrome.runtime.sendMessage({
action: 'setExtensionEnabled',
enabled: false,
pin
});
if (!verified?.success) {
showError(verified?.error || 'Incorrect Parent PIN');
const toggle = document.getElementById('toggleEnabled');
if (toggle) {
toggle.checked = true;
}
return;
}
isExtensionEnabled = false;
chrome.action.setBadgeText({ text: 'OFF' });
chrome.action.setBadgeBackgroundColor({ color: '#ef4444' });
showSuccessNotification('Extension disabled!');
return;
}
const enabledResp = await chrome.runtime.sendMessage({
action: 'setExtensionEnabled',
enabled: true
});
if (!enabledResp?.success) {
showError(enabledResp?.error || 'Failed to enable extension');
const toggle = document.getElementById('toggleEnabled');
if (toggle) {
toggle.checked = false;
}
return;
}
isExtensionEnabled = true;
chrome.action.setBadgeText({
text: ''
});
chrome.action.setBadgeBackgroundColor({
color: '#10b981'
});
showSuccessNotification('Extension enabled!');
} catch (error) {
console.error('Error toggling extension:', error);
showError('Failed to update extension state');
}
}
// Helper functions
function showSuccessNotification(message) {
// Add "Elly says:" prefix if not already included
const prefixedMessage = message.startsWith('Elly:') || message.startsWith('Elly says:')
? message
: `Elly says: ${message}`;
const notification = document.createElement('div');
notification.textContent = prefixedMessage;
notification.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%) translateY(-100px);
background: linear-gradient(135deg, #10b981, #059669);
color: white;
padding: 12px 24px;
border-radius: 12px;
font-weight: 600;
font-size: 13px;
z-index: 10000;
box-shadow: 0 10px 40px rgba(16, 185, 129, 0.4);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.transform = 'translateX(-50%) translateY(0)';
}, 100);
setTimeout(() => {
notification.style.transform = 'translateX(-50%) translateY(-100px)';
setTimeout(() => notification.remove(), 300);
}, 2000);
}
function showError(message) {
// Add "Elly says:" prefix if not already included
const prefixedMessage = message.startsWith('Elly:') || message.startsWith('Elly says:')
? message
: `Elly says: ${message}`;
const notification = document.createElement('div');
notification.textContent = prefixedMessage;
notification.style.cssText = `
position: fixed;
top: 20px;
left: 50%;
transform: translateX(-50%) translateY(-100px);
background: linear-gradient(135deg, #ef4444, #dc2626);
color: white;
padding: 12px 24px;
border-radius: 12px;
font-weight: 600;
font-size: 13px;
z-index: 10000;
box-shadow: 0 10px 40px rgba(239, 68, 68, 0.4);
transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);
`;
document.body.appendChild(notification);
setTimeout(() => {
notification.style.transform = 'translateX(-50%) translateY(0)';
}, 100);
setTimeout(() => {
notification.style.transform = 'translateX(-50%) translateY(-100px)';
setTimeout(() => notification.remove(), 300);
}, 2000);
}
// Initialize all premium effects
function initializePremiumEffects() {
addStaggeredAnimations();
initializeSmoothScrolling();
addMicroInteractions();
}
// Staggered entrance animations
function addStaggeredAnimations() {
const elements = document.querySelectorAll('.container.premium > *');
elements.forEach((el, index) => {
el.style.opacity = '0';
el.style.transform = 'translateY(20px)';
setTimeout(() => {
el.style.transition = 'opacity 0.6s ease, transform 0.6s ease';
el.style.opacity = '1';
el.style.transform = 'translateY(0)';
}, index * 50);
});
}
// Enhanced button interactions
function initializeButtonEffects() {
const buttons = document.querySelectorAll('button.premium');
buttons.forEach(button => {
// Ripple effect on click
button.addEventListener('click', (e) => {
createRipple(e, button);
});
// Magnetic effect on hover
button.addEventListener('mousemove', (e) => {
const rect = button.getBoundingClientRect();
const x = e.clientX - rect.left - rect.width / 2;
const y = e.clientY - rect.top - rect.height / 2;
button.style.transform = `translate(${x * 0.1}px, ${y * 0.1}px) scale(1.02)`;
});
button.addEventListener('mouseleave', () => {
button.style.transform = 'translate(0, 0) scale(1)';
});
});
}
// Create ripple effect
function createRipple(e, button) {
const ripple = document.createElement('span');
const rect = button.getBoundingClientRect();
const size = Math.max(rect.width, rect.height) * 2;
const x = e.clientX - rect.left - size / 2;
const y = e.clientY - rect.top - size / 2;
ripple.style.cssText = `
position: absolute;
width: ${size}px;
height: ${size}px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.4);
top: ${y}px;
left: ${x}px;
pointer-events: none;
transform: scale(0);
opacity: 1;
animation: rippleEffect 0.6s ease-out;
`;
button.style.position = 'relative';
button.style.overflow = 'hidden';
button.appendChild(ripple);
setTimeout(() => ripple.remove(), 600);
}
// Card hover animations
function initializeCardAnimations() {
const stats = document.querySelectorAll('.stat.premium.holographic');
stats.forEach(stat => {
stat.addEventListener('mouseenter', () => {
stat.style.transform = 'translateY(-4px) scale(1.02)';
});
stat.addEventListener('mouseleave', () => {
stat.style.transform = 'translateY(0) scale(1)';
});
});
// Current site card animation
const currentSite = document.querySelector('.current-site.premium');
if (currentSite) {
currentSite.addEventListener('mouseenter', () => {
currentSite.style.transform = 'translateY(-2px)';
currentSite.style.borderColor = 'rgba(139, 92, 246, 0.4)';
});
currentSite.addEventListener('mouseleave', () => {
currentSite.style.transform = 'translateY(0)';
currentSite.style.borderColor = 'rgba(255, 255, 255, 0.1)';
});
}
}
// Particle system
function initializeParticleSystem() {
const particlesContainer = document.querySelector('.particles');
if (!particlesContainer) return;
createFloatingParticles(particlesContainer);
}
function createFloatingParticles(container) {
const colors = ['rgba(99, 102, 241, 0.3)', 'rgba(139, 92, 246, 0.3)', 'rgba(217, 70, 239, 0.3)'];
const particleCount = 15;
for (let i = 0; i < particleCount; i++) {
const particle = document.createElement('div');
const size = Math.random() * 3 + 1;
const color = colors[Math.floor(Math.random() * colors.length)];
const startX = Math.random() * 100;
const startY = Math.random() * 100;
const duration = Math.random() * 15 + 10;
const delay = Math.random() * 5;
particle.style.cssText = `
position: absolute;
width: ${size}px;
height: ${size}px;
background: ${color};
border-radius: 50%;
left: ${startX}%;
top: ${startY}%;
filter: blur(1px);
box-shadow: 0 0 ${size * 4}px ${color};
animation: floatParticle ${duration}s ease-in-out ${delay}s infinite;
pointer-events: none;
`;
container.appendChild(particle);
}
}
// Smooth scrolling
function initializeSmoothScrolling() {
const container = document.querySelector('.container.premium');
if (!container) return;
container.style.scrollBehavior = 'smooth';
}
// Micro-interactions
function addMicroInteractions() {
// Input focus effects
const inputs = document.querySelectorAll('input[type="text"].premium');
inputs.forEach(input => {
input.addEventListener('focus', () => {
input.style.transform = 'scale(1.02)';
});
input.addEventListener('blur', () => {
input.style.transform = 'scale(1)';
});
});
// Toggle switch interaction
const toggleSlider = document.querySelector('.toggle-slider.premium');
if (toggleSlider) {
toggleSlider.addEventListener('click', () => {
toggleSlider.style.transform = 'scale(0.95)';
setTimeout(() => {
toggleSlider.style.transform = 'scale(1)';
}, 100);
});
}
// Preset button interactions
const presetBtns = document.querySelectorAll('.preset-btn.premium');
presetBtns.forEach(btn => {
btn.addEventListener('click', () => {
btn.style.transform = 'scale(0.95)';
setTimeout(() => {
btn.style.transform = 'scale(1)';
}, 100);
});
});
}
// Add required CSS animations
function addRequiredStyles() {
// Ripple animation
const rippleStyle = document.createElement('style');
rippleStyle.textContent = `
@keyframes rippleEffect {
to {
transform: scale(1);
opacity: 0;
}
}
@keyframes floatParticle {
0%, 100% {
transform: translate(0, 0);
opacity: 0;
}
25% {
opacity: 1;
}
50% {
transform: translate(${Math.random() * 100 - 50}px, ${Math.random() * 100 - 50}px);
opacity: 0.8;
}
75% {
opacity: 1;
}
}
@keyframes gradientShift {
0% { background-position: 0% 50%; }
50% { background-position: 100% 50%; }
100% { background-position: 0% 50%; }
}
`;
document.head.appendChild(rippleStyle);
}
console.log('%c✨ ELLY PREMIUM POPUP LOADED ✨',
'background: linear-gradient(135deg, #6366f1, #8b5cf6, #d946ef); color: white; padding: 12px 24px; border-radius: 8px; font-size: 14px; font-weight: bold;');