Canlı önizleme: https://eczane724.net/araclar.html
<!DOCTYPE html> <html lang="tr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Pro Web Toolkit</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"> <script src="https://cdn.jsdelivr.net/npm/diff@5.1.0/dist/diff.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/svgo@3.3.2/dist/svgo.browser.js"></script> <style> :root { --primary-color: #0d9488; --primary-dark: #0a7c72; --secondary-color: #f59e0b; --danger-color: #ef4444; --success-color: #22c55e; --background-color: #f8fafc; --text-color: #0f172a; --card-background: #ffffff; --input-border-color: #e2e8f0; --header-background: rgba(255, 255, 255, 0.7); --footer-background: #0f172a; --footer-text-color: #cbd5e1; --font-family: 'Inter', sans-serif; --success-bg: rgba(34, 197, 94, 0.1); --danger-bg: rgba(239, 68, 68, 0.1); } body.dark-mode { --background-color: #0f172a; --text-color: #e2e8f0; --card-background: #1e293b; --input-border-color: #334155; --header-background: rgba(30, 41, 59, 0.7); --footer-background: #020617; --footer-text-color: #94a3b8; --success-bg: rgba(34, 197, 94, 0.15); --danger-bg: rgba(239, 68, 68, 0.15); } @keyframes gradient-animation { 0%{background-position:0% 50%} 50%{background-position:100% 50%} 100%{background-position:0% 50%} } @keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } * { margin: 0; padding: 0; box-sizing: border-box; } html { scroll-behavior: smooth; } body { background-color: var(--background-color); color: var(--text-color); font-family: var(--font-family); line-height: 1.6; transition: background-color 0.3s, color 0.3s; } .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } header { background-color: var(--header-background); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 1px 0 rgba(0,0,0,0.08); padding: 15px 0; position: sticky; top: 0; z-index: 1000; } .header-container { display: flex; justify-content: space-between; align-items: center; } .logo { height: 45px; } .nav-right { display: flex; align-items: center; gap: 25px; } #main-nav ul { display: flex; list-style: none; gap: 25px; } #main-nav a { color: var(--text-color); text-decoration: none; font-weight: 500; transition: color 0.2s; } #main-nav a:hover { color: var(--primary-color); } .theme-toggle { cursor: pointer; font-size: 20px; background: none; border: none; color: var(--text-color); } .hamburger-menu { display: none; cursor: pointer; font-size: 24px; z-index: 1001; } .hero { text-align: center; padding: 60px 20px; color: white; background: linear-gradient(135deg, #0d9488, #0a7c72, #f59e0b, #ef4444); background-size: 400% 400%; animation: gradient-animation 15s ease infinite; transition: padding 0.3s; } .hero h1 { font-size: 2.8rem; text-shadow: 1px 1px 5px rgba(0,0,0,0.3); transition: font-size 0.3s; } #app { padding: 30px 0; animation: fade-in 0.5s ease-out; min-height: 60vh; } .category-section.hidden { display: none; } .category-title { font-size: 1.8rem; font-weight: 700; color: var(--primary-color); margin-bottom: 20px; margin-top: 30px; border-bottom: 2px solid var(--primary-color); padding-bottom: 10px; } .tool-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; } .tool-link-card { background-color: var(--card-background); border-radius: 12px; padding: 20px; text-decoration: none; color: var(--text-color); border: 1px solid var(--input-border-color); transition: all 0.2s; display: flex; align-items: center; gap: 15px; } .tool-link-card.hidden { display: none; } .tool-link-card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0,0,0,0.07); } .tool-link-card .icon { font-size: 1.5rem; color: var(--primary-color); width: 40px; text-align: center; transition: font-size 0.2s; } .tool-link-card .title { font-weight: 600; font-size: 1.1rem; transition: font-size 0.2s; } .tool-view-container { background-color: var(--card-background); border: 1px solid var(--input-border-color); border-radius: 16px; padding: 30px; transition: padding 0.3s; } .tool-view-header { display: flex; align-items: center; gap: 15px; margin-bottom: 25px; } .tool-view-header i { font-size: 2rem; color: var(--primary-color); } .tool-view-header h2 { font-size: 2rem; transition: font-size 0.3s; } .tool-input, textarea.tool-input { width: 100%; padding: 12px; margin-bottom: 15px; border: 1px solid var(--input-border-color); background-color: var(--background-color); color: var(--text-color); border-radius: 6px; font-family: 'Menlo', monospace; resize: vertical;} .tool-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; background-color: var(--primary-color); color: white; border: none; padding: 12px 20px; border-radius: 6px; cursor: pointer; transition: all 0.2s; font-weight: 500; } .tool-btn:disabled { opacity: 0.6; cursor: not-allowed; } .tool-btn.secondary { background-color: transparent; color: var(--primary-color); border: 2px solid var(--primary-color); } .tool-btn.secondary:hover:not(:disabled) { background-color: var(--primary-color); color: white; } .result-wrapper { position: relative; margin-top: 20px;} .btn-copy { position: absolute; top: 10px; right: 10px; background-color: var(--card-background); border: 1px solid var(--input-border-color); color: var(--text-color); cursor: pointer; padding: 5px 10px; border-radius: 5px; font-size: 0.8rem; z-index: 2;} .btn-copy:hover { background-color: var(--primary-color); color:white; } .result { padding: 15px; border-radius: 8px; background-color: var(--background-color); word-break: break-word; min-height: 50px; } .result.placeholder { color: var(--footer-text-color); display: flex; align-items: center; justify-content: center; font-style: italic; } .diff-output del { background-color: var(--danger-bg); color: var(--danger-color); text-decoration: none; padding: 0 2px; border-radius: 3px;} .diff-output ins { background-color: var(--success-bg); color: var(--success-color); text-decoration: none; padding: 0 2px; border-radius: 3px;} .responsive-grid-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } .gradient-grid-layout { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; } .case-converter-buttons { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; } .tool-button-group { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; } .checkbox-group { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 20px; margin: 15px 0; } .checkbox-group label { display: flex; align-items: center; gap: 8px; cursor: pointer; } footer { background-color: var(--footer-background); color: var(--footer-text-color); text-align: center; padding: 40px 20px; margin-top: 50px; } .footer-nav { margin-bottom: 20px; display: flex; justify-content: center; flex-wrap: wrap; gap: 15px 30px; } .footer-nav a { color: var(--footer-text-color); text-decoration: none; } .footer-nav a:hover { color: var(--primary-light); } .footer-copyright a { color: var(--primary-light); text-decoration: none; } #toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 2000; display: flex; flex-direction: column; gap: 10px; } .toast { background-color: var(--footer-background); color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); transition: all 0.5s; animation: toast-in 0.5s; } .toast.success { background-color: var(--primary-dark); } @keyframes toast-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } #command-palette-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 2000; display: none; } #command-palette { position: absolute; top: 20%; left: 50%; transform: translateX(-50%); width: 90%; max-width: 600px; background-color: var(--card-background); border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); overflow: hidden; } #palette-input { width: 100%; padding: 20px; font-size: 1.2rem; border: none; background-color: transparent; color: var(--text-color); border-bottom: 1px solid var(--input-border-color); outline: none; } #palette-results { max-height: 300px; overflow-y: auto; } #palette-results ul { list-style: none; } #palette-results li a { display: flex; padding: 15px 20px; align-items: center; gap: 15px; text-decoration: none; color: var(--text-color); border-bottom: 1px solid var(--input-border-color); } #palette-results li:last-child a { border-bottom: none; } #palette-results li a.selected, #palette-results li a:hover { background-color: var(--primary-color); color: white; } #palette-results li a.selected .icon, #palette-results li a:hover .icon { color: white; } .palette-hint { font-size: 0.8rem; color: #999; padding: 10px 20px; text-align: right; background: var(--background-color); } *:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; box-shadow: 0 0 0 4px rgba(13, 148, 136, 0.2); border-radius: 6px; } #palette-input:focus-visible, .tool-input:focus-visible { box-shadow: none; } @media (max-width: 992px) { .hamburger-menu { display: block; } .nav-right { gap: 20px; } #main-nav { display: none; position: absolute; top: 76px; left: 0; width: 100%; background-color: var(--header-background); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 5px 10px rgba(0,0,0,0.1); } #main-nav.active { display: block; } #main-nav ul { flex-direction: column; width: 100%; gap: 0; } #main-nav li a { padding: 20px; display: block; text-align: center; border-bottom: 1px solid var(--input-border-color); } #main-nav li:last-child a { border-bottom: none; } .case-converter-buttons { grid-template-columns: 1fr 1fr; } } @media (max-width: 768px) { .hero { padding: 40px 20px; } .hero h1 { font-size: 2.2rem; } .tool-view-container { padding: 20px; } .tool-view-header h2 { font-size: 1.6rem; } .responsive-grid-two-col, .gradient-grid-layout { grid-template-columns: 1fr; } .tool-grid { grid-template-columns: 1fr 1fr; gap: 15px; } .tool-link-card { padding: 15px; gap: 10px; } .tool-link-card .icon { font-size: 1.4rem; } .tool-link-card .title { font-size: 1rem; } } @media (max-width: 480px) { .case-converter-buttons { grid-template-columns: 1fr; } .tool-button-group { flex-direction: column; } .tool-button-group .tool-btn { width: 100%; } .tool-grid { grid-template-columns: 1fr; } } </style> </head> <body> <header> <div class="container header-container"> <a href="#" class="logo-link"><img src="https://images.squarespace-cdn.com/content/v1/5c732c457eb88c53322767d8/1649631719556-E92EBEQKHVS7IIOT87T1/Webtools+Logo+Colour.png" alt="Logo" class="logo"></a> <div class="nav-right"> <nav id="main-nav"> <ul> <li><a href="#" target="_blank">Ana Sayfa</a></li> <li><a href="#" target="_blank">Hakkımızda</a></li> <li><a href="#" target="_blank">İletişim</a></li> <li><a href="#" target="_blank">Gizlilik Politikası</a></li> </ul> </nav> <button class="theme-toggle" id="theme-toggle" aria-label="Temayı değiştir"><i class="fas fa-moon"></i></button> <div class="hamburger-menu" id="hamburger-menu" aria-label="Menüyü aç/kapat"><i class="fas fa-bars"></i></div> </div> </div> </header> <div class="hero"> <div class="container"> <h1>Pro Web Toolkit</h1> <p>Projelerinizi hızlandıracak modern, hızlı ve ücretsiz araçlar.</p> <p style="font-size: 0.9rem; margin-top: 15px; opacity: 0.8;">Hızlı erişim için <strong>Ctrl+K</strong> (veya Cmd+K) basın</p> </div> </div> <main id="app" class="container"></main> <footer> <div class="container"> <div class="footer-nav"> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> </div> <p class="footer-copyright">© 2025 <a href="#" target="_blank">Siteismi</a> | Tüm Hakları Saklıdır.</p> </div> </footer> <div id="toast-container"></div> <div id="command-palette-overlay"> <div id="command-palette"> <input type="text" id="palette-input" placeholder="Araç ara..."> <div id="palette-results"></div> <div class="palette-hint">Gezinmek için ↑↓, seçmek için Enter, kapatmak için Esc kullanın.</div> </div> </div> <script> // Global utilities function copyToClipboard(text) { if (!text) return; navigator.clipboard.writeText(text).then(() => showToast('Panoya kopyalandı!', 'success')).catch(() => showToast('Kopyalama başarısız oldu.', 'error')); } window.showToast = (message, type = '') => { const container = document.getElementById('toast-container'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 500); }, 3000); }; // Main Application Logic document.addEventListener('DOMContentLoaded', () => { const app = document.getElementById('app'); const tools = [ // Üreticiler { id: 'sifre-uretici', title: 'Şifre Üretici', icon: 'fa-key', category: 'Üreticiler', render: renderPasswordGenerator }, { id: 'css-gradient-uretici', title: 'CSS Gradient Üretici', icon: 'fa-palette', category: 'Üreticiler', render: renderGradientGenerator }, { id: 'qr-kod-uretici', title: 'QR Kod Üretici', icon: 'fa-qrcode', category: 'Üreticiler', render: renderQrCodeGenerator }, { id: 'lorem-ipsum-uretici', title: 'Lorem Ipsum Üretici', icon: 'fa-paragraph', category: 'Üreticiler', render: renderLoremIpsumGenerator }, { id: 'css-box-shadow-uretici', title: 'CSS Box-Shadow Üretici', icon: 'fa-layer-group', category: 'Üreticiler', render: renderBoxShadowGenerator }, // Metin Araçları { id: 'karakter-sayici', title: 'Karakter Sayacı', icon: 'fa-text-width', category: 'Metin Araçları', render: renderCharCounter }, { id: 'metin-karsilastirma', title: 'Metin Karşılaştırma', icon: 'fa-right-left', category: 'Metin Araçları', render: renderDiffChecker }, { id: 'metin-bicimlendirme', title: 'Metin Biçimlendirme', icon: 'fa-spell-check', category: 'Metin Araçları', render: renderCaseConverter }, { id: 'slug-uretici', title: 'URL Slug Üretici', icon: 'fa-link', category: 'Metin Araçları', render: renderSlugGenerator }, // Geliştirici Araçları { id: 'json-formatlayici', title: 'JSON Formatlayıcı', icon: 'fa-code-branch', category: 'Geliştirici Araçları', render: renderJsonFormatter }, { id: 'svg-optimize-edici', title: 'SVG Optimize Edici', icon: 'fa-wand-magic-sparkles', category: 'Geliştirici Araçları', render: renderSvgOptimizer }, { id: 'base64-donusturucu', title: 'Base64 Dönüştürücü', icon: 'fa-retweet', category: 'Geliştirici Araçları', render: renderBase64Converter }, { id: 'url-donusturucu', title: 'URL Dönüştürücü', icon: 'fa-link', category: 'Geliştirici Araçları', render: renderUrlConverter }, { id: 'renk-donusturucu', title: 'Renk Kodu Dönüştürücü', icon: 'fa-eye-dropper', category: 'Geliştirici Araçları', render: renderColorConverter }, { id: 'px-rem-donusturucu', title: 'PX <> REM Dönüştürücü', icon: 'fa-text-height', category: 'Geliştirici Araçları', render: renderPxRemConverter }, { id: 'unix-zaman-damgasi', title: 'UNIX Zaman Damgası', icon: 'fa-clock', category: 'Geliştirici Araçları', render: renderTimestampConverter }, // Güvenlik Araçları { id: 'jwt-ayristirici', title: 'JWT Ayrıştırıcı', icon: 'fa-user-secret', category: 'Güvenlik Araçları', render: renderJwtDecoder }, { id: 'hash-uretici', title: 'Hash Üretici', icon: 'fa-fingerprint', category: 'Güvenlik Araçları', render: renderHashGenerator }, // Medya Araçları { id: 'resim-base64-donusturucu', title: 'Resim > Base64', icon: 'fa-image', category: 'Medya Araçları', render: renderImageToBase64Converter }, // Web Araçları { id: 'ip-bilgisi', title: 'IP Bilgisi', icon: 'fa-globe', category: 'Web Araçları', render: renderIpInfo }, ]; const core = { init() { this.themeManager.init(); this.navigationManager.init(); this.commandPalette.init(tools); window.addEventListener('hashchange', () => this.router(tools)); this.router(tools); }, router(tools) { core.navigationManager.close(); const hash = window.location.hash || '#'; const tool = tools.find(t => `#${t.id}` === hash); app.innerHTML = ''; app.style.animation = 'none'; void app.offsetWidth; app.style.animation = 'fade-in 0.5s ease-out'; if (tool && typeof tool.render === 'function') { tool.render(app, tools); } else { renderHomePage(app, tools); } }, themeManager: { init() { this.themeToggle = document.getElementById('theme-toggle'); const theme = localStorage.getItem('theme') || 'light'; this.apply(theme); this.themeToggle.addEventListener('click', () => this.toggle()); }, apply(theme) { document.body.classList.toggle('dark-mode', theme === 'dark'); this.themeToggle.innerHTML = theme === 'dark' ? '<i class="fas fa-sun"></i>' : '<i class="fas fa-moon"></i>'; }, toggle() { let newTheme = document.body.classList.contains('dark-mode') ? 'light' : 'dark'; localStorage.setItem('theme', newTheme); this.apply(newTheme); } }, navigationManager: { init() { this.hamburgerMenu = document.getElementById('hamburger-menu'); this.mainNav = document.getElementById('main-nav'); this.hamburgerMenu.addEventListener('click', () => this.toggle()); }, toggle() { this.mainNav.classList.toggle('active'); }, close() { this.mainNav.classList.remove('active'); } }, commandPalette: { init(tools) { this.overlay = document.getElementById('command-palette-overlay'); this.input = document.getElementById('palette-input'); this.resultsContainer = document.getElementById('palette-results'); this.selectedIndex = -1; this.tools = tools; document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); this.open(); } if (e.key === 'Escape') this.close(); }); this.overlay.addEventListener('click', (e) => { if (e.target === this.overlay) this.close(); }); this.input.addEventListener('input', () => this.updateResults(this.input.value)); this.input.addEventListener('keydown', (e) => this.navigate(e)); this.resultsContainer.addEventListener('click', (e) => { if (e.target.closest('a')) this.close(); }); }, open() { this.overlay.style.display = 'block'; this.updateResults(''); this.input.focus(); }, close() { this.overlay.style.display = 'none'; }, updateResults(query) { const filteredTools = this.tools.filter(t => t.title.toLowerCase().includes(query.toLowerCase())); let html = '<ul>'; filteredTools.forEach(t => { html += `<li><a href="#${t.id}" data-toolid="${t.id}"><span class="icon"><i class="fas ${t.icon}"></i></span> ${t.title}</a></li>`; }); html += '</ul>'; this.resultsContainer.innerHTML = html; this.selectedIndex = -1; }, navigate(e) { const links = this.resultsContainer.querySelectorAll('a'); if (!links.length) return; if (e.key === 'ArrowDown') this.selectedIndex = (this.selectedIndex + 1) % links.length; else if (e.key === 'ArrowUp') this.selectedIndex = (this.selectedIndex - 1 + links.length) % links.length; else if (e.key === 'Enter' && this.selectedIndex >= 0) { links[this.selectedIndex].click(); return; } else return; e.preventDefault(); links.forEach((link, index) => link.classList.toggle('selected', index === this.selectedIndex)); links[this.selectedIndex].scrollIntoView({ block: 'nearest' }); } } }; core.init(); }); // --- View Renderers --- function renderHomePage(app, tools) { const categories = [...new Set(tools.map(t => t.category))].sort((a,b) => a.localeCompare(b)); let html = `<div style="margin-bottom: 30px; position: relative;"><input type="search" id="tool-search" class="tool-input" placeholder="Araçlarda ara..." style="padding-left: 40px;"><i class="fas fa-search" style="position: absolute; left: 15px; top: 15px; color: var(--footer-text-color);"></i></div>`; categories.forEach(cat => { html += `<div class="category-section" data-category="${cat}"><h2 class="category-title">${cat}</h2><div class="tool-grid">`; tools.filter(t => t.category === cat).forEach(t => { html += `<a href="#${t.id}" class="tool-link-card"><div class="icon"><i class="fas ${t.icon}"></i></div><div class="details"><div class="title">${t.title}</div></div></a>`; }); html += '</div></div>'; }); app.innerHTML = html; const searchInput = document.getElementById('tool-search'); searchInput.addEventListener('keyup', () => { const query = searchInput.value.toLowerCase().trim(); document.querySelectorAll('.tool-link-card').forEach(card => { const title = card.querySelector('.title').textContent.toLowerCase(); card.classList.toggle('hidden', !title.includes(query)); }); document.querySelectorAll('.category-section').forEach(section => { const visibleCards = section.querySelectorAll('.tool-link-card:not(.hidden)'); section.classList.toggle('hidden', visibleCards.length === 0); }); }); } function renderToolView(app, tools, toolId, content) { const tool = tools.find(t => t.id === toolId); if (!tool) return; app.innerHTML = `<div class="tool-view-container"><div class="tool-view-header"><a href="#" aria-label="Ana Sayfaya Dön" style="color:var(--text-color);"><i class="fas fa-arrow-left"></i></a><i class="fas ${tool.icon}"></i><h2>${tool.title}</h2></div>${content}</div>`; } //--- TÜM ARAÇ FONKSİYONLARI --- function renderPasswordGenerator(app, tools) { renderToolView(app, tools, 'sifre-uretici', `<label>Uzunluk: <input type="number" id="passLength" value="16" min="8" max="64" class="tool-input" style="width:100px; display:inline-block;"></label><div class="checkbox-group"><label><input type="checkbox" id="passNum" checked> Rakamlar</label><label><input type="checkbox" id="passSym" checked> Semboller</label></div><button class="tool-btn" id="generatePassBtn">Şifre Oluştur</button><div class="result-wrapper"><textarea id="passwordResult" class="result tool-input placeholder" readonly>Sonucunuz burada görünecek.</textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('passwordResult').value)">Kopyala</button></div>`); app.querySelector('#generatePassBtn').addEventListener('click', () => { const l=app.querySelector('#passLength').value, n=app.querySelector('#passNum').checked, s=app.querySelector('#passSym').checked; let c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + (n?'0123456789':'') + (s?'!@#$%^&*()_+-=[]{}|;:,./<>?':''); let p = ''; for (let i=0; i<l; i++) p += c.charAt(Math.floor(Math.random()*c.length)); const resultDiv = app.querySelector('#passwordResult'); resultDiv.value = p; resultDiv.classList.remove('placeholder'); }); } function renderGradientGenerator(app, tools) { const savedState = JSON.parse(localStorage.getItem('gradientState')) || { type: 'linear', angle: 90, colors: ['#0d9488', '#f59e0b'] }; let colorInputsHTML = savedState.colors.map(color => `<input type="color" value="${color}" class="tool-input color-input">`).join(''); renderToolView(app, tools, 'css-gradient-uretici', `<div class="gradient-grid-layout"><div><h4>Ayarlar</h4><label>Tür: <select id="gradType" class="tool-input"><option value="linear">Linear</option><option value="radial">Radial</option></select></label><label>Açı: <input type="range" id="gradAngle" min="0" max="360" value="${savedState.angle}" class="tool-input"></label><div id="gradColors">${colorInputsHTML}</div><button id="addColorBtn" class="tool-btn" style="margin-top:10px;">Renk Ekle</button></div><div class="result-wrapper" style="margin-top:0;"><h4>Önizleme & Kod</h4><div id="gradPreview" style="height: 200px; border-radius: 8px; border: 1px solid var(--input-border-color); margin-bottom:10px;"></div><textarea id="gradResult" class="result tool-input" readonly></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('gradResult').value)">Kopyala</button></div></div>`); app.querySelector('#gradType').value = savedState.type; const update = () => { const state = { type: app.querySelector('#gradType').value, angle: app.querySelector('#gradAngle').value, colors: [...app.querySelectorAll('.color-input')].map(i => i.value) }; const g = (state.type === 'linear') ? `linear-gradient(${state.angle}deg, ${state.colors.join(', ')})` : `radial-gradient(circle, ${state.colors.join(', ')})`; app.querySelector('#gradPreview').style.background = g; app.querySelector('#gradResult').value = `background: ${g};`; localStorage.setItem('gradientState', JSON.stringify(state)); }; app.querySelector('#addColorBtn').addEventListener('click', () => { const i = document.createElement('input'); i.type = 'color'; i.className = 'tool-input color-input'; i.value = '#ffffff'; app.querySelector('#gradColors').appendChild(i); i.addEventListener('input', update); update(); }); app.querySelectorAll('.tool-input').forEach(el => el.addEventListener('input', update)); update(); } function renderQrCodeGenerator(app, tools) { renderToolView(app, tools, 'qr-kod-uretici', `<input type="text" id="qrText" class="tool-input" placeholder="Metin veya URL girin"><button id="qrBtn" class="tool-btn">QR Kod Oluştur</button><div class="result placeholder" id="qrResult" style="text-align: center;">QR kodunuz burada görünecek.</div>`); app.querySelector('#qrBtn').addEventListener('click', () => { const text = app.querySelector('#qrText').value.trim(); if (!text) { showToast('Lütfen bir metin girin.'); return; } const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(text)}`; const resDiv = app.querySelector('#qrResult'); resDiv.innerHTML = `<img src="${qrUrl}" alt="QR Code" style="max-width: 100%; height: auto;">`; resDiv.classList.remove('placeholder'); }); } function renderLoremIpsumGenerator(app, tools) { renderToolView(app, tools, 'lorem-ipsum-uretici', `<div style="display: flex; gap: 20px; flex-wrap: wrap; margin-bottom:15px;"><label>Miktar: <input type="number" id="loremAmount" value="5" min="1" max="100" class="tool-input" style="width:100px; display:inline-block; margin-bottom:0;"></label><label>Tür: <select id="loremType" class="tool-input" style="width:150px; display:inline-block; margin-bottom:0;"><option value="p">Paragraf</option><option value="s">Cümle</option></select></label></div><button id="loremBtn" class="tool-btn">Oluştur</button><div class="result-wrapper"><textarea id="loremResult" class="result tool-input" style="text-align: left; line-height:1.8;" readonly></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('loremResult').value)">Kopyala</button></div>`); const loremBtn = app.querySelector('#loremBtn'); const loremResult = app.querySelector('#loremResult'); const generate = () => { const amount = parseInt(app.querySelector('#loremAmount').value, 10); const type = app.querySelector('#loremType').value; const text = ["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.", "Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.", "Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem."]; let result = ''; if (type === 'p') { for (let i = 0; i < amount; i++) { let shuffled = text.sort(() => 0.5 - Math.random()); result += shuffled.slice(0, Math.floor(Math.random() * 3) + 3).join(' ') + (i < amount - 1 ? '\n\n' : ''); } } else { let sentences = text.slice(0, amount); result = sentences.join(' '); } loremResult.value = result; }; loremBtn.addEventListener('click', generate); generate(); } function renderCharCounter(app, tools) { renderToolView(app, tools, 'karakter-sayici', `<textarea id="charCounterInput" class="tool-input" rows="8" placeholder="Metninizi buraya yazın..."></textarea><div class="result" style="margin-top:0;">Karakter: <strong id="charCount">0</strong> | Kelime: <strong id="wordCount">0</strong> | Satır: <strong id="lineCount">0</strong></div>`); app.querySelector('#charCounterInput').addEventListener('input', e => { const txt = e.target.value; app.querySelector('#charCount').textContent = txt.length; app.querySelector('#wordCount').textContent = txt.trim() ? txt.trim().split(/\s+/).length : 0; app.querySelector('#lineCount').textContent = txt.split('\n').length; }); } function renderDiffChecker(app, tools) { renderToolView(app, tools, 'metin-karsilastirma', `<div class="responsive-grid-two-col"><textarea id="diffText1" class="tool-input" rows="10" placeholder="Orijinal metin...">${localStorage.getItem('diffCheckerText1') || ''}</textarea><textarea id="diffText2" class="tool-input" rows="10" placeholder="Değiştirilmiş metin...">${localStorage.getItem('diffCheckerText2') || ''}</textarea></div><button id="diffBtn" class="tool-btn" style="margin-top:10px;">Karşılaştır</button><div class="result diff-output placeholder" id="diffResult">Karşılaştırma sonucu burada görünecek.</div>`); const t1 = app.querySelector('#diffText1'), t2 = app.querySelector('#diffText2'); t1.addEventListener('input', () => localStorage.setItem('diffCheckerText1', t1.value)); t2.addEventListener('input', () => localStorage.setItem('diffCheckerText2', t2.value)); app.querySelector('#diffBtn').addEventListener('click', () => { const diff = Diff.diffChars(t1.value, t2.value), frag = document.createDocumentFragment(); diff.forEach(p => { const n = document.createElement(p.added ? 'ins' : p.removed ? 'del' : 'span'); n.appendChild(document.createTextNode(p.value)); frag.appendChild(n); }); const resDiv = app.querySelector('#diffResult'); resDiv.innerHTML = ''; resDiv.appendChild(frag); if (resDiv.innerHTML.trim() === '<span></span>') resDiv.innerHTML = 'Fark bulunamadı.'; else resDiv.classList.remove('placeholder'); }); } function renderCaseConverter(app, tools) { renderToolView(app, tools, 'metin-bicimlendirme', `<textarea id="caseInput" class="tool-input" rows="5" placeholder="Dönüştürülecek metin..."></textarea><div class="case-converter-buttons"><button class="tool-btn" data-case="upper">BÜYÜK</button><button class="tool-btn" data-case="lower">küçük</button><button class="tool-btn" data-case="title">Başlık</button><button class="tool-btn" data-case="sentence">Cümle</button></div><div class="result-wrapper"><textarea id="caseResult" class="tool-input result" rows="5" readonly placeholder="Sonucunuz burada görünecek."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('caseResult').value)">Kopyala</button></div>`); app.querySelectorAll('[data-case]').forEach(btn => btn.addEventListener('click', (e) => { const type = e.target.dataset.case, input = app.querySelector('#caseInput').value; let result = ''; if (type === 'upper') result = input.toUpperCase(); if (type === 'lower') result = input.toLowerCase(); if (type === 'title') result = input.toLowerCase().split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); if (type === 'sentence') result = input.toLowerCase().replace(/(^\w{1})|([.!?]\s*\w{1})/g, c => c.toUpperCase()); app.querySelector('#caseResult').value = result; })); } function renderSlugGenerator(app, tools) { const slugify = (text) => { const a = 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;'; const b = 'aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnoooooooooprrrsssssttuuuuuuuuuwxyyzzz------'; const p = new RegExp(a.split('').join('|'), 'g'); return text.toString().toLowerCase().replace(/\s+/g, '-').replace(p, c => b.charAt(a.indexOf(c))).replace(/&/g, '-and-').replace(/[^\w\-]+/g, '').replace(/\-\-+/g, '-').replace(/^-+/, '').replace(/-+$/, '') }; renderToolView(app, tools, 'slug-uretici', `<textarea id="slug-input" class="tool-input" rows="4" placeholder="URL'e dönüştürülecek metni buraya girin..."></textarea><div class="result-wrapper" style="margin-top:0;"><textarea id="slug-result" class="tool-input result" rows="4" readonly placeholder="URL dostu metin burada görünecek..."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('slug-result').value)">Kopyala</button></div>`); const input = app.querySelector('#slug-input'), result = app.querySelector('#slug-result'); input.addEventListener('input', () => { result.value = slugify(input.value); }); } function renderJsonFormatter(app, tools) { const initialJson = localStorage.getItem('jsonFormatterValue') || '{\n "ad": "Ahmet",\n "aktif": true\n}'; renderToolView(app, tools, 'json-formatlayici', `<textarea id="jsonInput" class="tool-input" rows="10">${initialJson}</textarea><div class="tool-button-group"><button id="formatBtn" class="tool-btn">Formatla / Doğrula</button><button id="minifyBtn" class="tool-btn secondary">Küçült</button></div><div class="result placeholder" id="jsonResult">İşlem durumu burada görünecek.</div>`); const i = app.querySelector('#jsonInput'), r = app.querySelector('#jsonResult'); const save = () => localStorage.setItem('jsonFormatterValue', i.value); const process = (action) => { r.classList.remove('placeholder'); try { const parsed = JSON.parse(i.value); if (action === 'format') { i.value = JSON.stringify(parsed, null, 4); r.innerHTML = `<div style="color:var(--success-color)">JSON geçerli ve formatlandı.</div>`; } else { i.value = JSON.stringify(parsed); r.innerHTML = `<div style="color:var(--success-color)">JSON küçültüldü.</div>`; } save(); } catch (e) { r.innerHTML = `<div style="color:var(--danger-color)">Geçersiz JSON: ${e.message}</div>`; } }; i.addEventListener('input', save); app.querySelector('#formatBtn').addEventListener('click', () => process('format')); app.querySelector('#minifyBtn').addEventListener('click', () => process('minify')); } function renderSvgOptimizer(app, tools) { renderToolView(app, tools, 'svg-optimize-edici', `<div class="responsive-grid-two-col"><textarea id="svgInput" class="tool-input" rows="10" placeholder="<svg> kodunu buraya yapıştırın..."></textarea><div class="result-wrapper" style="margin-top:0;"><textarea id="svgOutput" class="tool-input" rows="10" readonly placeholder="Optimize edilmiş SVG kodu..."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('svgOutput').value)">Kopyala</button></div></div><button id="optimizeBtn" class="tool-btn" style="margin-top:10px;">Optimize Et</button><div class="result placeholder" id="svgResult">Optimizasyon sonucu burada görünecek.</div>`); const i = app.querySelector('#svgInput'), o = app.querySelector('#svgOutput'), r = app.querySelector('#svgResult'); app.querySelector('#optimizeBtn').addEventListener('click', () => { if (!i.value.trim()) { showToast('Lütfen optimize edilecek bir SVG girin.'); return; } r.classList.remove('placeholder'); try { const result = SVGO.optimize(i.value); const originalSize = new TextEncoder().encode(i.value).length; const optimizedSize = new TextEncoder().encode(result.data).length; const reduction = (((originalSize - optimizedSize) / originalSize) * 100).toFixed(2); o.value = result.data; r.innerHTML = `<div style="color: var(--success-color)">Başarılı! Boyut <strong>%${reduction}</strong> oranında küçüldü. (${originalSize} bayt -> ${optimizedSize} bayt)</div>`; } catch (e) { r.innerHTML = `<div style="color: var(--danger-color)">Optimizasyon Hatası: ${e.message}</div>`; } }); } function renderBase64Converter(app, tools) { renderToolView(app, tools, 'base64-donusturucu', `<textarea class="tool-input" id="base64Input" rows="5" placeholder="Metin girin veya Base64 kodu yapıştırın..."></textarea><div class="tool-button-group"><button id="b64Encode" class="tool-btn">Encode</button><button id="b64Decode" class="tool-btn secondary">Decode</button></div><div class="result-wrapper"><textarea class="result tool-input" id="base64Result" readonly placeholder="Sonucunuz burada görünecek."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('base64Result').value)">Kopyala</button></div>`); const i = app.querySelector('#base64Input'), r = app.querySelector('#base64Result'); app.querySelector('#b64Encode').addEventListener('click', () => { try { r.value = btoa(unescape(encodeURIComponent(i.value))); } catch (e) { r.value = 'Hata: Geçersiz karakter.'; } }); app.querySelector('#b64Decode').addEventListener('click', () => { try { r.value = decodeURIComponent(escape(atob(i.value))); } catch (e) { r.value = 'Hata: Geçersiz Base64 kodu.'; } }); } function renderUrlConverter(app, tools) { renderToolView(app, tools, 'url-donusturucu', `<textarea class="tool-input" id="urlInput" rows="5" placeholder="URL veya metin girin..."></textarea><div class="tool-button-group"><button id="urlEncode" class="tool-btn">Encode</button><button id="urlDecode" class="tool-btn secondary">Decode</button></div><div class="result-wrapper"><textarea class="result tool-input" id="urlResult" readonly placeholder="Sonucunuz burada görünecek."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('urlResult').value)">Kopyala</button></div>`); const i = app.querySelector('#urlInput'), r = app.querySelector('#urlResult'); app.querySelector('#urlEncode').addEventListener('click', () => { r.value = encodeURIComponent(i.value); }); app.querySelector('#urlDecode').addEventListener('click', () => { try { r.value = decodeURIComponent(i.value); } catch(e) { r.value = "Geçersiz URL kodu."; }}); } function renderColorConverter(app, tools) { renderToolView(app, tools, 'renk-donusturucu', `<input type="text" class="tool-input" id="colorInput" placeholder="HEX Kodu Girin (örn: #0d9488)"><button id="colorBtn" class="tool-btn">Dönüştür</button><div class="result placeholder" id="colorResult">Dönüşüm sonucu burada görünecek.</div>`); const i = app.querySelector('#colorInput'), r = app.querySelector('#colorResult'); const convert = () => { const hexRegex = /^#([0-9A-F]{3}){1,2}$/i; if (!i.value.trim()) return; r.classList.remove('placeholder'); if (!hexRegex.test(i.value.trim())) { r.textContent = 'Geçersiz HEX kodu.'; return; } let hex = i.value.trim().substring(1); if (hex.length === 3) hex = hex.split('').map(c => c + c).join(''); const red = parseInt(hex.substring(0, 2), 16), green = parseInt(hex.substring(2, 4), 16), blue = parseInt(hex.substring(4, 6), 16); const rgbString = `rgb(${red}, ${green}, ${blue})`; r.innerHTML = `<div style="display:flex; align-items:center; gap:15px; cursor: pointer;" onclick="copyToClipboard('${rgbString}')"><div style="width:40px; height:40px; background-color:${rgbString}; border-radius:6px; border:1px solid var(--input-border-color);"></div> <strong>${rgbString}</strong></div>`; }; app.querySelector('#colorBtn').addEventListener('click', convert); i.addEventListener('input', convert); } function renderPxRemConverter(app, tools) { renderToolView(app, tools, 'px-rem-donusturucu', `<div class="responsive-grid-two-col"><div><label for="px-input">Pixel (px)</label><input type="number" id="px-input" class="tool-input" value="16"></div><div><label for="rem-input">Rem</label><input type="number" id="rem-input" class="tool-input" value="1"></div></div><div><label for="base-size">Temel Yazı Tipi Boyutu (px)</label><input type="number" id="base-size" class="tool-input" value="16"></div>`); const pxInput = app.querySelector('#px-input'), remInput = app.querySelector('#rem-input'), baseSizeInput = app.querySelector('#base-size'); const updateValues = (source) => { const base = parseFloat(baseSizeInput.value) || 16; if (source === 'px') { const px = parseFloat(pxInput.value) || 0; remInput.value = parseFloat((px / base).toPrecision(4)); } else { const rem = parseFloat(remInput.value) || 0; pxInput.value = parseFloat((rem * base).toPrecision(4)); } }; pxInput.addEventListener('input', () => updateValues('px')); remInput.addEventListener('input', () => updateValues('rem')); baseSizeInput.addEventListener('input', () => updateValues('px')); } function renderImageToBase64Converter(app, tools) { renderToolView(app, tools, 'resim-base64-donusturucu', `<label for="image-input" class="tool-btn secondary" style="cursor:pointer; text-align:center; display:block;"><i class="fas fa-upload"></i> Resim Seç (Max 2MB)</label><input type="file" id="image-input" accept="image/png, image/jpeg, image/gif, image/svg+xml" style="display:none;"><div id="image-preview" class="placeholder result" style="min-height: 150px; margin-top: 20px;">Resim önizlemesi burada görünecek.</div><div class="result-wrapper"><textarea id="base64-result" class="tool-input result" rows="8" readonly placeholder="Base64 kodu burada görünecek..."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('base64-result').value)">Kopyala</button></div>`); const input = app.querySelector('#image-input'), preview = app.querySelector('#image-preview'), result = app.querySelector('#base64-result'); const MAX_SIZE = 2 * 1024 * 1024; input.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; if (file.size > MAX_SIZE) { showToast('Hata: Dosya boyutu 2MB\'den büyük olamaz.', 'error'); preview.innerHTML = 'Dosya çok büyük!'; preview.classList.add('placeholder'); result.value = ''; return; } const reader = new FileReader(); reader.onload = (event) => { preview.innerHTML = `<img src="${event.target.result}" style="max-width: 200px; max-height: 200px; border-radius: 8px; border: 1px solid var(--input-border-color);">`; preview.classList.remove('placeholder'); result.value = event.target.result; }; reader.onerror = () => { showToast('Hata: Dosya okunamadı.', 'error'); preview.innerHTML = 'Dosya okunamadı!'; preview.classList.add('placeholder'); result.value = ''; }; reader.readAsDataURL(file); }); } function renderIpInfo(app, tools) { renderToolView(app, tools, 'ip-bilgisi', `<button id="ipBtn" class="tool-btn">IP Bilgimi Göster</button><div class="result placeholder" id="ipResult">IP bilgileriniz için butona tıklayın.</div>`); app.querySelector('#ipBtn').addEventListener('click', async (e) => { const btn = e.target, resDiv = app.querySelector('#ipResult'); btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Alınıyor...'; resDiv.classList.remove('placeholder'); resDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; try { const resp = await fetch('https://ipapi.co/json/'); if (!resp.ok) throw new Error('Servis yanıt vermiyor.'); const data = await resp.json(); resDiv.innerHTML = `<div style="text-align:left;"><strong>IP:</strong> ${data.ip}<br><strong>Lokasyon:</strong> ${data.city}, ${data.region}, ${data.country_name}<br><strong>ISP:</strong> ${data.org}</div>`; } catch (err) { resDiv.textContent = `Hata: ${err.message}`; } btn.disabled = false; btn.innerHTML = 'IP Bilgimi Göster'; }); } function renderJwtDecoder(app, tools) { renderToolView(app, tools, 'jwt-ayristirici', `<label for="jwt-input">JWT Token</label><textarea id="jwt-input" class="tool-input" rows="5" placeholder="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE4OTM0NTYwMDB9.some_signature"></textarea><div id="jwt-status" style="margin-bottom: 15px; font-weight:bold; text-align:center;"></div><h4>Header</h4><div class="result-wrapper"><pre id="jwt-header" class="result" style="min-height: 80px;"></pre><button class="btn-copy" onclick="copyToClipboard(document.getElementById('jwt-header').innerText)">Kopyala</button></div><h4>Payload</h4><div class="result-wrapper"><pre id="jwt-payload" class="result" style="min-height: 120px;"></pre><button class="btn-copy" onclick="copyToClipboard(document.getElementById('jwt-payload').innerText)">Kopyala</button></div>`); const input = app.querySelector('#jwt-input'); const headerEl = app.querySelector('#jwt-header'); const payloadEl = app.querySelector('#jwt-payload'); const statusEl = app.querySelector('#jwt-status'); const decode = () => { const token = input.value.trim(); headerEl.textContent = ''; payloadEl.textContent = ''; statusEl.innerHTML = ''; if (!token) return; try { const [header, payload, signature] = token.split('.'); if (!header || !payload || !signature) throw new Error("Geçersiz JWT formatı"); const decodedHeader = JSON.parse(atob(header.replace(/-/g, '+').replace(/_/g, '/'))); const decodedPayload = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))); headerEl.textContent = JSON.stringify(decodedHeader, null, 2); payloadEl.textContent = JSON.stringify(decodedPayload, null, 2); if(decodedPayload.exp) { const expiryDate = new Date(decodedPayload.exp * 1000); const now = new Date(); if (expiryDate < now) { statusEl.innerHTML = `<div style="color:var(--danger-color);">Token SÜRESİ DOLMUŞ - ${expiryDate.toLocaleString()}</div>`; } else { statusEl.innerHTML = `<div style="color:var(--success-color);">Token GEÇERLİ - Bitiş: ${expiryDate.toLocaleString()}</div>`; } } } catch (e) { payloadEl.textContent = `Hata: ${e.message}. Token doğru formatta veya Base64 ile kodlanmış değil.`; } }; input.addEventListener('input', decode); } function renderHashGenerator(app, tools) { renderToolView(app, tools, 'hash-uretici', `<label for="hash-input">Metin</label><textarea id="hash-input" class="tool-input" rows="5" placeholder="Özetlenecek metni girin..."></textarea><div class="tool-button-group"><button class="tool-btn" data-algo="SHA-256">SHA-256</button><button class="tool-btn" data-algo="SHA-512">SHA-512</button><button class="tool-btn secondary" data-algo="SHA-1">SHA-1</button></div><div id="hash-results"></div>`); const input = app.querySelector('#hash-input'); const generateHash = async (algo) => { const resultsContainer = app.querySelector('#hash-results'); const text = input.value; if (!text) { showToast("Lütfen özetlenecek bir metin girin."); return; } try { const encoder = new TextEncoder(); const data = encoder.encode(text); const hashBuffer = await crypto.subtle.digest(algo, data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); let resultHTML = `<div class="result-wrapper" style="margin-top:10px;"><label style="font-weight:bold;">${algo}</label><textarea class="result tool-input" readonly>${hashHex}</textarea><button class="btn-copy" onclick="copyToClipboard('${hashHex}')">Kopyala</button></div>`; resultsContainer.innerHTML = resultHTML; } catch (e) { resultsContainer.innerHTML = `<div class="result" style="color:var(--danger-color)">Hata oluştu: ${e.message}</div>`; } }; app.querySelectorAll('.tool-btn[data-algo]').forEach(btn => { btn.addEventListener('click', (e) => generateHash(e.target.dataset.algo)); }); } function renderBoxShadowGenerator(app, tools) { const initialState = { h: 10, v: 10, b: 5, s: 0, c: '#000000', o: 0.5, i: false }; renderToolView(app, tools, 'css-box-shadow-uretici', `<div class="responsive-grid-two-col"><div><h4>Ayarlar</h4><label>Yatay Ofset (X): <span id="h-val">${initialState.h}</span>px</label><input type="range" id="h-offset" class="tool-input" min="-100" max="100" value="${initialState.h}"><label>Dikey Ofset (Y): <span id="v-val">${initialState.v}</span>px</label><input type="range" id="v-offset" class="tool-input" min="-100" max="100" value="${initialState.v}"><label>Bulanıklık (Blur): <span id="b-val">${initialState.b}</span>px</label><input type="range" id="blur" class="tool-input" min="0" max="100" value="${initialState.b}"><label>Yayılma (Spread): <span id="s-val">${initialState.s}</span>px</label><input type="range" id="spread" class="tool-input" min="-50" max="50" value="${initialState.s}"><label>Opaklık: <span id="o-val">${initialState.o}</span></label><input type="range" id="opacity" class="tool-input" min="0" max="1" step="0.01" value="${initialState.o}"><div style="display:flex; gap:20px; align-items:center; margin-top:10px;"><label>Gölge Rengi: <input type="color" id="color" value="${initialState.c}"></label><label><input type="checkbox" id="inset"> İç Gölge (inset)</label></div></div><div><h4>Önizleme</h4><div id="shadow-preview" style="width: 100%; height: 200px; background-color: var(--secondary-color); border-radius: 12px; display:flex; align-items:center; justify-content:center; color:white; font-weight:bold; transition: box-shadow 0.2s;"></div><div class="result-wrapper" style="margin-top:20px;"><textarea id="shadow-code" class="result tool-input" rows="3" readonly></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('shadow-code').value)">Kopyala</button></div></div></div>`); const controls = { h: app.querySelector('#h-offset'), v: app.querySelector('#v-offset'), b: app.querySelector('#blur'), s: app.querySelector('#spread'), c: app.querySelector('#color'), o: app.querySelector('#opacity'), i: app.querySelector('#inset') }; const vals = { h: app.querySelector('#h-val'), v: app.querySelector('#v-val'), b: app.querySelector('#b-val'), s: app.querySelector('#s-val'), o: app.querySelector('#o-val') }; const preview = app.querySelector('#shadow-preview'); const codeOutput = app.querySelector('#shadow-code'); const hexToRgba = (hex, opacity) => { let r = 0, g = 0, b = 0; r = "0x" + hex[1] + hex[2]; g = "0x" + hex[3] + hex[4]; b = "0x" + hex[5] + hex[6]; return `rgba(${+r}, ${+g}, ${+b}, ${opacity})`; }; const updateShadow = () => { const h = controls.h.value; const v = controls.v.value; const b = controls.b.value; const s = controls.s.value; const c = controls.c.value; const o = controls.o.value; const i = controls.i.checked ? 'inset' : ''; vals.h.textContent = h; vals.v.textContent = v; vals.b.textContent = b; vals.s.textContent = s; vals.o.textContent = o; const rgbaColor = hexToRgba(c, o); const shadowValue = `${i} ${h}px ${v}px ${b}px ${s}px ${rgbaColor}`.trim(); preview.style.boxShadow = shadowValue; codeOutput.value = `box-shadow: ${shadowValue};`; }; Object.values(controls).forEach(control => control.addEventListener('input', updateShadow)); updateShadow(); } function renderTimestampConverter(app, tools) { const now = Math.floor(Date.now() / 1000); const nowISO = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().substring(0, 16); renderToolView(app, tools, 'unix-zaman-damgasi', `<div class="result" style="text-align:center; margin-bottom: 20px;">Mevcut Zaman Damgası: <strong id="current-ts">${now}</strong><button class="tool-btn secondary" id="refresh-ts" style="padding: 2px 8px; font-size:0.8rem; margin-left:10px;">Yenile</button></div><div class="responsive-grid-two-col"><div><h4>Tarih > Zaman Damgası</h4><input type="datetime-local" id="date-to-ts-input" class="tool-input" value="${nowISO}"><div class="result-wrapper" style="margin-top:0;"><input type="text" id="date-to-ts-result" class="tool-input result" readonly><button class="btn-copy" onclick="copyToClipboard(document.getElementById('date-to-ts-result').value)">Kopyala</button></div></div><div><h4>Zaman Damgası > Tarih</h4><input type="number" id="ts-to-date-input" class="tool-input" value="${now}"><div class="result-wrapper" style="margin-top:0;"><input type="text" id="ts-to-date-result" class="tool-input result" readonly><button class="btn-copy" onclick="copyToClipboard(document.getElementById('ts-to-date-result').value)">Kopyala</button></div></div></div>`); const dateInput = app.querySelector('#date-to-ts-input'); const dateResult = app.querySelector('#date-to-ts-result'); const tsInput = app.querySelector('#ts-to-date-input'); const tsResult = app.querySelector('#ts-to-date-result'); const currentTsEl = app.querySelector('#current-ts'); const refreshBtn = app.querySelector('#refresh-ts'); const convertDateToTs = () => { const date = new Date(dateInput.value); dateResult.value = Math.floor(date.getTime() / 1000); }; const convertTsToDate = () => { const ts = parseInt(tsInput.value, 10); if (isNaN(ts)) { tsResult.value = "Geçersiz zaman damgası"; return; } tsResult.value = new Date(ts * 1000).toLocaleString('tr-TR'); }; refreshBtn.addEventListener('click', () => { const newNow = Math.floor(Date.now() / 1000); currentTsEl.textContent = newNow; tsInput.value = newNow; convertTsToDate(); }); dateInput.addEventListener('input', convertDateToTs); tsInput.addEventListener('input', convertTsToDate); convertDateToTs(); convertTsToDate(); } </script> </body> </html><!DOCTYPE html> <html lang="tr"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Pro Web Toolkit</title> <link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css"> <script src="https://cdn.jsdelivr.net/npm/diff@5.1.0/dist/diff.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/svgo@3.3.2/dist/svgo.browser.js"></script> <style> :root { --primary-color: #0d9488; --primary-dark: #0a7c72; --secondary-color: #f59e0b; --danger-color: #ef4444; --success-color: #22c55e; --background-color: #f8fafc; --text-color: #0f172a; --card-background: #ffffff; --input-border-color: #e2e8f0; --header-background: rgba(255, 255, 255, 0.7); --footer-background: #0f172a; --footer-text-color: #cbd5e1; --font-family: 'Inter', sans-serif; --success-bg: rgba(34, 197, 94, 0.1); --danger-bg: rgba(239, 68, 68, 0.1); } body.dark-mode { --background-color: #0f172a; --text-color: #e2e8f0; --card-background: #1e293b; --input-border-color: #334155; --header-background: rgba(30, 41, 59, 0.7); --footer-background: #020617; --footer-text-color: #94a3b8; --success-bg: rgba(34, 197, 94, 0.15); --danger-bg: rgba(239, 68, 68, 0.15); } @keyframes gradient-animation { 0%{background-position:0% 50%} 50%{background-position:100% 50%} 100%{background-position:0% 50%} } @keyframes fade-in { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: translateY(0); } } * { margin: 0; padding: 0; box-sizing: border-box; } html { scroll-behavior: smooth; } body { background-color: var(--background-color); color: var(--text-color); font-family: var(--font-family); line-height: 1.6; transition: background-color 0.3s, color 0.3s; } .container { max-width: 1200px; margin: 0 auto; padding: 0 20px; } header { background-color: var(--header-background); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 1px 0 rgba(0,0,0,0.08); padding: 15px 0; position: sticky; top: 0; z-index: 1000; } .header-container { display: flex; justify-content: space-between; align-items: center; } .logo { height: 45px; } .nav-right { display: flex; align-items: center; gap: 25px; } #main-nav ul { display: flex; list-style: none; gap: 25px; } #main-nav a { color: var(--text-color); text-decoration: none; font-weight: 500; transition: color 0.2s; } #main-nav a:hover { color: var(--primary-color); } .theme-toggle { cursor: pointer; font-size: 20px; background: none; border: none; color: var(--text-color); } .hamburger-menu { display: none; cursor: pointer; font-size: 24px; z-index: 1001; } .hero { text-align: center; padding: 60px 20px; color: white; background: linear-gradient(135deg, #0d9488, #0a7c72, #f59e0b, #ef4444); background-size: 400% 400%; animation: gradient-animation 15s ease infinite; transition: padding 0.3s; } .hero h1 { font-size: 2.8rem; text-shadow: 1px 1px 5px rgba(0,0,0,0.3); transition: font-size 0.3s; } #app { padding: 30px 0; animation: fade-in 0.5s ease-out; min-height: 60vh; } .category-section.hidden { display: none; } .category-title { font-size: 1.8rem; font-weight: 700; color: var(--primary-color); margin-bottom: 20px; margin-top: 30px; border-bottom: 2px solid var(--primary-color); padding-bottom: 10px; } .tool-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 20px; } .tool-link-card { background-color: var(--card-background); border-radius: 12px; padding: 20px; text-decoration: none; color: var(--text-color); border: 1px solid var(--input-border-color); transition: all 0.2s; display: flex; align-items: center; gap: 15px; } .tool-link-card.hidden { display: none; } .tool-link-card:hover { transform: translateY(-5px); box-shadow: 0 10px 20px rgba(0,0,0,0.07); } .tool-link-card .icon { font-size: 1.5rem; color: var(--primary-color); width: 40px; text-align: center; transition: font-size 0.2s; } .tool-link-card .title { font-weight: 600; font-size: 1.1rem; transition: font-size 0.2s; } .tool-view-container { background-color: var(--card-background); border: 1px solid var(--input-border-color); border-radius: 16px; padding: 30px; transition: padding 0.3s; } .tool-view-header { display: flex; align-items: center; gap: 15px; margin-bottom: 25px; } .tool-view-header i { font-size: 2rem; color: var(--primary-color); } .tool-view-header h2 { font-size: 2rem; transition: font-size 0.3s; } .tool-input, textarea.tool-input { width: 100%; padding: 12px; margin-bottom: 15px; border: 1px solid var(--input-border-color); background-color: var(--background-color); color: var(--text-color); border-radius: 6px; font-family: 'Menlo', monospace; resize: vertical;} .tool-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; background-color: var(--primary-color); color: white; border: none; padding: 12px 20px; border-radius: 6px; cursor: pointer; transition: all 0.2s; font-weight: 500; } .tool-btn:disabled { opacity: 0.6; cursor: not-allowed; } .tool-btn.secondary { background-color: transparent; color: var(--primary-color); border: 2px solid var(--primary-color); } .tool-btn.secondary:hover:not(:disabled) { background-color: var(--primary-color); color: white; } .result-wrapper { position: relative; margin-top: 20px;} .btn-copy { position: absolute; top: 10px; right: 10px; background-color: var(--card-background); border: 1px solid var(--input-border-color); color: var(--text-color); cursor: pointer; padding: 5px 10px; border-radius: 5px; font-size: 0.8rem; z-index: 2;} .btn-copy:hover { background-color: var(--primary-color); color:white; } .result { padding: 15px; border-radius: 8px; background-color: var(--background-color); word-break: break-word; min-height: 50px; } .result.placeholder { color: var(--footer-text-color); display: flex; align-items: center; justify-content: center; font-style: italic; } .diff-output del { background-color: var(--danger-bg); color: var(--danger-color); text-decoration: none; padding: 0 2px; border-radius: 3px;} .diff-output ins { background-color: var(--success-bg); color: var(--success-color); text-decoration: none; padding: 0 2px; border-radius: 3px;} .responsive-grid-two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; } .gradient-grid-layout { display: grid; grid-template-columns: 1fr 2fr; gap: 20px; } .case-converter-buttons { display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; } .tool-button-group { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 15px; } .checkbox-group { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 20px; margin: 15px 0; } .checkbox-group label { display: flex; align-items: center; gap: 8px; cursor: pointer; } footer { background-color: var(--footer-background); color: var(--footer-text-color); text-align: center; padding: 40px 20px; margin-top: 50px; } .footer-nav { margin-bottom: 20px; display: flex; justify-content: center; flex-wrap: wrap; gap: 15px 30px; } .footer-nav a { color: var(--footer-text-color); text-decoration: none; } .footer-nav a:hover { color: var(--primary-light); } .footer-copyright a { color: var(--primary-light); text-decoration: none; } #toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 2000; display: flex; flex-direction: column; gap: 10px; } .toast { background-color: var(--footer-background); color: white; padding: 15px 20px; border-radius: 8px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); transition: all 0.5s; animation: toast-in 0.5s; } .toast.success { background-color: var(--primary-dark); } @keyframes toast-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } } #command-palette-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); z-index: 2000; display: none; } #command-palette { position: absolute; top: 20%; left: 50%; transform: translateX(-50%); width: 90%; max-width: 600px; background-color: var(--card-background); border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); overflow: hidden; } #palette-input { width: 100%; padding: 20px; font-size: 1.2rem; border: none; background-color: transparent; color: var(--text-color); border-bottom: 1px solid var(--input-border-color); outline: none; } #palette-results { max-height: 300px; overflow-y: auto; } #palette-results ul { list-style: none; } #palette-results li a { display: flex; padding: 15px 20px; align-items: center; gap: 15px; text-decoration: none; color: var(--text-color); border-bottom: 1px solid var(--input-border-color); } #palette-results li:last-child a { border-bottom: none; } #palette-results li a.selected, #palette-results li a:hover { background-color: var(--primary-color); color: white; } #palette-results li a.selected .icon, #palette-results li a:hover .icon { color: white; } .palette-hint { font-size: 0.8rem; color: #999; padding: 10px 20px; text-align: right; background: var(--background-color); } *:focus-visible { outline: 2px solid var(--primary-color); outline-offset: 2px; box-shadow: 0 0 0 4px rgba(13, 148, 136, 0.2); border-radius: 6px; } #palette-input:focus-visible, .tool-input:focus-visible { box-shadow: none; } @media (max-width: 992px) { .hamburger-menu { display: block; } .nav-right { gap: 20px; } #main-nav { display: none; position: absolute; top: 76px; left: 0; width: 100%; background-color: var(--header-background); backdrop-filter: blur(10px); -webkit-backdrop-filter: blur(10px); box-shadow: 0 5px 10px rgba(0,0,0,0.1); } #main-nav.active { display: block; } #main-nav ul { flex-direction: column; width: 100%; gap: 0; } #main-nav li a { padding: 20px; display: block; text-align: center; border-bottom: 1px solid var(--input-border-color); } #main-nav li:last-child a { border-bottom: none; } .case-converter-buttons { grid-template-columns: 1fr 1fr; } } @media (max-width: 768px) { .hero { padding: 40px 20px; } .hero h1 { font-size: 2.2rem; } .tool-view-container { padding: 20px; } .tool-view-header h2 { font-size: 1.6rem; } .responsive-grid-two-col, .gradient-grid-layout { grid-template-columns: 1fr; } .tool-grid { grid-template-columns: 1fr 1fr; gap: 15px; } .tool-link-card { padding: 15px; gap: 10px; } .tool-link-card .icon { font-size: 1.4rem; } .tool-link-card .title { font-size: 1rem; } } @media (max-width: 480px) { .case-converter-buttons { grid-template-columns: 1fr; } .tool-button-group { flex-direction: column; } .tool-button-group .tool-btn { width: 100%; } .tool-grid { grid-template-columns: 1fr; } } </style> </head> <body> <header> <div class="container header-container"> <a href="#" class="logo-link"><img src="https://images.squarespace-cdn.com/content/v1/5c732c457eb88c53322767d8/1649631719556-E92EBEQKHVS7IIOT87T1/Webtools+Logo+Colour.png" alt="Logo" class="logo"></a> <div class="nav-right"> <nav id="main-nav"> <ul> <li><a href="#" target="_blank">Ana Sayfa</a></li> <li><a href="#" target="_blank">Hakkımızda</a></li> <li><a href="#" target="_blank">İletişim</a></li> <li><a href="#" target="_blank">Gizlilik Politikası</a></li> </ul> </nav> <button class="theme-toggle" id="theme-toggle" aria-label="Temayı değiştir"><i class="fas fa-moon"></i></button> <div class="hamburger-menu" id="hamburger-menu" aria-label="Menüyü aç/kapat"><i class="fas fa-bars"></i></div> </div> </div> </header> <div class="hero"> <div class="container"> <h1>Pro Web Toolkit</h1> <p>Projelerinizi hızlandıracak modern, hızlı ve ücretsiz araçlar.</p> <p style="font-size: 0.9rem; margin-top: 15px; opacity: 0.8;">Hızlı erişim için <strong>Ctrl+K</strong> (veya Cmd+K) basın</p> </div> </div> <main id="app" class="container"></main> <footer> <div class="container"> <div class="footer-nav"> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> <a href="#" target="_blank">Ekle</a> </div> <p class="footer-copyright">© 2025 <a href="#" target="_blank">Siteismi</a> | Tüm Hakları Saklıdır.</p> </div> </footer> <div id="toast-container"></div> <div id="command-palette-overlay"> <div id="command-palette"> <input type="text" id="palette-input" placeholder="Araç ara..."> <div id="palette-results"></div> <div class="palette-hint">Gezinmek için ↑↓, seçmek için Enter, kapatmak için Esc kullanın.</div> </div> </div> <script> // Global utilities function copyToClipboard(text) { if (!text) return; navigator.clipboard.writeText(text).then(() => showToast('Panoya kopyalandı!', 'success')).catch(() => showToast('Kopyalama başarısız oldu.', 'error')); } window.showToast = (message, type = '') => { const container = document.getElementById('toast-container'); if (!container) return; const toast = document.createElement('div'); toast.className = `toast ${type}`; toast.textContent = message; container.appendChild(toast); setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => toast.remove(), 500); }, 3000); }; // Main Application Logic document.addEventListener('DOMContentLoaded', () => { const app = document.getElementById('app'); const tools = [ // Üreticiler { id: 'sifre-uretici', title: 'Şifre Üretici', icon: 'fa-key', category: 'Üreticiler', render: renderPasswordGenerator }, { id: 'css-gradient-uretici', title: 'CSS Gradient Üretici', icon: 'fa-palette', category: 'Üreticiler', render: renderGradientGenerator }, { id: 'qr-kod-uretici', title: 'QR Kod Üretici', icon: 'fa-qrcode', category: 'Üreticiler', render: renderQrCodeGenerator }, { id: 'lorem-ipsum-uretici', title: 'Lorem Ipsum Üretici', icon: 'fa-paragraph', category: 'Üreticiler', render: renderLoremIpsumGenerator }, { id: 'css-box-shadow-uretici', title: 'CSS Box-Shadow Üretici', icon: 'fa-layer-group', category: 'Üreticiler', render: renderBoxShadowGenerator }, // Metin Araçları { id: 'karakter-sayici', title: 'Karakter Sayacı', icon: 'fa-text-width', category: 'Metin Araçları', render: renderCharCounter }, { id: 'metin-karsilastirma', title: 'Metin Karşılaştırma', icon: 'fa-right-left', category: 'Metin Araçları', render: renderDiffChecker }, { id: 'metin-bicimlendirme', title: 'Metin Biçimlendirme', icon: 'fa-spell-check', category: 'Metin Araçları', render: renderCaseConverter }, { id: 'slug-uretici', title: 'URL Slug Üretici', icon: 'fa-link', category: 'Metin Araçları', render: renderSlugGenerator }, // Geliştirici Araçları { id: 'json-formatlayici', title: 'JSON Formatlayıcı', icon: 'fa-code-branch', category: 'Geliştirici Araçları', render: renderJsonFormatter }, { id: 'svg-optimize-edici', title: 'SVG Optimize Edici', icon: 'fa-wand-magic-sparkles', category: 'Geliştirici Araçları', render: renderSvgOptimizer }, { id: 'base64-donusturucu', title: 'Base64 Dönüştürücü', icon: 'fa-retweet', category: 'Geliştirici Araçları', render: renderBase64Converter }, { id: 'url-donusturucu', title: 'URL Dönüştürücü', icon: 'fa-link', category: 'Geliştirici Araçları', render: renderUrlConverter }, { id: 'renk-donusturucu', title: 'Renk Kodu Dönüştürücü', icon: 'fa-eye-dropper', category: 'Geliştirici Araçları', render: renderColorConverter }, { id: 'px-rem-donusturucu', title: 'PX <> REM Dönüştürücü', icon: 'fa-text-height', category: 'Geliştirici Araçları', render: renderPxRemConverter }, { id: 'unix-zaman-damgasi', title: 'UNIX Zaman Damgası', icon: 'fa-clock', category: 'Geliştirici Araçları', render: renderTimestampConverter }, // Güvenlik Araçları { id: 'jwt-ayristirici', title: 'JWT Ayrıştırıcı', icon: 'fa-user-secret', category: 'Güvenlik Araçları', render: renderJwtDecoder }, { id: 'hash-uretici', title: 'Hash Üretici', icon: 'fa-fingerprint', category: 'Güvenlik Araçları', render: renderHashGenerator }, // Medya Araçları { id: 'resim-base64-donusturucu', title: 'Resim > Base64', icon: 'fa-image', category: 'Medya Araçları', render: renderImageToBase64Converter }, // Web Araçları { id: 'ip-bilgisi', title: 'IP Bilgisi', icon: 'fa-globe', category: 'Web Araçları', render: renderIpInfo }, ]; const core = { init() { this.themeManager.init(); this.navigationManager.init(); this.commandPalette.init(tools); window.addEventListener('hashchange', () => this.router(tools)); this.router(tools); }, router(tools) { core.navigationManager.close(); const hash = window.location.hash || '#'; const tool = tools.find(t => `#${t.id}` === hash); app.innerHTML = ''; app.style.animation = 'none'; void app.offsetWidth; app.style.animation = 'fade-in 0.5s ease-out'; if (tool && typeof tool.render === 'function') { tool.render(app, tools); } else { renderHomePage(app, tools); } }, themeManager: { init() { this.themeToggle = document.getElementById('theme-toggle'); const theme = localStorage.getItem('theme') || 'light'; this.apply(theme); this.themeToggle.addEventListener('click', () => this.toggle()); }, apply(theme) { document.body.classList.toggle('dark-mode', theme === 'dark'); this.themeToggle.innerHTML = theme === 'dark' ? '<i class="fas fa-sun"></i>' : '<i class="fas fa-moon"></i>'; }, toggle() { let newTheme = document.body.classList.contains('dark-mode') ? 'light' : 'dark'; localStorage.setItem('theme', newTheme); this.apply(newTheme); } }, navigationManager: { init() { this.hamburgerMenu = document.getElementById('hamburger-menu'); this.mainNav = document.getElementById('main-nav'); this.hamburgerMenu.addEventListener('click', () => this.toggle()); }, toggle() { this.mainNav.classList.toggle('active'); }, close() { this.mainNav.classList.remove('active'); } }, commandPalette: { init(tools) { this.overlay = document.getElementById('command-palette-overlay'); this.input = document.getElementById('palette-input'); this.resultsContainer = document.getElementById('palette-results'); this.selectedIndex = -1; this.tools = tools; document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key === 'k') { e.preventDefault(); this.open(); } if (e.key === 'Escape') this.close(); }); this.overlay.addEventListener('click', (e) => { if (e.target === this.overlay) this.close(); }); this.input.addEventListener('input', () => this.updateResults(this.input.value)); this.input.addEventListener('keydown', (e) => this.navigate(e)); this.resultsContainer.addEventListener('click', (e) => { if (e.target.closest('a')) this.close(); }); }, open() { this.overlay.style.display = 'block'; this.updateResults(''); this.input.focus(); }, close() { this.overlay.style.display = 'none'; }, updateResults(query) { const filteredTools = this.tools.filter(t => t.title.toLowerCase().includes(query.toLowerCase())); let html = '<ul>'; filteredTools.forEach(t => { html += `<li><a href="#${t.id}" data-toolid="${t.id}"><span class="icon"><i class="fas ${t.icon}"></i></span> ${t.title}</a></li>`; }); html += '</ul>'; this.resultsContainer.innerHTML = html; this.selectedIndex = -1; }, navigate(e) { const links = this.resultsContainer.querySelectorAll('a'); if (!links.length) return; if (e.key === 'ArrowDown') this.selectedIndex = (this.selectedIndex + 1) % links.length; else if (e.key === 'ArrowUp') this.selectedIndex = (this.selectedIndex - 1 + links.length) % links.length; else if (e.key === 'Enter' && this.selectedIndex >= 0) { links[this.selectedIndex].click(); return; } else return; e.preventDefault(); links.forEach((link, index) => link.classList.toggle('selected', index === this.selectedIndex)); links[this.selectedIndex].scrollIntoView({ block: 'nearest' }); } } }; core.init(); }); // --- View Renderers --- function renderHomePage(app, tools) { const categories = [...new Set(tools.map(t => t.category))].sort((a,b) => a.localeCompare(b)); let html = `<div style="margin-bottom: 30px; position: relative;"><input type="search" id="tool-search" class="tool-input" placeholder="Araçlarda ara..." style="padding-left: 40px;"><i class="fas fa-search" style="position: absolute; left: 15px; top: 15px; color: var(--footer-text-color);"></i></div>`; categories.forEach(cat => { html += `<div class="category-section" data-category="${cat}"><h2 class="category-title">${cat}</h2><div class="tool-grid">`; tools.filter(t => t.category === cat).forEach(t => { html += `<a href="#${t.id}" class="tool-link-card"><div class="icon"><i class="fas ${t.icon}"></i></div><div class="details"><div class="title">${t.title}</div></div></a>`; }); html += '</div></div>'; }); app.innerHTML = html; const searchInput = document.getElementById('tool-search'); searchInput.addEventListener('keyup', () => { const query = searchInput.value.toLowerCase().trim(); document.querySelectorAll('.tool-link-card').forEach(card => { const title = card.querySelector('.title').textContent.toLowerCase(); card.classList.toggle('hidden', !title.includes(query)); }); document.querySelectorAll('.category-section').forEach(section => { const visibleCards = section.querySelectorAll('.tool-link-card:not(.hidden)'); section.classList.toggle('hidden', visibleCards.length === 0); }); }); } function renderToolView(app, tools, toolId, content) { const tool = tools.find(t => t.id === toolId); if (!tool) return; app.innerHTML = `<div class="tool-view-container"><div class="tool-view-header"><a href="#" aria-label="Ana Sayfaya Dön" style="color:var(--text-color);"><i class="fas fa-arrow-left"></i></a><i class="fas ${tool.icon}"></i><h2>${tool.title}</h2></div>${content}</div>`; } //--- TÜM ARAÇ FONKSİYONLARI --- function renderPasswordGenerator(app, tools) { renderToolView(app, tools, 'sifre-uretici', `<label>Uzunluk: <input type="number" id="passLength" value="16" min="8" max="64" class="tool-input" style="width:100px; display:inline-block;"></label><div class="checkbox-group"><label><input type="checkbox" id="passNum" checked> Rakamlar</label><label><input type="checkbox" id="passSym" checked> Semboller</label></div><button class="tool-btn" id="generatePassBtn">Şifre Oluştur</button><div class="result-wrapper"><textarea id="passwordResult" class="result tool-input placeholder" readonly>Sonucunuz burada görünecek.</textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('passwordResult').value)">Kopyala</button></div>`); app.querySelector('#generatePassBtn').addEventListener('click', () => { const l=app.querySelector('#passLength').value, n=app.querySelector('#passNum').checked, s=app.querySelector('#passSym').checked; let c = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' + (n?'0123456789':'') + (s?'!@#$%^&*()_+-=[]{}|;:,./<>?':''); let p = ''; for (let i=0; i<l; i++) p += c.charAt(Math.floor(Math.random()*c.length)); const resultDiv = app.querySelector('#passwordResult'); resultDiv.value = p; resultDiv.classList.remove('placeholder'); }); } function renderGradientGenerator(app, tools) { const savedState = JSON.parse(localStorage.getItem('gradientState')) || { type: 'linear', angle: 90, colors: ['#0d9488', '#f59e0b'] }; let colorInputsHTML = savedState.colors.map(color => `<input type="color" value="${color}" class="tool-input color-input">`).join(''); renderToolView(app, tools, 'css-gradient-uretici', `<div class="gradient-grid-layout"><div><h4>Ayarlar</h4><label>Tür: <select id="gradType" class="tool-input"><option value="linear">Linear</option><option value="radial">Radial</option></select></label><label>Açı: <input type="range" id="gradAngle" min="0" max="360" value="${savedState.angle}" class="tool-input"></label><div id="gradColors">${colorInputsHTML}</div><button id="addColorBtn" class="tool-btn" style="margin-top:10px;">Renk Ekle</button></div><div class="result-wrapper" style="margin-top:0;"><h4>Önizleme & Kod</h4><div id="gradPreview" style="height: 200px; border-radius: 8px; border: 1px solid var(--input-border-color); margin-bottom:10px;"></div><textarea id="gradResult" class="result tool-input" readonly></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('gradResult').value)">Kopyala</button></div></div>`); app.querySelector('#gradType').value = savedState.type; const update = () => { const state = { type: app.querySelector('#gradType').value, angle: app.querySelector('#gradAngle').value, colors: [...app.querySelectorAll('.color-input')].map(i => i.value) }; const g = (state.type === 'linear') ? `linear-gradient(${state.angle}deg, ${state.colors.join(', ')})` : `radial-gradient(circle, ${state.colors.join(', ')})`; app.querySelector('#gradPreview').style.background = g; app.querySelector('#gradResult').value = `background: ${g};`; localStorage.setItem('gradientState', JSON.stringify(state)); }; app.querySelector('#addColorBtn').addEventListener('click', () => { const i = document.createElement('input'); i.type = 'color'; i.className = 'tool-input color-input'; i.value = '#ffffff'; app.querySelector('#gradColors').appendChild(i); i.addEventListener('input', update); update(); }); app.querySelectorAll('.tool-input').forEach(el => el.addEventListener('input', update)); update(); } function renderQrCodeGenerator(app, tools) { renderToolView(app, tools, 'qr-kod-uretici', `<input type="text" id="qrText" class="tool-input" placeholder="Metin veya URL girin"><button id="qrBtn" class="tool-btn">QR Kod Oluştur</button><div class="result placeholder" id="qrResult" style="text-align: center;">QR kodunuz burada görünecek.</div>`); app.querySelector('#qrBtn').addEventListener('click', () => { const text = app.querySelector('#qrText').value.trim(); if (!text) { showToast('Lütfen bir metin girin.'); return; } const qrUrl = `https://api.qrserver.com/v1/create-qr-code/?size=200x200&data=${encodeURIComponent(text)}`; const resDiv = app.querySelector('#qrResult'); resDiv.innerHTML = `<img src="${qrUrl}" alt="QR Code" style="max-width: 100%; height: auto;">`; resDiv.classList.remove('placeholder'); }); } function renderLoremIpsumGenerator(app, tools) { renderToolView(app, tools, 'lorem-ipsum-uretici', `<div style="display: flex; gap: 20px; flex-wrap: wrap; margin-bottom:15px;"><label>Miktar: <input type="number" id="loremAmount" value="5" min="1" max="100" class="tool-input" style="width:100px; display:inline-block; margin-bottom:0;"></label><label>Tür: <select id="loremType" class="tool-input" style="width:150px; display:inline-block; margin-bottom:0;"><option value="p">Paragraf</option><option value="s">Cümle</option></select></label></div><button id="loremBtn" class="tool-btn">Oluştur</button><div class="result-wrapper"><textarea id="loremResult" class="result tool-input" style="text-align: left; line-height:1.8;" readonly></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('loremResult').value)">Kopyala</button></div>`); const loremBtn = app.querySelector('#loremBtn'); const loremResult = app.querySelector('#loremResult'); const generate = () => { const amount = parseInt(app.querySelector('#loremAmount').value, 10); const type = app.querySelector('#loremType').value; const text = ["Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.", "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.", "Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.", "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo.", "Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt.", "Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem."]; let result = ''; if (type === 'p') { for (let i = 0; i < amount; i++) { let shuffled = text.sort(() => 0.5 - Math.random()); result += shuffled.slice(0, Math.floor(Math.random() * 3) + 3).join(' ') + (i < amount - 1 ? '\n\n' : ''); } } else { let sentences = text.slice(0, amount); result = sentences.join(' '); } loremResult.value = result; }; loremBtn.addEventListener('click', generate); generate(); } function renderCharCounter(app, tools) { renderToolView(app, tools, 'karakter-sayici', `<textarea id="charCounterInput" class="tool-input" rows="8" placeholder="Metninizi buraya yazın..."></textarea><div class="result" style="margin-top:0;">Karakter: <strong id="charCount">0</strong> | Kelime: <strong id="wordCount">0</strong> | Satır: <strong id="lineCount">0</strong></div>`); app.querySelector('#charCounterInput').addEventListener('input', e => { const txt = e.target.value; app.querySelector('#charCount').textContent = txt.length; app.querySelector('#wordCount').textContent = txt.trim() ? txt.trim().split(/\s+/).length : 0; app.querySelector('#lineCount').textContent = txt.split('\n').length; }); } function renderDiffChecker(app, tools) { renderToolView(app, tools, 'metin-karsilastirma', `<div class="responsive-grid-two-col"><textarea id="diffText1" class="tool-input" rows="10" placeholder="Orijinal metin...">${localStorage.getItem('diffCheckerText1') || ''}</textarea><textarea id="diffText2" class="tool-input" rows="10" placeholder="Değiştirilmiş metin...">${localStorage.getItem('diffCheckerText2') || ''}</textarea></div><button id="diffBtn" class="tool-btn" style="margin-top:10px;">Karşılaştır</button><div class="result diff-output placeholder" id="diffResult">Karşılaştırma sonucu burada görünecek.</div>`); const t1 = app.querySelector('#diffText1'), t2 = app.querySelector('#diffText2'); t1.addEventListener('input', () => localStorage.setItem('diffCheckerText1', t1.value)); t2.addEventListener('input', () => localStorage.setItem('diffCheckerText2', t2.value)); app.querySelector('#diffBtn').addEventListener('click', () => { const diff = Diff.diffChars(t1.value, t2.value), frag = document.createDocumentFragment(); diff.forEach(p => { const n = document.createElement(p.added ? 'ins' : p.removed ? 'del' : 'span'); n.appendChild(document.createTextNode(p.value)); frag.appendChild(n); }); const resDiv = app.querySelector('#diffResult'); resDiv.innerHTML = ''; resDiv.appendChild(frag); if (resDiv.innerHTML.trim() === '<span></span>') resDiv.innerHTML = 'Fark bulunamadı.'; else resDiv.classList.remove('placeholder'); }); } function renderCaseConverter(app, tools) { renderToolView(app, tools, 'metin-bicimlendirme', `<textarea id="caseInput" class="tool-input" rows="5" placeholder="Dönüştürülecek metin..."></textarea><div class="case-converter-buttons"><button class="tool-btn" data-case="upper">BÜYÜK</button><button class="tool-btn" data-case="lower">küçük</button><button class="tool-btn" data-case="title">Başlık</button><button class="tool-btn" data-case="sentence">Cümle</button></div><div class="result-wrapper"><textarea id="caseResult" class="tool-input result" rows="5" readonly placeholder="Sonucunuz burada görünecek."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('caseResult').value)">Kopyala</button></div>`); app.querySelectorAll('[data-case]').forEach(btn => btn.addEventListener('click', (e) => { const type = e.target.dataset.case, input = app.querySelector('#caseInput').value; let result = ''; if (type === 'upper') result = input.toUpperCase(); if (type === 'lower') result = input.toLowerCase(); if (type === 'title') result = input.toLowerCase().split(' ').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '); if (type === 'sentence') result = input.toLowerCase().replace(/(^\w{1})|([.!?]\s*\w{1})/g, c => c.toUpperCase()); app.querySelector('#caseResult').value = result; })); } function renderSlugGenerator(app, tools) { const slugify = (text) => { const a = 'àáâäæãåāăąçćčđďèéêëēėęěğǵḧîïíīįìłḿñńǹňôöòóœøōõőṕŕřßśšşșťțûüùúūǘůűųẃẍÿýžźż·/_,:;'; const b = 'aaaaaaaaaacccddeeeeeeeegghiiiiiilmnnnnoooooooooprrrsssssttuuuuuuuuuwxyyzzz------'; const p = new RegExp(a.split('').join('|'), 'g'); return text.toString().toLowerCase().replace(/\s+/g, '-').replace(p, c => b.charAt(a.indexOf(c))).replace(/&/g, '-and-').replace(/[^\w\-]+/g, '').replace(/\-\-+/g, '-').replace(/^-+/, '').replace(/-+$/, '') }; renderToolView(app, tools, 'slug-uretici', `<textarea id="slug-input" class="tool-input" rows="4" placeholder="URL'e dönüştürülecek metni buraya girin..."></textarea><div class="result-wrapper" style="margin-top:0;"><textarea id="slug-result" class="tool-input result" rows="4" readonly placeholder="URL dostu metin burada görünecek..."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('slug-result').value)">Kopyala</button></div>`); const input = app.querySelector('#slug-input'), result = app.querySelector('#slug-result'); input.addEventListener('input', () => { result.value = slugify(input.value); }); } function renderJsonFormatter(app, tools) { const initialJson = localStorage.getItem('jsonFormatterValue') || '{\n "ad": "Ahmet",\n "aktif": true\n}'; renderToolView(app, tools, 'json-formatlayici', `<textarea id="jsonInput" class="tool-input" rows="10">${initialJson}</textarea><div class="tool-button-group"><button id="formatBtn" class="tool-btn">Formatla / Doğrula</button><button id="minifyBtn" class="tool-btn secondary">Küçült</button></div><div class="result placeholder" id="jsonResult">İşlem durumu burada görünecek.</div>`); const i = app.querySelector('#jsonInput'), r = app.querySelector('#jsonResult'); const save = () => localStorage.setItem('jsonFormatterValue', i.value); const process = (action) => { r.classList.remove('placeholder'); try { const parsed = JSON.parse(i.value); if (action === 'format') { i.value = JSON.stringify(parsed, null, 4); r.innerHTML = `<div style="color:var(--success-color)">JSON geçerli ve formatlandı.</div>`; } else { i.value = JSON.stringify(parsed); r.innerHTML = `<div style="color:var(--success-color)">JSON küçültüldü.</div>`; } save(); } catch (e) { r.innerHTML = `<div style="color:var(--danger-color)">Geçersiz JSON: ${e.message}</div>`; } }; i.addEventListener('input', save); app.querySelector('#formatBtn').addEventListener('click', () => process('format')); app.querySelector('#minifyBtn').addEventListener('click', () => process('minify')); } function renderSvgOptimizer(app, tools) { renderToolView(app, tools, 'svg-optimize-edici', `<div class="responsive-grid-two-col"><textarea id="svgInput" class="tool-input" rows="10" placeholder="<svg> kodunu buraya yapıştırın..."></textarea><div class="result-wrapper" style="margin-top:0;"><textarea id="svgOutput" class="tool-input" rows="10" readonly placeholder="Optimize edilmiş SVG kodu..."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('svgOutput').value)">Kopyala</button></div></div><button id="optimizeBtn" class="tool-btn" style="margin-top:10px;">Optimize Et</button><div class="result placeholder" id="svgResult">Optimizasyon sonucu burada görünecek.</div>`); const i = app.querySelector('#svgInput'), o = app.querySelector('#svgOutput'), r = app.querySelector('#svgResult'); app.querySelector('#optimizeBtn').addEventListener('click', () => { if (!i.value.trim()) { showToast('Lütfen optimize edilecek bir SVG girin.'); return; } r.classList.remove('placeholder'); try { const result = SVGO.optimize(i.value); const originalSize = new TextEncoder().encode(i.value).length; const optimizedSize = new TextEncoder().encode(result.data).length; const reduction = (((originalSize - optimizedSize) / originalSize) * 100).toFixed(2); o.value = result.data; r.innerHTML = `<div style="color: var(--success-color)">Başarılı! Boyut <strong>%${reduction}</strong> oranında küçüldü. (${originalSize} bayt -> ${optimizedSize} bayt)</div>`; } catch (e) { r.innerHTML = `<div style="color: var(--danger-color)">Optimizasyon Hatası: ${e.message}</div>`; } }); } function renderBase64Converter(app, tools) { renderToolView(app, tools, 'base64-donusturucu', `<textarea class="tool-input" id="base64Input" rows="5" placeholder="Metin girin veya Base64 kodu yapıştırın..."></textarea><div class="tool-button-group"><button id="b64Encode" class="tool-btn">Encode</button><button id="b64Decode" class="tool-btn secondary">Decode</button></div><div class="result-wrapper"><textarea class="result tool-input" id="base64Result" readonly placeholder="Sonucunuz burada görünecek."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('base64Result').value)">Kopyala</button></div>`); const i = app.querySelector('#base64Input'), r = app.querySelector('#base64Result'); app.querySelector('#b64Encode').addEventListener('click', () => { try { r.value = btoa(unescape(encodeURIComponent(i.value))); } catch (e) { r.value = 'Hata: Geçersiz karakter.'; } }); app.querySelector('#b64Decode').addEventListener('click', () => { try { r.value = decodeURIComponent(escape(atob(i.value))); } catch (e) { r.value = 'Hata: Geçersiz Base64 kodu.'; } }); } function renderUrlConverter(app, tools) { renderToolView(app, tools, 'url-donusturucu', `<textarea class="tool-input" id="urlInput" rows="5" placeholder="URL veya metin girin..."></textarea><div class="tool-button-group"><button id="urlEncode" class="tool-btn">Encode</button><button id="urlDecode" class="tool-btn secondary">Decode</button></div><div class="result-wrapper"><textarea class="result tool-input" id="urlResult" readonly placeholder="Sonucunuz burada görünecek."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('urlResult').value)">Kopyala</button></div>`); const i = app.querySelector('#urlInput'), r = app.querySelector('#urlResult'); app.querySelector('#urlEncode').addEventListener('click', () => { r.value = encodeURIComponent(i.value); }); app.querySelector('#urlDecode').addEventListener('click', () => { try { r.value = decodeURIComponent(i.value); } catch(e) { r.value = "Geçersiz URL kodu."; }}); } function renderColorConverter(app, tools) { renderToolView(app, tools, 'renk-donusturucu', `<input type="text" class="tool-input" id="colorInput" placeholder="HEX Kodu Girin (örn: #0d9488)"><button id="colorBtn" class="tool-btn">Dönüştür</button><div class="result placeholder" id="colorResult">Dönüşüm sonucu burada görünecek.</div>`); const i = app.querySelector('#colorInput'), r = app.querySelector('#colorResult'); const convert = () => { const hexRegex = /^#([0-9A-F]{3}){1,2}$/i; if (!i.value.trim()) return; r.classList.remove('placeholder'); if (!hexRegex.test(i.value.trim())) { r.textContent = 'Geçersiz HEX kodu.'; return; } let hex = i.value.trim().substring(1); if (hex.length === 3) hex = hex.split('').map(c => c + c).join(''); const red = parseInt(hex.substring(0, 2), 16), green = parseInt(hex.substring(2, 4), 16), blue = parseInt(hex.substring(4, 6), 16); const rgbString = `rgb(${red}, ${green}, ${blue})`; r.innerHTML = `<div style="display:flex; align-items:center; gap:15px; cursor: pointer;" onclick="copyToClipboard('${rgbString}')"><div style="width:40px; height:40px; background-color:${rgbString}; border-radius:6px; border:1px solid var(--input-border-color);"></div> <strong>${rgbString}</strong></div>`; }; app.querySelector('#colorBtn').addEventListener('click', convert); i.addEventListener('input', convert); } function renderPxRemConverter(app, tools) { renderToolView(app, tools, 'px-rem-donusturucu', `<div class="responsive-grid-two-col"><div><label for="px-input">Pixel (px)</label><input type="number" id="px-input" class="tool-input" value="16"></div><div><label for="rem-input">Rem</label><input type="number" id="rem-input" class="tool-input" value="1"></div></div><div><label for="base-size">Temel Yazı Tipi Boyutu (px)</label><input type="number" id="base-size" class="tool-input" value="16"></div>`); const pxInput = app.querySelector('#px-input'), remInput = app.querySelector('#rem-input'), baseSizeInput = app.querySelector('#base-size'); const updateValues = (source) => { const base = parseFloat(baseSizeInput.value) || 16; if (source === 'px') { const px = parseFloat(pxInput.value) || 0; remInput.value = parseFloat((px / base).toPrecision(4)); } else { const rem = parseFloat(remInput.value) || 0; pxInput.value = parseFloat((rem * base).toPrecision(4)); } }; pxInput.addEventListener('input', () => updateValues('px')); remInput.addEventListener('input', () => updateValues('rem')); baseSizeInput.addEventListener('input', () => updateValues('px')); } function renderImageToBase64Converter(app, tools) { renderToolView(app, tools, 'resim-base64-donusturucu', `<label for="image-input" class="tool-btn secondary" style="cursor:pointer; text-align:center; display:block;"><i class="fas fa-upload"></i> Resim Seç (Max 2MB)</label><input type="file" id="image-input" accept="image/png, image/jpeg, image/gif, image/svg+xml" style="display:none;"><div id="image-preview" class="placeholder result" style="min-height: 150px; margin-top: 20px;">Resim önizlemesi burada görünecek.</div><div class="result-wrapper"><textarea id="base64-result" class="tool-input result" rows="8" readonly placeholder="Base64 kodu burada görünecek..."></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('base64-result').value)">Kopyala</button></div>`); const input = app.querySelector('#image-input'), preview = app.querySelector('#image-preview'), result = app.querySelector('#base64-result'); const MAX_SIZE = 2 * 1024 * 1024; input.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; if (file.size > MAX_SIZE) { showToast('Hata: Dosya boyutu 2MB\'den büyük olamaz.', 'error'); preview.innerHTML = 'Dosya çok büyük!'; preview.classList.add('placeholder'); result.value = ''; return; } const reader = new FileReader(); reader.onload = (event) => { preview.innerHTML = `<img src="${event.target.result}" style="max-width: 200px; max-height: 200px; border-radius: 8px; border: 1px solid var(--input-border-color);">`; preview.classList.remove('placeholder'); result.value = event.target.result; }; reader.onerror = () => { showToast('Hata: Dosya okunamadı.', 'error'); preview.innerHTML = 'Dosya okunamadı!'; preview.classList.add('placeholder'); result.value = ''; }; reader.readAsDataURL(file); }); } function renderIpInfo(app, tools) { renderToolView(app, tools, 'ip-bilgisi', `<button id="ipBtn" class="tool-btn">IP Bilgimi Göster</button><div class="result placeholder" id="ipResult">IP bilgileriniz için butona tıklayın.</div>`); app.querySelector('#ipBtn').addEventListener('click', async (e) => { const btn = e.target, resDiv = app.querySelector('#ipResult'); btn.disabled = true; btn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Alınıyor...'; resDiv.classList.remove('placeholder'); resDiv.innerHTML = '<i class="fas fa-spinner fa-spin"></i>'; try { const resp = await fetch('https://ipapi.co/json/'); if (!resp.ok) throw new Error('Servis yanıt vermiyor.'); const data = await resp.json(); resDiv.innerHTML = `<div style="text-align:left;"><strong>IP:</strong> ${data.ip}<br><strong>Lokasyon:</strong> ${data.city}, ${data.region}, ${data.country_name}<br><strong>ISP:</strong> ${data.org}</div>`; } catch (err) { resDiv.textContent = `Hata: ${err.message}`; } btn.disabled = false; btn.innerHTML = 'IP Bilgimi Göster'; }); } function renderJwtDecoder(app, tools) { renderToolView(app, tools, 'jwt-ayristirici', `<label for="jwt-input">JWT Token</label><textarea id="jwt-input" class="tool-input" rows="5" placeholder="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJleHAiOjE4OTM0NTYwMDB9.some_signature"></textarea><div id="jwt-status" style="margin-bottom: 15px; font-weight:bold; text-align:center;"></div><h4>Header</h4><div class="result-wrapper"><pre id="jwt-header" class="result" style="min-height: 80px;"></pre><button class="btn-copy" onclick="copyToClipboard(document.getElementById('jwt-header').innerText)">Kopyala</button></div><h4>Payload</h4><div class="result-wrapper"><pre id="jwt-payload" class="result" style="min-height: 120px;"></pre><button class="btn-copy" onclick="copyToClipboard(document.getElementById('jwt-payload').innerText)">Kopyala</button></div>`); const input = app.querySelector('#jwt-input'); const headerEl = app.querySelector('#jwt-header'); const payloadEl = app.querySelector('#jwt-payload'); const statusEl = app.querySelector('#jwt-status'); const decode = () => { const token = input.value.trim(); headerEl.textContent = ''; payloadEl.textContent = ''; statusEl.innerHTML = ''; if (!token) return; try { const [header, payload, signature] = token.split('.'); if (!header || !payload || !signature) throw new Error("Geçersiz JWT formatı"); const decodedHeader = JSON.parse(atob(header.replace(/-/g, '+').replace(/_/g, '/'))); const decodedPayload = JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/'))); headerEl.textContent = JSON.stringify(decodedHeader, null, 2); payloadEl.textContent = JSON.stringify(decodedPayload, null, 2); if(decodedPayload.exp) { const expiryDate = new Date(decodedPayload.exp * 1000); const now = new Date(); if (expiryDate < now) { statusEl.innerHTML = `<div style="color:var(--danger-color);">Token SÜRESİ DOLMUŞ - ${expiryDate.toLocaleString()}</div>`; } else { statusEl.innerHTML = `<div style="color:var(--success-color);">Token GEÇERLİ - Bitiş: ${expiryDate.toLocaleString()}</div>`; } } } catch (e) { payloadEl.textContent = `Hata: ${e.message}. Token doğru formatta veya Base64 ile kodlanmış değil.`; } }; input.addEventListener('input', decode); } function renderHashGenerator(app, tools) { renderToolView(app, tools, 'hash-uretici', `<label for="hash-input">Metin</label><textarea id="hash-input" class="tool-input" rows="5" placeholder="Özetlenecek metni girin..."></textarea><div class="tool-button-group"><button class="tool-btn" data-algo="SHA-256">SHA-256</button><button class="tool-btn" data-algo="SHA-512">SHA-512</button><button class="tool-btn secondary" data-algo="SHA-1">SHA-1</button></div><div id="hash-results"></div>`); const input = app.querySelector('#hash-input'); const generateHash = async (algo) => { const resultsContainer = app.querySelector('#hash-results'); const text = input.value; if (!text) { showToast("Lütfen özetlenecek bir metin girin."); return; } try { const encoder = new TextEncoder(); const data = encoder.encode(text); const hashBuffer = await crypto.subtle.digest(algo, data); const hashArray = Array.from(new Uint8Array(hashBuffer)); const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); let resultHTML = `<div class="result-wrapper" style="margin-top:10px;"><label style="font-weight:bold;">${algo}</label><textarea class="result tool-input" readonly>${hashHex}</textarea><button class="btn-copy" onclick="copyToClipboard('${hashHex}')">Kopyala</button></div>`; resultsContainer.innerHTML = resultHTML; } catch (e) { resultsContainer.innerHTML = `<div class="result" style="color:var(--danger-color)">Hata oluştu: ${e.message}</div>`; } }; app.querySelectorAll('.tool-btn[data-algo]').forEach(btn => { btn.addEventListener('click', (e) => generateHash(e.target.dataset.algo)); }); } function renderBoxShadowGenerator(app, tools) { const initialState = { h: 10, v: 10, b: 5, s: 0, c: '#000000', o: 0.5, i: false }; renderToolView(app, tools, 'css-box-shadow-uretici', `<div class="responsive-grid-two-col"><div><h4>Ayarlar</h4><label>Yatay Ofset (X): <span id="h-val">${initialState.h}</span>px</label><input type="range" id="h-offset" class="tool-input" min="-100" max="100" value="${initialState.h}"><label>Dikey Ofset (Y): <span id="v-val">${initialState.v}</span>px</label><input type="range" id="v-offset" class="tool-input" min="-100" max="100" value="${initialState.v}"><label>Bulanıklık (Blur): <span id="b-val">${initialState.b}</span>px</label><input type="range" id="blur" class="tool-input" min="0" max="100" value="${initialState.b}"><label>Yayılma (Spread): <span id="s-val">${initialState.s}</span>px</label><input type="range" id="spread" class="tool-input" min="-50" max="50" value="${initialState.s}"><label>Opaklık: <span id="o-val">${initialState.o}</span></label><input type="range" id="opacity" class="tool-input" min="0" max="1" step="0.01" value="${initialState.o}"><div style="display:flex; gap:20px; align-items:center; margin-top:10px;"><label>Gölge Rengi: <input type="color" id="color" value="${initialState.c}"></label><label><input type="checkbox" id="inset"> İç Gölge (inset)</label></div></div><div><h4>Önizleme</h4><div id="shadow-preview" style="width: 100%; height: 200px; background-color: var(--secondary-color); border-radius: 12px; display:flex; align-items:center; justify-content:center; color:white; font-weight:bold; transition: box-shadow 0.2s;"></div><div class="result-wrapper" style="margin-top:20px;"><textarea id="shadow-code" class="result tool-input" rows="3" readonly></textarea><button class="btn-copy" onclick="copyToClipboard(document.getElementById('shadow-code').value)">Kopyala</button></div></div></div>`); const controls = { h: app.querySelector('#h-offset'), v: app.querySelector('#v-offset'), b: app.querySelector('#blur'), s: app.querySelector('#spread'), c: app.querySelector('#color'), o: app.querySelector('#opacity'), i: app.querySelector('#inset') }; const vals = { h: app.querySelector('#h-val'), v: app.querySelector('#v-val'), b: app.querySelector('#b-val'), s: app.querySelector('#s-val'), o: app.querySelector('#o-val') }; const preview = app.querySelector('#shadow-preview'); const codeOutput = app.querySelector('#shadow-code'); const hexToRgba = (hex, opacity) => { let r = 0, g = 0, b = 0; r = "0x" + hex[1] + hex[2]; g = "0x" + hex[3] + hex[4]; b = "0x" + hex[5] + hex[6]; return `rgba(${+r}, ${+g}, ${+b}, ${opacity})`; }; const updateShadow = () => { const h = controls.h.value; const v = controls.v.value; const b = controls.b.value; const s = controls.s.value; const c = controls.c.value; const o = controls.o.value; const i = controls.i.checked ? 'inset' : ''; vals.h.textContent = h; vals.v.textContent = v; vals.b.textContent = b; vals.s.textContent = s; vals.o.textContent = o; const rgbaColor = hexToRgba(c, o); const shadowValue = `${i} ${h}px ${v}px ${b}px ${s}px ${rgbaColor}`.trim(); preview.style.boxShadow = shadowValue; codeOutput.value = `box-shadow: ${shadowValue};`; }; Object.values(controls).forEach(control => control.addEventListener('input', updateShadow)); updateShadow(); } function renderTimestampConverter(app, tools) { const now = Math.floor(Date.now() / 1000); const nowISO = new Date(Date.now() - new Date().getTimezoneOffset() * 60000).toISOString().substring(0, 16); renderToolView(app, tools, 'unix-zaman-damgasi', `<div class="result" style="text-align:center; margin-bottom: 20px;">Mevcut Zaman Damgası: <strong id="current-ts">${now}</strong><button class="tool-btn secondary" id="refresh-ts" style="padding: 2px 8px; font-size:0.8rem; margin-left:10px;">Yenile</button></div><div class="responsive-grid-two-col"><div><h4>Tarih > Zaman Damgası</h4><input type="datetime-local" id="date-to-ts-input" class="tool-input" value="${nowISO}"><div class="result-wrapper" style="margin-top:0;"><input type="text" id="date-to-ts-result" class="tool-input result" readonly><button class="btn-copy" onclick="copyToClipboard(document.getElementById('date-to-ts-result').value)">Kopyala</button></div></div><div><h4>Zaman Damgası > Tarih</h4><input type="number" id="ts-to-date-input" class="tool-input" value="${now}"><div class="result-wrapper" style="margin-top:0;"><input type="text" id="ts-to-date-result" class="tool-input result" readonly><button class="btn-copy" onclick="copyToClipboard(document.getElementById('ts-to-date-result').value)">Kopyala</button></div></div></div>`); const dateInput = app.querySelector('#date-to-ts-input'); const dateResult = app.querySelector('#date-to-ts-result'); const tsInput = app.querySelector('#ts-to-date-input'); const tsResult = app.querySelector('#ts-to-date-result'); const currentTsEl = app.querySelector('#current-ts'); const refreshBtn = app.querySelector('#refresh-ts'); const convertDateToTs = () => { const date = new Date(dateInput.value); dateResult.value = Math.floor(date.getTime() / 1000); }; const convertTsToDate = () => { const ts = parseInt(tsInput.value, 10); if (isNaN(ts)) { tsResult.value = "Geçersiz zaman damgası"; return; } tsResult.value = new Date(ts * 1000).toLocaleString('tr-TR'); }; refreshBtn.addEventListener('click', () => { const newNow = Math.floor(Date.now() / 1000); currentTsEl.textContent = newNow; tsInput.value = newNow; convertTsToDate(); }); dateInput.addEventListener('input', convertDateToTs); tsInput.addEventListener('input', convertTsToDate); convertDateToTs(); convertTsToDate(); } </script> </body> </html>