+
${h.time}
+
${h.data.replace(/
+
`
+ ).join('');
+}
+
+// Stop camera when navigating away
+const origShowPage = window.showPage;
+if (origShowPage) {
+ window.showPage = function (name) {
+ if (name !== 'qrreader' && qrCameraStream) stopQrCamera();
+ origShowPage(name);
+ };
+}
+
diff --git a/public/js/tools/regex.js b/public/js/tools/regex.js
new file mode 100644
index 0000000..23d4799
--- /dev/null
+++ b/public/js/tools/regex.js
@@ -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 = '
Enter a pattern and test string... '; listEl.innerHTML=''; return; }
+ try {
+ const re = new RegExp(pattern, flags);
+ // Highlight matches
+ let highlighted = input.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]));
+ const safePattern = new RegExp(pattern, flags);
+ highlighted = input.replace(safePattern, m => `
${m.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]))} `);
+ resEl.innerHTML = highlighted || '
No matches ';
+ // Match list
+ const matches = [...input.matchAll(new RegExp(pattern, flags.includes('g') ? flags : flags + 'g'))];
+ if (matches.length) {
+ listEl.innerHTML = '
Matches (' + matches.length + ')
' +
+ matches.map((m, i) => `
`).join('');
+ } else listEl.innerHTML = '';
+ } catch (e) {
+ resEl.innerHTML = `
${e.message} `;
+ listEl.innerHTML = '';
+ }
+}
+
diff --git a/public/js/tools/slugify.js b/public/js/tools/slugify.js
new file mode 100644
index 0000000..93a8850
--- /dev/null
+++ b/public/js/tools/slugify.js
@@ -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);
+}
+
diff --git a/public/js/tools/sqlformat.js b/public/js/tools/sqlformat.js
new file mode 100644
index 0000000..95793a7
--- /dev/null
+++ b/public/js/tools/sqlformat.js
@@ -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);
+}
+
diff --git a/public/js/tools/textenc.js b/public/js/tools/textenc.js
new file mode 100644
index 0000000..0bc614e
--- /dev/null
+++ b/public/js/tools/textenc.js
@@ -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;
+}
+
diff --git a/public/js/tools/timestamp.js b/public/js/tools/timestamp.js
new file mode 100644
index 0000000..c9e7a09
--- /dev/null
+++ b/public/js/tools/timestamp.js
@@ -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]) => `
`).join('');
+ setStatus('tsStatus','success','Converted ✓');
+ } else setStatus('tsStatus','error', d.error);
+}
+
+// Keyboard shortcut
+document.getElementById('tsInput').addEventListener('keydown', e => { if(e.key==='Enter') convertTimestamp(); });
+
diff --git a/public/js/tools/url.js b/public/js/tools/url.js
new file mode 100644
index 0000000..86ce677
--- /dev/null
+++ b/public/js/tools/url.js
@@ -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 => `
+
`).join('');
+}
+
+// Keyboard shortcut
+document.getElementById('urlInput').addEventListener('keydown', e => { if(e.key==='Enter') shortenURL(); });
+
diff --git a/public/js/tools/uuid.js b/public/js/tools/uuid.js
new file mode 100644
index 0000000..06c0962
--- /dev/null
+++ b/public/js/tools/uuid.js
@@ -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 =>
+ `
`).join('');
+ }
+}
+function copyUUIDs() { if (lastUUIDs.length) copyText(lastUUIDs.join('\n')); }
+
diff --git a/public/js/tools/youtube.js b/public/js/tools/youtube.js
new file mode 100644
index 0000000..1f1d223
--- /dev/null
+++ b/public/js/tools/youtube.js
@@ -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 = `
`;
+ 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(); });
+
diff --git a/public/tools/ascii.html b/public/tools/ascii.html
new file mode 100644
index 0000000..a694ff2
--- /dev/null
+++ b/public/tools/ascii.html
@@ -0,0 +1,38 @@
+
+
+
← Back to Tools
+
+
+
Input Text
+
+
+ Generate
+
+
Output
+
+
+ Copy
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/ascii/generate
+
Generate ASCII art text from a string (A-Z, 0-9, common punctuation).
+
Copy const res = await fetch (`${BASE_URL}/api/ascii/generate` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "HI" })
+});
+// → { success: true, result: "█ █ █████\n█ █ █ \n████ █ \n█ █ █ \n█ █ █████" }
+
+
+
+
+
+
diff --git a/public/tools/base64.html b/public/tools/base64.html
new file mode 100644
index 0000000..27a6686
--- /dev/null
+++ b/public/tools/base64.html
@@ -0,0 +1,54 @@
+
+
+
← Back to Tools
+
+
+
+
Input
+
+
+ Encode
+ Decode
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/base64/encode
+
Encode a string to Base64.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/base64/encode` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "Hello World" })
+});
+const data = await res.json ();
+// → { success: true, result: "SGVsbG8gV29ybGQ=" }
+
+
+
POST /api/base64/decode
+
Decode a Base64 string back to text.
+
Copy await fetch (`${BASE_URL}/api/base64/decode` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "SGVsbG8gV29ybGQ=" })
+});
+// → { success: true, result: "Hello World" }
+
+
+
+
+
diff --git a/public/tools/byteconv.html b/public/tools/byteconv.html
new file mode 100644
index 0000000..8949d57
--- /dev/null
+++ b/public/tools/byteconv.html
@@ -0,0 +1,50 @@
+
+
+
← Back to Tools
+
+
+
+
+
+
Unit
+
+ Bytes (B)
+ Kilobytes (KB)
+ Megabytes (MB)
+ Gigabytes (GB)
+ Terabytes (TB)
+ Petabytes (PB)
+
+
+
+
+ Binary (1024)
+ SI / Decimal (1000)
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/bytes/convert
+
Convert a value between byte units. Use mode: "binary" (1024) or "si" (1000).
+
Copy const res = await fetch (`${BASE_URL}/api/bytes/convert` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ value: 1, unit: "GB" , mode: "binary" })
+});
+// → { success: true, B: 1073741824, KB: 1048576, MB: 1024, GB: 1, TB: 0.000977, PB: 0.00000095 }
+
+
+
+
+
+
diff --git a/public/tools/caseconv.html b/public/tools/caseconv.html
new file mode 100644
index 0000000..b7ed977
--- /dev/null
+++ b/public/tools/caseconv.html
@@ -0,0 +1,40 @@
+
+
+
← Back to Tools
+
+
+
Input Text
+
+
+ Convert All
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/text/case
+
Convert text to all case variations at once.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/text/case` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "Hello World Example" })
+});
+const data = await res.json ();
+// → { success: true, uppercase: "HELLO WORLD EXAMPLE",
+// lowercase: "hello world example", titleCase: "Hello World Example",
+// camelCase: "helloWorldExample", snakeCase: "hello_world_example",
+// kebabCase: "hello-world-example", dotCase: "hello.world.example",
+// reversed: "elpmaxE dlroW olleH" }
+
+
+
+
+
+
diff --git a/public/tools/chmod.html b/public/tools/chmod.html
new file mode 100644
index 0000000..ba3b7c5
--- /dev/null
+++ b/public/tools/chmod.html
@@ -0,0 +1,45 @@
+
+
+
← Back to Tools
+
+
+
+
Permission Matrix
+
+
Command
+
+
CHMOD
+
chmod 755 filename
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/chmod/calculate
+
Convert between numeric and symbolic chmod. Pass numeric or symbolic.
+
Copy const res = await fetch (`${BASE_URL}/api/chmod/calculate` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ numeric: "755" })
+});
+// → { success: true, numeric: "755", symbolic: "rwxr-xr-x", owner: {...}, group: {...}, others: {...} }
+
+
+
+
+
+
diff --git a/public/tools/color.html b/public/tools/color.html
new file mode 100644
index 0000000..0d41f2e
--- /dev/null
+++ b/public/tools/color.html
@@ -0,0 +1,43 @@
+
+
+
← Back to Tools
+
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/color/convert
+
Convert a color between HEX, RGB, and HSL formats.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/color/convert` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ color: "#6c63ff" })
+ // Also accepts: "rgb(108,99,255)" or "hsl(245,100,69)"
+});
+const data = await res.json ();
+// → { success: true, hex: "#6c63ff", rgb: "rgb(108, 99, 255)",
+// hsl: "hsl(243, 100%, 69%)", r: 108, g: 99, b: 255 }
+
+
+
+
+
+
diff --git a/public/tools/counter.html b/public/tools/counter.html
new file mode 100644
index 0000000..8f30a5b
--- /dev/null
+++ b/public/tools/counter.html
@@ -0,0 +1,34 @@
+
+
+
← Back to Tools
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/text/stats
+
Get detailed text statistics: characters, words, sentences, reading time, and top character frequency.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/text/stats` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "Hello world. This is a test." })
+});
+const data = await res.json ();
+// → { success: true, characters: 28, charactersNoSpaces: 23,
+// words: 6, sentences: 2, paragraphs: 1, lines: 1,
+// readingTime: "1 min", topChars: [["l",3], ["s",3], ...] }
+
+
+
+
+
+
diff --git a/public/tools/cron.html b/public/tools/cron.html
new file mode 100644
index 0000000..94d661d
--- /dev/null
+++ b/public/tools/cron.html
@@ -0,0 +1,47 @@
+
+
+
← Back to Tools
+
+
+
Cron Expression
+
+
+ Parse
+ Every 5 min
+ Daily midnight
+ Weekdays 9am
+ 1st of month
+ Every 2 hours
+
+
+
+
Human-readable
+
+
Fields Breakdown
+
+
Next 5 Run Times
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL, e.g. https://winnieapi-v2.yourdomain.com
+
+
POST /api/cron/parse
+
Parse a cron expression into a human-readable description and next run times.
+
Copy const res = await fetch (`${BASE_URL}/api/cron/parse` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ expression: "*/5 * * * *" })
+});
+const data = await res.json ();
+// → { success: true, description: "Every 5 minutes", fields: {...}, nextRuns: [...] }
+
+
+
+
+
diff --git a/public/tools/cssmin.html b/public/tools/cssmin.html
new file mode 100644
index 0000000..feff6b8
--- /dev/null
+++ b/public/tools/cssmin.html
@@ -0,0 +1,44 @@
+
+
+
← Back to Tools
+
+
+
+
+
Minified Output
+
+
Copy
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/css/minify
+
Minify CSS by removing comments, whitespace, and unnecessary characters. Returns size savings.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/css/minify` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ css: ".container {\n display: flex;\n /* comment */\n gap: 16px;\n}"
+ })
+});
+const data = await res.json ();
+// → { success: true, result: ".container{display:flex;gap:16px}",
+// original: 58, minified: 33, saved: 25, percentage: 43 }
+
+
+
+
+
diff --git a/public/tools/diff.html b/public/tools/diff.html
new file mode 100644
index 0000000..12c12d7
--- /dev/null
+++ b/public/tools/diff.html
@@ -0,0 +1,52 @@
+
+
+
← Back to Tools
+
+
+
+ Compare
+
+
+
+
API Usage Client-side
+
+
ℹ️ Diff Checker runs entirely client-side . No server API is needed. Here's the line-by-line comparison logic:
+
+
Simple line-by-line diff in JavaScript.
+
Copy // Client-side diff — no API call needed
+function diffLines (original, modified) {
+ const a = original.split ('\n' );
+ const b = modified.split ('\n' );
+ const result = [];
+ const max = Math.max (a.length, b.length);
+ for (let i = 0; i < max; i++) {
+ if (a[i] === undefined) result.push ({ type: 'add' , line: b[i] });
+ else if (b[i] === undefined) result.push ({ type: 'del' , line: a[i] });
+ else if (a[i] === b[i]) result.push ({ type: 'same' , line: a[i] });
+ else {
+ result.push ({ type: 'del' , line: a[i] });
+ result.push ({ type: 'add' , line: b[i] });
+ }
+ }
+ return result;
+}
+
+const diff = diffLines ("hello\nworld" , "hello\nearth" );
+// → [{ type: "same", line: "hello" }, { type: "del", line: "world" }, { type: "add", line: "earth" }]
+
+
+
+
+
diff --git a/public/tools/envjson.html b/public/tools/envjson.html
new file mode 100644
index 0000000..1a36283
--- /dev/null
+++ b/public/tools/envjson.html
@@ -0,0 +1,57 @@
+
+
+
← Back to Tools
+
+
+
+
.env Input
+
+
+ ENV → JSON
+
+
+
+
JSON Input
+
+
+ JSON → ENV
+
+
+
+
Output
+
+
+ Copy
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/convert/env-to-json
+
Convert .env format text to a JSON object.
+
Copy const res = await fetch (`${BASE_URL}/api/convert/env-to-json` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ env: "DB_HOST=localhost\nDB_PORT=5432" })
+});
+// → { success: true, result: { DB_HOST: "localhost", DB_PORT: "5432" } }
+
+
+
POST /api/convert/json-to-env
+
Convert a flat JSON object to .env format text.
+
Copy const res = await fetch (`${BASE_URL}/api/convert/json-to-env` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ json: '{"DB_HOST":"localhost","DB_PORT":"5432"}' })
+});
+// → { success: true, result: "DB_HOST=localhost\nDB_PORT=5432" }
+
+
+
+
+
diff --git a/public/tools/escape.html b/public/tools/escape.html
new file mode 100644
index 0000000..ecc07d4
--- /dev/null
+++ b/public/tools/escape.html
@@ -0,0 +1,54 @@
+
+
+
← Back to Tools
+
+
+
+
Input
+
+
+ Escape
+ Unescape
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/escape
+
Escape special characters in a string (backslash sequences like \n, \t, \", etc).
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/escape` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: 'Hello "World"\nNew line' })
+});
+const data = await res.json ();
+// → { success: true, result: "Hello \\\"World\\\"\\nNew line" }
+
+
+
POST /api/unescape
+
Unescape backslash sequences back to their original characters.
+
Copy await fetch (`${BASE_URL}/api/unescape` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: 'Hello \\\"World\\\"\\nNew line' })
+});
+// → { success: true, result: 'Hello "World"\nNew line' }
+
+
+
+
+
diff --git a/public/tools/hash.html b/public/tools/hash.html
new file mode 100644
index 0000000..72cafe4
--- /dev/null
+++ b/public/tools/hash.html
@@ -0,0 +1,45 @@
+
+
+
← Back to Tools
+
+
+
Input Text
+
+
+ Generate Hashes
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/hash
+
Generate MD5, SHA-1, SHA-256, and SHA-512 hashes. Optionally specify a single algorithm.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+// Get all hashes at once:
+const res = await fetch (`${BASE_URL}/api/hash` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "hello world" })
+});
+// → { success: true, hashes: { md5: "5eb6...", sha1: "2aae...", sha256: "b94d...", sha512: "309e..." } }
+
+// Or get a single algorithm:
+await fetch (`${BASE_URL}/api/hash` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "hello" , algorithm: "sha256" })
+});
+// → { success: true, hashes: { sha256: "2cf2..." } }
+
+
+
+
+
+
diff --git a/public/tools/hmac.html b/public/tools/hmac.html
new file mode 100644
index 0000000..3bdc613
--- /dev/null
+++ b/public/tools/hmac.html
@@ -0,0 +1,49 @@
+
+
+
← Back to Tools
+
+
+
Message
+
+
Secret Key
+
+
+ Algorithm:
+
+ SHA-256
+ SHA-512
+ SHA-1
+ MD5
+
+
+
+ Generate HMAC
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/hmac
+
Generate an HMAC signature. Supports sha256, sha512, sha1, md5.
+
Copy const res = await fetch (`${BASE_URL}/api/hmac` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ message: "hello world" ,
+ secret: "my-secret-key" ,
+ algorithm: "sha256"
+ })
+});
+// → { success: true, hmac: "734c...", algorithm: "sha256" }
+
+
+
+
+
+
diff --git a/public/tools/htmlent.html b/public/tools/htmlent.html
new file mode 100644
index 0000000..7415be6
--- /dev/null
+++ b/public/tools/htmlent.html
@@ -0,0 +1,53 @@
+
+
+
← Back to Tools
+
+
+
+
Input
+
+
+ Encode
+ Decode
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/html/encode
+
Convert special characters to HTML entities.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/html/encode` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: '<div class="test">&</div>' })
+});
+// → { success: true, result: "<div class="test">..." }
+
+
+
POST /api/html/decode
+
Decode HTML entities back to characters.
+
Copy await fetch (`${BASE_URL}/api/html/decode` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "<p>Hello</p>" })
+});
+// → { success: true, result: "<p>Hello</p>" }
+
+
+
+
+
diff --git a/public/tools/httpstatus.html b/public/tools/httpstatus.html
new file mode 100644
index 0000000..33f583d
--- /dev/null
+++ b/public/tools/httpstatus.html
@@ -0,0 +1,43 @@
+
+
+
← Back to Tools
+
+
+
Search / Lookup
+
+
+ All
+ 1xx Info
+ 2xx Success
+ 3xx Redirect
+ 4xx Client Error
+ 5xx Server Error
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL, e.g. https://winnieapi-v2.yourdomain.com
+
+
GET /api/http-status
+
Get all HTTP status codes with their text and category.
+
Copy const res = await fetch (`${BASE_URL}/api/http-status` );
+const data = await res.json ();
+// → { success: true, statuses: [{ code: 200, text: "OK", category: "2xx" }, ...] }
+
+
+
GET /api/http-status/:code
+
Look up a specific HTTP status code.
+
Copy const res = await fetch (`${BASE_URL}/api/http-status/404` );
+const data = await res.json ();
+// → { success: true, code: 404, text: "Not Found", category: "4xx" }
+
+
+
+
+
diff --git a/public/tools/ip.html b/public/tools/ip.html
new file mode 100644
index 0000000..9bea3ae
--- /dev/null
+++ b/public/tools/ip.html
@@ -0,0 +1,43 @@
+
+
+
← Back to Tools
+
+
+
+
+ Lookup
+ My IP
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
GET /api/ip
+
Get geolocation info for the server's public IP.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/ip` );
+const data = await res.json ();
+// → { success: true, query: "203.0.113.1", country: "United States",
+// regionName: "California", city: "San Jose", timezone: "America/Los_Angeles",
+// isp: "Example ISP", ... }
+
+
+
GET /api/ip/:ip
+
Lookup a specific IP address.
+
Copy const res = await fetch (`${BASE_URL}/api/ip/8.8.8.8` );
+const data = await res.json ();
+// → { success: true, query: "8.8.8.8", country: "United States",
+// city: "Ashburn", isp: "Google LLC", ... }
+
+
+
+
+
+
diff --git a/public/tools/json.html b/public/tools/json.html
new file mode 100644
index 0000000..5cb1da2
--- /dev/null
+++ b/public/tools/json.html
@@ -0,0 +1,77 @@
+
+
+
← Back to Tools
+
+
+
+
Input
+
+
+ Beautify
+ Minify
+ Validate
+ Clear
+
+
+ Indent:
+
+ 2 spaces 4 spaces Tab
+
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL, e.g. https://winnieapi-v2.yourdomain.com
+
+
POST /api/json/format
+
Beautify JSON with configurable indentation.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/json/format` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ json: '{"name":"WinnieAPI-v2","version":1}' ,
+ indent: 2 // 2, 4, or "\t"
+ })
+});
+const data = await res.json ();
+// → { success: true, result: "{\n \"name\": \"WinnieAPI-v2\",\n ...}" }
+
+
+
POST /api/json/minify
+
Minify JSON to a single line.
+
Copy await fetch (`${BASE_URL}/api/json/minify` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ json: '{ "a" : 1 , "b" : 2 }' })
+});
+// → { success: true, result: '{"a":1,"b":2}' }
+
+
+
POST /api/json/validate
+
Check if a string is valid JSON.
+
Copy await fetch (`${BASE_URL}/api/json/validate` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ json: '{"valid": true}' })
+});
+// → { success: true, valid: true, message: "Valid JSON ✓" }
+
+
+
+
+
diff --git a/public/tools/jsoncsv.html b/public/tools/jsoncsv.html
new file mode 100644
index 0000000..140c87a
--- /dev/null
+++ b/public/tools/jsoncsv.html
@@ -0,0 +1,58 @@
+
+
+
← Back to Tools
+
+
+
+
JSON Input
+
+
+ JSON → CSV
+
+
+
+
CSV Input
+
+
+ CSV → JSON
+
+
+
+
Output
+
+
+ Copy
+ Download
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL, e.g. https://winnieapi-v2.yourdomain.com
+
+
POST /api/convert/json-to-csv
+
Convert a JSON array to CSV format.
+
Copy const res = await fetch (`${BASE_URL}/api/convert/json-to-csv` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ json: '[{"name":"Alice","age":30}]' })
+});
+// → { success: true, result: "name,age\nAlice,30" }
+
+
+
POST /api/convert/csv-to-json
+
Convert CSV text to a JSON array.
+
Copy const res = await fetch (`${BASE_URL}/api/convert/csv-to-json` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ csv: "name,age\nAlice,30" })
+});
+// → { success: true, result: [{"name":"Alice","age":"30"}] }
+
+
+
+
+
diff --git a/public/tools/jwt.html b/public/tools/jwt.html
new file mode 100644
index 0000000..ba13f91
--- /dev/null
+++ b/public/tools/jwt.html
@@ -0,0 +1,40 @@
+
+
+
← Back to Tools
+
+
+
JWT Token
+
+
+ Decode
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/jwt/decode
+
Decode a JWT token and inspect its header, payload, and expiry.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/jwt/decode` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ token: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"
+ })
+});
+const data = await res.json ();
+// → { success: true, header: { alg: "HS256", typ: "JWT" },
+// payload: { sub: "1234567890" }, expired: null, signature: "..." }
+
+
+
+
+
+
diff --git a/public/tools/lorem.html b/public/tools/lorem.html
new file mode 100644
index 0000000..f932264
--- /dev/null
+++ b/public/tools/lorem.html
@@ -0,0 +1,37 @@
+
+
+
← Back to Tools
+
+
+
+ Paragraphs:
+
+ Generate
+ Copy
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/lorem
+
Generate lorem ipsum placeholder text.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/lorem` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ paragraphs: 3 }) // 1–20
+});
+const data = await res.json ();
+// → { success: true, result: "Lorem ipsum dolor sit amet..." }
+
+
+
+
+
+
diff --git a/public/tools/markdown.html b/public/tools/markdown.html
new file mode 100644
index 0000000..61b0119
--- /dev/null
+++ b/public/tools/markdown.html
@@ -0,0 +1,42 @@
+
+
+
← Back to Tools
+
+
+
+
+
Preview
+
Preview will appear here...
+
+
+
+
API Usage Client-side
+
+
ℹ️ Markdown Preview runs entirely client-side using regex-based rendering. No server API is needed. Here's how to use a similar approach in your own code:
+
+
Simple Markdown to HTML conversion in JavaScript.
+
Copy // Client-side Markdown rendering — no API call needed
+function renderMarkdown (md) {
+ return md
+ .replace (/^### (.+)$/gm, '<h3>$1</h3>' )
+ .replace (/^## (.+)$/gm, '<h2>$1</h2>' )
+ .replace (/^# (.+)$/gm, '<h1>$1</h1>' )
+ .replace (/\*\*(.+?)\*\*/g, '<strong>$1</strong>' )
+ .replace (/\*(.+?)\*/g, '<em>$1</em>' )
+ .replace (/`([^`]+)`/g, '<code>$1</code>' )
+ .replace (/\n\n/g, '</p><p>' );
+}
+
+const html = renderMarkdown ("# Hello\n\n**Bold** and *italic*" );
+// → "<h1>Hello</h1></p><p><strong>Bold</strong> and <em>italic</em>"
+
+
+
+
+
diff --git a/public/tools/numbase.html b/public/tools/numbase.html
new file mode 100644
index 0000000..f93c770
--- /dev/null
+++ b/public/tools/numbase.html
@@ -0,0 +1,52 @@
+
+
+
← Back to Tools
+
+
+
+
+
+
From Base
+
+ Decimal (10)
+ Binary (2)
+ Octal (8)
+ Hex (16)
+
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/number/convert
+
Convert a number between decimal, binary, octal, and hex bases.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/number/convert` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ value: "255" ,
+ fromBase: 10 // 2, 8, 10, or 16
+ })
+});
+const data = await res.json ();
+// → { success: true, decimal: "255", binary: "11111111",
+// octal: "377", hex: "FF" }
+
+
+
+
+
+
diff --git a/public/tools/password.html b/public/tools/password.html
new file mode 100644
index 0000000..23e978b
--- /dev/null
+++ b/public/tools/password.html
@@ -0,0 +1,54 @@
+
+
+
← Back to Tools
+
+
+
Click "Generate" to create a password
+
+
+ Length: 16
+
+
+
+ Uppercase (A-Z)
+ Lowercase (a-z)
+ Numbers (0-9)
+ Symbols (!@#$%)
+
+
+ Generate
+ Copy
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/password
+
Generate cryptographically secure passwords with configurable rules.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/password` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ length: 24, // 4–128 (default: 16)
+ uppercase: true, // include A-Z
+ lowercase: true, // include a-z
+ numbers: true, // include 0-9
+ symbols: true, // include !@#$%...
+ count: 5 // 1–20 passwords at once
+ })
+});
+const data = await res.json ();
+// → { success: true, passwords: ["xK#9mL...", "Qw!7pR...", ...] }
+
+
+
+
+
+
diff --git a/public/tools/placeholder.html b/public/tools/placeholder.html
new file mode 100644
index 0000000..5e2f17e
--- /dev/null
+++ b/public/tools/placeholder.html
@@ -0,0 +1,80 @@
+
+
+
← Back to Tools
+
+
+
+
Settings
+
+
+ Text (leave empty for dimensions)
+
+
+
+
+ Font Size (px)
+
+ 28px
+
+
+ Download PNG
+ Copy Data URL
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL, e.g. https://winnieapi-v2.yourdomain.com
+
+
GET /api/placeholder/:width/:height
+
Generate a placeholder image (SVG). Supports query params: bg, fg, text, fontSize.
+
Copy // Returns an SVG image directly — use as an <img> src
+
+// Basic usage:
+<img src="${BASE_URL}/api/placeholder/400/300" />
+
+// Custom colors & text:
+<img src="${BASE_URL}/api/placeholder/800/400?bg=1a1a25&fg=6c63ff&text=Hero+Image&fontSize=36" />
+
+// Fetch as SVG text:
+const res = await fetch (`${BASE_URL}/api/placeholder/200/200?text=Avatar` );
+const svg = await res.text ();
+// → <svg xmlns=...>...</svg>
+
+
+
+
+
diff --git a/public/tools/qrcode.html b/public/tools/qrcode.html
new file mode 100644
index 0000000..572fdf8
--- /dev/null
+++ b/public/tools/qrcode.html
@@ -0,0 +1,43 @@
+
+
+
← Back to Tools
+
+
+
Data
+
+
+
+
Type something above to generate a QR code
+
+
+ Download PNG
+
+
+
API Usage Client-side
+
+
ℹ️ QR Code generation runs entirely client-side using the qrcode-generator library. No server API is needed.
+
+
Generate QR codes using the qrcode-generator package (works in Node.js and browsers).
+
Copy // Install: npm install qrcode-generator
+// Browser CDN: https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js
+
+const qr = qrcode (0, 'M' );
+qr.addData ("https://example.com" );
+qr.make ();
+
+// Render as an <img> tag:
+document.getElementById ('container' ).innerHTML =
+ qr.createImgTag (4, 8);
+
+// Or get a data URL:
+const dataUrl = qr.createDataURL (4, 8);
+// → "data:image/gif;base64,..."
+
+
+
+
+
+
diff --git a/public/tools/qrreader.html b/public/tools/qrreader.html
new file mode 100644
index 0000000..2287ddb
--- /dev/null
+++ b/public/tools/qrreader.html
@@ -0,0 +1,90 @@
+
+
+
← Back to Tools
+
+
+
+
+
+
Scan Method
+
+ Use Camera
+ Stop Camera
+
+ Upload Image
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Scan Result
+
+
+ Point your camera at a QR code or upload an image to scan.
+
+
+
+
Decoded Content
+
+
Type
+
+
+ Copy
+ Open Link
+
+
Scan History
+
+
+
+
+
+
+
+
+
API Usage Client-side
+
+
ℹ️ QR Code reading runs entirely client-side using the jsQR library. No server API is needed.
+
+
Decode QR codes from image data using the jsQR library (works in Node.js and browsers).
+
Copy // Install: npm install jsqr
+// Browser CDN: https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js
+
+// Read from a canvas element:
+const canvas = document.getElementById ('myCanvas' );
+const ctx = canvas.getContext ('2d' );
+const imageData = ctx.getImageData (0, 0, canvas.width, canvas.height);
+
+const code = jsQR (imageData.data, imageData.width, imageData.height);
+if (code) {
+ console.log ("Decoded:" , code.data);
+ // → "https://example.com"
+}
+
+// Read from camera (MediaDevices API):
+const stream = await navigator.mediaDevices.getUserMedia ({ video: { facingMode: 'environment' } });
+const video = document.createElement ('video' );
+video.srcObject = stream;
+video.play ();
+// Then draw video frames to canvas and scan with jsQR
+
+
+
+
+
diff --git a/public/tools/regex.html b/public/tools/regex.html
new file mode 100644
index 0000000..9466d9a
--- /dev/null
+++ b/public/tools/regex.html
@@ -0,0 +1,48 @@
+
+
+
← Back to Tools
+
+
+
+
Test String
+
+
Matches
+
+
+
+
API Usage Client-side
+
+
ℹ️ Regex Tester runs entirely client-side using JavaScript's native RegExp. No server API is needed. Here's how to use the same logic in your own code:
+
+
Test a regex pattern against a string in JavaScript.
+
Copy // Client-side regex testing — no API call needed
+const pattern = "[a-z]+@[a-z]+\\.[a-z]+" ;
+const flags = "gi" ;
+const testString = "Contact us at hello@example.com or info@test.org" ;
+
+const regex = new RegExp (pattern, flags);
+const matches = [...testString.matchAll (regex)];
+
+matches.forEach ((m, i) => {
+ console.log (`Match ${i+1}: ${m[0]} (index: ${m.index})` );
+});
+// → Match 1: hello@example.com (index: 14)
+// → Match 2: info@test.org (index: 36)
+
+
+
+
+
+
diff --git a/public/tools/slugify.html b/public/tools/slugify.html
new file mode 100644
index 0000000..696f03e
--- /dev/null
+++ b/public/tools/slugify.html
@@ -0,0 +1,53 @@
+
+
+
← Back to Tools
+
+
+
Input Text
+
+
+ Separator:
+
+ Hyphen (-)
+ Underscore (_)
+ Dot (.)
+
+
+ Lowercase
+
+
+
Slug Output
+
+
+ Copy
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/text/slugify
+
Convert text to a URL-friendly slug. Optional: separator (-, _, .) and lowercase (boolean).
+
Copy const res = await fetch (`${BASE_URL}/api/text/slugify` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ text: "Hello World! My Blog Post #1" ,
+ separator: "-" ,
+ lowercase: true
+ })
+});
+// → { success: true, result: "hello-world-my-blog-post-1" }
+
+
+
+
+
+
diff --git a/public/tools/sqlformat.html b/public/tools/sqlformat.html
new file mode 100644
index 0000000..a18c79f
--- /dev/null
+++ b/public/tools/sqlformat.html
@@ -0,0 +1,54 @@
+
+
+
← Back to Tools
+
+
+
+
Input SQL
+
+
+ Beautify
+ Minify
+ Clear
+
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/sql/format
+
Beautify a SQL query with indentation.
+
Copy const res = await fetch (`${BASE_URL}/api/sql/format` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ sql: "SELECT * FROM users WHERE active = 1" })
+});
+// → { success: true, result: "SELECT\n *\nFROM\n users\nWHERE\n active = 1" }
+
+
+
POST /api/sql/minify
+
Minify a SQL query to a single line.
+
Copy await fetch (`${BASE_URL}/api/sql/minify` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ sql: "SELECT\n *\nFROM\n users" })
+});
+// → { success: true, result: "SELECT * FROM users" }
+
+
+
+
+
diff --git a/public/tools/textenc.html b/public/tools/textenc.html
new file mode 100644
index 0000000..e23431d
--- /dev/null
+++ b/public/tools/textenc.html
@@ -0,0 +1,44 @@
+
+
+
← Back to Tools
+
+
+
Input Text
+
+
+ ROT13
+ Binary
+ Morse
+ Reverse
+ L33t
+ Upside Down
+
+
+
Output
+
+
+ Copy
+ Swap
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL, e.g. https://winnieapi-v2.yourdomain.com
+
+
POST /api/text/encode
+
Encode text with the specified method (rot13, binary, morse, reverse, leetspeak, upside_down).
+
Copy const res = await fetch (`${BASE_URL}/api/text/encode` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ text: "Hello World" , method: "morse" })
+});
+// → { success: true, result: ".... . .-.. .-.. --- / .-- --- .-. .-.. -.." }
+
+
+
+
+
diff --git a/public/tools/timestamp.html b/public/tools/timestamp.html
new file mode 100644
index 0000000..5306b5e
--- /dev/null
+++ b/public/tools/timestamp.html
@@ -0,0 +1,42 @@
+
+
+
← Back to Tools
+
+
+
Input (Unix timestamp, ISO date, or "now")
+
+
+ Convert
+ Now
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/timestamp
+
Convert between Unix timestamps, ISO dates, and relative time. Accepts 10-digit (seconds), 13-digit (ms), ISO strings, or "now".
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+// From Unix timestamp:
+const res = await fetch (`${BASE_URL}/api/timestamp` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ value: "1709683200" })
+ // Also: "now", "2024-03-06", or "1709683200000" (ms)
+});
+const data = await res.json ();
+// → { success: true, unix: 1709683200, unixMs: 1709683200000,
+// iso: "2024-03-06T00:00:00.000Z", utc: "Wed, 06 Mar 2024...",
+// local: "...", relative: "1y ago" }
+
+
+
+
+
+
diff --git a/public/tools/url.html b/public/tools/url.html
new file mode 100644
index 0000000..8ea6dbc
--- /dev/null
+++ b/public/tools/url.html
@@ -0,0 +1,60 @@
+
+
+
← Back to Tools
+
+
+
Long URL
+
+
+ Shorten
+
+
+
+
Recent Links
No links shortened yet.
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/url/shorten
+
Create a shortened URL.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/url/shorten` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ url: "https://github.com/long/repo/path" })
+});
+const data = await res.json ();
+// → { success: true, shortUrl: "http://localhost:3000/s/abc1234", id: "abc1234" }
+
+
+
GET /api/url/stats/:id
+
Get click stats for a shortened URL.
+
Copy const res = await fetch (`${BASE_URL}/api/url/stats/abc1234` );
+const data = await res.json ();
+// → { success: true, url: "https://...", clicks: 42, createdAt: "..." }
+
+
+
GET /s/:id
+
Redirect to the original URL (use in browser).
+
Copy // Simply open in browser or use as a link:
+window.open (`${BASE_URL}/s/abc1234` );
+// → 302 redirect to the original URL
+
+
+
+
+
+
diff --git a/public/tools/uuid.html b/public/tools/uuid.html
new file mode 100644
index 0000000..47f4cde
--- /dev/null
+++ b/public/tools/uuid.html
@@ -0,0 +1,37 @@
+
+
+
← Back to Tools
+
+
+
+ Count:
+
+ Generate
+ Copy All
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/uuid
+
Generate random v4 UUIDs. Batch up to 100.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/uuid` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({ count: 5 }) // 1–100
+});
+const data = await res.json ();
+// → { success: true, uuids: ["550e8400-e29b-41d4-...", ...] }
+
+
+
+
+
+
diff --git a/public/tools/youtube.html b/public/tools/youtube.html
new file mode 100644
index 0000000..23b2969
--- /dev/null
+++ b/public/tools/youtube.html
@@ -0,0 +1,58 @@
+
+
+
← Back to Tools
+
+
+
YouTube URL
+
+
+ Fetch
+
+
+
+
+
+
+
+
Thumbnail URLs (click to copy)
+
+
+
+
+
Embed Code
+
+
+
+
+
+
API Usage REST
+
+
All examples use BASE_URL — set it to your deployment URL.
+
+
POST /api/youtube/info
+
Extract metadata, thumbnails, and embed info from a YouTube video.
+
Copy const BASE_URL = "http://localhost:3000" ;
+
+const res = await fetch (`${BASE_URL}/api/youtube/info` , {
+ method: "POST" ,
+ headers: { "Content-Type" : "application/json" },
+ body: JSON.stringify ({
+ url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
+ })
+});
+const data = await res.json ();
+// → { success: true, videoId: "dQw4w9WgXcQ", title: "...",
+// author: "...", thumbnail: "https://img.youtube.com/...",
+// embedUrl: "https://youtube.com/embed/...", ... }
+
+
+
+
+
+
diff --git a/server.js b/server.js
new file mode 100644
index 0000000..04c7076
--- /dev/null
+++ b/server.js
@@ -0,0 +1,968 @@
+const express = require('express');
+const path = require('path');
+const crypto = require('crypto');
+const { nanoid } = require('nanoid');
+
+const app = express();
+const PORT = process.env.PORT || 3000;
+
+// ─── Middleware ────────────────────────────────────────────────────────────────
+app.use(express.json({ limit: '5mb' }));
+app.use(express.urlencoded({ extended: true }));
+app.use(express.static(path.join(__dirname, 'public')));
+
+// ─── In-memory URL store ──────────────────────────────────────────────────────
+const urlStore = new Map();
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: JSON Formatter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/json/format', (req, res) => {
+ try {
+ const { json, indent } = req.body;
+ const parsed = JSON.parse(json);
+ const formatted = JSON.stringify(parsed, null, indent || 2);
+ res.json({ success: true, result: formatted });
+ } catch (err) {
+ res.json({ success: false, error: err.message });
+ }
+});
+
+app.post('/api/json/minify', (req, res) => {
+ try {
+ const { json } = req.body;
+ const parsed = JSON.parse(json);
+ const minified = JSON.stringify(parsed);
+ res.json({ success: true, result: minified });
+ } catch (err) {
+ res.json({ success: false, error: err.message });
+ }
+});
+
+app.post('/api/json/validate', (req, res) => {
+ try {
+ const { json } = req.body;
+ JSON.parse(json);
+ res.json({ success: true, valid: true, message: 'Valid JSON ✓' });
+ } catch (err) {
+ res.json({ success: true, valid: false, message: err.message });
+ }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: URL Shortener
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/url/shorten', (req, res) => {
+ try {
+ const { url } = req.body;
+ if (!url || !url.match(/^https?:\/\/.+/)) {
+ return res.json({ success: false, error: 'Please enter a valid URL starting with http:// or https://' });
+ }
+ const id = nanoid(7);
+ urlStore.set(id, { url, createdAt: new Date(), clicks: 0 });
+ const shortUrl = `${req.protocol}://${req.get('host')}/s/${id}`;
+ res.json({ success: true, shortUrl, id });
+ } catch (err) {
+ res.json({ success: false, error: err.message });
+ }
+});
+
+app.get('/api/url/stats/:id', (req, res) => {
+ const entry = urlStore.get(req.params.id);
+ if (!entry) return res.json({ success: false, error: 'Short URL not found' });
+ res.json({ success: true, ...entry, id: req.params.id });
+});
+
+app.get('/s/:id', (req, res) => {
+ const entry = urlStore.get(req.params.id);
+ if (!entry) return res.status(404).send('Short URL not found');
+ entry.clicks++;
+ res.redirect(entry.url);
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: YouTube Info
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/youtube/info', async (req, res) => {
+ try {
+ const { url } = req.body;
+ if (!url) return res.json({ success: false, error: 'Please enter a YouTube URL' });
+ const patterns = [
+ /youtube\.com\/watch\?v=([a-zA-Z0-9_-]{11})/,
+ /youtu\.be\/([a-zA-Z0-9_-]{11})/,
+ /youtube\.com\/embed\/([a-zA-Z0-9_-]{11})/,
+ /youtube\.com\/shorts\/([a-zA-Z0-9_-]{11})/
+ ];
+ let videoId = null;
+ for (const p of patterns) {
+ const m = url.match(p);
+ if (m) { videoId = m[1]; break; }
+ }
+ if (!videoId) return res.json({ success: false, error: 'Could not extract video ID from URL' });
+ const oembedUrl = `https://www.youtube.com/oembed?url=https://www.youtube.com/watch?v=${videoId}&format=json`;
+ const response = await fetch(oembedUrl);
+ if (!response.ok) return res.json({ success: false, error: 'Video not found or unavailable' });
+ const data = await response.json();
+ res.json({
+ success: true, videoId,
+ title: data.title, author: data.author_name, authorUrl: data.author_url,
+ thumbnail: `https://img.youtube.com/vi/${videoId}/maxresdefault.jpg`,
+ thumbnailHQ: `https://img.youtube.com/vi/${videoId}/hqdefault.jpg`,
+ embedUrl: `https://www.youtube.com/embed/${videoId}`,
+ watchUrl: `https://www.youtube.com/watch?v=${videoId}`
+ });
+ } catch (err) {
+ res.json({ success: false, error: err.message });
+ }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Hash Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/hash', (req, res) => {
+ try {
+ const { text, algorithm } = req.body;
+ if (!text) return res.json({ success: false, error: 'Please enter some text' });
+ const algos = ['md5', 'sha1', 'sha256', 'sha512'];
+ if (algorithm && algos.includes(algorithm)) {
+ const hash = crypto.createHash(algorithm).update(text, 'utf8').digest('hex');
+ return res.json({ success: true, hashes: { [algorithm]: hash } });
+ }
+ const hashes = {};
+ for (const a of algos) {
+ hashes[a] = crypto.createHash(a).update(text, 'utf8').digest('hex');
+ }
+ res.json({ success: true, hashes });
+ } catch (err) {
+ res.json({ success: false, error: err.message });
+ }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Base64 Encode / Decode
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/base64/encode', (req, res) => {
+ try {
+ const { text } = req.body;
+ res.json({ success: true, result: Buffer.from(text || '', 'utf8').toString('base64') });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+app.post('/api/base64/decode', (req, res) => {
+ try {
+ const { text } = req.body;
+ res.json({ success: true, result: Buffer.from(text || '', 'base64').toString('utf8') });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: UUID Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/uuid', (req, res) => {
+ try {
+ const { count } = req.body;
+ const n = Math.min(Math.max(parseInt(count) || 1, 1), 100);
+ const uuids = [];
+ for (let i = 0; i < n; i++) uuids.push(crypto.randomUUID());
+ res.json({ success: true, uuids });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Password Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/password', (req, res) => {
+ try {
+ const { length, uppercase, lowercase, numbers, symbols, count } = req.body;
+ const len = Math.min(Math.max(parseInt(length) || 16, 4), 128);
+ const n = Math.min(Math.max(parseInt(count) || 1, 1), 20);
+ let chars = '';
+ if (uppercase !== false) chars += 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ if (lowercase !== false) chars += 'abcdefghijklmnopqrstuvwxyz';
+ if (numbers !== false) chars += '0123456789';
+ if (symbols) chars += '!@#$%^&*()_+-=[]{}|;:,.<>?';
+ if (!chars) chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
+ const passwords = [];
+ for (let i = 0; i < n; i++) {
+ const bytes = crypto.randomBytes(len);
+ let pw = '';
+ for (let j = 0; j < len; j++) pw += chars[bytes[j] % chars.length];
+ passwords.push(pw);
+ }
+ res.json({ success: true, passwords });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Lorem Ipsum Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/lorem', (req, res) => {
+ try {
+ const words = '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 aliquip ex ea commodo consequat duis aute irure dolor in reprehenderit voluptate velit esse cillum dolore eu fugiat nulla pariatur excepteur sint occaecat cupidatat non proident sunt culpa qui officia deserunt mollit anim id est laborum'.split(' ');
+ const { paragraphs } = req.body;
+ const n = Math.min(Math.max(parseInt(paragraphs) || 3, 1), 20);
+ const result = [];
+ for (let i = 0; i < n; i++) {
+ const sentenceCount = 4 + Math.floor(Math.random() * 5);
+ const sentences = [];
+ for (let s = 0; s < sentenceCount; s++) {
+ const wordCount = 8 + Math.floor(Math.random() * 12);
+ const sentence = [];
+ for (let w = 0; w < wordCount; w++) sentence.push(words[Math.floor(Math.random() * words.length)]);
+ sentence[0] = sentence[0][0].toUpperCase() + sentence[0].slice(1);
+ sentences.push(sentence.join(' ') + '.');
+ }
+ result.push(sentences.join(' '));
+ }
+ res.json({ success: true, result: result.join('\n\n') });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Color Converter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/color/convert', (req, res) => {
+ try {
+ const { color } = req.body;
+ if (!color) return res.json({ success: false, error: 'No color provided' });
+ let r, g, b;
+ const hexMatch = color.match(/^#?([0-9a-f]{3,8})$/i);
+ if (hexMatch) {
+ let hex = hexMatch[1];
+ if (hex.length === 3) hex = hex[0]+hex[0]+hex[1]+hex[1]+hex[2]+hex[2];
+ r = parseInt(hex.slice(0,2), 16);
+ g = parseInt(hex.slice(2,4), 16);
+ b = parseInt(hex.slice(4,6), 16);
+ }
+ const rgbMatch = color.match(/rgb\s*\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\)/i);
+ if (rgbMatch) { r = +rgbMatch[1]; g = +rgbMatch[2]; b = +rgbMatch[3]; }
+ const hslMatch = color.match(/hsl\s*\(\s*(\d+)\s*,\s*(\d+)%?\s*,\s*(\d+)%?\s*\)/i);
+ if (hslMatch) {
+ const h = +hslMatch[1] / 360, s = +hslMatch[2] / 100, l = +hslMatch[3] / 100;
+ if (s === 0) { r = g = b = Math.round(l * 255); }
+ else {
+ const hue2rgb = (p, q, t) => { if (t < 0) t += 1; if (t > 1) t -= 1; if (t < 1/6) return p + (q - p) * 6 * t; if (t < 1/2) return q; if (t < 2/3) return p + (q - p) * (2/3 - t) * 6; return p; };
+ const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
+ const p = 2 * l - q;
+ r = Math.round(hue2rgb(p, q, h + 1/3) * 255);
+ g = Math.round(hue2rgb(p, q, h) * 255);
+ b = Math.round(hue2rgb(p, q, h - 1/3) * 255);
+ }
+ }
+ if (r === undefined) return res.json({ success: false, error: 'Unrecognized format. Use HEX (#ff0000), RGB (rgb(255,0,0)), or HSL (hsl(0,100,50))' });
+ const rn = r/255, gn = g/255, bn = b/255;
+ const max = Math.max(rn, gn, bn), min = Math.min(rn, gn, bn);
+ let h, s, l = (max + min) / 2;
+ if (max === min) { h = s = 0; }
+ else {
+ const d = max - min;
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
+ switch (max) {
+ case rn: h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6; break;
+ case gn: h = ((bn - rn) / d + 2) / 6; break;
+ case bn: h = ((rn - gn) / d + 4) / 6; break;
+ }
+ }
+ const hex = '#' + [r, g, b].map(x => x.toString(16).padStart(2, '0')).join('');
+ res.json({
+ success: true,
+ hex, rgb: `rgb(${r}, ${g}, ${b})`,
+ hsl: `hsl(${Math.round(h*360)}, ${Math.round(s*100)}%, ${Math.round(l*100)}%)`,
+ r, g, b
+ });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: JWT Decoder
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/jwt/decode', (req, res) => {
+ try {
+ const { token } = req.body;
+ if (!token) return res.json({ success: false, error: 'No token provided' });
+ const parts = token.split('.');
+ if (parts.length < 2) return res.json({ success: false, error: 'Invalid JWT format' });
+ const decodeB64 = s => JSON.parse(Buffer.from(s.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8'));
+ const header = decodeB64(parts[0]);
+ const payload = decodeB64(parts[1]);
+ let expired = null;
+ if (payload.exp) expired = Date.now() / 1000 > payload.exp;
+ res.json({ success: true, header, payload, expired, signature: parts[2] || null });
+ } catch (err) { res.json({ success: false, error: 'Invalid JWT: ' + err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Timestamp Converter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/timestamp', (req, res) => {
+ try {
+ const { value } = req.body;
+ let date;
+ if (!value || value === 'now') date = new Date();
+ else if (/^\d{10}$/.test(value)) date = new Date(parseInt(value) * 1000);
+ else if (/^\d{13}$/.test(value)) date = new Date(parseInt(value));
+ else date = new Date(value);
+ if (isNaN(date.getTime())) return res.json({ success: false, error: 'Invalid date/timestamp' });
+ res.json({
+ success: true,
+ unix: Math.floor(date.getTime() / 1000),
+ unixMs: date.getTime(),
+ iso: date.toISOString(),
+ utc: date.toUTCString(),
+ local: date.toString(),
+ relative: getRelativeTime(date)
+ });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+function getRelativeTime(date) {
+ const diff = (Date.now() - date.getTime()) / 1000;
+ const abs = Math.abs(diff);
+ const future = diff < 0;
+ const prefix = future ? 'in ' : '';
+ const suffix = future ? '' : ' ago';
+ if (abs < 60) return prefix + Math.round(abs) + 's' + suffix;
+ if (abs < 3600) return prefix + Math.round(abs/60) + 'm' + suffix;
+ if (abs < 86400) return prefix + Math.round(abs/3600) + 'h' + suffix;
+ if (abs < 2592000) return prefix + Math.round(abs/86400) + 'd' + suffix;
+ if (abs < 31536000) return prefix + Math.round(abs/2592000) + 'mo' + suffix;
+ return prefix + Math.round(abs/31536000) + 'y' + suffix;
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: CSS Minifier
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/css/minify', (req, res) => {
+ try {
+ const { css } = req.body;
+ if (!css) return res.json({ success: false, error: 'No CSS provided' });
+ const minified = css
+ .replace(/\/\*[\s\S]*?\*\//g, '')
+ .replace(/\s+/g, ' ')
+ .replace(/\s*([{}:;,>~+])\s*/g, '$1')
+ .replace(/;}/g, '}')
+ .trim();
+ const saved = css.length - minified.length;
+ const pct = css.length > 0 ? Math.round((saved / css.length) * 100) : 0;
+ res.json({ success: true, result: minified, original: css.length, minified: minified.length, saved, percentage: pct });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: HTML Entity Encode / Decode
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/html/encode', (req, res) => {
+ try {
+ const { text } = req.body;
+ const result = (text || '').replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, ''');
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+app.post('/api/html/decode', (req, res) => {
+ try {
+ const { text } = req.body;
+ const result = (text || '').replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"').replace(/'/g, "'").replace(/'/g, "'").replace(///g, '/');
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: String Escape / Unescape
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/escape', (req, res) => {
+ try {
+ const { text } = req.body;
+ const result = JSON.stringify(text || '');
+ res.json({ success: true, result: result.slice(1, -1) });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+app.post('/api/unescape', (req, res) => {
+ try {
+ const { text } = req.body;
+ const result = JSON.parse('"' + (text || '') + '"');
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: IP Lookup
+// ═══════════════════════════════════════════════════════════════════════════════
+app.get('/api/ip', async (req, res) => {
+ try {
+ const response = await fetch('http://ip-api.com/json/?fields=status,message,country,regionName,city,zip,lat,lon,timezone,isp,org,as,query');
+ const data = await response.json();
+ res.json({ success: true, ...data });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+app.get('/api/ip/:ip', async (req, res) => {
+ try {
+ const response = await fetch(`http://ip-api.com/json/${req.params.ip}?fields=status,message,country,regionName,city,zip,lat,lon,timezone,isp,org,as,query`);
+ const data = await response.json();
+ res.json({ success: true, ...data });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Text Statistics
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/text/stats', (req, res) => {
+ try {
+ const { text } = req.body;
+ const t = text || '';
+ const words = t.trim() ? t.trim().split(/\s+/) : [];
+ const sentences = t.split(/[.!?]+/).filter(s => s.trim().length > 0);
+ const paragraphs = t.split(/\n\s*\n/).filter(p => p.trim().length > 0);
+ const readingTime = Math.max(1, Math.ceil(words.length / 200));
+ const freq = {};
+ for (const ch of t.toLowerCase()) {
+ if (/[a-z]/.test(ch)) freq[ch] = (freq[ch] || 0) + 1;
+ }
+ const topChars = Object.entries(freq).sort((a,b) => b[1]-a[1]).slice(0, 10);
+ res.json({
+ success: true,
+ characters: t.length,
+ charactersNoSpaces: t.replace(/\s/g, '').length,
+ words: words.length,
+ sentences: sentences.length,
+ paragraphs: paragraphs.length,
+ lines: t.split('\n').length,
+ readingTime: readingTime + ' min',
+ topChars
+ });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Number Base Converter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/number/convert', (req, res) => {
+ try {
+ const { value, fromBase } = req.body;
+ const base = parseInt(fromBase) || 10;
+ const num = parseInt(value, base);
+ if (isNaN(num)) return res.json({ success: false, error: 'Invalid number for the given base' });
+ res.json({
+ success: true,
+ decimal: num.toString(10),
+ binary: num.toString(2),
+ octal: num.toString(8),
+ hex: num.toString(16).toUpperCase()
+ });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Case Converter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/text/case', (req, res) => {
+ try {
+ const { text } = req.body;
+ const t = text || '';
+ res.json({
+ success: true,
+ uppercase: t.toUpperCase(),
+ lowercase: t.toLowerCase(),
+ titleCase: t.replace(/\b\w/g, c => c.toUpperCase()),
+ camelCase: t.toLowerCase().replace(/[^a-zA-Z0-9]+(.)/g, (_, c) => c.toUpperCase()),
+ snakeCase: t.toLowerCase().replace(/[^a-zA-Z0-9]+/g, '_').replace(/^_|_$/g, ''),
+ kebabCase: t.toLowerCase().replace(/[^a-zA-Z0-9]+/g, '-').replace(/^-|-$/g, ''),
+ dotCase: t.toLowerCase().replace(/[^a-zA-Z0-9]+/g, '.').replace(/^\.|\.$/g, ''),
+ reversed: t.split('').reverse().join('')
+ });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Cron Expression Parser
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/cron/parse', (req, res) => {
+ try {
+ const { expression } = req.body;
+ if (!expression) return res.json({ success: false, error: 'No cron expression provided' });
+ const parts = expression.trim().split(/\s+/);
+ if (parts.length < 5 || parts.length > 6) return res.json({ success: false, error: 'Invalid cron expression. Expected 5 fields: minute hour day month weekday' });
+
+ const [minute, hour, dom, month, dow] = parts;
+ const fieldNames = { minute, hour, 'day of month': dom, month, 'day of week': dow };
+
+ // Build human-readable description
+ const desc = describeCron(minute, hour, dom, month, dow);
+
+ // Calculate next 5 run times
+ const nextRuns = getNextCronRuns(parts, 5);
+
+ res.json({ success: true, description: desc, fields: fieldNames, nextRuns });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+function describeCron(min, hr, dom, mon, dow) {
+ const segments = [];
+
+ if (min === '*' && hr === '*') segments.push('Every minute');
+ else if (min.startsWith('*/')) segments.push(`Every ${min.slice(2)} minutes`);
+ else if (hr.startsWith('*/')) { segments.push(`Every ${hr.slice(2)} hours`); if (min !== '0' && min !== '*') segments.push(`at minute ${min}`); }
+ else if (min !== '*' && hr !== '*') {
+ const h = parseInt(hr);
+ const ampm = h >= 12 ? 'PM' : 'AM';
+ const h12 = h === 0 ? 12 : h > 12 ? h - 12 : h;
+ segments.push(`At ${h12}:${min.padStart(2, '0')} ${ampm}`);
+ } else if (min !== '*') segments.push(`At minute ${min} of every hour`);
+ else if (hr !== '*') segments.push(`Every minute during hour ${hr}`);
+
+ const dowNames = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
+ const monNames = ['','January','February','March','April','May','June','July','August','September','October','November','December'];
+
+ if (dom !== '*') segments.push(`on day ${dom} of the month`);
+ if (mon !== '*') {
+ const m = parseInt(mon);
+ segments.push(`in ${(m >= 1 && m <= 12) ? monNames[m] : 'month ' + mon}`);
+ }
+ if (dow !== '*') {
+ if (dow.includes('-')) {
+ const [s, e] = dow.split('-').map(Number);
+ segments.push(`on ${dowNames[s] || s} through ${dowNames[e] || e}`);
+ } else {
+ const days = dow.split(',').map(d => dowNames[parseInt(d)] || d).join(', ');
+ segments.push(`on ${days}`);
+ }
+ }
+
+ return segments.join(', ') || 'Every minute';
+}
+
+function getNextCronRuns(parts, count) {
+ const [minExpr, hrExpr, domExpr, monExpr, dowExpr] = parts;
+ const runs = [];
+ const now = new Date();
+ let check = new Date(now.getTime() + 60000);
+ check.setSeconds(0, 0);
+
+ function matches(val, expr, max) {
+ if (expr === '*') return true;
+ if (expr.startsWith('*/')) return val % parseInt(expr.slice(2)) === 0;
+ if (expr.includes(',')) return expr.split(',').map(Number).includes(val);
+ if (expr.includes('-')) { const [s,e] = expr.split('-').map(Number); return val >= s && val <= e; }
+ return val === parseInt(expr);
+ }
+
+ let iterations = 0;
+ while (runs.length < count && iterations < 525600) {
+ iterations++;
+ const m = check.getMinutes(), h = check.getHours(), d = check.getDate(), mo = check.getMonth() + 1, w = check.getDay();
+ if (matches(m, minExpr) && matches(h, hrExpr) && matches(d, domExpr) && matches(mo, monExpr) && matches(w, dowExpr)) {
+ runs.push(check.toISOString().replace('T', ' ').slice(0, 16));
+ }
+ check = new Date(check.getTime() + 60000);
+ }
+ return runs;
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: JSON ↔ CSV Converter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/convert/json-to-csv', (req, res) => {
+ try {
+ const { json } = req.body;
+ if (!json) return res.json({ success: false, error: 'No JSON provided' });
+ const data = JSON.parse(json);
+ if (!Array.isArray(data) || data.length === 0) return res.json({ success: false, error: 'Input must be a non-empty JSON array of objects' });
+ const headers = [...new Set(data.flatMap(obj => Object.keys(obj)))];
+ const escapeCsv = v => {
+ const s = String(v === null || v === undefined ? '' : v);
+ return s.includes(',') || s.includes('"') || s.includes('\n') ? '"' + s.replace(/"/g, '""') + '"' : s;
+ };
+ const rows = data.map(obj => headers.map(h => escapeCsv(obj[h])).join(','));
+ const csv = [headers.join(','), ...rows].join('\n');
+ res.json({ success: true, result: csv, rows: data.length, columns: headers.length });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+app.post('/api/convert/csv-to-json', (req, res) => {
+ try {
+ const { csv } = req.body;
+ if (!csv) return res.json({ success: false, error: 'No CSV provided' });
+ const lines = csv.trim().split('\n');
+ if (lines.length < 2) return res.json({ success: false, error: 'CSV must have a header row and at least one data row' });
+ const headers = parseCsvLine(lines[0]);
+ const result = [];
+ for (let i = 1; i < lines.length; i++) {
+ if (!lines[i].trim()) continue;
+ const vals = parseCsvLine(lines[i]);
+ const obj = {};
+ headers.forEach((h, idx) => { obj[h] = vals[idx] || ''; });
+ result.push(obj);
+ }
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+function parseCsvLine(line) {
+ const result = [];
+ let current = '';
+ let inQuotes = false;
+ for (let i = 0; i < line.length; i++) {
+ const ch = line[i];
+ if (inQuotes) {
+ if (ch === '"' && line[i + 1] === '"') { current += '"'; i++; }
+ else if (ch === '"') inQuotes = false;
+ else current += ch;
+ } else {
+ if (ch === '"') inQuotes = true;
+ else if (ch === ',') { result.push(current); current = ''; }
+ else current += ch;
+ }
+ }
+ result.push(current);
+ return result;
+}
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Text Encoder (ROT13, Binary, Morse, etc.)
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/text/encode', (req, res) => {
+ try {
+ const { text, method } = req.body;
+ if (!text) return res.json({ success: false, error: 'No text provided' });
+ if (!method) return res.json({ success: false, error: 'No encoding method specified' });
+
+ let result;
+ switch (method) {
+ case 'rot13':
+ result = text.replace(/[a-zA-Z]/g, c => {
+ const base = c <= 'Z' ? 65 : 97;
+ return String.fromCharCode(((c.charCodeAt(0) - base + 13) % 26) + base);
+ });
+ break;
+
+ case 'binary':
+ result = text.split('').map(c => c.charCodeAt(0).toString(2).padStart(8, '0')).join(' ');
+ break;
+
+ case 'morse': {
+ const morseMap = {
+ 'A':'.-','B':'-...','C':'-.-.','D':'-..','E':'.','F':'..-.','G':'--.','H':'....',
+ 'I':'..','J':'.---','K':'-.-','L':'.-..','M':'--','N':'-.','O':'---','P':'.--.',
+ 'Q':'--.-','R':'.-.','S':'...','T':'-','U':'..-','V':'...-','W':'.--','X':'-..-',
+ 'Y':'-.--','Z':'--..','0':'-----','1':'.----','2':'..---','3':'...--','4':'....-',
+ '5':'.....','6':'-....','7':'--...','8':'---..','9':'----.', ' ':'/'
+ };
+ result = text.toUpperCase().split('').map(c => morseMap[c] || c).join(' ');
+ break;
+ }
+
+ case 'reverse':
+ result = text.split('').reverse().join('');
+ break;
+
+ case 'leetspeak': {
+ const leetMap = { 'A':'4','E':'3','G':'6','I':'1','O':'0','S':'5','T':'7','B':'8','L':'1' };
+ result = text.split('').map(c => leetMap[c.toUpperCase()] || c).join('');
+ break;
+ }
+
+ case 'upside_down': {
+ const flipMap = {
+ 'a':'ɐ','b':'q','c':'ɔ','d':'p','e':'ǝ','f':'ɟ','g':'ƃ','h':'ɥ','i':'ᴉ','j':'ɾ',
+ 'k':'ʞ','l':'l','m':'ɯ','n':'u','o':'o','p':'d','q':'b','r':'ɹ','s':'s','t':'ʇ',
+ 'u':'n','v':'ʌ','w':'ʍ','x':'x','y':'ʎ','z':'z',
+ 'A':'∀','B':'q','C':'Ɔ','D':'p','E':'Ǝ','F':'Ⅎ','G':'פ','H':'H','I':'I','J':'ſ',
+ 'K':'ʞ','L':'˥','M':'W','N':'N','O':'O','P':'Ԁ','Q':'Q','R':'ɹ','S':'S','T':'⊥',
+ 'U':'∩','V':'Λ','W':'M','X':'X','Y':'⅄','Z':'Z',
+ '1':'Ɩ','2':'ᄅ','3':'Ɛ','4':'ㄣ','5':'ϛ','6':'9','7':'ㄥ','8':'8','9':'6','0':'0',
+ '.':'˙',',':'\'','?':'¿','!':'¡','\'':',','"':'„','(':')',')':'(','{':'}','}':'{',
+ '[':']',']':'[','<':'>','>':'<','&':'⅋','_':'‾'
+ };
+ result = text.split('').map(c => flipMap[c] || c).reverse().join('');
+ break;
+ }
+
+ default:
+ return res.json({ success: false, error: `Unknown encoding method: ${method}` });
+ }
+
+ res.json({ success: true, result, method });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: HTTP Status Code Lookup
+// ═══════════════════════════════════════════════════════════════════════════════
+const HTTP_STATUS_CODES = {
+ 100:'Continue',101:'Switching Protocols',102:'Processing',103:'Early Hints',
+ 200:'OK',201:'Created',202:'Accepted',203:'Non-Authoritative Information',204:'No Content',
+ 205:'Reset Content',206:'Partial Content',207:'Multi-Status',208:'Already Reported',226:'IM Used',
+ 300:'Multiple Choices',301:'Moved Permanently',302:'Found',303:'See Other',304:'Not Modified',
+ 307:'Temporary Redirect',308:'Permanent Redirect',
+ 400:'Bad Request',401:'Unauthorized',402:'Payment Required',403:'Forbidden',404:'Not Found',
+ 405:'Method Not Allowed',406:'Not Acceptable',407:'Proxy Authentication Required',
+ 408:'Request Timeout',409:'Conflict',410:'Gone',411:'Length Required',412:'Precondition Failed',
+ 413:'Payload Too Large',414:'URI Too Long',415:'Unsupported Media Type',416:'Range Not Satisfiable',
+ 418:"I'm a Teapot",422:'Unprocessable Entity',425:'Too Early',429:'Too Many Requests',
+ 451:'Unavailable For Legal Reasons',
+ 500:'Internal Server Error',501:'Not Implemented',502:'Bad Gateway',503:'Service Unavailable',
+ 504:'Gateway Timeout',505:'HTTP Version Not Supported',507:'Insufficient Storage',
+ 508:'Loop Detected',511:'Network Authentication Required'
+};
+
+app.get('/api/http-status', (req, res) => {
+ const entries = Object.entries(HTTP_STATUS_CODES).map(([code, text]) => ({
+ code: parseInt(code), text, category: code[0] + 'xx'
+ }));
+ res.json({ success: true, statuses: entries });
+});
+
+app.get('/api/http-status/:code', (req, res) => {
+ const code = req.params.code;
+ const text = HTTP_STATUS_CODES[code];
+ if (!text) return res.json({ success: false, error: `Unknown status code: ${code}` });
+ res.json({ success: true, code: parseInt(code), text, category: code[0] + 'xx' });
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Placeholder Image Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.get('/api/placeholder/:width/:height', (req, res) => {
+ try {
+ const w = Math.min(Math.max(parseInt(req.params.width) || 400, 1), 2000);
+ const h = Math.min(Math.max(parseInt(req.params.height) || 300, 1), 2000);
+ const bg = (req.query.bg || '2a2a3a').replace(/[^0-9a-fA-F]/g, '').slice(0, 6);
+ const fg = (req.query.fg || '8888a0').replace(/[^0-9a-fA-F]/g, '').slice(0, 6);
+ const text = req.query.text || `${w}×${h}`;
+ const fontSize = Math.min(Math.max(parseInt(req.query.fontSize) || Math.min(w, h) / 8, 8), 200);
+
+ // Generate SVG
+ const svg = `
+
+
+
+ ${text.replace(/&/g,'&').replace(//g,'>')}
+ `;
+
+ res.setHeader('Content-Type', 'image/svg+xml');
+ res.setHeader('Cache-Control', 'public, max-age=86400');
+ res.send(svg);
+ } catch (err) { res.status(500).json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: SQL Formatter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/sql/format', (req, res) => {
+ try {
+ const { sql } = req.body;
+ if (!sql) return res.json({ success: false, error: 'No SQL provided' });
+ const majorKw = ['SELECT','FROM','WHERE','AND','OR','ORDER BY','GROUP BY','HAVING','LIMIT','OFFSET','INSERT INTO','VALUES','UPDATE','SET','DELETE FROM','CREATE TABLE','ALTER TABLE','DROP TABLE','JOIN','INNER JOIN','LEFT JOIN','RIGHT JOIN','FULL JOIN','CROSS JOIN','ON','UNION','UNION ALL','EXCEPT','INTERSECT','CASE','WHEN','THEN','ELSE','END','WITH','AS'];
+ let formatted = sql.replace(/\s+/g, ' ').trim();
+ for (const kw of majorKw) {
+ const re = new RegExp('\\b' + kw.replace(/ /g, '\\s+') + '\\b', 'gi');
+ formatted = formatted.replace(re, '\n' + kw.toUpperCase());
+ }
+ // Indent non-keyword continuation lines
+ const lines = formatted.split('\n').filter(l => l.trim());
+ const result = lines.map((line, i) => {
+ const trimmed = line.trim();
+ if (i === 0) return trimmed;
+ const isKw = majorKw.some(kw => trimmed.toUpperCase().startsWith(kw));
+ return isKw ? trimmed : ' ' + trimmed;
+ }).join('\n');
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+app.post('/api/sql/minify', (req, res) => {
+ try {
+ const { sql } = req.body;
+ if (!sql) return res.json({ success: false, error: 'No SQL provided' });
+ const result = sql.replace(/\s+/g, ' ').replace(/\s*([,;()=<>!])\s*/g, '$1').trim();
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Byte Size Converter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/bytes/convert', (req, res) => {
+ try {
+ const { value, unit, mode } = req.body;
+ if (value === undefined || value === null) return res.json({ success: false, error: 'No value provided' });
+ const base = mode === 'si' ? 1000 : 1024;
+ const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
+ const idx = units.indexOf(unit || 'B');
+ if (idx === -1) return res.json({ success: false, error: 'Invalid unit. Use B, KB, MB, GB, TB, or PB' });
+ // Convert to bytes first
+ const bytes = parseFloat(value) * Math.pow(base, idx);
+ const result = {};
+ for (let i = 0; i < units.length; i++) {
+ const val = bytes / Math.pow(base, i);
+ result[units[i]] = val >= 1 ? parseFloat(val.toPrecision(10)) : parseFloat(val.toExponential(4));
+ }
+ res.json({ success: true, ...result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: HMAC Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/hmac', (req, res) => {
+ try {
+ const { message, secret, algorithm } = req.body;
+ if (!message) return res.json({ success: false, error: 'No message provided' });
+ if (!secret) return res.json({ success: false, error: 'No secret key provided' });
+ const algo = algorithm || 'sha256';
+ const supported = ['sha256', 'sha512', 'sha1', 'md5'];
+ if (!supported.includes(algo)) return res.json({ success: false, error: `Unsupported algorithm. Use: ${supported.join(', ')}` });
+ const hmac = crypto.createHmac(algo, secret).update(message, 'utf8').digest('hex');
+ res.json({ success: true, hmac, algorithm: algo });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Slug Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/text/slugify', (req, res) => {
+ try {
+ const { text, separator, lowercase } = req.body;
+ if (!text) return res.json({ success: false, error: 'No text provided' });
+ const sep = separator || '-';
+ let slug = text.normalize('NFD').replace(/[\u0300-\u036f]/g, ''); // strip diacritics
+ if (lowercase !== false) slug = slug.toLowerCase();
+ slug = slug
+ .replace(/[^a-zA-Z0-9\s-]/g, '') // remove special chars
+ .replace(/[\s-]+/g, sep) // replace spaces/hyphens with separator
+ .replace(new RegExp(`^\\${sep}+|\\${sep}+$`, 'g'), ''); // trim separator edges
+ res.json({ success: true, result: slug });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: Chmod Calculator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/chmod/calculate', (req, res) => {
+ try {
+ const { numeric, symbolic } = req.body;
+ if (!numeric && !symbolic) return res.json({ success: false, error: 'Provide numeric or symbolic permissions' });
+
+ const digitToPerms = d => ({ read: !!(d & 4), write: !!(d & 2), execute: !!(d & 1) });
+ const permsToDigit = (r, w, x) => (r ? 4 : 0) + (w ? 2 : 0) + (x ? 1 : 0);
+ const permsToStr = p => (p.read ? 'r' : '-') + (p.write ? 'w' : '-') + (p.execute ? 'x' : '-');
+
+ let owner, group, others;
+
+ if (numeric) {
+ const digits = numeric.length === 4 ? numeric.slice(1) : numeric;
+ if (!/^[0-7]{3}$/.test(digits)) return res.json({ success: false, error: 'Invalid numeric permissions (use 3 octal digits, e.g. 755)' });
+ owner = digitToPerms(parseInt(digits[0]));
+ group = digitToPerms(parseInt(digits[1]));
+ others = digitToPerms(parseInt(digits[2]));
+ } else {
+ const s = symbolic.replace(/^[-dlcbps]/, ''); // strip optional file type prefix
+ if (s.length < 9) return res.json({ success: false, error: 'Invalid symbolic permissions (need 9 characters, e.g. rwxr-xr-x)' });
+ owner = { read: s[0] === 'r', write: s[1] === 'w', execute: s[2] === 'x' || s[2] === 's' };
+ group = { read: s[3] === 'r', write: s[4] === 'w', execute: s[5] === 'x' || s[5] === 's' };
+ others = { read: s[6] === 'r', write: s[7] === 'w', execute: s[8] === 'x' || s[8] === 't' };
+ }
+
+ const numStr = '' + permsToDigit(owner.read, owner.write, owner.execute) +
+ permsToDigit(group.read, group.write, group.execute) +
+ permsToDigit(others.read, others.write, others.execute);
+ const symStr = permsToStr(owner) + permsToStr(group) + permsToStr(others);
+
+ res.json({ success: true, numeric: numStr, symbolic: symStr, owner, group, others });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: ASCII Art Generator
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/ascii/generate', (req, res) => {
+ try {
+ const { text } = req.body;
+ if (!text) return res.json({ success: false, error: 'No text provided' });
+ const font = {
+ A:[' █ ','█ █','█████','█ █','█ █'],B:['████ ','█ █','████ ','█ █','████ '],
+ C:[' ████','█ ','█ ','█ ',' ████'],D:['████ ','█ █','█ █','█ █','████ '],
+ E:['█████','█ ','████ ','█ ','█████'],F:['█████','█ ','████ ','█ ','█ '],
+ G:[' ████','█ ','█ ██','█ █',' ████'],H:['█ █','█ █','█████','█ █','█ █'],
+ I:['█████',' █ ',' █ ',' █ ','█████'],J:['█████',' █',' █','█ █',' ███ '],
+ K:['█ █','█ █ ','███ ','█ █ ','█ █'],L:['█ ','█ ','█ ','█ ','█████'],
+ M:['█ █','██ ██','█ █ █','█ █','█ █'],N:['█ █','██ █','█ █ █','█ ██','█ █'],
+ O:[' ███ ','█ █','█ █','█ █',' ███ '],P:['████ ','█ █','████ ','█ ','█ '],
+ Q:[' ███ ','█ █','█ █ █','█ █ ',' ██ █'],R:['████ ','█ █','████ ','█ █ ','█ █'],
+ S:[' ████','█ ',' ███ ',' █','████ '],T:['█████',' █ ',' █ ',' █ ',' █ '],
+ U:['█ █','█ █','█ █','█ █',' ███ '],V:['█ █','█ █','█ █',' █ █ ',' █ '],
+ W:['█ █','█ █','█ █ █','██ ██','█ █'],X:['█ █',' █ █ ',' █ ',' █ █ ','█ █'],
+ Y:['█ █',' █ █ ',' █ ',' █ ',' █ '],Z:['█████',' █ ',' █ ',' █ ','█████'],
+ '0':[' ███ ','█ ██','█ █ █','██ █',' ███ '],'1':[' █ ',' ██ ',' █ ',' █ ','█████'],
+ '2':[' ███ ','█ █',' ██ ',' █ ','█████'],'3':['████ ',' █',' ███ ',' █','████ '],
+ '4':['█ █','█ █','█████',' █',' █'],'5':['█████','█ ','████ ',' █','████ '],
+ '6':[' ███ ','█ ','████ ','█ █',' ███ '],'7':['█████',' █ ',' █ ',' █ ',' █ '],
+ '8':[' ███ ','█ █',' ███ ','█ █',' ███ '],'9':[' ███ ','█ █',' ████',' █',' ███ '],
+ '!':[' █ ',' █ ',' █ ',' ',' █ '],'?':[' ███ ','█ █',' ██ ',' ',' █ '],
+ '.':[' ',' ',' ',' ',' █ '],'-':[' ',' ','█████',' ',' '],
+ ' ':[' ',' ',' ',' ',' ']
+ };
+ const input = text.toUpperCase().slice(0, 30);
+ const lines = [[], [], [], [], []];
+ for (const ch of input) {
+ const glyph = font[ch] || font[' '];
+ for (let row = 0; row < 5; row++) {
+ lines[row].push(glyph[row]);
+ }
+ }
+ const result = lines.map(row => row.join(' ')).join('\n');
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// API: ENV ↔ JSON Converter
+// ═══════════════════════════════════════════════════════════════════════════════
+app.post('/api/convert/env-to-json', (req, res) => {
+ try {
+ const { env } = req.body;
+ if (!env) return res.json({ success: false, error: 'No .env content provided' });
+ const result = {};
+ const lines = env.split('\n');
+ for (const line of lines) {
+ const trimmed = line.trim();
+ if (!trimmed || trimmed.startsWith('#')) continue; // skip empty lines and comments
+ const eqIdx = trimmed.indexOf('=');
+ if (eqIdx === -1) continue;
+ const key = trimmed.slice(0, eqIdx).trim();
+ let val = trimmed.slice(eqIdx + 1).trim();
+ // strip surrounding quotes
+ if ((val.startsWith('"') && val.endsWith('"')) || (val.startsWith("'") && val.endsWith("'"))) {
+ val = val.slice(1, -1);
+ }
+ if (key) result[key] = val;
+ }
+ res.json({ success: true, result });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+app.post('/api/convert/json-to-env', (req, res) => {
+ try {
+ const { json } = req.body;
+ if (!json) return res.json({ success: false, error: 'No JSON provided' });
+ const data = typeof json === 'string' ? JSON.parse(json) : json;
+ if (typeof data !== 'object' || Array.isArray(data)) return res.json({ success: false, error: 'JSON must be a flat object' });
+ const lines = [];
+ for (const [key, val] of Object.entries(data)) {
+ const v = String(val);
+ const needsQuotes = v.includes(' ') || v.includes('=') || v.includes('#');
+ lines.push(`${key}=${needsQuotes ? '"' + v + '"' : v}`);
+ }
+ res.json({ success: true, result: lines.join('\n'), count: lines.length });
+ } catch (err) { res.json({ success: false, error: err.message }); }
+});
+
+// ═══════════════════════════════════════════════════════════════════════════════
+// SPA fallback
+// ═══════════════════════════════════════════════════════════════════════════════
+app.get('*', (req, res) => {
+ res.sendFile(path.join(__dirname, 'public', 'index.html'));
+});
+
+// ─── Start ────────────────────────────────────────────────────────────────────
+app.listen(PORT, () => {
+ console.log(`\n ⚡ WinnieAPI-v2 running at http://localhost:${PORT}\n`);
+});