first commit

This commit is contained in:
Patrick
2026-05-01 20:02:13 +02:00
commit 75fb753fc0
77 changed files with 4793 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
// ═══════════════════════════════════════════════════════
// Particles
// ═══════════════════════════════════════════════════════
(function initParticles() {
const c = document.getElementById('particles');
for (let i = 0; i < 30; i++) {
const p = document.createElement('div');
p.className = 'particle';
p.style.left = Math.random() * 100 + '%';
p.style.animationDelay = Math.random() * 15 + 's';
p.style.animationDuration = (10 + Math.random() * 20) + 's';
p.style.width = p.style.height = (1 + Math.random() * 3) + 'px';
c.appendChild(p);
}
})();
// ═══════════════════════════════════════════════════════
// Navigation
// ═══════════════════════════════════════════════════════
function showPage(name) {
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
document.getElementById('page-' + name).classList.add('active');
window.scrollTo({ top: 0, behavior: 'smooth' });
}
// ═══════════════════════════════════════════════════════
// Search & Filter
// ═══════════════════════════════════════════════════════
function filterTools(query) {
const q = query.toLowerCase();
document.querySelectorAll('.tool-card').forEach(card => {
const name = card.dataset.name.toLowerCase();
card.style.display = name.includes(q) ? '' : 'none';
});
}
function filterCategory(cat, btn) {
document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active'));
if (btn) btn.classList.add('active');
document.querySelectorAll('.tool-card').forEach(card => {
card.style.display = (cat === 'all' || card.dataset.cat === cat) ? '' : 'none';
});
}
// Ctrl+K to focus search
document.addEventListener('keydown', e => {
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
e.preventDefault();
const el = document.getElementById('searchInput');
if (el) el.focus();
}
});
// ═══════════════════════════════════════════════════════
// Helpers
// ═══════════════════════════════════════════════════════
// ✦ Change this for production deployment:
const BASE_URL = window.location.origin; // e.g. "https://winnieapi-v2.yourdomain.com"
function copyText(text) {
navigator.clipboard.writeText(text).then(() => {
const t = document.getElementById('copyToast');
t.classList.add('show');
setTimeout(() => t.classList.remove('show'), 1500);
});
}
function copyOutput(id) { copyText(document.getElementById(id).value); }
function setStatus(id, type, msg) {
const el = document.getElementById(id);
el.className = 'status ' + type;
el.textContent = msg;
}
async function apiPost(url, body) {
const r = await fetch(BASE_URL + url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
return r.json();
}
async function apiGet(url) {
const r = await fetch(BASE_URL + url);
return r.json();
}
function toggleApiUsage(btn) {
btn.classList.toggle('open');
const body = btn.nextElementSibling;
body.classList.toggle('open');
}
function copyApiCode(btn) {
const code = btn.parentElement.textContent.replace('Copy', '').trim();
copyText(code);
}
+14
View File
@@ -0,0 +1,14 @@
// ═══════════════════════════════════════════════════════
// ASCII Art Generator
// ═══════════════════════════════════════════════════════
async function generateAscii() {
const text = document.getElementById('asciiInput').value.trim();
if (!text) { document.getElementById('asciiOutput').value = ''; return; }
const d = await apiPost('/api/ascii/generate', { text });
if (d.success) {
document.getElementById('asciiOutput').value = d.result;
setStatus('asciiStatus', 'success', 'Generated ✓');
} else setStatus('asciiStatus', 'error', d.error);
}
setTimeout(generateAscii, 200);
+11
View File
@@ -0,0 +1,11 @@
// ═══════════════════════════════════════════════════════
// Base64
// ═══════════════════════════════════════════════════════
async function base64Op(op) {
const text = document.getElementById('b64Input').value;
if (!text) return setStatus('b64Status','error','Enter some text.');
const d = await apiPost('/api/base64/' + op, { text });
if (d.success) { document.getElementById('b64Output').value = d.result; setStatus('b64Status','success', op === 'encode' ? 'Encoded ✓' : 'Decoded ✓'); }
else setStatus('b64Status','error', d.error);
}
+22
View File
@@ -0,0 +1,22 @@
// ═══════════════════════════════════════════════════════
// Byte Size Converter
// ═══════════════════════════════════════════════════════
async function convertBytes() {
const value = parseFloat(document.getElementById('byteValue').value);
if (isNaN(value)) return;
const unit = document.getElementById('byteUnit').value;
const mode = document.querySelector('input[name="byteMode"]:checked').value;
const d = await apiPost('/api/bytes/convert', { value, unit, mode });
if (d.success) {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
document.getElementById('byteResults').innerHTML = units.map(u =>
`<div class="result-row">
<div class="label">${u}</div>
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${d[u]}</div>
</div>`
).join('');
setStatus('byteStatus', 'success', `Converted (${mode === 'binary' ? '1024' : '1000'} base) ✓`);
} else setStatus('byteStatus', 'error', d.error);
}
setTimeout(convertBytes, 200);
+16
View File
@@ -0,0 +1,16 @@
// ═══════════════════════════════════════════════════════
// Case Converter
// ═══════════════════════════════════════════════════════
async function convertCase() {
const text = document.getElementById('caseInput').value;
if (!text) return;
const d = await apiPost('/api/text/case', { text });
if (d.success) {
document.getElementById('caseResults').innerHTML = [
['UPPERCASE', d.uppercase], ['lowercase', d.lowercase], ['Title Case', d.titleCase],
['camelCase', d.camelCase], ['snake_case', d.snakeCase], ['kebab-case', d.kebabCase],
['dot.case', d.dotCase], ['desreveR', d.reversed]
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
}
}
+36
View File
@@ -0,0 +1,36 @@
// ═══════════════════════════════════════════════════════
// Chmod Calculator
// ═══════════════════════════════════════════════════════
async function chmodFromNumeric() {
const val = document.getElementById('chmodNumeric').value.trim();
if (!val || !/^[0-7]{3,4}$/.test(val)) return;
const d = await apiPost('/api/chmod/calculate', { numeric: val });
if (d.success) updateChmodUI(d);
}
async function chmodFromSymbolic() {
const val = document.getElementById('chmodSymbolic').value.trim();
if (!val || val.length < 9) return;
const d = await apiPost('/api/chmod/calculate', { symbolic: val });
if (d.success) updateChmodUI(d);
}
function updateChmodUI(d) {
document.getElementById('chmodNumeric').value = d.numeric;
document.getElementById('chmodSymbolic').value = d.symbolic;
document.getElementById('chmodCommand').textContent = `chmod ${d.numeric} filename`;
const roles = ['owner', 'group', 'others'];
const perms = ['read', 'write', 'execute'];
let html = '<div style="display:grid;grid-template-columns:100px repeat(3,1fr);gap:6px;font-size:0.82rem;">';
html += '<div></div>' + perms.map(p => `<div style="text-align:center;color:var(--text-muted);text-transform:uppercase;font-size:0.7rem;font-weight:600;">${p}</div>`).join('');
for (const role of roles) {
html += `<div style="color:var(--text-secondary);font-weight:600;text-transform:capitalize;">${role}</div>`;
for (const perm of perms) {
const on = d[role][perm];
html += `<div style="text-align:center;padding:6px;background:${on ? 'var(--green)' : 'var(--bg-input)'};color:${on ? '#fff' : 'var(--text-muted)'};border-radius:var(--radius-sm);font-weight:600;border:1px solid var(--border);">${on ? '✓' : '—'}</div>`;
}
}
html += '</div>';
document.getElementById('chmodMatrix').innerHTML = html;
setStatus('chmodStatus', 'success', `${d.numeric} = ${d.symbolic}`);
}
setTimeout(chmodFromNumeric, 200);
+21
View File
@@ -0,0 +1,21 @@
// ═══════════════════════════════════════════════════════
// Color Converter
// ═══════════════════════════════════════════════════════
async function convertColor() {
const color = document.getElementById('colorInput').value.trim();
if (!color) return setStatus('colorStatus','error','Enter a color.');
const d = await apiPost('/api/color/convert', { color });
if (d.success) {
document.getElementById('colorPreview').style.background = d.hex;
document.getElementById('colorPicker').value = d.hex;
const c = document.getElementById('colorResults');
c.innerHTML = [
['HEX', d.hex], ['RGB', d.rgb], ['HSL', d.hsl]
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
setStatus('colorStatus','success','Converted ✓');
} else setStatus('colorStatus','error', d.error);
}
// Keyboard shortcut
document.getElementById('colorInput').addEventListener('keydown', e => { if(e.key==='Enter') convertColor(); });
+21
View File
@@ -0,0 +1,21 @@
// ═══════════════════════════════════════════════════════
// Word Counter
// ═══════════════════════════════════════════════════════
async function updateCounter() {
const text = document.getElementById('counterInput').value;
const d = await apiPost('/api/text/stats', { text });
if (d.success) {
document.getElementById('counterResults').innerHTML = `
<div class="ip-grid">
<div class="ip-card"><div class="label">Characters</div><div class="value">${d.characters}</div></div>
<div class="ip-card"><div class="label">No Spaces</div><div class="value">${d.charactersNoSpaces}</div></div>
<div class="ip-card"><div class="label">Words</div><div class="value">${d.words}</div></div>
<div class="ip-card"><div class="label">Sentences</div><div class="value">${d.sentences}</div></div>
<div class="ip-card"><div class="label">Paragraphs</div><div class="value">${d.paragraphs}</div></div>
<div class="ip-card"><div class="label">Lines</div><div class="value">${d.lines}</div></div>
<div class="ip-card"><div class="label">Reading Time</div><div class="value">${d.readingTime}</div></div>
</div>
${d.topChars.length ? '<div class="panel-label" style="margin-top:14px;">Top Characters</div><div style="display:flex;gap:6px;flex-wrap:wrap;">' + d.topChars.map(([ch,n]) => `<span style="background:var(--bg-input);border:1px solid var(--border);padding:4px 10px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:0.8rem;"><strong style="color:var(--accent);">${ch}</strong> <span style="color:var(--text-muted);">×${n}</span></span>`).join('') + '</div>' : ''}`;
}
}
+28
View File
@@ -0,0 +1,28 @@
// ═══════════════════════════════════════════════════════
// Cron Parser
// ═══════════════════════════════════════════════════════
function setCronPreset(expr) {
document.getElementById('cronInput').value = expr;
parseCron();
}
async function parseCron() {
const expr = document.getElementById('cronInput').value.trim();
if (!expr) return setStatus('cronStatus', 'error', 'Enter a cron expression.');
const d = await apiPost('/api/cron/parse', { expression: expr });
if (d.success) {
document.getElementById('cronResult').style.display = 'block';
document.getElementById('cronDescription').textContent = d.description;
document.getElementById('cronFields').innerHTML = Object.entries(d.fields).map(([k, v]) =>
`<div class="result-row"><div class="label">${k}</div><div class="value">${v}</div></div>`
).join('');
document.getElementById('cronNextRuns').innerHTML = d.nextRuns.map((t, i) =>
`<div class="result-row"><div class="label">Run #${i + 1}</div><div class="value" onclick="copyText(this.textContent)" title="Click to copy">${t}</div></div>`
).join('');
setStatus('cronStatus', 'success', 'Parsed ✓');
} else {
document.getElementById('cronResult').style.display = 'none';
setStatus('cronStatus', 'error', d.error);
}
}
+13
View File
@@ -0,0 +1,13 @@
// ═══════════════════════════════════════════════════════
// CSS Minifier
// ═══════════════════════════════════════════════════════
async function minifyCSS() {
const css = document.getElementById('cssInput').value;
if (!css) return setStatus('cssStatus','error','Enter some CSS.');
const d = await apiPost('/api/css/minify', { css });
if (d.success) {
document.getElementById('cssOutput').value = d.result;
setStatus('cssStatus','success', `Minified ✓ — saved ${d.saved} chars (${d.percentage}% smaller)`);
} else setStatus('cssStatus','error', d.error);
}
+23
View File
@@ -0,0 +1,23 @@
// ═══════════════════════════════════════════════════════
// Diff Checker
// ═══════════════════════════════════════════════════════
function computeDiff() {
const a = document.getElementById('diffA').value.split('\n');
const b = document.getElementById('diffB').value.split('\n');
const max = Math.max(a.length, b.length);
let html = '';
for (let i = 0; i < max; i++) {
const la = a[i] !== undefined ? a[i] : null;
const lb = b[i] !== undefined ? b[i] : null;
const esc = s => s.replace(/[<>&]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;'}[c]));
if (la === null) html += `<div class="diff-line diff-add">+ ${esc(lb)}</div>`;
else if (lb === null) html += `<div class="diff-line diff-del">- ${esc(la)}</div>`;
else if (la === lb) html += `<div class="diff-line diff-same"> ${esc(la)}</div>`;
else {
html += `<div class="diff-line diff-del">- ${esc(la)}</div>`;
html += `<div class="diff-line diff-add">+ ${esc(lb)}</div>`;
}
}
document.getElementById('diffOutput').innerHTML = html || '<span style="color:var(--text-muted);">Texts are identical.</span>';
}
+22
View File
@@ -0,0 +1,22 @@
// ═══════════════════════════════════════════════════════
// ENV ↔ JSON Converter
// ═══════════════════════════════════════════════════════
async function envToJson() {
const input = document.getElementById('envInput').value.trim();
if (!input) return setStatus('envJsonStatus', 'error', 'Paste some .env content first.');
const d = await apiPost('/api/convert/env-to-json', { env: input });
if (d.success) {
document.getElementById('envJsonOutput').value = JSON.stringify(d.result, null, 2);
setStatus('envJsonStatus', 'success', `Converted ✓ — ${Object.keys(d.result).length} variables`);
} else setStatus('envJsonStatus', 'error', d.error);
}
async function jsonToEnv() {
const input = document.getElementById('envJsonInput').value.trim();
if (!input) return setStatus('envJsonStatus', 'error', 'Paste some JSON first.');
const d = await apiPost('/api/convert/json-to-env', { json: input });
if (d.success) {
document.getElementById('envJsonOutput').value = d.result;
setStatus('envJsonStatus', 'success', `Converted ✓ — ${d.count} variables`);
} else setStatus('envJsonStatus', 'error', d.error);
}
+11
View File
@@ -0,0 +1,11 @@
// ═══════════════════════════════════════════════════════
// String Escape
// ═══════════════════════════════════════════════════════
async function escapeOp(op) {
const text = document.getElementById('escInput').value;
if (!text) return setStatus('escStatus','error','Enter text.');
const d = await apiPost('/api/' + op, { text });
if (d.success) { document.getElementById('escOutput').value = d.result; setStatus('escStatus','success', op === 'escape' ? 'Escaped ✓' : 'Unescaped ✓'); }
else setStatus('escStatus','error', d.error);
}
+18
View File
@@ -0,0 +1,18 @@
// ═══════════════════════════════════════════════════════
// Hash
// ═══════════════════════════════════════════════════════
async function generateHash() {
const text = document.getElementById('hashInput').value;
if (!text) return setStatus('hashStatus','error','Enter text to hash.');
const d = await apiPost('/api/hash', { text });
if (d.success) {
const c = document.getElementById('hashResults');
c.innerHTML = Object.entries(d.hashes).map(([algo, hash]) => `
<div class="result-row">
<div class="label">${algo.toUpperCase()}</div>
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${hash}</div>
</div>`).join('');
setStatus('hashStatus','success','Generated ✓');
} else setStatus('hashStatus','error', d.error);
}
+20
View File
@@ -0,0 +1,20 @@
// ═══════════════════════════════════════════════════════
// HMAC Generator
// ═══════════════════════════════════════════════════════
async function generateHmac() {
const message = document.getElementById('hmacMessage').value;
const secret = document.getElementById('hmacSecret').value;
const algorithm = document.getElementById('hmacAlgo').value;
if (!message) return setStatus('hmacStatus', 'error', 'Enter a message.');
if (!secret) return setStatus('hmacStatus', 'error', 'Enter a secret key.');
const d = await apiPost('/api/hmac', { message, secret, algorithm });
if (d.success) {
document.getElementById('hmacResults').innerHTML = `
<div class="result-row">
<div class="label">${d.algorithm.toUpperCase()} HMAC</div>
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${d.hmac}</div>
</div>`;
setStatus('hmacStatus', 'success', 'Generated ✓');
} else setStatus('hmacStatus', 'error', d.error);
}
+11
View File
@@ -0,0 +1,11 @@
// ═══════════════════════════════════════════════════════
// HTML Entities
// ═══════════════════════════════════════════════════════
async function htmlEntOp(op) {
const text = document.getElementById('htmlEntInput').value;
if (!text) return setStatus('htmlEntStatus','error','Enter text.');
const d = await apiPost('/api/html/' + op, { text });
if (d.success) { document.getElementById('htmlEntOutput').value = d.result; setStatus('htmlEntStatus','success', op === 'encode' ? 'Encoded ✓' : 'Decoded ✓'); }
else setStatus('htmlEntStatus','error', d.error);
}
+103
View File
@@ -0,0 +1,103 @@
// ═══════════════════════════════════════════════════════
// HTTP Status Codes Reference
// ═══════════════════════════════════════════════════════
const HTTP_CODES = [
// 1xx
{ code: 100, text: 'Continue', desc: 'The server has received the request headers, and the client should proceed to send the request body.', cat: '1xx' },
{ code: 101, text: 'Switching Protocols', desc: 'The server is switching protocols as requested by the client (e.g., to WebSocket).', cat: '1xx' },
{ code: 102, text: 'Processing', desc: 'The server has received and is processing the request, but no response is available yet.', cat: '1xx' },
{ code: 103, text: 'Early Hints', desc: 'Used to return some response headers before the final HTTP message.', cat: '1xx' },
// 2xx
{ code: 200, text: 'OK', desc: 'The request has succeeded. The meaning depends on the HTTP method used.', cat: '2xx' },
{ code: 201, text: 'Created', desc: 'The request has been fulfilled and a new resource has been created.', cat: '2xx' },
{ code: 202, text: 'Accepted', desc: 'The request has been accepted for processing, but processing is not complete.', cat: '2xx' },
{ code: 203, text: 'Non-Authoritative Information', desc: 'The returned meta-information is from a local or third-party copy.', cat: '2xx' },
{ code: 204, text: 'No Content', desc: 'The server has fulfilled the request but does not need to return an entity-body.', cat: '2xx' },
{ code: 205, text: 'Reset Content', desc: 'The server has fulfilled the request and the user agent should reset the document view.', cat: '2xx' },
{ code: 206, text: 'Partial Content', desc: 'The server is delivering only part of the resource due to a range header sent by the client.', cat: '2xx' },
{ code: 207, text: 'Multi-Status', desc: 'A Multi-Status response conveys information about multiple resources (WebDAV).', cat: '2xx' },
{ code: 208, text: 'Already Reported', desc: 'Members of a DAV binding have already been enumerated in a previous reply.', cat: '2xx' },
{ code: 226, text: 'IM Used', desc: 'The server has fulfilled a GET request for the resource with instance manipulations applied.', cat: '2xx' },
// 3xx
{ code: 300, text: 'Multiple Choices', desc: 'There are multiple options for the resource, each with specific attributes.', cat: '3xx' },
{ code: 301, text: 'Moved Permanently', desc: 'This and all future requests should be directed to the given URI.', cat: '3xx' },
{ code: 302, text: 'Found', desc: 'The resource was found at a different URI temporarily.', cat: '3xx' },
{ code: 303, text: 'See Other', desc: 'The response can be found at another URI using a GET method.', cat: '3xx' },
{ code: 304, text: 'Not Modified', desc: 'The resource has not been modified since the last request.', cat: '3xx' },
{ code: 307, text: 'Temporary Redirect', desc: 'The request should be repeated with another URI, but future requests should still use the original URI.', cat: '3xx' },
{ code: 308, text: 'Permanent Redirect', desc: 'This and all future requests should be directed to the given URI (no method change).', cat: '3xx' },
// 4xx
{ code: 400, text: 'Bad Request', desc: 'The server cannot process the request due to a client error (e.g., malformed request syntax).', cat: '4xx' },
{ code: 401, text: 'Unauthorized', desc: 'Authentication is required and has failed or has not been provided.', cat: '4xx' },
{ code: 402, text: 'Payment Required', desc: 'Reserved for future use. Some APIs use this for rate limiting or paid features.', cat: '4xx' },
{ code: 403, text: 'Forbidden', desc: 'The server understood the request but refuses to authorize it.', cat: '4xx' },
{ code: 404, text: 'Not Found', desc: 'The requested resource could not be found on this server.', cat: '4xx' },
{ code: 405, text: 'Method Not Allowed', desc: 'The request method is not supported for the requested resource.', cat: '4xx' },
{ code: 406, text: 'Not Acceptable', desc: 'The requested resource can only generate content not acceptable per the Accept headers.', cat: '4xx' },
{ code: 407, text: 'Proxy Authentication Required', desc: 'The client must authenticate itself with the proxy.', cat: '4xx' },
{ code: 408, text: 'Request Timeout', desc: 'The server timed out waiting for the request.', cat: '4xx' },
{ code: 409, text: 'Conflict', desc: 'The request could not be processed because of conflict in the current state of the resource.', cat: '4xx' },
{ code: 410, text: 'Gone', desc: 'The resource requested is no longer available and will not be available again.', cat: '4xx' },
{ code: 411, text: 'Length Required', desc: 'The request did not specify the length of its content, which is required.', cat: '4xx' },
{ code: 412, text: 'Precondition Failed', desc: 'The server does not meet one of the preconditions set by the requester.', cat: '4xx' },
{ code: 413, text: 'Payload Too Large', desc: 'The request is larger than the server is willing or able to process.', cat: '4xx' },
{ code: 414, text: 'URI Too Long', desc: 'The URI provided was too long for the server to process.', cat: '4xx' },
{ code: 415, text: 'Unsupported Media Type', desc: 'The request entity has a media type which the server does not support.', cat: '4xx' },
{ code: 416, text: 'Range Not Satisfiable', desc: 'The client has asked for a portion of the file that the server cannot supply.', cat: '4xx' },
{ code: 418, text: "I'm a Teapot", desc: "The server refuses to brew coffee because it is, permanently, a teapot. (RFC 2324)", cat: '4xx' },
{ code: 422, text: 'Unprocessable Entity', desc: 'The request was well-formed but was unable to be followed due to semantic errors.', cat: '4xx' },
{ code: 425, text: 'Too Early', desc: 'The server is unwilling to risk processing a request that might be replayed.', cat: '4xx' },
{ code: 429, text: 'Too Many Requests', desc: 'The user has sent too many requests in a given amount of time (rate limiting).', cat: '4xx' },
{ code: 451, text: 'Unavailable For Legal Reasons', desc: 'The resource is unavailable due to legal demands (e.g., censorship or government order).', cat: '4xx' },
// 5xx
{ code: 500, text: 'Internal Server Error', desc: 'An unexpected condition was encountered by the server.', cat: '5xx' },
{ code: 501, text: 'Not Implemented', desc: 'The server does not support the functionality required to fulfill the request.', cat: '5xx' },
{ code: 502, text: 'Bad Gateway', desc: 'The server received an invalid response from an upstream server.', cat: '5xx' },
{ code: 503, text: 'Service Unavailable', desc: 'The server is currently unable to handle the request (overloaded or maintenance).', cat: '5xx' },
{ code: 504, text: 'Gateway Timeout', desc: 'The server did not receive a timely response from an upstream server.', cat: '5xx' },
{ code: 505, text: 'HTTP Version Not Supported', desc: 'The server does not support the HTTP protocol version used in the request.', cat: '5xx' },
{ code: 507, text: 'Insufficient Storage', desc: 'The server is unable to store the representation needed to complete the request.', cat: '5xx' },
{ code: 508, text: 'Loop Detected', desc: 'The server detected an infinite loop while processing the request.', cat: '5xx' },
{ code: 511, text: 'Network Authentication Required', desc: 'The client needs to authenticate to gain network access (captive portal).', cat: '5xx' },
];
const catColors = { '1xx': 'var(--accent)', '2xx': 'var(--green)', '3xx': 'var(--cyan)', '4xx': 'var(--orange)', '5xx': 'var(--red)' };
let currentHttpCat = 'all';
function renderHttpStatus(filtered) {
const list = document.getElementById('httpStatusList');
if (filtered.length === 0) {
list.innerHTML = '<div style="text-align:center;color:var(--text-muted);padding:32px;">No matching status codes found.</div>';
return;
}
list.innerHTML = filtered.map(c => `
<div class="result-row" style="flex-direction:column;align-items:flex-start;gap:6px;margin-bottom:8px;cursor:pointer;" onclick="copyText('${c.code} ${c.text}')">
<div style="display:flex;align-items:center;gap:10px;width:100%;">
<span style="font-family:var(--font-mono);font-size:1.1rem;font-weight:800;color:${catColors[c.cat]};">${c.code}</span>
<span style="font-weight:600;color:var(--text-primary);">${c.text}</span>
<span class="tag" style="background:${catColors[c.cat]}20;color:${catColors[c.cat]};margin-left:auto;">${c.cat}</span>
</div>
<div style="font-size:0.8rem;color:var(--text-secondary);line-height:1.5;">${c.desc}</div>
</div>
`).join('');
}
function filterHttpStatus() {
const q = document.getElementById('httpStatusInput').value.toLowerCase();
let filtered = HTTP_CODES;
if (currentHttpCat !== 'all') filtered = filtered.filter(c => c.cat === currentHttpCat);
if (q) filtered = filtered.filter(c => String(c.code).includes(q) || c.text.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q));
renderHttpStatus(filtered);
}
function filterHttpCat(cat, btn) {
currentHttpCat = cat;
document.querySelectorAll('#httpCatBtns .btn').forEach(b => {
b.className = b === btn ? 'btn btn-sm btn-primary' : 'btn btn-sm btn-secondary';
});
filterHttpStatus();
}
// Initialize
setTimeout(() => renderHttpStatus(HTTP_CODES), 100);
+23
View File
@@ -0,0 +1,23 @@
// ═══════════════════════════════════════════════════════
// IP Lookup
// ═══════════════════════════════════════════════════════
async function lookupIP() {
const ip = document.getElementById('ipInput').value.trim();
setStatus('ipStatus','info','Looking up...');
const d = await apiGet(ip ? '/api/ip/' + ip : '/api/ip');
if (d.success && d.status !== 'fail') {
const fields = [
['IP Address', d.query], ['Country', d.country], ['Region', d.regionName],
['City', d.city], ['ZIP', d.zip], ['Latitude', d.lat],
['Longitude', d.lon], ['Timezone', d.timezone], ['ISP', d.isp],
['Organization', d.org], ['AS', d.as]
];
document.getElementById('ipResults').innerHTML = fields.map(([l, v]) =>
`<div class="ip-card"><div class="label">${l}</div><div class="value">${v || '—'}</div></div>`).join('');
setStatus('ipStatus','success','Lookup complete ✓');
} else setStatus('ipStatus','error', d.message || d.error || 'Lookup failed');
}
// Keyboard shortcut
document.getElementById('ipInput').addEventListener('keydown', e => { if(e.key==='Enter') lookupIP(); });
+36
View File
@@ -0,0 +1,36 @@
// ═══════════════════════════════════════════════════════
// JSON Formatter
// ═══════════════════════════════════════════════════════
async function formatJSON() {
const input = document.getElementById('jsonInput').value.trim();
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
const iv = document.getElementById('jsonIndent').value;
const indent = iv === '\\t' ? '\t' : parseInt(iv);
const d = await apiPost('/api/json/format', { json: input, indent });
if (d.success) { document.getElementById('jsonOutput').value = d.result; setStatus('jsonStatus','success','Formatted ✓'); }
else setStatus('jsonStatus','error', d.error);
}
async function minifyJSON() {
const input = document.getElementById('jsonInput').value.trim();
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
const d = await apiPost('/api/json/minify', { json: input });
if (d.success) { document.getElementById('jsonOutput').value = d.result; setStatus('jsonStatus','success','Minified ✓'); }
else setStatus('jsonStatus','error', d.error);
}
async function validateJSON() {
const input = document.getElementById('jsonInput').value.trim();
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
const d = await apiPost('/api/json/validate', { json: input });
setStatus('jsonStatus', d.valid ? 'success' : 'error', d.message);
}
function clearJSON() {
document.getElementById('jsonInput').value=''; document.getElementById('jsonOutput').value='';
document.getElementById('jsonStatus').className='status';
}
// Keyboard shortcuts
document.getElementById('jsonInput').addEventListener('keydown', e => {
if(e.key==='Enter' && (e.ctrlKey||e.metaKey)) formatJSON();
if(e.key==='Tab') { e.preventDefault(); const t=e.target,s=t.selectionStart,en=t.selectionEnd; t.value=t.value.substring(0,s)+' '+t.value.substring(en); t.selectionStart=t.selectionEnd=s+2; }
});
+41
View File
@@ -0,0 +1,41 @@
// ═══════════════════════════════════════════════════════
// JSON ↔ CSV Converter
// ═══════════════════════════════════════════════════════
let lastJsoncsvType = 'csv'; // track last conversion type for download
async function jsonToCsv() {
const input = document.getElementById('jsoncsvJsonInput').value.trim();
if (!input) return setStatus('jsoncsvStatus', 'error', 'Paste some JSON first.');
const d = await apiPost('/api/convert/json-to-csv', { json: input });
if (d.success) {
document.getElementById('jsoncsvOutput').value = d.result;
lastJsoncsvType = 'csv';
setStatus('jsoncsvStatus', 'success', `Converted ✓ — ${d.rows} rows, ${d.columns} columns`);
} else setStatus('jsoncsvStatus', 'error', d.error);
}
async function csvToJson() {
const input = document.getElementById('jsoncsvCsvInput').value.trim();
if (!input) return setStatus('jsoncsvStatus', 'error', 'Paste some CSV first.');
const d = await apiPost('/api/convert/csv-to-json', { csv: input });
if (d.success) {
document.getElementById('jsoncsvOutput').value = JSON.stringify(d.result, null, 2);
lastJsoncsvType = 'json';
setStatus('jsoncsvStatus', 'success', `Converted ✓ — ${d.result.length} records`);
} else setStatus('jsoncsvStatus', 'error', d.error);
}
function downloadJsoncsvOutput() {
const output = document.getElementById('jsoncsvOutput').value;
if (!output) return setStatus('jsoncsvStatus', 'error', 'Nothing to download.');
const ext = lastJsoncsvType === 'csv' ? 'csv' : 'json';
const mime = lastJsoncsvType === 'csv' ? 'text/csv' : 'application/json';
const blob = new Blob([output], { type: mime });
const a = document.createElement('a');
a.href = URL.createObjectURL(blob);
a.download = `converted.${ext}`;
a.click();
URL.revokeObjectURL(a.href);
setStatus('jsoncsvStatus', 'success', 'Downloaded ✓');
}
+22
View File
@@ -0,0 +1,22 @@
// ═══════════════════════════════════════════════════════
// JWT Decoder
// ═══════════════════════════════════════════════════════
async function decodeJWT() {
const token = document.getElementById('jwtInput').value.trim();
if (!token) return setStatus('jwtStatus','error','Paste a JWT.');
const d = await apiPost('/api/jwt/decode', { token });
if (d.success) {
const expStr = d.expired === null ? '—' : d.expired ? '<span style="color:var(--red)">EXPIRED ✗</span>' : '<span style="color:var(--green)">VALID ✓</span>';
document.getElementById('jwtResults').innerHTML = `
<div class="result-card">
<div class="panel-label">Header</div>
<pre style="background:var(--bg-input);padding:12px;border-radius:var(--radius-sm);font-size:0.82rem;color:var(--cyan);overflow-x:auto;">${JSON.stringify(d.header, null, 2)}</pre>
<div class="panel-label" style="margin-top:14px;">Payload</div>
<pre style="background:var(--bg-input);padding:12px;border-radius:var(--radius-sm);font-size:0.82rem;color:var(--green);overflow-x:auto;">${JSON.stringify(d.payload, null, 2)}</pre>
<div class="panel-label" style="margin-top:14px;">Expiry</div>
<div style="font-size:0.9rem;">${expStr}</div>
</div>`;
setStatus('jwtStatus','success','Decoded ✓');
} else setStatus('jwtStatus','error', d.error);
}
+8
View File
@@ -0,0 +1,8 @@
// ═══════════════════════════════════════════════════════
// Lorem Ipsum
// ═══════════════════════════════════════════════════════
async function generateLorem() {
const d = await apiPost('/api/lorem', { paragraphs: parseInt(document.getElementById('loremCount').value) });
if (d.success) document.getElementById('loremOutput').value = d.result;
}
+30
View File
@@ -0,0 +1,30 @@
// ═══════════════════════════════════════════════════════
// Markdown Preview
// ═══════════════════════════════════════════════════════
function renderMarkdown() {
const md = document.getElementById('mdInput').value;
if (!md) { document.getElementById('mdPreview').innerHTML = '<span style="color:var(--text-muted)">Preview will appear here...</span>'; return; }
let html = md
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
.replace(/^---$/gm, '<hr>')
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
.replace(/\*(.+?)\*/g, '<em>$1</em>')
.replace(/~~(.+?)~~/g, '<del>$1</del>')
.replace(/^\> (.+)$/gm, '<blockquote>$1</blockquote>')
.replace(/^\- (.+)$/gm, '<li>$1</li>')
.replace(/^\* (.+)$/gm, '<li>$1</li>')
.replace(/^\d+\. (.+)$/gm, '<li>$1</li>')
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;border-radius:8px;">')
.replace(/\n\n/g, '</p><p>')
.replace(/\n/g, '<br>');
html = '<p>' + html + '</p>';
// Wrap consecutive li in ul
html = html.replace(/(<li>.*?<\/li>)+/gs, '<ul>$&</ul>');
document.getElementById('mdPreview').innerHTML = html;
}
+18
View File
@@ -0,0 +1,18 @@
// ═══════════════════════════════════════════════════════
// Number Base Converter
// ═══════════════════════════════════════════════════════
async function convertNumber() {
const v = document.getElementById('numInput').value.trim();
if (!v) return setStatus('numStatus','error','Enter a number.');
const d = await apiPost('/api/number/convert', { value: v, fromBase: document.getElementById('numBase').value });
if (d.success) {
document.getElementById('numResults').innerHTML = [
['Decimal', d.decimal], ['Binary', d.binary], ['Octal', d.octal], ['Hexadecimal', d.hex]
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
setStatus('numStatus','success','Converted ✓');
} else setStatus('numStatus','error', d.error);
}
// Keyboard shortcut
document.getElementById('numInput').addEventListener('keydown', e => { if(e.key==='Enter') convertNumber(); });
+27
View File
@@ -0,0 +1,27 @@
// ═══════════════════════════════════════════════════════
// Password Generator
// ═══════════════════════════════════════════════════════
async function generatePassword() {
const d = await apiPost('/api/password', {
length: parseInt(document.getElementById('pwLength').value),
uppercase: document.getElementById('pwUpper').checked,
lowercase: document.getElementById('pwLower').checked,
numbers: document.getElementById('pwNumbers').checked,
symbols: document.getElementById('pwSymbols').checked,
count: 5
});
if (d.success) {
document.getElementById('pwDisplay').textContent = d.passwords[0];
// Strength
const len = d.passwords[0].length;
const str = len >= 20 ? 100 : len >= 12 ? 75 : len >= 8 ? 50 : 25;
const colors = { 25: 'var(--red)', 50: 'var(--orange)', 75: 'var(--yellow)', 100: 'var(--green)' };
const fill = document.getElementById('pwStrength');
fill.style.width = str + '%';
fill.style.background = colors[str];
// Batch
document.getElementById('pwBatch').innerHTML = '<div class="panel-label">More Passwords</div>' +
d.passwords.slice(1).map(pw => `<div class="result-row"><div class="value" onclick="copyText(this.textContent)" style="max-width:100%;text-align:left;">${pw}</div></div>`).join('');
}
}
+60
View File
@@ -0,0 +1,60 @@
// ═══════════════════════════════════════════════════════
// Placeholder Image Generator
// ═══════════════════════════════════════════════════════
function generatePlaceholder() {
const w = parseInt(document.getElementById('phWidth').value) || 400;
const h = parseInt(document.getElementById('phHeight').value) || 300;
const text = document.getElementById('phText').value || `${w} × ${h}`;
const bg = document.getElementById('phBg').value;
const fg = document.getElementById('phFg').value;
const fontSize = parseInt(document.getElementById('phFontSize').value) || 28;
document.getElementById('phBgText').value = bg;
document.getElementById('phFgText').value = fg;
document.getElementById('phFontSizeVal').textContent = fontSize + 'px';
const canvas = document.getElementById('phCanvas');
canvas.width = w;
canvas.height = h;
const ctx = canvas.getContext('2d');
// Background
ctx.fillStyle = bg;
ctx.fillRect(0, 0, w, h);
// Cross lines
ctx.strokeStyle = fg + '20';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, 0); ctx.lineTo(w, h);
ctx.moveTo(w, 0); ctx.lineTo(0, h);
ctx.stroke();
// Text
ctx.fillStyle = fg;
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(text, w / 2, h / 2);
}
function downloadPlaceholder() {
const canvas = document.getElementById('phCanvas');
const link = document.createElement('a');
const w = document.getElementById('phWidth').value || 400;
const h = document.getElementById('phHeight').value || 300;
link.download = `placeholder-${w}x${h}.png`;
link.href = canvas.toDataURL('image/png');
link.click();
setStatus('phStatus', 'success', 'Downloaded ✓');
}
function copyPlaceholderDataUrl() {
const canvas = document.getElementById('phCanvas');
copyText(canvas.toDataURL('image/png'));
setStatus('phStatus', 'success', 'Data URL copied ✓');
}
// Initialize on load
setTimeout(generatePlaceholder, 100);
+56
View File
@@ -0,0 +1,56 @@
// ═══════════════════════════════════════════════════════
// QR Code (using qrcode-generator library)
// ═══════════════════════════════════════════════════════
function generateQR() {
const data = document.getElementById('qrInput').value.trim();
const container = document.getElementById('qrCanvas');
const ph = document.getElementById('qrPlaceholder');
if (!data) { container.style.display = 'none'; ph.style.display = ''; return; }
if (typeof qrcode === 'undefined') { ph.textContent = 'Loading QR library...'; return; }
try {
const qr = qrcode(0, 'M');
qr.addData(data);
qr.make();
container.innerHTML = qr.createSvgTag({ cellSize: 4, margin: 8, scalable: true });
// Style the SVG
const svg = container.querySelector('svg');
if (svg) {
svg.style.width = '256px';
svg.style.height = '256px';
svg.style.background = '#fff';
svg.style.borderRadius = '12px';
}
container.style.display = '';
ph.style.display = 'none';
} catch (err) {
ph.textContent = err.message;
container.style.display = 'none';
ph.style.display = '';
}
}
function downloadQR() {
const container = document.getElementById('qrCanvas');
if (container.style.display === 'none') return;
const svg = container.querySelector('svg');
if (!svg) return;
// Convert SVG to PNG via canvas
const svgData = new XMLSerializer().serializeToString(svg);
const img = new Image();
img.onload = function () {
const canvas = document.createElement('canvas');
canvas.width = 512;
canvas.height = 512;
const ctx = canvas.getContext('2d');
ctx.fillStyle = '#ffffff';
ctx.fillRect(0, 0, 512, 512);
ctx.drawImage(img, 0, 0, 512, 512);
const a = document.createElement('a');
a.href = canvas.toDataURL('image/png');
a.download = 'qrcode.png';
a.click();
};
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));
}
+163
View File
@@ -0,0 +1,163 @@
// ═══════════════════════════════════════════════════════
// QR Code Reader (using jsQR library)
// ═══════════════════════════════════════════════════════
let qrCameraStream = null;
let qrScanInterval = null;
let qrScanHistoryList = [];
function startQrCamera() {
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
return setStatus('qrReaderStatus', 'error', 'Camera API not supported in this browser.');
}
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
.then(stream => {
qrCameraStream = stream;
const video = document.getElementById('qrVideo');
video.srcObject = stream;
video.play();
document.getElementById('qrCameraContainer').style.display = 'block';
document.getElementById('qrCameraBtn').style.display = 'none';
document.getElementById('qrStopBtn').style.display = '';
document.getElementById('qrImagePreview').style.display = 'none';
setStatus('qrReaderStatus', 'info', 'Camera active — scanning for QR codes...');
// Start scanning frames
const canvas = document.getElementById('qrScanCanvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
qrScanInterval = setInterval(() => {
if (video.readyState === video.HAVE_ENOUGH_DATA) {
canvas.width = video.videoWidth;
canvas.height = video.videoHeight;
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
if (typeof jsQR !== 'undefined') {
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'dontInvert' });
if (code) {
showQrResult(code.data);
setStatus('qrReaderStatus', 'success', 'QR code detected ✓');
}
}
}
}, 250);
})
.catch(err => {
setStatus('qrReaderStatus', 'error', 'Camera access denied: ' + err.message);
});
}
function stopQrCamera() {
if (qrScanInterval) { clearInterval(qrScanInterval); qrScanInterval = null; }
if (qrCameraStream) {
qrCameraStream.getTracks().forEach(t => t.stop());
qrCameraStream = null;
}
const video = document.getElementById('qrVideo');
video.srcObject = null;
document.getElementById('qrCameraContainer').style.display = 'none';
document.getElementById('qrCameraBtn').style.display = '';
document.getElementById('qrStopBtn').style.display = 'none';
setStatus('qrReaderStatus', 'info', 'Camera stopped.');
}
function scanQrFromFile(event) {
const file = event.target.files[0];
if (!file) return;
// Stop camera if running
stopQrCamera();
const img = document.getElementById('qrPreviewImg');
const reader = new FileReader();
reader.onload = function (e) {
img.src = e.target.result;
document.getElementById('qrImagePreview').style.display = 'block';
const tempImg = new Image();
tempImg.onload = function () {
const canvas = document.getElementById('qrImgCanvas');
const ctx = canvas.getContext('2d', { willReadFrequently: true });
canvas.width = tempImg.width;
canvas.height = tempImg.height;
ctx.drawImage(tempImg, 0, 0);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
if (typeof jsQR !== 'undefined') {
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'attemptBoth' });
if (code) {
showQrResult(code.data);
setStatus('qrReaderStatus', 'success', 'QR code detected in image ✓');
} else {
setStatus('qrReaderStatus', 'error', 'No QR code found in this image.');
}
} else {
setStatus('qrReaderStatus', 'error', 'QR scanning library not loaded.');
}
};
tempImg.src = e.target.result;
};
reader.readAsDataURL(file);
// Reset file input so same file can be re-selected
event.target.value = '';
}
function showQrResult(data) {
document.getElementById('qrReaderPlaceholder').style.display = 'none';
document.getElementById('qrReaderResult').style.display = 'block';
document.getElementById('qrDecodedText').textContent = data;
// Detect type
let type = 'Plain Text';
const openBtn = document.getElementById('qrOpenLinkBtn');
openBtn.style.display = 'none';
if (/^https?:\/\//i.test(data)) {
type = '🔗 URL';
openBtn.style.display = '';
} else if (/^mailto:/i.test(data)) {
type = '📧 Email';
openBtn.style.display = '';
} else if (/^tel:/i.test(data)) {
type = '📞 Phone Number';
} else if (/^BEGIN:VCARD/i.test(data)) {
type = '👤 vCard Contact';
} else if (/^BEGIN:VEVENT/i.test(data)) {
type = '📅 Calendar Event';
} else if (/^WIFI:/i.test(data)) {
type = '📶 Wi-Fi Network';
} else if (/^smsto:/i.test(data)) {
type = '💬 SMS';
} else if (/^geo:/i.test(data)) {
type = '📍 Geolocation';
}
document.getElementById('qrDecodedType').textContent = type;
// Add to history (keep last 10)
const now = new Date().toLocaleTimeString();
qrScanHistoryList.unshift({ data: data.length > 80 ? data.slice(0, 80) + '...' : data, time: now, full: data });
if (qrScanHistoryList.length > 10) qrScanHistoryList.pop();
document.getElementById('qrScanHistory').innerHTML = qrScanHistoryList.map(h =>
`<div class="result-row" style="cursor:pointer;" onclick="copyText('${h.full.replace(/'/g, "\\'")}')">
<div class="label">${h.time}</div>
<div class="value" style="font-size:0.78rem;">${h.data.replace(/</g, '&lt;')}</div>
</div>`
).join('');
}
// Stop camera when navigating away
const origShowPage = window.showPage;
if (origShowPage) {
window.showPage = function (name) {
if (name !== 'qrreader' && qrCameraStream) stopQrCamera();
origShowPage(name);
};
}
+29
View File
@@ -0,0 +1,29 @@
// ═══════════════════════════════════════════════════════
// Regex Tester
// ═══════════════════════════════════════════════════════
function testRegex() {
const pattern = document.getElementById('regexPattern').value;
const flags = document.getElementById('regexFlags').value;
const input = document.getElementById('regexInput').value;
const resEl = document.getElementById('regexResults');
const listEl = document.getElementById('regexMatchList');
if (!pattern || !input) { resEl.innerHTML = '<span style="color:var(--text-muted)">Enter a pattern and test string...</span>'; listEl.innerHTML=''; return; }
try {
const re = new RegExp(pattern, flags);
// Highlight matches
let highlighted = input.replace(/[<>&]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;'}[c]));
const safePattern = new RegExp(pattern, flags);
highlighted = input.replace(safePattern, m => `<span class="regex-match">${m.replace(/[<>&]/g, c => ({'<':'&lt;','>':'&gt;','&':'&amp;'}[c]))}</span>`);
resEl.innerHTML = highlighted || '<span style="color:var(--text-muted)">No matches</span>';
// Match list
const matches = [...input.matchAll(new RegExp(pattern, flags.includes('g') ? flags : flags + 'g'))];
if (matches.length) {
listEl.innerHTML = '<div class="panel-label">Matches (' + matches.length + ')</div>' +
matches.map((m, i) => `<div class="result-row"><div class="label">Match ${i+1}</div><div class="value" onclick="copyText(this.textContent)">${m[0]}</div></div>`).join('');
} else listEl.innerHTML = '';
} catch (e) {
resEl.innerHTML = `<span style="color:var(--red)">${e.message}</span>`;
listEl.innerHTML = '';
}
}
+15
View File
@@ -0,0 +1,15 @@
// ═══════════════════════════════════════════════════════
// Slug Generator
// ═══════════════════════════════════════════════════════
async function generateSlug() {
const text = document.getElementById('slugInput').value;
if (!text.trim()) { document.getElementById('slugOutput').textContent = '—'; return; }
const separator = document.getElementById('slugSeparator').value;
const lowercase = document.getElementById('slugLower').checked;
const d = await apiPost('/api/text/slugify', { text, separator, lowercase });
if (d.success) {
document.getElementById('slugOutput').textContent = d.result;
setStatus('slugStatus', 'success', 'Generated ✓');
} else setStatus('slugStatus', 'error', d.error);
}
+18
View File
@@ -0,0 +1,18 @@
// ═══════════════════════════════════════════════════════
// SQL Formatter
// ═══════════════════════════════════════════════════════
async function formatSQL() {
const input = document.getElementById('sqlInput').value.trim();
if (!input) return setStatus('sqlStatus', 'error', 'Paste some SQL first.');
const d = await apiPost('/api/sql/format', { sql: input });
if (d.success) { document.getElementById('sqlOutput').value = d.result; setStatus('sqlStatus', 'success', 'Formatted ✓'); }
else setStatus('sqlStatus', 'error', d.error);
}
async function minifySQL() {
const input = document.getElementById('sqlInput').value.trim();
if (!input) return setStatus('sqlStatus', 'error', 'Paste some SQL first.');
const d = await apiPost('/api/sql/minify', { sql: input });
if (d.success) { document.getElementById('sqlOutput').value = d.result; setStatus('sqlStatus', 'success', 'Minified ✓'); }
else setStatus('sqlStatus', 'error', d.error);
}
+21
View File
@@ -0,0 +1,21 @@
// ═══════════════════════════════════════════════════════
// Text Encoder / Decoder
// ═══════════════════════════════════════════════════════
async function encodeText(method) {
const text = document.getElementById('textencInput').value;
if (!text) return setStatus('textencStatus', 'error', 'Enter some text first.');
const d = await apiPost('/api/text/encode', { text, method });
if (d.success) {
document.getElementById('textencOutput').value = d.result;
setStatus('textencStatus', 'success', `Encoded with ${method}`);
} else setStatus('textencStatus', 'error', d.error);
}
function swapTextEnc() {
const input = document.getElementById('textencInput');
const output = document.getElementById('textencOutput');
const tmp = input.value;
input.value = output.value;
output.value = tmp;
}
+18
View File
@@ -0,0 +1,18 @@
// ═══════════════════════════════════════════════════════
// Timestamp
// ═══════════════════════════════════════════════════════
async function convertTimestamp() {
const v = document.getElementById('tsInput').value.trim();
const d = await apiPost('/api/timestamp', { value: v || 'now' });
if (d.success) {
document.getElementById('tsResults').innerHTML = [
['Unix (s)', d.unix], ['Unix (ms)', d.unixMs], ['ISO 8601', d.iso],
['UTC', d.utc], ['Local', d.local], ['Relative', d.relative]
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
setStatus('tsStatus','success','Converted ✓');
} else setStatus('tsStatus','error', d.error);
}
// Keyboard shortcut
document.getElementById('tsInput').addEventListener('keydown', e => { if(e.key==='Enter') convertTimestamp(); });
+32
View File
@@ -0,0 +1,32 @@
// ═══════════════════════════════════════════════════════
// URL Shortener
// ═══════════════════════════════════════════════════════
const urlHist = [];
async function shortenURL() {
const url = document.getElementById('urlInput').value.trim();
if (!url) return setStatus('urlStatus','error','Enter a URL.');
const d = await apiPost('/api/url/shorten', { url });
if (d.success) {
document.getElementById('shortUrlLink').href = d.shortUrl;
document.getElementById('shortUrlLink').textContent = d.shortUrl;
document.getElementById('urlResult').classList.add('visible');
setStatus('urlStatus','success','Shortened ✓');
urlHist.unshift({ short: d.shortUrl, original: url, time: new Date().toLocaleTimeString() });
renderUrlHistory();
} else setStatus('urlStatus','error', d.error);
}
function copyShortUrl() { copyText(document.getElementById('shortUrlLink').textContent); }
function renderUrlHistory() {
const c = document.getElementById('urlHistory');
if (!urlHist.length) { c.textContent = 'No links shortened yet.'; return; }
c.innerHTML = urlHist.slice(0,10).map(h => `
<div class="result-row">
<div style="overflow:hidden;flex:1;"><a href="${h.short}" target="_blank" style="color:var(--accent);font-family:var(--font-mono);font-size:0.8rem;text-decoration:none;">${h.short}</a>
<div style="font-size:0.7rem;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${h.original}</div></div>
<span style="font-size:0.68rem;color:var(--text-muted);margin-left:12px;">${h.time}</span>
</div>`).join('');
}
// Keyboard shortcut
document.getElementById('urlInput').addEventListener('keydown', e => { if(e.key==='Enter') shortenURL(); });
+14
View File
@@ -0,0 +1,14 @@
// ═══════════════════════════════════════════════════════
// UUID
// ═══════════════════════════════════════════════════════
let lastUUIDs = [];
async function generateUUIDs() {
const d = await apiPost('/api/uuid', { count: parseInt(document.getElementById('uuidCount').value) });
if (d.success) {
lastUUIDs = d.uuids;
document.getElementById('uuidResults').innerHTML = d.uuids.map(u =>
`<div class="result-row"><div class="value" onclick="copyText(this.textContent)" style="max-width:100%;text-align:left;">${u}</div></div>`).join('');
}
}
function copyUUIDs() { if (lastUUIDs.length) copyText(lastUUIDs.join('\n')); }
+26
View File
@@ -0,0 +1,26 @@
// ═══════════════════════════════════════════════════════
// YouTube
// ═══════════════════════════════════════════════════════
async function fetchYouTube() {
const url = document.getElementById('ytInput').value.trim();
if (!url) return setStatus('ytStatus','error','Enter a YouTube URL.');
setStatus('ytStatus','info','Fetching...');
const d = await apiPost('/api/youtube/info', { url });
if (d.success) {
document.getElementById('ytThumb').src = d.thumbnail;
document.getElementById('ytThumb').onerror = function() { this.src = d.thumbnailHQ; };
document.getElementById('ytTitle').textContent = d.title;
document.getElementById('ytAuthor').textContent = d.author;
document.getElementById('ytAuthor').href = d.authorUrl;
document.getElementById('ytThumbMax').textContent = d.thumbnail;
document.getElementById('ytThumbHQ').textContent = d.thumbnailHQ;
document.getElementById('ytEmbed').value = `<iframe width="560" height="315" src="${d.embedUrl}" frameborder="0" allowfullscreen></iframe>`;
document.getElementById('ytWatch').href = d.watchUrl;
document.getElementById('ytResult').classList.add('visible');
setStatus('ytStatus','success','Fetched ✓');
} else { document.getElementById('ytResult').classList.remove('visible'); setStatus('ytStatus','error', d.error); }
}
// Keyboard shortcut
document.getElementById('ytInput').addEventListener('keydown', e => { if(e.key==='Enter') fetchYouTube(); });