您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Automatically redeem free FAB products with Dynamic Island
// ==UserScript== // @name FAB (Unreal Engine) Asset Auto Redeemer // @namespace https://github.com/creosB // @version 2.1 // @description Automatically redeem free FAB products with Dynamic Island // @author CreosB // @match https://www.fab.com/* // @grant none // @license This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. // ==/UserScript== (function() { 'use strict'; const utils = { wait: (ms) => new Promise(resolve => setTimeout(resolve, ms)), log: (message, type = 'info') => { const timestamp = new Date().toLocaleTimeString(); console.log(`[FAB-GRABBER ${timestamp}] ${message}`); utils.updateStatus(message, type); }, updateStatus: (message, type = 'info') => { const statusEl = document.getElementById('status'); if (statusEl) { statusEl.textContent = `${new Date().toLocaleTimeString()}: ${message}`; statusEl.className = `status-${type}`; } }, generateAssetId: (card) => { const linkEl = card.querySelector('a[href*="/listings/"]'); return linkEl ? linkEl.href.split('/').pop() : `asset-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`; }, safeClick: async (element, description) => { try { if (!element) { throw new Error(`Element not found: ${description}`); } element.focus(); await utils.wait(100); element.click(); await utils.wait(200); if (element.disabled || !element.offsetParent) { element.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true, view: window })); } utils.log(`Successfully clicked: ${description}`); return true; } catch (error) { utils.log(`Failed to click ${description}: ${error.message}`, 'error'); return false; } }, waitForElement: (selector, timeout = 5000, parent = document) => { return new Promise((resolve, reject) => { const startTime = Date.now(); const checkElement = () => { const element = parent.querySelector(selector); if (element) { resolve(element); return; } if (Date.now() - startTime > timeout) { reject(new Error(`Element not found: ${selector} (timeout: ${timeout}ms)`)); return; } setTimeout(checkElement, 100); }; checkElement(); }); }, waitForElementToDisappear: (selector, timeout = 5000, parent = document) => { return new Promise((resolve) => { const startTime = Date.now(); const checkElement = () => { const element = parent.querySelector(selector); if (!element) { resolve(true); return; } if (Date.now() - startTime > timeout) { resolve(false); return; } setTimeout(checkElement, 100); }; checkElement(); }); } }; const licenseProcessor = { selectLicenseAndAdd: async (dialog, assetName) => { try { const licenseOptions = licenseProcessor.findLicenseOptions(dialog); let selectedLicense; let selectedRadio; if (!licenseOptions.personal && !licenseOptions.professional) { utils.log('No license options found, attempting direct add for ' + assetName); const addButton = await utils.waitForElement('button:not([disabled])[class*="Button--primary"]', 10000, dialog); if (!addButton) { throw new Error('Direct add button not found'); } const addButtonText = addButton.textContent.toLowerCase(); if (!addButtonText.includes('add') && !addButtonText.includes('library')) { throw new Error('Direct add button not ready'); } await utils.safeClick(addButton, 'Direct Add to Library button'); const closed = await utils.waitForElementToDisappear('[role="dialog"][aria-modal="true"]', 5000); if (!closed) { throw new Error('Dialog did not close after direct add'); } return { success: true, license: 'direct' }; } if (config.preferredLicense === 'personal') { if (licenseOptions.personal && licenseOptions.personal.isFree) { selectedLicense = 'personal'; selectedRadio = licenseOptions.personal.radio; } else if (licenseOptions.professional && licenseOptions.professional.isFree) { selectedLicense = 'professional'; selectedRadio = licenseOptions.professional.radio; } } else { if (licenseOptions.professional && licenseOptions.professional.isFree) { selectedLicense = 'professional'; selectedRadio = licenseOptions.professional.radio; } else if (licenseOptions.personal && licenseOptions.personal.isFree) { selectedLicense = 'personal'; selectedRadio = licenseOptions.personal.radio; } } if (!selectedLicense) { throw new Error('No free license available'); } await utils.safeClick(selectedRadio, `${selectedLicense} license`); await utils.wait(1000); const addButton = await utils.waitForElement('button:not([disabled])[class*="Button--primary"]', 3000, dialog); if (!addButton) { throw new Error('Add button not found or not enabled'); } const addButtonText = addButton.textContent.toLowerCase(); if (!addButtonText.includes('add') && !addButtonText.includes('library')) { throw new Error('Add button not found or not ready'); } await utils.safeClick(addButton, 'Add to Library button'); await utils.waitForElementToDisappear('[role="dialog"][aria-modal="true"]', 5000); return { success: true, license: selectedLicense }; } catch (error) { return { success: false, error: error.message }; } }, findLicenseOptions: (dialog) => { const options = { personal: null, professional: null }; const formFields = dialog.querySelectorAll('[class*="FormField-root"]'); formFields.forEach(field => { const label = field.querySelector('[class*="FormField-label"]'); const radio = field.querySelector('input[type="radio"]'); const priceEl = field.querySelector('[class*="Typography--intent-primary"][class*="Text--lg"][class*="Text--bold"]'); if (label && radio) { const labelText = label.textContent.toLowerCase(); const isFree = priceEl && priceEl.textContent.trim().toLowerCase() === 'free'; if (labelText.includes('personal')) { options.personal = { radio, isFree }; } else if (labelText.includes('professional')) { options.professional = { radio, isFree }; } } }); return options; }, closeDialog: async (dialog) => { try { const closeMethods = [ () => dialog.querySelector('button[aria-label="Close"]'), () => dialog.querySelector('[class*="closeButton"]'), () => dialog.querySelector('[class*="Modal-closeButton"]'), () => dialog.querySelector('button:has(svg)') ]; for (const method of closeMethods) { const closeBtn = method(); if (closeBtn) { await utils.safeClick(closeBtn, 'close button'); await utils.wait(500); if (!document.querySelector('[role="dialog"][aria-modal="true"]')) { return true; } } } document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape' })); await utils.wait(500); return !document.querySelector('[role="dialog"][aria-modal="true"]'); } catch (error) { utils.log(`Error closing dialog: ${error.message}`, 'error'); return false; } } }; const assetProcessor = { getFreeAssetCards: () => { const cards = document.querySelectorAll('[class*="fabkit-Stack-root"][class*="nTa5u2sc"], [class*="card"], [class*="asset-card"]'); const freeCards = []; cards.forEach(card => { const priceElements = card.querySelectorAll('*'); for (const el of priceElements) { if (el.textContent && el.textContent.trim().toLowerCase() === 'free') { freeCards.push(card); break; } } }); return freeCards; }, getAssetName: (card) => { const nameSelectors = [ '[class*="Typography--intent-primary"][class*="Text--bold"]', '[class*="asset-name"]', '[class*="title"]', 'a[href*="/listings/"]' ]; for (const selector of nameSelectors) { const nameEl = card.querySelector(selector); if (nameEl && nameEl.textContent.trim()) { return nameEl.textContent.trim(); } } return 'Unknown Asset'; }, findCartButton: (card) => { const buttonSelectors = [ 'button[aria-label*="Add"][aria-label*="cart"]', 'button[aria-label*="Add to cart"]', 'button[class*="cart"]', 'button:has([class*="shopping-cart"])', '[class*="shopping-cart-plus"]' ]; for (const selector of buttonSelectors) { const button = card.querySelector(selector); if (button) return button; } return null; }, processCard: async (card, retryCount = 0) => { const assetId = utils.generateAssetId(card); const assetName = assetProcessor.getAssetName(card); try { utils.log(`Processing: ${assetName} (Attempt ${retryCount + 1})`, 'info'); utils.log(`Simulating hover on card for ${assetName}`); card.dispatchEvent(new MouseEvent('mouseover', { bubbles: true })); card.dispatchEvent(new MouseEvent('mouseenter', { bubbles: true })); await utils.wait(500); const cartButton = await utils.waitForElement('button[aria-label*="Add"], button[class*="cart"], button:has([class*="shopping-cart"]), [class*="shopping-cart-plus"]', 3000, card); if (!cartButton) { throw new Error('Cart button not found after hover'); } await utils.safeClick(cartButton, 'cart button'); await utils.wait(500); const dialog = await utils.waitForElement('[role="dialog"][aria-modal="true"]', config.dialogTimeout); utils.log(`Dialog opened for: ${assetName}`); const result = await licenseProcessor.selectLicenseAndAdd(dialog, assetName); if (result.success) { state.processedAssets.set(assetId, { name: assetName, license: result.license, status: 'success', timestamp: new Date().toISOString() }); utils.log(`✅ Successfully added: ${assetName} (${result.license})`, 'success'); return true; } else { throw new Error(result.error || 'Failed to add asset'); } } catch (error) { utils.log(`❌ Error processing ${assetName}: ${error.message}`, 'error'); const dialog = document.querySelector('[role="dialog"][aria-modal="true"]'); if (dialog) { await licenseProcessor.closeDialog(dialog); } if (retryCount < config.maxRetries) { utils.log(`Retrying ${assetName} in 3 seconds...`); await utils.wait(3000); return assetProcessor.processCard(card, retryCount + 1); } state.processedAssets.set(assetId, { name: assetName, license: 'none', status: 'error', timestamp: new Date().toISOString(), error: error.message }); return false; } } }; const controller = { start: async () => { if (state.isRunning) return; state.isRunning = true; state.processedAssets = new Map(); state.errors = []; state.currentAssetIndex = 0; updateDynamicIsland(); const exportBtn = document.getElementById('fab-export-btn'); if (exportBtn) exportBtn.disabled = true; utils.log('🚀 Starting auto-grabber...'); const cards = assetProcessor.getFreeAssetCards(); state.totalAssets = cards.length; utils.log(`Found ${cards.length} free assets`); updateDynamicIsland(); for (let i = 0; i < cards.length && state.isRunning; i++) { state.currentAssetIndex = i + 1; utils.log(`Processing asset ${state.currentAssetIndex}/${state.totalAssets}`); try { await assetProcessor.processCard(cards[i]); } catch (error) { utils.log(`Unexpected error: ${error.message}`, 'error'); } updateDynamicIsland(); if (state.isRunning && i < cards.length - 1) { await utils.wait(config.delayBetweenActions); } } controller.stop(); utils.log('🎉 Auto-grabber finished!', 'success'); }, stop: () => { state.isRunning = false; const exportBtn = document.getElementById('fab-export-btn'); if (exportBtn) exportBtn.disabled = false; updateDynamicIsland(); updateAssetsList(); utils.log('🛑 Auto-grabber stopped'); } }; const assetList = { addAsset: (name, license, status, error = null) => { // Deprecated: now using updateAssetsList() }, clear: () => { const listEl = document.getElementById('fab-assets-list'); if (listEl) listEl.innerHTML = ''; }, exportData: () => { const data = Array.from(state.processedAssets.values()); const csv = 'Name,License,Status,Timestamp,Error\n' + data.map(item => `\"${item.name}\",\"${item.license}\",\"${item.status}\",\"${item.timestamp}\",\"${item.error || ''}\"`).join('\n'); const blob = new Blob([csv], { type: 'text/csv' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `fab-assets-${new Date().toISOString().split('T')[0]}.csv`; a.click(); URL.revokeObjectURL(url); } }; function createDynamicIsland() { const svgFilter = document.createElement('div'); svgFilter.innerHTML = ` <svg style="display: none"> <filter id="glass-distortion"> <feTurbulence type="turbulence" baseFrequency="0.008" numOctaves="2" result="noise" /> <feDisplacementMap in="SourceGraphic" in2="noise" scale="77" /> </filter> </svg> `; document.head.appendChild(svgFilter); const container = document.createElement('div'); container.id = 'fab-dynamic-island'; container.style.cssText = ` position: fixed; top: 20px; right: 20px; z-index: 999999; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; transition: all 0.4s cubic-bezier(0.25, 0.1, 0.25, 1); user-select: none; pointer-events: auto; `; const island = document.createElement('div'); island.id = 'fab-island'; island.style.cssText = ` position: relative; border-radius: 20px; overflow: hidden; cursor: pointer; transition: all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1); box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 2px 8px rgba(0, 0, 0, 0.3); min-width: 200px; `; const glassFilter = document.createElement('div'); glassFilter.className = 'glass-filter'; glassFilter.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 1; backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); filter: url(#glass-distortion) saturate(120%) brightness(1.15); `; const glassOverlay = document.createElement('div'); glassOverlay.className = 'glass-overlay'; glassOverlay.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 2; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.2); `; const glassSpecular = document.createElement('div'); glassSpecular.className = 'glass-specular'; glassSpecular.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 3; box-shadow: inset 1px 1px 1px rgba(255, 255, 255, 0.4); `; const glassContent = document.createElement('div'); glassContent.className = 'glass-content'; glassContent.style.cssText = ` position: relative; z-index: 4; padding: 12px 20px; color: white; display: flex; align-items: center; gap: 12px; min-height: 44px; `; const dragHandle = document.createElement('div'); dragHandle.style.cssText = ` width: 4px; height: 20px; background: rgba(255, 255, 255, 0.6); border-radius: 2px; cursor: move; flex-shrink: 0; border: 1px solid rgba(0, 0, 0, 0.2); `; const statusDot = document.createElement('div'); statusDot.id = 'fab-status-dot'; statusDot.style.cssText = ` width: 8px; height: 8px; border-radius: 50%; background: #34c759; transition: all 0.3s ease; flex-shrink: 0; box-shadow: 0 0 10px rgba(52, 199, 89, 0.5); `; const textContent = document.createElement('div'); textContent.style.cssText = ` flex: 1; display: flex; flex-direction: column; gap: 2px; `; const mainText = document.createElement('span'); mainText.id = 'fab-main-text'; mainText.textContent = 'Fab Auto Grab'; mainText.style.cssText = ` color: white; font-size: 14px; font-weight: 600; white-space: nowrap; transition: all 0.3s ease; text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8); `; const subText = document.createElement('span'); subText.id = 'fab-sub-text'; subText.textContent = 'Ready'; subText.style.cssText = ` color: rgba(255, 255, 255, 0.9); font-size: 11px; font-weight: 400; white-space: nowrap; transition: all 0.3s ease; overflow: hidden; text-overflow: ellipsis; max-width: 200px; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); `; textContent.appendChild(mainText); textContent.appendChild(subText); glassContent.appendChild(dragHandle); glassContent.appendChild(statusDot); glassContent.appendChild(textContent); island.appendChild(glassFilter); island.appendChild(glassOverlay); island.appendChild(glassSpecular); island.appendChild(glassContent); const expandedPanel = document.createElement('div'); expandedPanel.id = 'fab-expanded-panel'; expandedPanel.style.cssText = ` position: absolute; top: 60px; right: 0; width: 350px; max-height: 500px; border-radius: 20px; overflow: hidden; box-shadow: 0 20px 60px rgba(0, 0, 0, 0.6), 0 8px 24px rgba(0, 0, 0, 0.4); opacity: 0; pointer-events: none; transform: scale(0.9) translateY(-10px); transition: all 0.4s cubic-bezier(0.25, 0.1, 0.25, 1); `; const expandedGlassFilter = document.createElement('div'); expandedGlassFilter.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 1; backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px); filter: url(#glass-distortion) saturate(120%) brightness(1.15); `; const expandedGlassOverlay = document.createElement('div'); expandedGlassOverlay.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 2; background: rgba(0, 0, 0, 0.4); border: 1px solid rgba(255, 255, 255, 0.2); `; const expandedGlassSpecular = document.createElement('div'); expandedGlassSpecular.style.cssText = ` position: absolute; inset: 0; border-radius: inherit; z-index: 3; box-shadow: inset 1px 1px 1px rgba(255, 255, 255, 0.4); `; const expandedGlassContent = document.createElement('div'); expandedGlassContent.style.cssText = ` position: relative; z-index: 4; padding: 20px; color: white; height: 100%; `; expandedPanel.appendChild(expandedGlassFilter); expandedPanel.appendChild(expandedGlassOverlay); expandedPanel.appendChild(expandedGlassSpecular); expandedPanel.appendChild(expandedGlassContent); createExpandedContent(expandedGlassContent); container.appendChild(island); container.appendChild(expandedPanel); setupEventListeners(container, island, dragHandle); document.body.appendChild(container); return container; } function createExpandedContent(container) { const mainContent = document.createElement('div'); mainContent.id = 'fab-main-content'; mainContent.style.cssText = ` transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); opacity: 1; transform: translateX(0); `; const progressSection = document.createElement('div'); progressSection.style.cssText = ` margin-bottom: 20px; `; const progressTitle = document.createElement('h4'); progressTitle.style.cssText = ` margin: 0 0 10px 0; font-size: 16px; font-weight: 600; color: white; `; progressTitle.textContent = 'Current Progress'; const progressBar = document.createElement('div'); progressBar.id = 'fab-progress-bar'; progressBar.style.cssText = ` width: 100%; height: 6px; background: rgba(255, 255, 255, 0.1); border-radius: 3px; overflow: hidden; margin-bottom: 8px; `; const progressFill = document.createElement('div'); progressFill.id = 'fab-progress-fill'; progressFill.style.cssText = ` height: 100%; background: linear-gradient(90deg, #34c759, #30d158); border-radius: 3px; width: 0%; transition: width 0.3s ease; `; const progressText = document.createElement('div'); progressText.id = 'fab-progress-text'; progressText.style.cssText = ` font-size: 12px; color: rgba(255, 255, 255, 0.9); text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); `; progressText.textContent = 'Ready to start'; const foundText = document.createElement('div'); foundText.id = 'fab-found-text'; foundText.style.cssText = ` font-size: 12px; color: rgba(255, 255, 255, 0.9); text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); `; foundText.textContent = `Found: 0 assets`; progressBar.appendChild(progressFill); progressSection.appendChild(progressTitle); progressSection.appendChild(progressBar); progressSection.appendChild(progressText); progressSection.appendChild(foundText); const configSection = document.createElement('div'); configSection.style.cssText = ` margin-bottom: 20px; display: flex; flex-direction: column; gap: 15px; `; const licenseGroup = document.createElement('div'); licenseGroup.style.cssText = ` display: flex; flex-direction: column; gap: 5px; `; const licenseLabel = document.createElement('label'); licenseLabel.textContent = 'License Preference:'; licenseLabel.style.cssText = ` font-size: 12px; color: rgba(255, 255, 255, 0.9); `; const licenseSelect = document.createElement('select'); licenseSelect.id = 'fab-license-select'; licenseSelect.innerHTML = ` <option value="personal">Personal</option> <option value="professional">Professional</option> `; licenseSelect.style.cssText = ` padding: 8px; border-radius: 8px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 13px; `; licenseGroup.appendChild(licenseLabel); licenseGroup.appendChild(licenseSelect); const delayGroup = document.createElement('div'); delayGroup.style.cssText = ` display: flex; flex-direction: column; gap: 5px; `; const delayLabel = document.createElement('label'); delayLabel.textContent = 'Delay (ms):'; delayLabel.style.cssText = ` font-size: 12px; color: rgba(255, 255, 255, 0.9); `; const delayInput = document.createElement('input'); delayInput.id = 'fab-delay-input'; delayInput.type = 'number'; delayInput.value = config.delayBetweenActions; delayInput.min = '500'; delayInput.max = '10000'; delayInput.style.cssText = ` padding: 8px; border-radius: 8px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 13px; `; delayGroup.appendChild(delayLabel); delayGroup.appendChild(delayInput); configSection.appendChild(licenseGroup); configSection.appendChild(delayGroup); const buttonsSection = document.createElement('div'); buttonsSection.style.cssText = ` display: flex; gap: 10px; margin-bottom: 20px; `; const actionButton = document.createElement('button'); actionButton.id = 'fab-action-btn'; actionButton.style.cssText = ` flex: 1; padding: 12px; border-radius: 12px; background: linear-gradient(135deg, #34c759, #30d158); border: none; color: white; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; box-shadow: 0 4px 20px rgba(52, 199, 89, 0.3); `; actionButton.textContent = 'Start Auto Grab'; const showAssetsButton = document.createElement('button'); showAssetsButton.id = 'fab-show-assets-btn'; showAssetsButton.style.cssText = ` padding: 12px; border-radius: 12px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 14px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; white-space: nowrap; `; showAssetsButton.textContent = `Assets (0)`; buttonsSection.appendChild(actionButton); buttonsSection.appendChild(showAssetsButton); // Social buttons const socialContainer = document.createElement('div'); socialContainer.style.cssText = ` display: flex; gap: 8px; justify-content: center; margin-bottom: 15px; `; const socialButtons = [ { icon: `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 64 64"><style><![CDATA[.C{clip-rule:evenodd}.D{fill:#100f0d}]]></style><defs><clipPath id="A"><path d="M2332.45 5723.53v37c0 20.44-16.57 37-37 37h-12.33c-20.44 0-37-16.57-37-37v-37h-37c-20.44 0-37-16.57-37-37v-12.33c0-20.44 16.57-37 37-37h37v-37c0-20.44 16.57-37 37-37h12.33c20.44 0 37 16.57 37 37v37h37c20.44 0 37 16.57 37 37v12.33c0 20.44-16.57 37-37 37z" class="C"/></clipPath><clipPath id="B"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath><clipPath id="C"><path d="M1534.22 5533.96l-387.92 2.36 190.5-1210.2h464.12l190.5 1210.2z" class="C"/></clipPath><clipPath id="D"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath><clipPath id="E"><path d="M1534.22 5533.96l-387.92 2.36 190.5-1210.2h367.14l190.5 1210.2z" class="C"/></clipPath><clipPath id="F"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath><clipPath id="G"><path d="M1035.47 5536.32h1000.97v111.602H1035.47z"/></clipPath><clipPath id="H"><path d="M1842.48 5829.28H1222.5l-72.73-167.4h765.44z" class="C"/></clipPath><clipPath id="I"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath><clipPath id="J"><path d="M2005.26 5194.53H1059.7l85.37-481.3 387.4 4.2 387.4-4.2z" class="C"/></clipPath><clipPath id="K"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath><clipPath id="L"><path d="M707.297 5228.36c78.582 0 142.293 63.7 142.293 142.3s-63.7 142.3-142.293 142.3S565 5449.24 565 5370.65c0-78.58 63.7-142.3 142.297-142.3z" class="C"/></clipPath><clipPath id="M"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath><clipPath id="N"><path d="M2188.83 4876.8c36.98 0 66.96 29.98 66.96 66.96s-29.98 66.96-66.96 66.96-66.95-29.98-66.95-66.96 29.97-66.96 66.95-66.96z" class="C"/></clipPath><clipPath id="O"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath><clipPath id="P"><path d="M933.293 4491.78c36.98 0 66.957 29.98 66.957 66.96s-29.977 66.96-66.957 66.96-66.965-29.98-66.965-66.96 29.98-66.96 66.965-66.96z" class="C"/></clipPath><clipPath id="Q"><path d="M-2297.97 261.46H12137.2v9623.42H-2297.97z"/></clipPath></defs><g transform="matrix(2.704792 0 0 2.704792 -90.948353 -1833.1808)"><g transform="matrix(.012849 0 0 -.012849 26.365039 755.74948)"><g clip-path="url(#A)"><g clip-path="url(#B)"><path d="M2111.94 5503.03h354.66v354.66h-354.66z" fill="#f9dd05"/></g></g><g clip-path="url(#C)"><g clip-path="url(#D)"><path d="M1086.15 4265.97h965.402v1330.5H1086.15z" fill="#f68313"/></g></g><g clip-path="url(#E)"><g clip-path="url(#F)"><path d="M1086.15 4265.97h868.43v1330.5h-868.43z" fill="#f9dd05"/></g></g><g clip-path="url(#G)"><path d="M975.32 5476.17h1121.26v231.898H975.32z" fill="#fff"/></g></g><path d="M40.004 684.277h12.194v-.766H40.004zm12.862.668h-13.53v-2.102h13.53v2.102" class="D"/><g transform="matrix(.012849 0 0 -.012849 26.365039 755.74948)" clip-path="url(#H)"><g clip-path="url(#I)"><path d="M1089.62 5601.72h885.738v287.703H1089.62z" fill="#fff"/></g></g><path d="M41.648 682.664h8.818l-.645-1.484h-7.53zm9.835.667H40.63l1.225-2.82h8.404l1.225 2.82" fill="#12110f"/><path d="M42.937 699.828h6.283l2.342-14.88-5.486.028-5.482-.028zm6.854.667h-7.424l-2.553-16.22 6.265.032 6.266-.032-2.553 16.22" class="D"/><g transform="matrix(.012849 0 0 -.012849 26.365039 755.74948)" clip-path="url(#J)"><g clip-path="url(#K)"><path d="M999.563 4653.1h1065.85v601.578H999.563z" fill="#fff"/></g></g><path d="M46.053 694.8l4.702.05.978-5.514H40.38l.978 5.514zm5.26.724l-5.26-.057-5.254.057-1.216-6.855H52.53l-1.216 6.855" class="D"/><g transform="matrix(.012849 0 0 -.012849 26.365039 755.74948)"><g clip-path="url(#L)"><g clip-path="url(#M)"><path d="M707.297 5429.25c-32.3 0-58.598-26.3-58.598-58.6s26.3-58.6 58.598-58.6 58.594 26.3 58.594 58.6-26.3 58.6-58.594 58.6zm0-284.6c-124.62 0-225.996 101.38-225.996 226s101.375 226 225.996 226 225.996-101.38 225.996-226c0-124.6-101.38-226-225.996-226" fill="#f1f1f1"/></g></g><g clip-path="url(#N)"><g clip-path="url(#O)"><path d="M2061.73 4816.66h254.2v254.22h-254.2z" fill="#f1f1f1"/></g></g><g clip-path="url(#P)"><g clip-path="url(#Q)"><path d="M806.184 4431.63H1060.4v254.223H806.184z" fill="#f46c35"/></g></g></g></g></svg>`, url: 'http://buymeacoffee.com/creos', title: 'Buy me a coffee' }, { icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <path d="M12 0C5.374 0 0 5.373 0 12 0 17.302 3.438 21.8 8.207 23.387c.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23A11.509 11.509 0 0 1 12 5.803c1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576C20.566 21.797 24 17.300 24 12c0-6.627-5.373-12-12-12z"/> </svg>`, url: 'https://github.com/creosb', title: 'GitHub' }, { icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <path d="M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z"/> </svg>`, url: 'https://x.com/CreosB', title: 'X (Twitter)' }, { icon: `<svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"> <path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/> </svg>`, url: 'https://www.youtube.com/@CreosB', title: 'YouTube' } ]; socialButtons.forEach(btn => { const button = document.createElement('button'); button.style.cssText = ` width: 40px; height: 40px; border-radius: 12px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.15); color: white; font-size: 16px; display: flex; align-items: center; justify-content: center; cursor: pointer; transition: all 0.3s ease; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); `; button.innerHTML = btn.icon; button.title = btn.title; button.addEventListener('click', () => window.open(btn.url, '_blank')); button.addEventListener('mouseenter', () => { button.style.background = 'rgba(255, 255, 255, 0.2)'; button.style.transform = 'scale(1.1)'; }); button.addEventListener('mouseleave', () => { button.style.background = 'rgba(255, 255, 255, 0.1)'; button.style.transform = 'scale(1)'; }); socialContainer.appendChild(button); }); // Tip text const tipText = document.createElement('p'); tipText.style.cssText = ` color: rgba(255, 255, 255, 0.8); font-size: 11px; margin: 0; line-height: 1.4; text-align: center; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.6); `; tipText.textContent = 'Depends on your filters on this page, it automatically adjusts what needs to be grabbed'; mainContent.appendChild(progressSection); mainContent.appendChild(configSection); mainContent.appendChild(buttonsSection); mainContent.appendChild(socialContainer); mainContent.appendChild(tipText); const assetsView = document.createElement('div'); assetsView.id = 'fab-assets-view'; assetsView.style.cssText = ` position: absolute; top: 0; left: 0; right: 0; bottom: 0; padding: 20px; background: inherit; opacity: 0; transform: translateX(100%); transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); pointer-events: none; display: flex; flex-direction: column; `; const assetsHeader = document.createElement('div'); assetsHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px; padding-bottom: 15px; border-bottom: 1px solid rgba(255, 255, 255, 0.1); `; const assetsTitle = document.createElement('h4'); assetsTitle.style.cssText = ` margin: 0; font-size: 16px; font-weight: 600; color: white; `; assetsTitle.textContent = 'Grabbed Assets'; const backButton = document.createElement('button'); backButton.id = 'fab-back-btn'; backButton.style.cssText = ` background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); color: white; font-size: 18px; cursor: pointer; padding: 8px 12px; border-radius: 8px; transition: all 0.3s ease; display: flex; align-items: center; gap: 6px; `; backButton.innerHTML = '← Back'; assetsHeader.appendChild(assetsTitle); assetsHeader.appendChild(backButton); const searchContainer = document.createElement('div'); searchContainer.style.cssText = ` position: relative; margin-bottom: 15px; `; const searchInput = document.createElement('input'); searchInput.id = 'fab-assets-search'; searchInput.type = 'text'; searchInput.placeholder = 'Search assets...'; searchInput.style.cssText = ` width: 100%; padding: 12px 40px 12px 15px; background: rgba(255, 255, 255, 0.1); border: 1px solid rgba(255, 255, 255, 0.2); border-radius: 12px; color: white; font-size: 14px; transition: all 0.3s ease; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); `; const searchIcon = document.createElement('div'); searchIcon.style.cssText = ` position: absolute; right: 15px; top: 50%; transform: translateY(-50%); color: rgba(255, 255, 255, 0.6); font-size: 16px; pointer-events: none; `; searchIcon.textContent = '🔍'; const clearButton = document.createElement('button'); clearButton.id = 'fab-search-clear'; clearButton.style.cssText = ` position: absolute; right: 40px; top: 50%; transform: translateY(-50%); background: none; border: none; color: rgba(255, 255, 255, 0.6); font-size: 18px; cursor: pointer; padding: 2px; border-radius: 50%; opacity: 0; transition: all 0.3s ease; `; clearButton.innerHTML = '×'; searchContainer.appendChild(searchInput); searchContainer.appendChild(searchIcon); searchContainer.appendChild(clearButton); const exportButton = document.createElement('button'); exportButton.id = 'fab-export-btn'; exportButton.textContent = 'Export CSV'; exportButton.style.cssText = ` padding: 10px; background: linear-gradient(135deg, #2196F3, #1E88E5); border: none; border-radius: 8px; color: white; font-weight: 600; cursor: pointer; margin-bottom: 15px; box-shadow: 0 2px 10px rgba(33, 150, 243, 0.3); `; const assetsList = document.createElement('div'); assetsList.id = 'fab-assets-list'; assetsList.style.cssText = ` flex: 1; overflow-y: auto; max-height: 300px; padding-right: 8px; `; assetsView.appendChild(assetsHeader); assetsView.appendChild(searchContainer); assetsView.appendChild(exportButton); assetsView.appendChild(assetsList); container.appendChild(mainContent); container.appendChild(assetsView); setupSearchFunctionality(searchInput, clearButton, assetsList); setupViewSwitching(showAssetsButton, backButton, mainContent, assetsView, exportButton); } function setupEventListeners(container, island, dragHandle) { let isDragging = false; let dragOffset = { x: 0, y: 0 }; dragHandle.addEventListener('mousedown', (e) => { isDragging = true; const rect = container.getBoundingClientRect(); dragOffset.x = e.clientX - rect.left; dragOffset.y = e.clientY - rect.top; dragHandle.style.cursor = 'grabbing'; container.style.transition = 'none'; island.style.transition = 'none'; e.preventDefault(); e.stopPropagation(); }); document.addEventListener('mousemove', (e) => { if (!isDragging) return; const newX = e.clientX - dragOffset.x; const newY = e.clientY - dragOffset.y; const maxX = window.innerWidth - container.offsetWidth; const maxY = window.innerHeight - container.offsetHeight; const clampedX = Math.max(0, Math.min(newX, maxX)); const clampedY = Math.max(0, Math.min(newY, maxY)); container.style.left = clampedX + 'px'; container.style.top = clampedY + 'px'; container.style.right = 'auto'; }); document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; dragHandle.style.cursor = 'move'; container.style.transition = 'all 0.4s cubic-bezier(0.25, 0.1, 0.25, 1)'; island.style.transition = 'all 0.3s cubic-bezier(0.25, 0.1, 0.25, 1)'; } }); island.addEventListener('click', (e) => { if (e.target !== dragHandle && !isDragging) { toggleExpanded(); } }); setTimeout(() => { const actionButton = document.getElementById('fab-action-btn'); if (actionButton) { actionButton.addEventListener('click', (e) => { e.stopPropagation(); console.log('[DEBUG] Action button clicked, isRunning:', state.isRunning); if (state.isRunning) { controller.stop(); } else { controller.start(); } }); actionButton.addEventListener('mouseenter', () => { actionButton.style.transform = 'scale(1.05)'; }); actionButton.addEventListener('mouseleave', () => { actionButton.style.transform = 'scale(1)'; }); } else { console.log('[DEBUG] Action button not found'); } const licenseSelect = document.getElementById('fab-license-select'); if (licenseSelect) { licenseSelect.value = config.preferredLicense; licenseSelect.addEventListener('change', (e) => { config.preferredLicense = e.target.value; }); } const delayInput = document.getElementById('fab-delay-input'); if (delayInput) { delayInput.addEventListener('change', (e) => { config.delayBetweenActions = parseInt(e.target.value) || 2000; }); } }, 100); let animationFrame; container.addEventListener('mousemove', (e) => { if (animationFrame) cancelAnimationFrame(animationFrame); animationFrame = requestAnimationFrame(() => { const rect = container.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; const specular = container.querySelector('.glass-specular'); if (specular) { specular.style.background = `radial-gradient(circle at ${x}px ${y}px, rgba(255,255,255,0.3) 0%, rgba(255,255,255,0.15) 30%, rgba(255,255,255,0) 60%)`; } }); }); container.addEventListener('mouseleave', () => { if (animationFrame) cancelAnimationFrame(animationFrame); const specular = container.querySelector('.glass-specular'); if (specular) { specular.style.background = 'none'; } }); island.addEventListener('mouseenter', () => { if (!isExpanded && !isDragging) { island.style.transform = 'scale(1.02)'; } }); island.addEventListener('mouseleave', () => { if (!isExpanded && !isDragging) { island.style.transform = 'scale(1)'; } }); } let isExpanded = false; function toggleExpanded() { isExpanded = !isExpanded; const island = document.getElementById('fab-island'); const expandedPanel = document.getElementById('fab-expanded-panel'); if (isExpanded) { expandedPanel.style.opacity = '1'; expandedPanel.style.transform = 'scale(1) translateY(0)'; expandedPanel.style.pointerEvents = 'auto'; island.style.borderBottomLeftRadius = '12px'; island.style.borderBottomRightRadius = '12px'; } else { expandedPanel.style.opacity = '0'; expandedPanel.style.transform = 'scale(0.9) translateY(-10px)'; expandedPanel.style.pointerEvents = 'none'; island.style.borderBottomLeftRadius = '20px'; island.style.borderBottomRightRadius = '20px'; } } function updateDynamicIsland() { const statusDot = document.getElementById('fab-status-dot'); const mainText = document.getElementById('fab-main-text'); const subText = document.getElementById('fab-sub-text'); const actionButton = document.getElementById('fab-action-btn'); const showAssetsButton = document.getElementById('fab-show-assets-btn'); const progressFill = document.getElementById('fab-progress-fill'); const progressText = document.getElementById('fab-progress-text'); const foundTextEl = document.getElementById('fab-found-text'); if (!statusDot || !mainText || !subText) return; if (state.isRunning) { statusDot.style.background = '#ff3b30'; statusDot.style.boxShadow = '0 0 10px rgba(255, 59, 48, 0.5)'; if (state.totalAssets > 0) { const progress = (state.currentAssetIndex / state.totalAssets) * 100; mainText.textContent = `${state.currentAssetIndex}/${state.totalAssets}`; subText.textContent = 'Processing...'; if (progressFill) { progressFill.style.width = progress + '%'; } if (progressText) { progressText.textContent = `${state.currentAssetIndex} of ${state.totalAssets} assets`; } } else { mainText.textContent = 'Running...'; subText.textContent = 'Searching for assets...' ; if (progressText) { progressText.textContent = 'Initializing...'; } } if (actionButton) { actionButton.textContent = 'Stop Process'; actionButton.style.background = 'linear-gradient(135deg, #ff3b30, #ff453a)'; actionButton.style.boxShadow = '0 4px 20px rgba(255, 59, 48, 0.3)'; } } else { statusDot.style.background = '#34c759'; statusDot.style.boxShadow = '0 0 10px rgba(52, 199, 89, 0.5)'; mainText.textContent = 'Fab Auto Grab'; subText.textContent = `Ready • ${state.processedAssets.size} assets grabbed`; if (actionButton) { actionButton.textContent = 'Start Auto Grab'; actionButton.style.background = 'linear-gradient(135deg, #34c759, #30d158)'; actionButton.style.boxShadow = '0 4px 20px rgba(52, 199, 89, 0.3)'; } if (progressFill) { progressFill.style.width = '0%'; } if (progressText) { progressText.textContent = 'Ready to start'; } } if (showAssetsButton) { showAssetsButton.textContent = `Assets (${state.processedAssets.size})`; } if (foundTextEl) { foundTextEl.textContent = `Found: ${state.totalAssets} assets`; } } function updateAssetsList() { const assetsList = document.getElementById('fab-assets-list'); if (!assetsList) return; assetsList.innerHTML = ''; const assets = Array.from(state.processedAssets.values()); if (assets.length === 0) { const emptyMessage = document.createElement('div'); emptyMessage.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.6); font-size: 14px; padding: 40px 20px; display: flex; flex-direction: column; align-items: center; gap: 10px; `; emptyMessage.innerHTML = ` <div style="font-size: 32px; opacity: 0.5;">📦</div> <div>No assets grabbed yet</div> <div style="font-size: 12px; opacity: 0.7;">Start the auto grabber to grab free assets</div> `; assetsList.appendChild(emptyMessage); return; } assets.forEach((asset, index) => { const assetItem = document.createElement('div'); assetItem.className = 'asset-item'; assetItem.style.cssText = ` padding: 12px; margin-bottom: 8px; background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(255, 255, 255, 0.1); border-radius: 10px; transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); cursor: pointer; backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); opacity: 0; transform: translateY(10px); animation: fadeInUp 0.3s ease forwards; animation-delay: ${index * 0.05}s; `; const assetHeader = document.createElement('div'); assetHeader.style.cssText = ` display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 6px; `; const assetName = document.createElement('div'); assetName.style.cssText = ` font-weight: 600; font-size: 14px; color: white; line-height: 1.3; flex: 1; text-shadow: 0 1px 2px rgba(0, 0, 0, 0.7); `; assetName.textContent = asset.name; const assetIndex = document.createElement('div'); assetIndex.style.cssText = ` font-size: 11px; color: rgba(255, 255, 255, 0.5); background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 6px; margin-left: 8px; `; assetIndex.textContent = `#${index + 1}`; const assetInfo = document.createElement('div'); assetInfo.style.cssText = ` font-size: 12px; color: rgba(255, 255, 255, 0.6); display: flex; justify-content: space-between; align-items: center; `; const assetLicense = document.createElement('span'); assetLicense.style.cssText = ` font-family: monospace; background: rgba(255, 255, 255, 0.1); padding: 2px 6px; border-radius: 4px; font-size: 11px; `; assetLicense.textContent = asset.license; const assetTime = document.createElement('span'); assetTime.style.cssText = ` font-size: 11px; opacity: 0.7; `; assetTime.textContent = new Date(asset.timestamp).toLocaleTimeString(); assetInfo.appendChild(assetLicense); assetInfo.appendChild(assetTime); assetHeader.appendChild(assetName); assetHeader.appendChild(assetIndex); assetItem.appendChild(assetHeader); assetItem.appendChild(assetInfo); assetItem.addEventListener('mouseenter', () => { assetItem.style.background = 'rgba(255, 255, 255, 0.1)'; assetItem.style.borderColor = 'rgba(255, 255, 255, 0.2)'; assetItem.style.transform = 'translateY(-2px)'; assetItem.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.2)'; }); assetItem.addEventListener('mouseleave', () => { assetItem.style.background = 'rgba(255, 255, 255, 0.05)'; assetItem.style.borderColor = 'rgba(255, 255, 255, 0.1)'; assetItem.style.transform = 'translateY(0)'; assetItem.style.boxShadow = 'none'; }); assetsList.appendChild(assetItem); }); if (!document.getElementById('fab-assets-animation')) { const style = document.createElement('style'); style.id = 'fab-assets-animation'; style.textContent = ` @keyframes fadeInUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } `; document.head.appendChild(style); } } function filterAssetsList(searchTerm) { const assetItems = document.querySelectorAll('.asset-item'); let visibleCount = 0; assetItems.forEach((item, index) => { const assetName = item.querySelector('div div').textContent.toLowerCase(); const shouldShow = assetName.includes(searchTerm); if (shouldShow) { item.style.display = 'block'; item.style.animationDelay = `${visibleCount * 0.03}s`; visibleCount++; } else { item.style.display = 'none'; } }); const assetsList = document.getElementById('fab-assets-list'); if (assetsList && searchTerm && visibleCount === 0 && state.processedAssets.size > 0) { const noResults = document.createElement('div'); noResults.className = 'no-results-message'; noResults.style.cssText = ` text-align: center; color: rgba(255, 255, 255, 0.6); font-size: 14px; padding: 40px 20px; display: flex; flex-direction: column; align-items: center; gap: 10px; `; noResults.innerHTML = ` <div style="font-size: 32px; opacity: 0.5;">🔍</div> <div>No assets found</div> <div style="font-size: 12px; opacity: 0.7;">Try a different search term</div> `; const existingNoResults = assetsList.querySelector('.no-results-message'); if (existingNoResults) { existingNoResults.remove(); } assetsList.appendChild(noResults); } else { const existingNoResults = assetsList.querySelector('.no-results-message'); if (existingNoResults) { existingNoResults.remove(); } } } function setupSearchFunctionality(searchInput, clearButton, assetsList) { searchInput.addEventListener('input', (e) => { const searchTerm = e.target.value.toLowerCase(); filterAssetsList(searchTerm); if (searchTerm.length > 0) { clearButton.style.opacity = '1'; } else { clearButton.style.opacity = '0'; } }); searchInput.addEventListener('focus', () => { searchInput.style.background = 'rgba(255, 255, 255, 0.15)'; searchInput.style.borderColor = 'rgba(255, 255, 255, 0.3)'; searchInput.style.transform = 'translateY(-1px)'; searchInput.style.boxShadow = '0 4px 12px rgba(0, 0, 0, 0.2)'; }); searchInput.addEventListener('blur', () => { searchInput.style.background = 'rgba(255, 255, 255, 0.1)'; searchInput.style.borderColor = 'rgba(255, 255, 255, 0.2)'; searchInput.style.transform = 'translateY(0)'; searchInput.style.boxShadow = 'none'; }); clearButton.addEventListener('click', () => { searchInput.value = ''; clearButton.style.opacity = '0'; filterAssetsList(''); searchInput.focus(); }); clearButton.addEventListener('mouseenter', () => { clearButton.style.background = 'rgba(255, 255, 255, 0.1)'; clearButton.style.color = 'white'; }); clearButton.addEventListener('mouseleave', () => { clearButton.style.background = 'none'; clearButton.style.color = 'rgba(255, 255, 255, 0.6)'; }); } function setupViewSwitching(showAssetsButton, backButton, mainContent, assetsView, exportButton) { let isInAssetsView = false; showAssetsButton.addEventListener('click', (e) => { e.stopPropagation(); console.log('[DEBUG] Show assets button clicked'); if (!isInAssetsView) { updateAssetsList(); mainContent.style.opacity = '0'; mainContent.style.transform = 'translateX(-100%)'; setTimeout(() => { assetsView.style.opacity = '1'; assetsView.style.transform = 'translateX(0)'; assetsView.style.pointerEvents = 'auto'; const searchInput = document.getElementById('fab-assets-search'); if (searchInput) { setTimeout(() => searchInput.focus(), 200); } }, 200); isInAssetsView = true; } }); backButton.addEventListener('click', (e) => { e.stopPropagation(); if (isInAssetsView) { assetsView.style.opacity = '0'; assetsView.style.transform = 'translateX(100%)'; assetsView.style.pointerEvents = 'none'; setTimeout(() => { mainContent.style.opacity = '1'; mainContent.style.transform = 'translateX(0)'; }, 200); isInAssetsView = false; const searchInput = document.getElementById('fab-assets-search'); if (searchInput) { searchInput.value = ''; filterAssetsList(''); } } }); exportButton.addEventListener('click', (e) => { e.stopPropagation(); assetList.exportData(); }); backButton.addEventListener('mouseenter', () => { backButton.style.background = 'rgba(255, 255, 255, 0.15)'; backButton.style.transform = 'translateX(-2px)'; }); backButton.addEventListener('mouseleave', () => { backButton.style.background = 'rgba(255, 255, 255, 0.1)'; backButton.style.transform = 'translateX(0)'; }); } document.addEventListener('click', (e) => { if (isExpanded && !e.target.closest('#fab-dynamic-island')) { toggleExpanded(); } }); let config = { preferredLicense: 'professional', delayBetweenActions: 2000, maxRetries: 2, dialogTimeout: 10000, autoStart: false }; let state = { isRunning: false, processedAssets: new Map(), currentAssetIndex: 0, totalAssets: 0, errors: [] }; function init() { if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', init); return; } const checkInterval = setInterval(() => { const cards = assetProcessor.getFreeAssetCards(); if (cards.length > 0) { clearInterval(checkInterval); createDynamicIsland(); console.log('[INFO] Fab.com Free Assets Auto-Grabber Pro loaded with Dynamic Island UI.'); } }, 1000); setTimeout(() => { clearInterval(checkInterval); }, 30000); setInterval(() => { if (!state.isRunning) { const cards = assetProcessor.getFreeAssetCards(); state.totalAssets = cards.length; updateDynamicIsland(); } }, 5000); } init(); })();