Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Swiss Army Web Tools 🛠️</title> | |
| <link rel="icon" type="image/x-icon" href="favicon.ico"> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://unpkg.com/feather-icons"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script> | |
| <script src="https://cdn.jsdelivr.net/npm/[email protected]/qrcode.min.js"></script> | |
| <script src="https://cdnjs.cloudflare.com/ajax/libs/dom-to-image/2.6.0/dom-to-image.min.js"></script> | |
| <style> | |
| .tool-card { | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .tool-card:hover { | |
| transform: translateY(-5px); | |
| box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04); | |
| } | |
| .tab-btn.active { | |
| border-bottom-color: #22d3ee; | |
| color: white; | |
| } | |
| .tab-content { | |
| display: none; | |
| } | |
| .tab-content.active { | |
| display: block; | |
| animation: fadeIn 0.5s ease; | |
| } | |
| @keyframes fadeIn { | |
| from { opacity: 0; transform: translateY(10px); } | |
| to { opacity: 1; transform: translateY(0); } | |
| } | |
| #qrCodeCanvas { | |
| transition: all 0.3s ease; | |
| } | |
| #bgRemoverPreview { | |
| max-width: 100%; | |
| max-height: 320px; | |
| display: none; | |
| border-radius: 1rem; | |
| box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1); | |
| border: 1px solid rgba(255,255,255,0.1); | |
| } | |
| ::selection { | |
| background: rgba(34, 211, 238, 0.2); | |
| } | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(31, 41, 55, 0.5); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: rgba(74, 222, 128, 0.5); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: rgba(74, 222, 128, 0.7); | |
| } | |
| </style> | |
| </head> | |
| <body class="min-h-screen bg-gradient-to-br from-gray-900 to-gray-800"> | |
| <div class="container mx-auto px-4 py-8"> | |
| <header class="text-center mb-12"> | |
| <h1 class="text-5xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-cyan-400 to-blue-500 mb-3">Swiss Army Web Tools</h1> | |
| <p class="text-xl text-gray-300">All-in-one utility toolkit for your daily web needs</p> | |
| </header> | |
| <div class="bg-gray-800 rounded-2xl shadow-xl overflow-hidden max-w-5xl mx-auto border border-gray-700 backdrop-blur-sm bg-opacity-50"> | |
| <div class="flex overflow-x-auto"> | |
| <button class="tab-btn px-6 py-4 font-medium text-gray-300 hover:text-white border-b-2 border-transparent hover:border-cyan-400 transition-all" data-tab="qr"> | |
| <div class="flex items-center gap-2"> | |
| <i data-feather="qr-code" class="w-5 h-5"></i> | |
| QR Generator | |
| </div> | |
| </button> | |
| <button class="tab-btn px-6 py-4 font-medium text-gray-300 hover:text-white border-b-2 border-transparent hover:border-cyan-400 transition-all" data-tab="counter"> | |
| <div class="flex items-center gap-2"> | |
| <i data-feather="type" class="w-5 h-5"></i> | |
| Text Counter | |
| </div> | |
| </button> | |
| <button class="tab-btn px-6 py-4 font-medium text-gray-300 hover:text-white border-b-2 border-transparent hover:border-cyan-400 transition-all" data-tab="shortener"> | |
| <div class="flex items-center gap-2"> | |
| <i data-feather="link-2" class="w-5 h-5"></i> | |
| Link Shortener | |
| </div> | |
| </button> | |
| <button class="tab-btn px-6 py-4 font-medium text-gray-300 hover:text-white border-b-2 border-transparent hover:border-cyan-400 transition-all" data-tab="bgremover"> | |
| <div class="flex items-center gap-2"> | |
| <i data-feather="image" class="w-5 h-5"></i> | |
| BG Remover | |
| </div> | |
| </button> | |
| <button class="tab-btn px-6 py-4 font-medium text-gray-300 hover:text-white border-b-2 border-transparent hover:border-cyan-400 transition-all" data-tab="password"> | |
| <div class="flex items-center gap-2"> | |
| <i data-feather="key" class="w-5 h-5"></i> | |
| Password Gen | |
| </div> | |
| </button> | |
| <button class="tab-btn px-6 py-4 font-medium text-gray-300 hover:text-white border-b-2 border-transparent hover:border-cyan-400 transition-all" data-tab="llm"> | |
| <div class="flex items-center gap-2"> | |
| <i data-feather="message-square" class="w-5 h-5"></i> | |
| AI Chat | |
| </div> | |
| </button> | |
| </div> | |
| <div class="p-8"> | |
| <!-- QR Code Generator --> | |
| <div id="qr" class="tab-content active"> | |
| <h2 class="text-3xl font-semibold text-white mb-6 flex items-center gap-3"> | |
| <i data-feather="qr-code" class="w-8 h-8 text-cyan-400"></i> | |
| QR Code Generator | |
| </h2> | |
| <div class="flex flex-col md:flex-row gap-8"> | |
| <div class="flex-1"> | |
| <div class="mb-4"> | |
| <label for="qrText" class="block text-gray-300 mb-3">Enter text or URL</label> | |
| <textarea id="qrText" rows="3" class="w-full px-4 py-3 bg-gray-700 border border-gray-600 rounded-xl focus:outline-none focus:ring-2 focus:ring-cyan-500 focus:border-transparent text-white placeholder-gray-400" placeholder="Enter text or URL to generate QR code"></textarea> | |
| </div> | |
| <div class="flex gap-4"> | |
| <button id="generateQR" class="bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-600 hover:to-blue-700 text-white px-6 py-3 rounded-xl flex items-center gap-2 shadow-lg hover:shadow-cyan-500/20 transition-all"> | |
| <i data-feather="rotate-cw"></i> Generate | |
| </button> | |
| <button id="downloadQR" class="bg-gradient-to-r from-emerald-500 to-green-600 hover:from-emerald-600 hover:to-green-700 text-white px-6 py-3 rounded-xl flex items-center gap-2 shadow-lg hover:shadow-emerald-500/20 transition-all" disabled> | |
| <i data-feather="download"></i> Download | |
| </button> | |
| </div> | |
| </div> | |
| <div class="flex-1 flex flex-col items-center justify-center"> | |
| <div id="qrCodeContainer" class="bg-gray-700 p-6 rounded-2xl border border-gray-600 mb-4 flex items-center justify-center"> | |
| <div id="qrCodeCanvas" class="w-56 h-56 flex flex-col items-center justify-center text-gray-400"> | |
| <i data-feather="image" class="w-16 h-16 mb-2 opacity-50"></i> | |
| <p class="text-center text-gray-400">QR Code will appear here</p> | |
| </div> | |
| </div> | |
| <p class="text-sm text-gray-400 flex items-center justify-center gap-1"> | |
| <i data-feather="smartphone" class="w-4 h-4"></i> | |
| Scan the QR code with your phone | |
| </p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- LLM Chat --> | |
| <div id="llm" class="tab-content"> | |
| <div class="text-center py-12"> | |
| <h2 class="text-3xl font-semibold text-white mb-4">AI Chat Assistant</h2> | |
| <p class="text-gray-300 mb-6">This tool opens in a new page for better experience.</p> | |
| <a href="llm.html" class="bg-gradient-to-r from-cyan-500 to-blue-600 hover:from-cyan-600 hover:to-blue-700 text-white px-6 py-3 rounded-xl inline-flex items-center gap-2 shadow-lg hover:shadow-cyan-500/20 transition-all"> | |
| <i data-feather="message-square"></i> Open Chat | |
| </a> | |
| </div> | |
| </div> | |
| <!-- Password Generator --> | |
| <div id="password" class="tab-content"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Password Generator</h2> | |
| <div class="mb-4"> | |
| <div class="flex items-center gap-4 mb-4"> | |
| <div class="flex-1"> | |
| <label for="passwordLength" class="block text-gray-700 mb-2">Length (8-32)</label> | |
| <input type="range" id="passwordLength" min="8" max="32" value="12" class="w-full"> | |
| <div class="flex justify-between"> | |
| <span>8</span> | |
| <span id="lengthValue">12</span> | |
| <span>32</span> | |
| </div> | |
| </div> | |
| <div class="flex-1 flex justify-center items-center"> | |
| <button id="generatePassword" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i data-feather="refresh-cw"></i> Generate | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-4"> | |
| <div class="bg-white p-4 rounded-lg shadow"> | |
| <label class="flex items-center gap-2"> | |
| <input type="checkbox" id="uppercase" checked class="rounded text-indigo-600"> | |
| <span>Uppercase (A-Z)</span> | |
| </label> | |
| </div> | |
| <div class="bg-white p-4 rounded-lg shadow"> | |
| <label class="flex items-center gap-2"> | |
| <input type="checkbox" id="lowercase" checked class="rounded text-indigo-600"> | |
| <span>Lowercase (a-z)</span> | |
| </label> | |
| </div> | |
| <div class="bg-white p-4 rounded-lg shadow"> | |
| <label class="flex items-center gap-2"> | |
| <input type="checkbox" id="numbers" checked class="rounded text-indigo-600"> | |
| <span>Numbers (0-9)</span> | |
| </label> | |
| </div> | |
| <div class="bg-white p-4 rounded-lg shadow"> | |
| <label class="flex items-center gap-2"> | |
| <input type="checkbox" id="symbols" checked class="rounded text-indigo-600"> | |
| <span>Symbols (!@#$%)</span> | |
| </label> | |
| </div> | |
| </div> | |
| <div class="mb-4"> | |
| <label for="generatedPassword" class="block text-gray-700 mb-2">Generated Password</label> | |
| <div class="flex gap-2"> | |
| <input type="text" id="generatedPassword" class="flex-1 px-3 py-2 border border-gray-300 rounded-lg bg-white" readonly> | |
| <button id="copyPassword" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 py-2 rounded-lg flex items-center gap-1"> | |
| <i data-feather="copy"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="bg-gray-100 p-4 rounded-lg"> | |
| <h3 class="text-lg font-medium text-gray-800 mb-2">Password Strength</h3> | |
| <div class="flex items-center gap-2 mb-2"> | |
| <div id="strengthMeter" class="h-2 flex-1 bg-gray-300 rounded-full overflow-hidden"> | |
| <div class="h-full bg-red-500 w-0" id="strengthBar"></div> | |
| </div> | |
| <span id="strengthText" class="text-sm font-medium">Weak</span> | |
| </div> | |
| <p class="text-sm text-gray-600" id="strengthFeedback">Use a longer password with more character types</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Text Counter --> | |
| <div id="counter" class="tab-content"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Text & Character Counter</h2> | |
| <div class="mb-4"> | |
| <label for="textInput" class="block text-gray-700 mb-2">Enter your text</label> | |
| <textarea id="textInput" rows="6" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="Type or paste your text here..."></textarea> | |
| </div> | |
| <div class="grid grid-cols-1 md:grid-cols-3 gap-4 bg-gray-100 p-4 rounded-lg"> | |
| <div class="bg-white p-4 rounded-lg shadow text-center"> | |
| <p class="text-sm text-gray-500">Characters</p> | |
| <p id="charCount" class="text-3xl font-bold text-indigo-600">0</p> | |
| </div> | |
| <div class="bg-white p-4 rounded-lg shadow text-center"> | |
| <p class="text-sm text-gray-500">Words</p> | |
| <p id="wordCount" class="text-3xl font-bold text-indigo-600">0</p> | |
| </div> | |
| <div class="bg-white p-4 rounded-lg shadow text-center"> | |
| <p class="text-sm text-gray-500">Sentences</p> | |
| <p id="sentenceCount" class="text-3xl font-bold text-indigo-600">0</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Link Shortener --> | |
| <div id="shortener" class="tab-content"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Link Shortener</h2> | |
| <div class="mb-4"> | |
| <label for="longUrl" class="block text-gray-700 mb-2">Enter long URL</label> | |
| <input type="url" id="longUrl" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="https://example.com/very-long-url..."> | |
| <div class="mt-2"> | |
| <label for="customText" class="block text-gray-700 mb-2">Custom text (optional)</label> | |
| <input type="text" id="customText" class="w-full px-3 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-indigo-500" placeholder="my-custom-text" maxlength="30"> | |
| <p class="text-xs text-gray-500 mt-1">Max 30 characters, letters and numbers only</p> | |
| </div> | |
| </div> | |
| <button id="shortenBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 mb-4 hovered-element"> | |
| <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-scissors"><circle cx="6" cy="6" r="3"></circle><circle cx="6" cy="18" r="3"></circle><line x1="20" y1="4" x2="8.12" y2="15.88"></line><line x1="14.47" y1="14.48" x2="20" y2="20"></line><line x1="8.12" y1="8.12" x2="12" y2="12"></line></svg> Shorten URL | |
| </button> | |
| <div id="shortUrlResult" class="hidden bg-gray-100 p-4 rounded-lg"> | |
| <label class="block text-gray-700 mb-2">Short URL</label> | |
| <div class="flex gap-2"> | |
| <input type="text" id="shortUrl" class="flex-1 px-3 py-2 border border-gray-300 rounded-lg bg-white" readonly> | |
| <button id="copyShortUrl" class="bg-gray-200 hover:bg-gray-300 text-gray-800 px-3 py-2 rounded-lg flex items-center gap-1"> | |
| <i data-feather="copy"></i> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Background Remover --> | |
| <div id="bgremover" class="tab-content"> | |
| <h2 class="text-2xl font-semibold text-gray-800 mb-4">Background Remover</h2> | |
| <div class="mb-4"> | |
| <label for="imageUpload" class="block text-gray-700 mb-2">Upload image</label> | |
| <div class="border-2 border-dashed border-gray-300 rounded-lg p-6 text-center cursor-pointer hover:border-indigo-400 transition" id="dropZone"> | |
| <input type="file" id="imageUpload" accept="image/*" class="hidden"> | |
| <div class="flex flex-col items-center justify-center"> | |
| <i data-feather="upload" class="w-10 h-10 text-gray-400 mb-2"></i> | |
| <p class="text-gray-500">Drag & drop an image or click to browse</p> | |
| <p class="text-sm text-gray-400 mt-1">Supports JPG, PNG (Max 5MB)</p> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="flex justify-center mb-4"> | |
| <img id="bgRemoverPreview" alt="Preview" class="rounded-lg"> | |
| </div> | |
| <button id="removeBgBtn" class="bg-indigo-600 hover:bg-indigo-700 text-white px-4 py-2 rounded-lg flex items-center gap-2 mb-4 hidden"> | |
| <i data-feather="edit-3"></i> Remove Background | |
| </button> | |
| <div id="bgRemoverResult" class="hidden"> | |
| <div class="flex flex-col md:flex-row gap-4"> | |
| <div class="flex-1"> | |
| <p class="text-gray-700 mb-2">Original</p> | |
| <img id="originalImage" src="" alt="Original" class="w-full rounded-lg border border-gray-200"> | |
| </div> | |
| <div class="flex-1"> | |
| <p class="text-gray-700 mb-2">Background Removed</p> | |
| <img id="processedImage" src="" alt="Processed" class="w-full rounded-lg border border-gray-200"> | |
| </div> | |
| </div> | |
| <div class="mt-4 flex justify-center"> | |
| <button id="downloadProcessed" class="bg-green-600 hover:bg-green-700 text-white px-4 py-2 rounded-lg flex items-center gap-2"> | |
| <i data-feather="download"></i> Download Result | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <footer class="mt-16 text-center text-gray-400 text-sm"> | |
| <p class="flex items-center justify-center gap-1"> | |
| Made with <i data-feather="heart" class="inline text-pink-500 w-4 h-4"></i> for the web | |
| </p> | |
| <p class="mt-2 text-xs text-gray-500"> | |
| v1.0.0 · Swiss Army Web Tools | |
| </p> | |
| </footer> | |
| </div> | |
| <script> | |
| // Tab switching | |
| document.querySelectorAll('.tab-btn').forEach(btn => { | |
| btn.addEventListener('click', function() { | |
| // Remove active classes from all buttons | |
| document.querySelectorAll('.tab-btn').forEach(t => { | |
| t.classList.remove('border-indigo-600', 'text-indigo-600'); | |
| t.classList.add('text-gray-500'); | |
| }); | |
| // Add active classes to clicked button | |
| this.classList.add('border-indigo-600', 'text-indigo-600'); | |
| this.classList.remove('text-gray-500'); | |
| // Hide all tab contents | |
| document.querySelectorAll('.tab-content').forEach(tab => { | |
| tab.classList.remove('active'); | |
| }); | |
| // Show the selected tab content | |
| const tabId = this.getAttribute('data-tab'); | |
| document.getElementById(tabId).classList.add('active'); | |
| }); | |
| }); | |
| // QR Code Generator | |
| document.getElementById('generateQR').addEventListener('click', () => { | |
| const text = document.getElementById('qrText').value.trim(); | |
| if (!text) { | |
| alert('Please enter some text or URL'); | |
| return; | |
| } | |
| try { | |
| const qr = qrcode(0, 'L'); | |
| qr.addData(text); | |
| qr.make(); | |
| const qrContainer = document.getElementById('qrCodeCanvas'); | |
| qrContainer.innerHTML = qr.createImgTag(4, 0); | |
| qrContainer.querySelector('img').style.maxWidth = '100%'; | |
| document.getElementById('downloadQR').disabled = false; | |
| } catch (e) { | |
| console.error('QR generation error:', e); | |
| alert('Failed to generate QR code. Please try different text.'); | |
| } | |
| }); | |
| document.getElementById('downloadQR').addEventListener('click', () => { | |
| const qrImg = document.querySelector('#qrCodeCanvas img'); | |
| if (!qrImg) { | |
| alert('Please generate a QR code first'); | |
| return; | |
| } | |
| const link = document.createElement('a'); | |
| link.download = 'qrcode.png'; | |
| link.href = qrImg.src; | |
| link.click(); | |
| }); | |
| // Text Counter | |
| document.getElementById('textInput').addEventListener('input', () => { | |
| const text = document.getElementById('textInput').value; | |
| // Character count | |
| document.getElementById('charCount').textContent = text.length; | |
| // Word count | |
| const words = text.trim() ? text.trim().split(/\s+/).length : 0; | |
| document.getElementById('wordCount').textContent = words; | |
| // Sentence count (very basic) | |
| const sentences = text.trim() ? text.split(/[.!?]+/).filter(s => s.trim().length > 0).length : 0; | |
| document.getElementById('sentenceCount').textContent = sentences; | |
| }); | |
| // Link Shortener using is.gd API as fallback | |
| document.getElementById('shortenBtn').addEventListener('click', async () => { | |
| const longUrl = document.getElementById('longUrl').value.trim(); | |
| if (!longUrl) { | |
| alert('Please enter a URL'); | |
| return; | |
| } | |
| try { | |
| // First try is.gd | |
| const customText = document.getElementById('customText').value.trim(); | |
| let apiUrl = `https://is.gd/create.php?format=json&url=${encodeURIComponent(longUrl)}`; | |
| if (customText) { | |
| if (!/^[a-zA-Z0-9-]+$/.test(customText)) { | |
| alert('Custom text can only contain letters, numbers and hyphens'); | |
| return; | |
| } | |
| apiUrl += `&shorturl=${encodeURIComponent(customText)}`; | |
| } | |
| const isgdResponse = await fetch(apiUrl); | |
| if (isgdResponse.ok) { | |
| const isgdData = await isgdResponse.json(); | |
| if (isgdData.shorturl) { | |
| document.getElementById('shortUrl').value = isgdData.shorturl; | |
| document.getElementById('shortUrlResult').classList.remove('hidden'); | |
| return; | |
| } | |
| } | |
| // Fallback to v.gd | |
| const vgdResponse = await fetch(apiUrl.replace('is.gd', 'v.gd')); | |
| const vgdData = await vgdResponse.json(); | |
| if (vgdResponse.ok && vgdData.shorturl) { | |
| document.getElementById('shortUrl').value = vgdData.shorturl; | |
| } else { | |
| throw new Error('Failed to shorten URL with both services'); | |
| } | |
| document.getElementById('shortUrlResult').classList.remove('hidden'); | |
| } catch (error) { | |
| console.error('Error shortening URL:', error); | |
| alert('Failed to shorten URL. Please try again later or use a different URL.'); | |
| } | |
| }); | |
| document.getElementById('copyShortUrl').addEventListener('click', () => { | |
| const shortUrl = document.getElementById('shortUrl'); | |
| shortUrl.select(); | |
| document.execCommand('copy'); | |
| const copyBtn = document.getElementById('copyShortUrl'); | |
| copyBtn.innerHTML = '<i data-feather="check"></i>'; | |
| feather.replace(); | |
| setTimeout(() => { | |
| copyBtn.innerHTML = '<i data-feather="copy"></i>'; | |
| feather.replace(); | |
| }, 2000); | |
| }); | |
| // Background Remover using Remove.bg API (demo mode with mock response) | |
| const dropZone = document.getElementById('dropZone'); | |
| const fileInput = document.getElementById('imageUpload'); | |
| let currentFile = null; | |
| dropZone.addEventListener('click', () => fileInput.click()); | |
| dropZone.addEventListener('dragover', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.add('border-indigo-400'); | |
| }); | |
| dropZone.addEventListener('dragleave', () => { | |
| dropZone.classList.remove('border-indigo-400'); | |
| }); | |
| dropZone.addEventListener('drop', (e) => { | |
| e.preventDefault(); | |
| dropZone.classList.remove('border-indigo-400'); | |
| if (e.dataTransfer.files.length) { | |
| fileInput.files = e.dataTransfer.files; | |
| handleImageUpload(e.dataTransfer.files[0]); | |
| } | |
| }); | |
| fileInput.addEventListener('change', (e) => { | |
| if (e.target.files.length) { | |
| handleImageUpload(e.target.files[0]); | |
| } | |
| }); | |
| function handleImageUpload(file) { | |
| currentFile = file; | |
| if (!file.type.match('image.*')) { | |
| alert('Please upload an image file'); | |
| return; | |
| } | |
| if (file.size > 5 * 1024 * 1024) { | |
| alert('File size should be less than 5MB'); | |
| return; | |
| } | |
| const reader = new FileReader(); | |
| reader.onload = (e) => { | |
| const preview = document.getElementById('bgRemoverPreview'); | |
| preview.src = e.target.result; | |
| preview.style.display = 'block'; | |
| document.getElementById('removeBgBtn').classList.remove('hidden'); | |
| }; | |
| reader.readAsDataURL(file); | |
| } | |
| document.getElementById('removeBgBtn').addEventListener('click', async () => { | |
| const preview = document.getElementById('bgRemoverPreview'); | |
| const originalImg = document.getElementById('originalImage'); | |
| const processedImg = document.getElementById('processedImage'); | |
| try { | |
| // Show loading state | |
| const btn = document.getElementById('removeBgBtn'); | |
| const originalText = btn.innerHTML; | |
| btn.innerHTML = '<i data-feather="loader"></i> Processing...'; | |
| feather.replace(); | |
| // In a real implementation, you would call the Remove.bg API here | |
| // For demo purposes, we'll simulate a successful response after 2 seconds | |
| setTimeout(() => { | |
| originalImg.src = preview.src; | |
| processedImg.src = preview.src.replace(/^data:image\/[^;]+/, 'data:image/png') + '&processed=true'; | |
| document.getElementById('bgRemoverResult').classList.remove('hidden'); | |
| btn.innerHTML = originalText; | |
| feather.replace(); | |
| }, 2000); | |
| } catch (error) { | |
| console.error('Error removing background:', error); | |
| alert('Failed to process image. Please try again later.'); | |
| } | |
| }); | |
| document.getElementById('downloadProcessed').addEventListener('click', () => { | |
| const processedImg = document.getElementById('processedImage'); | |
| const link = document.createElement('a'); | |
| link.download = 'background_removed.png'; | |
| link.href = processedImg.src; | |
| link.click(); | |
| }); | |
| // Password Generator - improved with better character selection | |
| document.getElementById('passwordLength').addEventListener('input', function() { | |
| const length = this.value; | |
| document.getElementById('lengthValue').textContent = length; | |
| // Auto-adjust complexity for longer passwords | |
| if (length >= 16) { | |
| document.getElementById('symbols').checked = true; | |
| document.getElementById('uppercase').checked = true; | |
| document.getElementById('numbers').checked = true; | |
| } | |
| }); | |
| document.getElementById('generatePassword').addEventListener('click', function() { | |
| const length = document.getElementById('passwordLength').value; | |
| const uppercase = document.getElementById('uppercase').checked; | |
| const lowercase = document.getElementById('lowercase').checked; | |
| const numbers = document.getElementById('numbers').checked; | |
| const symbols = document.getElementById('symbols').checked; | |
| if (!uppercase && !lowercase && !numbers && !symbols) { | |
| alert('Please select at least one character type'); | |
| return; | |
| } | |
| // Build character sets separately to ensure at least one of each selected type | |
| const charSets = []; | |
| if (uppercase) charSets.push('ABCDEFGHIJKLMNOPQRSTUVWXYZ'); | |
| if (lowercase) charSets.push('abcdefghijklmnopqrstuvwxyz'); | |
| if (numbers) charSets.push('0123456789'); | |
| if (symbols) charSets.push('!@#$%^&*()_+-=[]{}|;:,.<>?'); | |
| // Ensure at least one character from each selected set | |
| let password = ''; | |
| charSets.forEach(set => { | |
| password += set.charAt(Math.floor(Math.random() * set.length)); | |
| }); | |
| // Fill the rest of the password | |
| const allChars = charSets.join(''); | |
| for (let i = password.length; i < length; i++) { | |
| password += allChars.charAt(Math.floor(Math.random() * allChars.length)); | |
| } | |
| // Shuffle the password to mix the guaranteed characters | |
| password = password.split('').sort(() => 0.5 - Math.random()).join(''); | |
| document.getElementById('generatedPassword').value = password; | |
| updatePasswordStrength(password); | |
| }); | |
| document.getElementById('copyPassword').addEventListener('click', function() { | |
| const password = document.getElementById('generatedPassword'); | |
| password.select(); | |
| document.execCommand('copy'); | |
| const copyBtn = document.getElementById('copyPassword'); | |
| copyBtn.innerHTML = '<i data-feather="check"></i>'; | |
| feather.replace(); | |
| setTimeout(() => { | |
| copyBtn.innerHTML = '<i data-feather="copy"></i>'; | |
| feather.replace(); | |
| }, 2000); | |
| }); | |
| function updatePasswordStrength(password) { | |
| let strength = 0; | |
| const length = password.length; | |
| // Length score (max 50) | |
| strength += Math.min(length * 2, 50); | |
| // Character variety (max 50) | |
| const hasUpper = /[A-Z]/.test(password); | |
| const hasLower = /[a-z]/.test(password); | |
| const hasNumber = /[0-9]/.test(password); | |
| const hasSymbol = /[^A-Za-z0-9]/.test(password); | |
| const uniqueChars = new Set(password.split('')).size; | |
| strength += (uniqueChars / length) * 20; // Uniqueness bonus | |
| let typeCount = 0; | |
| if (hasUpper) typeCount++; | |
| if (hasLower) typeCount++; | |
| if (hasNumber) typeCount++; | |
| if (hasSymbol) typeCount++; | |
| strength += typeCount * 10; | |
| // Cap at 100 | |
| strength = Math.min(strength, 100); | |
| // Update UI | |
| const strengthBar = document.getElementById('strengthBar'); | |
| const strengthText = document.getElementById('strengthText'); | |
| const strengthFeedback = document.getElementById('strengthFeedback'); | |
| let color, text, feedback; | |
| if (strength < 40) { | |
| color = 'bg-red-500'; | |
| text = 'Weak'; | |
| feedback = 'Consider using a longer password (12+ chars) with more character types'; | |
| } else if (strength < 70) { | |
| color = 'bg-yellow-500'; | |
| text = 'Moderate'; | |
| feedback = 'Good password. Adding symbols or length would improve it'; | |
| } else if (strength < 85) { | |
| color = 'bg-blue-500'; | |
| text = 'Strong'; | |
| feedback = 'Great password!'; | |
| } else { | |
| color = 'bg-green-500'; | |
| text = 'Very Strong'; | |
| feedback = 'Excellent password! Very secure!'; | |
| } | |
| strengthBar.className = `h-full ${color}`; | |
| strengthBar.style.width = `${strength}%`; | |
| strengthText.textContent = text; | |
| strengthFeedback.textContent = feedback; | |
| } | |
| // QR Code - Auto-generate when tab is selected | |
| document.querySelector('.tab-btn[data-tab="qr"]').addEventListener('click', () => { | |
| const qrText = document.getElementById('qrText').value.trim(); | |
| if (qrText && !document.querySelector('#qrCodeCanvas img')) { | |
| document.getElementById('generateQR').click(); | |
| } | |
| }); | |
| // Password - Auto-generate first password on tab select | |
| document.querySelector('.tab-btn[data-tab="password"]').addEventListener('click', () => { | |
| if (!document.getElementById('generatedPassword').value) { | |
| document.getElementById('generatePassword').click(); | |
| } | |
| }); | |
| // Initialize feather icons | |
| feather.replace(); | |
| // Add service worker for offline functionality | |
| if ('serviceWorker' in navigator) { | |
| window.addEventListener('load', () => { | |
| navigator.serviceWorker.register('/sw.js').catch(err => { | |
| console.log('ServiceWorker registration failed: ', err); | |
| }); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |