Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>InstaFlow - Automated DM Manager</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css" rel="stylesheet"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <style> | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background-color: #0f172a; | |
| color: #e2e8f0; | |
| overflow-x: hidden; | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: #1e293b; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: #475569; | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #64748b; | |
| } | |
| /* Gradient Text */ | |
| .text-gradient { | |
| background: linear-gradient(to right, #f472b6, #a855f7, #6366f1); | |
| -webkit-background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| background-clip: text; | |
| } | |
| /* Glassmorphism */ | |
| .glass { | |
| background: rgba(30, 41, 59, 0.7); | |
| backdrop-filter: blur(12px); | |
| -webkit-backdrop-filter: blur(12px); | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| /* Animations */ | |
| @keyframes pulse-glow { | |
| 0%, 100% { box-shadow: 0 0 10px rgba(168, 85, 247, 0.2); } | |
| 50% { box-shadow: 0 0 20px rgba(168, 85, 247, 0.5); } | |
| } | |
| .animate-glow { | |
| animation: pulse-glow 2s infinite; | |
| } | |
| @keyframes slideIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| .log-entry { | |
| animation: slideIn 0.3s ease-out forwards; | |
| } | |
| /* Toggle Switch */ | |
| .toggle-checkbox:checked { | |
| right: 0; | |
| border-color: #a855f7; | |
| } | |
| .toggle-checkbox:checked + .toggle-label { | |
| background-color: #a855f7; | |
| } | |
| /* Range Slider */ | |
| input[type=range] { | |
| -webkit-appearance: none; | |
| background: transparent; | |
| } | |
| input[type=range]::-webkit-slider-thumb { | |
| -webkit-appearance: none; | |
| height: 16px; | |
| width: 16px; | |
| border-radius: 50%; | |
| background: #a855f7; | |
| cursor: pointer; | |
| margin-top: -6px; | |
| box-shadow: 0 0 10px rgba(168, 85, 247, 0.5); | |
| } | |
| input[type=range]::-webkit-slider-runnable-track { | |
| width: 100%; | |
| height: 4px; | |
| cursor: pointer; | |
| background: #334155; | |
| border-radius: 2px; | |
| } | |
| /* Terminal/Code styling */ | |
| .code-block { | |
| font-family: 'Fira Code', monospace; | |
| background: #0f172a; | |
| border-left: 3px solid #a855f7; | |
| } | |
| /* Loader */ | |
| .loader { | |
| border: 3px solid rgba(255,255,255,0.1); | |
| border-radius: 50%; | |
| border-top: 3px solid #a855f7; | |
| width: 24px; | |
| height: 24px; | |
| -webkit-animation: spin 1s linear infinite; /* Safari */ | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen flex flex-col"> | |
| <!-- Navbar --> | |
| <nav class="glass sticky top-0 z-50 border-b border-slate-700/50"> | |
| <div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8"> | |
| <div class="flex items-center justify-between h-16"> | |
| <div class="flex items-center gap-3"> | |
| <div class="w-8 h-8 rounded-lg bg-gradient-to-br from-pink-500 to-violet-600 flex items-center justify-center text-white font-bold shadow-lg shadow-purple-500/20"> | |
| <i class="fa-brands fa-instagram"></i> | |
| </div> | |
| <span class="text-xl font-bold tracking-tight text-white">Insta<span class="text-gradient">Flow</span></span> | |
| </div> | |
| <div class="flex items-center gap-4"> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="text-xs font-medium text-slate-400 hover:text-white transition-colors flex items-center gap-1"> | |
| <i class="fa-solid fa-code"></i> Built with anycoder | |
| </a> | |
| <div class="h-4 w-px bg-slate-700"></div> | |
| <div class="flex items-center gap-2"> | |
| <div id="connection-status-dot" class="w-2 h-2 rounded-full bg-red-500"></div> | |
| <span id="connection-status-text" class="text-xs font-medium text-slate-400">Disconnected</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </nav> | |
| <!-- Main Content --> | |
| <main class="flex-grow p-4 sm:p-6 lg:p-8 max-w-7xl mx-auto w-full grid grid-cols-1 lg:grid-cols-12 gap-6"> | |
| <!-- Left Column: Controls & Settings (4 cols) --> | |
| <div class="lg:col-span-4 space-y-6"> | |
| <!-- Connection Card --> | |
| <div class="glass rounded-2xl p-6 shadow-xl shadow-black/20"> | |
| <div class="flex justify-between items-start mb-4"> | |
| <div> | |
| <h2 class="text-lg font-semibold text-white">API Connection</h2> | |
| <p class="text-xs text-slate-400 mt-1">Link your Instagram Business Account</p> | |
| </div> | |
| <div class="p-2 bg-slate-800/50 rounded-lg"> | |
| <i class="fa-solid fa-plug text-slate-400"></i> | |
| </div> | |
| </div> | |
| <div class="space-y-3"> | |
| <div> | |
| <label class="block text-xs font-medium text-slate-400 mb-1">Access Token</label> | |
| <div class="relative"> | |
| <input type="password" id="api-token" placeholder="Enter IG Graph API Token" class="w-full bg-slate-900/50 border border-slate-700 rounded-lg py-2 px-3 text-sm text-white focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500 transition-all placeholder-slate-600"> | |
| <button id="toggle-token" class="absolute right-3 top-2 text-slate-500 hover:text-white text-xs"> | |
| <i class="fa-solid fa-eye"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <button id="btn-connect" class="w-full bg-gradient-to-r from-purple-600 to-indigo-600 hover:from-purple-500 hover:to-indigo-500 text-white font-medium py-2 px-4 rounded-lg transition-all transform hover:scale-[1.02] active:scale-[0.98] shadow-lg shadow-purple-900/20 flex justify-center items-center gap-2"> | |
| <span>Connect Account</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Automation Settings --> | |
| <div class="glass rounded-2xl p-6 shadow-xl shadow-black/20 opacity-50 pointer-events-none transition-all duration-300" id="settings-panel"> | |
| <div class="flex justify-between items-center mb-6"> | |
| <h2 class="text-lg font-semibold text-white">Automation Rules</h2> | |
| <!-- Toggle Switch --> | |
| <div class="relative inline-block w-12 mr-2 align-middle select-none transition duration-200 ease-in"> | |
| <input type="checkbox" name="toggle" id="automation-toggle" class="toggle-checkbox absolute block w-6 h-6 rounded-full bg-white border-4 appearance-none cursor-pointer transition-all duration-300 left-0 border-slate-600"/> | |
| <label for="automation-toggle" class="toggle-label block overflow-hidden h-6 rounded-full bg-slate-700 cursor-pointer transition-colors duration-300"></label> | |
| </div> | |
| </div> | |
| <div class="space-y-5"> | |
| <!-- Delay Slider --> | |
| <div> | |
| <div class="flex justify-between text-xs mb-2"> | |
| <span class="text-slate-300">Response Delay</span> | |
| <span class="text-purple-400 font-mono" id="delay-display">5s</span> | |
| </div> | |
| <input type="range" id="delay-slider" min="1" max="60" value="5" class="w-full h-2 bg-slate-700 rounded-lg appearance-none cursor-pointer"> | |
| <div class="flex justify-between text-[10px] text-slate-500 mt-1"> | |
| <span>Instant</span> | |
| <span>1 Min</span> | |
| </div> | |
| </div> | |
| <!-- Message Template --> | |
| <div> | |
| <label class="block text-xs font-medium text-slate-400 mb-1">DM Template</label> | |
| <textarea id="message-template" rows="6" class="w-full bg-slate-900/50 border border-slate-700 rounded-lg py-2 px-3 text-sm text-white focus:outline-none focus:border-purple-500 focus:ring-1 focus:ring-purple-500 transition-all placeholder-slate-600 resize-none" placeholder="Hi {{name}}! 👋 Thanks for following me! Welcome to the community. Feel free to check out my latest post!"></textarea> | |
| <div class="flex justify-between mt-1"> | |
| <span class="text-[10px] text-slate-500">Use <span class="text-purple-400">{{name}}</span> for username</span> | |
| <span class="text-[10px] text-slate-500" id="char-count">0/1000</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Stats Mini --> | |
| <div class="grid grid-cols-2 gap-4"> | |
| <div class="glass rounded-xl p-4 text-center"> | |
| <div class="text-2xl font-bold text-white" id="stat-sent">0</div> | |
| <div class="text-xs text-slate-400">Sent Today</div> | |
| </div> | |
| <div class="glass rounded-xl p-4 text-center"> | |
| <div class="text-2xl font-bold text-emerald-400" id="stat-rate">0%</div> | |
| <div class="text-xs text-slate-400">Success Rate</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Right Column: Simulation & Logs (8 cols) --> | |
| <div class="lg:col-span-8 flex flex-col gap-6 h-full"> | |
| <!-- Simulation View --> | |
| <div class="glass rounded-2xl p-1 shadow-xl shadow-black/20 flex-grow flex flex-col min-h-[400px] relative overflow-hidden"> | |
| <!-- Phone Header --> | |
| <div class="bg-slate-800/80 p-3 rounded-t-xl flex items-center justify-between border-b border-slate-700/50"> | |
| <div class="flex items-center gap-2"> | |
| <div class="w-8 h-8 rounded-full bg-gradient-to-tr from-yellow-400 to-pink-600 p-[2px]"> | |
| <img src="https://api.dicebear.com/7.x/avataaars/svg?seed=MyProfile" alt="Me" class="rounded-full bg-slate-900"> | |
| </div> | |
| <div> | |
| <div class="text-xs font-bold text-white">My Brand</div> | |
| <div class="text-[10px] text-emerald-400 flex items-center gap-1"> | |
| <span class="w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse"></span> Active | |
| </div> | |
| </div> | |
| </div> | |
| <div class="text-slate-400 text-xs flex gap-3"> | |
| <button class="hover:text-white"><i class="fa-solid fa-phone"></i></button> | |
| <button class="hover:text-white"><i class="fa-solid fa-video"></i></button> | |
| <button class="hover:text-white"><i class="fa-solid fa-circle-info"></i></button> | |
| </div> | |
| </div> | |
| <!-- Chat Area --> | |
| <div id="chat-container" class="flex-grow bg-slate-900/30 p-4 overflow-y-auto space-y-4 scroll-smooth"> | |
| <!-- Initial System Message --> | |
| <div class="flex justify-center"> | |
| <span class="text-[10px] text-slate-500 bg-slate-800/50 px-2 py-1 rounded-full">Bot is monitoring for new followers...</span> | |
| </div> | |
| <!-- Dynamic messages will be injected here --> | |
| </div> | |
| <!-- Input Area (Visual Only) --> | |
| <div class="p-3 bg-slate-800/80 border-t border-slate-700/50 rounded-b-xl"> | |
| <div class="flex items-center gap-2 bg-slate-900/50 rounded-full px-4 py-2 border border-slate-700"> | |
| <i class="fa-regular fa-face-smile text-slate-500"></i> | |
| <input type="text" disabled placeholder="Automated responses active..." class="bg-transparent border-none outline-none text-sm text-slate-400 w-full"> | |
| <i class="fa-solid fa-paper-plane text-purple-500 opacity-50"></i> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Activity Log --> | |
| <div class="glass rounded-2xl p-0 shadow-xl shadow-black/20 h-64 flex flex-col"> | |
| <div class="p-4 border-b border-slate-700/50 flex justify-between items-center bg-slate-800/30"> | |
| <h3 class="text-sm font-semibold text-white"><i class="fa-solid fa-terminal text-purple-500 mr-2"></i>System Logs</h3> | |
| <button id="clear-logs" class="text-xs text-slate-400 hover:text-white transition-colors">Clear</button> | |
| </div> | |
| <div id="log-container" class="flex-grow overflow-y-auto p-4 font-mono text-xs space-y-2 bg-black/20"> | |
| <!-- Logs go here --> | |
| <div class="text-slate-500 italic">Waiting for connection...</div> | |
| </div> | |
| </div> | |
| </div> | |
| </main> | |
| <!-- Toast Notification Container --> | |
| <div id="toast-container" class="fixed bottom-4 right-4 z-50 flex flex-col gap-2 pointer-events-none"></div> | |
| <script> | |
| // --- Application State --- | |
| const state = { | |
| connected: false, | |
| automationActive: false, | |
| token: '', | |
| delay: 5, | |
| template: '', | |
| logs: [], | |
| followers: [], | |
| stats: { | |
| sent: 0, | |
| failed: 0 | |
| } | |
| }; | |
| // --- DOM Elements --- | |
| const els = { | |
| btnConnect: document.getElementById('btn-connect'), | |
| inputToken: document.getElementById('api-token'), | |
| toggleToken: document.getElementById('toggle-token'), | |
| settingsPanel: document.getElementById('settings-panel'), | |
| automationToggle: document.getElementById('automation-toggle'), | |
| delaySlider: document.getElementById('delay-slider'), | |
| delayDisplay: document.getElementById('delay-display'), | |
| messageTemplate: document.getElementById('message-template'), | |
| charCount: document.getElementById('char-count'), | |
| chatContainer: document.getElementById('chat-container'), | |
| logContainer: document.getElementById('log-container'), | |
| clearLogs: document.getElementById('clear-logs'), | |
| statusDot: document.getElementById('connection-status-dot'), | |
| statusText: document.getElementById('connection-status-text'), | |
| statSent: document.getElementById('stat-sent'), | |
| statRate: document.getElementById('stat-rate'), | |
| toastContainer: document.getElementById('toast-container') | |
| }; | |
| // --- Utility Functions --- | |
| function generateId() { | |
| return Math.random().toString(36).substr(2, 9); | |
| } | |
| function getTimestamp() { | |
| return new Date().toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' }); | |
| } | |
| function addLog(message, type = 'info') { | |
| const id = generateId(); | |
| const logEntry = document.createElement('div'); | |
| logEntry.className = `log-entry flex gap-2 ${type === 'error' ? 'text-red-400' : type === 'success' ? 'text-emerald-400' : 'text-slate-300'}`; | |
| let icon = 'fa-info-circle'; | |
| if (type === 'error') icon = 'fa-circle-xmark'; | |
| if (type === 'success') icon = 'fa-check-circle'; | |
| if (type === 'warning') icon = 'fa-triangle-exclamation'; | |
| logEntry.innerHTML = ` | |
| <span class="text-slate-600">[${getTimestamp()}]</span> | |
| <span class="opacity-70"><i class="fa-solid ${icon}"></i></span> | |
| <span>${message}</span> | |
| `; | |
| els.logContainer.appendChild(logEntry); | |
| els.logContainer.scrollTop = els.logContainer.scrollHeight; | |
| } | |
| function showToast(message, type = 'info') { | |
| const toast = document.createElement('div'); | |
| const colors = type === 'success' ? 'border-emerald-500 text-emerald-400' : | |
| type === 'error' ? 'border-red-500 text-red-400' : 'border-purple-500 text-purple-400'; | |
| toast.className = `pointer-events-auto bg-slate-800 border-l-4 ${colors} text-white px-4 py-3 rounded shadow-lg transform transition-all duration-300 translate-x-full flex items-center gap-3 min-w-[300px]`; | |
| toast.innerHTML = ` | |
| <span>${message}</span> | |
| `; | |
| els.toastContainer.appendChild(toast); | |
| // Animate in | |
| requestAnimationFrame(() => { | |
| toast.classList.remove('translate-x-full'); | |
| }); | |
| // Remove after 3s | |
| setTimeout(() => { | |
| toast.classList.add('translate-x-full', 'opacity-0'); | |
| setTimeout(() => toast.remove(), 300); | |
| }, 3000); | |
| } | |
| function updateStats() { | |
| els.statSent.innerText = state.stats.sent; | |
| const total = state.stats.sent + state.stats.failed; | |
| const rate = total === 0 ? 0 : Math.round((state.stats.sent / total) * 100); | |
| els.statRate.innerText = `${rate}%`; | |
| } | |
| // --- Core Logic --- | |
| // 1. Connection Simulation | |
| els.btnConnect.addEventListener('click', () => { | |
| if (state.connected) { | |
| // Disconnect logic | |
| state.connected = false; | |
| state.automationActive = false; | |
| els.automationToggle.checked = false; | |
| updateUIState(); | |
| addLog('Disconnected from Instagram API', 'warning'); | |
| showToast('Disconnected', 'warning'); | |
| return; | |
| } | |
| const token = els.inputToken.value; | |
| if (!token) { | |
| showToast('Please enter an API Token', 'error'); | |
| els.inputToken.focus(); | |
| return; | |
| } | |
| // Simulate API Call | |
| els.btnConnect.innerHTML = `<div class="loader border-white/30 border-t-white w-4 h-4"></div> Connecting...`; | |
| setTimeout(() => { | |
| state.connected = true; | |
| state.token = token; // In real app, don't store raw token in memory like this without security | |
| addLog('Successfully authenticated with Instagram Graph API', 'success'); | |
| addLog('Webhook listener initialized on port 3000', 'info'); | |
| showToast('Connected to Instagram', 'success'); | |
| updateUIState(); | |
| }, 1500); | |
| }); | |
| function updateUIState() { | |
| if (state.connected) { | |
| els.btnConnect.innerHTML = `<span>Disconnect</span>`; | |
| els.btnConnect.classList.remove('from-purple-600', 'to-indigo-600'); | |
| els.btnConnect.classList.add('from-red-600', 'to-pink-600', 'hover:from-red-500', 'hover:to-pink-500'); | |
| els.settingsPanel.classList.remove('opacity-50', 'pointer-events-none'); | |
| els.statusDot.classList.remove('bg-red-500'); | |
| els.statusDot.classList.add('bg-emerald-500', 'animate-pulse'); | |
| els.statusText.innerText = "Connected"; | |
| els.statusText.classList.add('text-emerald-400'); | |
| } else { | |
| els.btnConnect.innerHTML = `<span>Connect Account</span>`; | |
| els.btnConnect.classList.add('from-purple-600', 'to-indigo-600'); | |
| els.btnConnect.classList.remove('from-red-600', 'to-pink-600', 'hover:from-red-500', 'hover:to-pink-500'); | |
| els.settingsPanel.classList.add('opacity-50', 'pointer-events-none'); | |
| els.statusDot.classList.add('bg-red-500'); | |
| els.statusDot.classList.remove('bg-emerald-500', 'animate-pulse'); | |
| els.statusText.innerText = "Disconnected"; | |
| els.statusText.classList.remove('text-emerald-400'); | |
| } | |
| } | |
| // 2. Settings Logic | |
| els.toggleToken.addEventListener('click', () => { | |
| const type = els.inputToken.getAttribute('type') === 'password' ? 'text' : 'password'; | |
| els.inputToken.setAttribute('type', type); | |
| els.toggleToken.innerHTML = type === 'password' ? '<i class="fa-solid fa-eye"></i>' : '<i class="fa-solid fa-eye-slash"></i>'; | |
| }); | |
| els.delaySlider.addEventListener('input', (e) => { | |
| state.delay = e.target.value; | |
| els.delayDisplay.innerText = `${state.delay}s`; | |
| }); | |
| els.messageTemplate.addEventListener('input', (e) => { | |
| state.template = e.target.value; | |
| els.charCount.innerText = `${state.template.length}/1000`; | |
| }); | |
| els.automationToggle.addEventListener('change', (e) => { | |
| state.automationActive = e.target.checked; | |
| if (state.automationActive) { | |
| addLog('Automation sequence STARTED', 'success'); | |
| showToast('Bot is now active', 'success'); | |
| startSimulation(); | |
| } else { | |
| addLog('Automation sequence PAUSED', 'warning'); | |
| showToast('Bot paused', 'warning'); | |
| } | |
| }); | |
| els.clearLogs.addEventListener('click', () => { | |
| els.logContainer.innerHTML = ''; | |
| addLog('Logs cleared'); | |
| }); | |
| // 3. Simulation Logic (The "Fake" Engine) | |
| const names = ["sarah_designs", "crypto_king_99", "travel_jenny", "dev_mike", "artistic_soul", "fitness_guru", "foodie_life"]; | |
| const avatars = [ | |
| "https://api.dicebear.com/7.x/avataaars/svg?seed=Felix", | |
| "https://api.dicebear.com/7.x/avataaars/svg?seed=Aneka", | |
| "https://api.dicebear.com/7.x/avataaars/svg?seed=Zack", | |
| "https://api.dicebear.com/7.x/avataaars/svg?seed=Molly", | |
| "https://api.dicebear.com/7.x/avataaars/svg?seed=Bear", | |
| "https://api.dicebear.com/7.x/avataaars/svg?seed=Simba" | |
| ]; | |
| function getRandomItem(arr) { | |
| return arr[Math.floor(Math.random() * arr.length)]; | |
| } | |
| function startSimulation() { | |
| // Initial check | |
| if (!state.automationActive) return; | |
| // Random loop for incoming followers | |
| const loop = () => { | |
| if (!state.automationActive) return; | |
| // Random time between 3s and 10s for a new follower | |
| const nextFollowerTime = Math.random() * 7000 + 3000; | |
| setTimeout(() => { | |
| if (state.automationActive) simulateNewFollower(); | |
| loop(); | |
| }, nextFollowerTime); | |
| }; | |
| loop(); | |
| } | |
| function simulateNewFollower() { | |
| const user = { | |
| name: getRandomItem(names), | |
| avatar: getRandomItem(avatars), | |
| id: generateId() | |
| }; | |
| addLog(`Webhook received: ${user.name} followed you`, 'info'); | |
| // Visual notification in chat | |
| const notif = document.createElement('div'); | |
| notif.className = "flex justify-center animate-pulse"; | |
| notif.innerHTML = `<span class="text-[10px] text-purple-300 bg-purple-900/30 px-2 py-1 rounded-full border border-purple-500/30">${user.name} started following you</span>`; | |
| els.chatContainer.appendChild(notif); | |
| els.chatContainer.scrollTop = els.chatContainer.scrollHeight; | |
| // Wait for delay setting, then send DM | |
| setTimeout(() => { | |
| if (state.automationActive) sendAutoDM(user); | |
| }, state.delay * 1000); | |
| } | |
| function sendAutoDM(user) { | |
| // Typing indicator | |
| const typingId = `typing-${user.id}`; | |
| const typingIndicator = document.createElement('div'); | |
| typingIndicator.id = typingId; | |
| typingIndicator.className = "flex justify-end mb-2"; | |
| typingIndicator.innerHTML = ` | |
| <div class="bg-slate-700 rounded-2xl rounded-tr-sm py-2 px-4 flex gap-1 items-center h-10"> | |
| <div class="w-1 h-1 bg-slate-400 rounded-full animate-bounce"></div> | |
| <div class="w-1 h-1 bg-slate-400 rounded-full animate-bounce" style="animation-delay: 0.1s"></div> | |
| <div class="w-1 h-1 bg-slate-400 rounded-full animate-bounce" style="animation-delay: 0.2s"></div> | |
| </div> | |
| `; | |
| els.chatContainer.appendChild(typingIndicator); | |
| els.chatContainer.scrollTop = els.chatContainer.scrollHeight; | |
| // Simulate network delay for sending | |
| setTimeout(() => { | |
| // Remove typing | |
| document.getElementById(typingId).remove(); | |
| // Process Template | |
| let msg = state.template || "Hi {{name}}! Thanks for the follow!"; | |
| msg = msg.replace(/{{name}}/g, user.name); | |
| // Add Message Bubble | |
| const msgDiv = document.createElement('div'); | |
| msgDiv.className = "flex justify-end mb-4 log-entry"; | |
| msgDiv.innerHTML = ` | |
| <div class="flex flex-col items-end max-w-[80%]"> | |
| <div class="bg-gradient-to-br from-purple-600 to-indigo-600 text-white py-2 px-4 rounded-2xl rounded-tr-sm shadow-lg shadow-purple-900/20 text-sm leading-relaxed"> | |
| ${msg.replace(/\n/g, '<br>')} | |
| </div> | |
| <span class="text-[10px] text-slate-500 mt-1 mr-1">Delivered • ${getTimestamp()}</span> | |
| </div> | |
| `; | |
| els.chatContainer.appendChild(msgDiv); | |
| els.chatContainer.scrollTop = els.chatContainer.scrollHeight; | |
| // Update Stats | |
| state.stats.sent++; | |
| updateStats(); | |
| addLog(`DM sent successfully to ${user.name}`, 'success'); | |
| }, 1500); // Typing time | |
| } | |
| // Initialize | |
| addLog('System initialized. Ready for connection.'); | |
| // Set initial template | |
| els.messageTemplate.value = "Hey {{name}}! 👋\n\nThanks for hitting that follow button! 🎉\n\nWelcome to the community. Drop a comment on my latest post if you have a minute!\n\nCheers!"; | |
| els.charCount.innerText = `${els.messageTemplate.value.length}/1000`; | |
| state.template = els.messageTemplate.value; | |
| </script> | |
| </body> | |
| </html> |