(function () { const $ = (q, c = document) => c.querySelector(q); const $$ = (q, c = document) => Array.from(c.querySelectorAll(q)); // --- theme --- const setTheme = (t) => { document.documentElement.setAttribute('data-theme', t); try { localStorage.setItem('theme', t) } catch { } }; window.setTheme = setTheme; const toast = (msg) => { const el = $('#toast'); if (!el) return; el.textContent = msg; el.classList.add('show'); clearTimeout(el._t); el._t = setTimeout(() => el.classList.remove('show'), 2000); }; const host = () => `${location.protocol}//${location.host}`; // --- IP validators --- function isValidIPv4(ip) { if (!/^\d{1,3}(?:\.\d{1,3}){3}$/.test(ip)) return false; return ip.split('.').every(oct => { if (oct.length > 1 && oct[0] === '0') return oct === '0'; const n = Number(oct); return n >= 0 && n <= 255; }); } function isValidIPv6(ip) { let work = ip; const idx = work.lastIndexOf(':'); if (idx !== -1) { const tail = work.slice(idx + 1); if (/^\d{1,3}(?:\.\d{1,3}){3}$/.test(tail)) { if (!isValidIPv4(tail)) return false; work = work.slice(0, idx) + ':0:0'; } } if (work.split('::').length > 2) return false; const hasCompress = work.includes('::'); const parts = work.split(':').filter(Boolean); if ((!hasCompress && parts.length !== 8) || (hasCompress && parts.length > 7)) return false; return parts.every(g => /^[0-9a-fA-F]{1,4}$/.test(g)); } function isValidIP(ip) { const v = (ip || '').trim(); return isValidIPv4(v) || isValidIPv6(v); } // --- URL helpers --- function normalizeUrlMaybe(v) { const raw = (v || '').trim(); if (!raw) return ''; try { const test = raw.includes('://') ? raw : `https://${raw}`; const u = new URL(test); if (u.protocol !== 'http:' && u.protocol !== 'https:') throw new Error('scheme'); return u.toString(); } catch { return ''; } } function buildLink(url, ip) { if (!url || !ip) return ''; try { const enc = encodeURIComponent(url); const ipClean = (ip || '').trim(); return `${host()}/convert?url=${enc}&ip=${encodeURIComponent(ipClean)}`; } catch { return ''; } } document.addEventListener('click', (e) => { const t = e.target.closest('[data-action="toggle-theme"]'); if (t) { e.preventDefault(); const cur = document.documentElement.getAttribute('data-theme') || 'dark'; setTheme(cur === 'dark' ? 'light' : 'dark'); } }); const urlInput = $('#url-input'); const ipInput = $('#ip-input'); const ipPreset = $('#ip-preset'); const out = $('#generated-link'); const openBtn = $('#open-link'); const copyBtn = $('#copy-btn'); function showError(input, msg) { const id = input.getAttribute('id'); const box = document.querySelector(`.error[data-error-for="${id}"]`); if (box) box.textContent = msg || ''; input.setAttribute('aria-invalid', msg ? 'true' : 'false'); } function updatePreview() { const rawUrl = (urlInput?.value || '').trim(); const ip = (ipInput?.value || '').trim(); if (!ip || !isValidIP(ip)) { if (out) out.value = ''; if (openBtn) { openBtn.setAttribute('href', '#'); openBtn.setAttribute('aria-disabled', 'true'); openBtn.setAttribute('disabled', 'true'); } if (copyBtn) copyBtn.setAttribute('disabled', 'true'); $('.result-box')?.setAttribute('data-state', 'empty'); return; } const normalized = normalizeUrlMaybe(rawUrl); const guessed = rawUrl ? (rawUrl.includes('://') ? rawUrl : `https://${rawUrl}`) : ''; const previewUrl = normalized || guessed; if (!previewUrl) { if (out) out.value = ''; if (openBtn) { openBtn.setAttribute('href', '#'); openBtn.setAttribute('aria-disabled', 'true'); openBtn.setAttribute('disabled', 'true'); } if (copyBtn) copyBtn.setAttribute('disabled', 'true'); $('.result-box')?.setAttribute('data-state', 'empty'); return; } const link = buildLink(previewUrl, ip); if (out) out.value = link; const ok = !!normalized; if (openBtn) { if (ok) { openBtn.setAttribute('href', link); openBtn.setAttribute('aria-disabled', 'false'); openBtn.removeAttribute('disabled'); } else { openBtn.setAttribute('href', '#'); openBtn.setAttribute('aria-disabled', 'true'); openBtn.setAttribute('disabled', 'true'); } } if (copyBtn) copyBtn.toggleAttribute('disabled', !ok); $('.result-box')?.setAttribute('data-state', ok ? 'ready' : 'empty'); } // live update ['input', 'change', 'blur'].forEach(evt => { urlInput?.addEventListener(evt, updatePreview); ipInput?.addEventListener(evt, updatePreview); }); // presets ipPreset?.addEventListener('change', () => { const v = ipPreset.value; if (!v) return; if (v !== 'custom') ipInput.value = v; ipInput.focus(); const ok = isValidIP(ipInput.value.trim()); showError(ipInput, ok ? '' : 'Invalid IP address'); updatePreview(); }); // event delegation document.addEventListener('click', (e) => { let t = e.target; if (t.closest('[data-action="copy"]')) { e.preventDefault(); const btn = t.closest('[data-action="copy"]'); const sel = btn.getAttribute('data-target') || '#generated-link'; const el = $(sel); if (!el) return; const text = el.value || el.textContent || ''; navigator.clipboard?.writeText(text).then(() => { btn.classList.add('copied'); setTimeout(() => btn.classList.remove('copied'), 1200); toast('Link copied'); }).catch(() => { const range = document.createRange(); range.selectNodeContents(el); const selObj = getSelection(); selObj.removeAllRanges(); selObj.addRange(range); try { document.execCommand('copy'); toast('Link copied'); } catch { } selObj.removeAllRanges(); }); } if (t.closest('[data-action="copy-text"]')) { e.preventDefault(); const btn = t.closest('[data-action="copy-text"]'); let text = btn.getAttribute('data-text') || ''; if (!text) return; if (text.startsWith('/')) text = host() + text; navigator.clipboard?.writeText(text).then(() => toast('Copied')); } if (t.closest('[data-action="clear"]')) { e.preventDefault(); urlInput.value = ''; if (ipInput) ipInput.value = ''; if (ipPreset) ipPreset.value = ''; showError(urlInput, ''); showError(ipInput, ''); updatePreview(); urlInput.focus(); } if (t.closest('[data-action="collapse"]')) { e.preventDefault(); const btn = t.closest('[data-action="collapse"]'); const panel = $('#' + (btn.getAttribute('aria-controls') || '')); if (!panel) return; const expanded = btn.getAttribute('aria-expanded') === 'true'; const next = !expanded; btn.setAttribute('aria-expanded', next ? 'true' : 'false'); panel.hidden = !next; panel.style.display = next ? '' : 'none'; const newLabel = next ? 'Collapse' : 'Expand'; btn.textContent = newLabel; btn.setAttribute('aria-label', newLabel); } }); // field-level validation urlInput?.addEventListener('blur', () => { const raw = urlInput.value.trim(); if (!raw) return showError(urlInput, ''); const normalized = normalizeUrlMaybe(raw); showError(urlInput, normalized ? '' : 'Invalid URL'); }); ipInput?.addEventListener('blur', () => { const v = ipInput.value.trim(); if (!v) return showError(ipInput, ''); const ok = isValidIP(v); showError(ipInput, ok ? '' : 'Invalid IP address'); if (!ok) $('.result-box')?.setAttribute('data-state', 'empty'); }); // init (preview + recent-list sync) (function init() { const serverLink = out?.value?.trim(); if (serverLink) { $('.result-box')?.setAttribute('data-state', 'ready'); openBtn?.setAttribute('aria-disabled', 'false'); } else { updatePreview(); } })(); document.addEventListener('DOMContentLoaded', () => { const btn = document.querySelector('[data-action="collapse"]'); const panelId = btn?.getAttribute('aria-controls') || ''; const panel = panelId ? document.getElementById(panelId) : null; if (!btn || !panel) return; const expanded = btn.getAttribute('aria-expanded') === 'true'; panel.hidden = !expanded; panel.style.display = expanded ? '' : 'none'; const label = expanded ? 'Collapse' : 'Expand'; btn.textContent = label; btn.setAttribute('aria-label', label); }); form?.addEventListener('submit', (e) => e.preventDefault()); document.addEventListener('keydown', (e) => { if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'c') { const text = out?.value?.trim(); if (!text) return; navigator.clipboard?.writeText(text).then(() => toast('Link copied')); } }); })(); // --- theme color sync (poza IIFE) --- function updateThemeColor() { const meta = document.querySelector('meta[name="theme-color"]'); if (!meta) return; const isLight = document.documentElement.getAttribute('data-theme') === 'light'; meta.setAttribute('content', isLight ? '#f6f8fb' : '#0f1115'); } const _setTheme = setTheme; setTheme = function (t) { _setTheme(t); updateThemeColor(); }; document.addEventListener('DOMContentLoaded', updateThemeColor);