first commit
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
.idea
|
||||
node_modules
|
||||
package-lock.json
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "winnieapi-v2",
|
||||
"version": "1.0.0",
|
||||
"description": "A sleek dark-mode utilities website",
|
||||
"main": "server.js",
|
||||
"scripts": {
|
||||
"start": "node server.js",
|
||||
"dev": "node server.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"nanoid": "^3.3.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,482 @@
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg-primary: #0a0a0f;
|
||||
--bg-secondary: #12121a;
|
||||
--bg-card: #16161f;
|
||||
--bg-input: #1a1a25;
|
||||
--border: #2a2a3a;
|
||||
--border-glow: #6c63ff40;
|
||||
--text-primary: #e8e8f0;
|
||||
--text-secondary: #8888a0;
|
||||
--text-muted: #555570;
|
||||
--accent: #6c63ff;
|
||||
--accent-hover: #7f78ff;
|
||||
--accent-glow: #6c63ff30;
|
||||
--green: #22c55e;
|
||||
--red: #ef4444;
|
||||
--orange: #f59e0b;
|
||||
--cyan: #06b6d4;
|
||||
--pink: #ec4899;
|
||||
--yellow: #eab308;
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
/* ─── Background Glow ─────────────────────────────────── */
|
||||
.bg-glow { position: fixed; inset: 0; z-index: -1; overflow: hidden; }
|
||||
.bg-glow::before, .bg-glow::after {
|
||||
content: ''; position: absolute; width: 600px; height: 600px;
|
||||
border-radius: 50%; filter: blur(120px); opacity: 0.07;
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
.bg-glow::before { background: var(--accent); top: -200px; left: -100px; }
|
||||
.bg-glow::after { background: var(--pink); bottom: -200px; right: -100px; animation-delay: -10s; }
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
33% { transform: translate(100px, 50px) scale(1.1); }
|
||||
66% { transform: translate(-50px, -30px) scale(0.95); }
|
||||
}
|
||||
/* ─── Particles ─────────────────────────────────────── */
|
||||
.particles { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
|
||||
.particle {
|
||||
position: absolute; width: 2px; height: 2px;
|
||||
background: var(--accent); border-radius: 50%; opacity: 0;
|
||||
animation: particle-float 15s linear infinite;
|
||||
}
|
||||
@keyframes particle-float {
|
||||
0% { opacity: 0; transform: translateY(100vh) scale(0); }
|
||||
10% { opacity: 0.6; }
|
||||
90% { opacity: 0.6; }
|
||||
100% { opacity: 0; transform: translateY(-10vh) scale(1); }
|
||||
}
|
||||
/* ─── Header ──────────────────────────────────────────── */
|
||||
header {
|
||||
position: sticky; top: 0; z-index: 100;
|
||||
background: rgba(10, 10, 15, 0.85);
|
||||
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0 24px;
|
||||
}
|
||||
.header-inner {
|
||||
max-width: 1400px; margin: 0 auto;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
height: 64px; gap: 16px;
|
||||
}
|
||||
.logo {
|
||||
font-size: 1.35rem; font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--accent), var(--pink));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
||||
cursor: pointer; letter-spacing: -0.5px; white-space: nowrap;
|
||||
}
|
||||
.logo span { opacity: 0.5; font-weight: 400; }
|
||||
.search-bar {
|
||||
flex: 1; max-width: 400px; position: relative;
|
||||
}
|
||||
.search-bar input {
|
||||
width: 100%; background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: 24px; color: var(--text-primary);
|
||||
padding: 8px 16px 8px 36px; font-size: 0.85rem;
|
||||
transition: var(--transition); outline: none; font-family: var(--font-sans);
|
||||
}
|
||||
.search-bar input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||
.search-bar i { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 0.8rem; }
|
||||
.header-actions { display: flex; gap: 8px; align-items: center; }
|
||||
.header-actions button {
|
||||
background: var(--bg-input); border: 1px solid var(--border); color: var(--text-secondary);
|
||||
width: 36px; height: 36px; border-radius: 50%; cursor: pointer;
|
||||
transition: var(--transition); display: flex; align-items: center; justify-content: center;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.header-actions button:hover { border-color: var(--accent); color: var(--accent); }
|
||||
/* ─── Main ────────────────────────────────────────────── */
|
||||
main { max-width: 1400px; margin: 0 auto; padding: 32px 24px 80px; }
|
||||
.page { display: none; animation: fadeIn 0.35s ease; }
|
||||
.page.active { display: block; }
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
/* ─── Hero ────────────────────────────────────────────── */
|
||||
.hero { text-align: center; padding: 60px 0 48px; }
|
||||
.hero h1 {
|
||||
font-size: clamp(2.2rem, 5vw, 3.5rem);
|
||||
font-weight: 800; letter-spacing: -1.5px; line-height: 1.1; margin-bottom: 14px;
|
||||
}
|
||||
.hero h1 .gradient {
|
||||
background: linear-gradient(135deg, var(--accent), var(--cyan), var(--pink));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
||||
}
|
||||
.hero p { color: var(--text-secondary); font-size: 1.1rem; max-width: 520px; margin: 0 auto 12px; line-height: 1.6; }
|
||||
.hero .stats {
|
||||
display: flex; gap: 32px; justify-content: center; margin-top: 20px;
|
||||
}
|
||||
.hero .stat { text-align: center; }
|
||||
.hero .stat-num { font-size: 1.8rem; font-weight: 800; color: var(--accent); }
|
||||
.hero .stat-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; }
|
||||
/* ─── Category Tabs ───────────────────────────────────── */
|
||||
.categories { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; margin-bottom: 28px; }
|
||||
.cat-btn {
|
||||
background: var(--bg-input); border: 1px solid var(--border); color: var(--text-secondary);
|
||||
padding: 6px 16px; border-radius: 20px; font-size: 0.8rem; font-weight: 500;
|
||||
cursor: pointer; transition: var(--transition); font-family: var(--font-sans);
|
||||
}
|
||||
.cat-btn:hover { border-color: var(--accent); color: var(--text-primary); }
|
||||
.cat-btn.active { background: var(--accent-glow); border-color: var(--accent); color: var(--accent); }
|
||||
/* ─── Tools Grid ──────────────────────────────────────── */
|
||||
.tools-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.tool-card {
|
||||
background: var(--bg-card); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 24px; cursor: pointer;
|
||||
transition: var(--transition); position: relative; overflow: hidden;
|
||||
}
|
||||
.tool-card::before {
|
||||
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
|
||||
opacity: 0; transition: var(--transition);
|
||||
}
|
||||
.tool-card:hover {
|
||||
border-color: var(--border-glow); transform: translateY(-3px);
|
||||
box-shadow: 0 16px 32px rgba(0,0,0,0.3), 0 0 20px var(--accent-glow);
|
||||
}
|
||||
.tool-card:hover::before { opacity: 1; }
|
||||
.tool-card .tc-top { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
|
||||
.tool-card .icon {
|
||||
width: 42px; height: 42px; border-radius: var(--radius-sm);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.1rem; flex-shrink: 0;
|
||||
}
|
||||
.tool-card h3 { font-size: 1rem; font-weight: 700; }
|
||||
.tool-card p { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.5; }
|
||||
.tool-card .tag {
|
||||
display: inline-block; padding: 2px 8px; border-radius: 20px;
|
||||
font-size: 0.65rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
|
||||
margin-left: auto;
|
||||
}
|
||||
/* Icon colors */
|
||||
.ic-purple { background: #6c63ff15; color: var(--accent); }
|
||||
.ic-green { background: #22c55e15; color: var(--green); }
|
||||
.ic-red { background: #ef444415; color: var(--red); }
|
||||
.ic-cyan { background: #06b6d415; color: var(--cyan); }
|
||||
.ic-orange { background: #f59e0b15; color: var(--orange); }
|
||||
.ic-pink { background: #ec489915; color: var(--pink); }
|
||||
.ic-yellow { background: #eab30815; color: var(--yellow); }
|
||||
/* Gradient bars */
|
||||
[data-cat="format"]::before { background: linear-gradient(90deg, var(--accent), var(--cyan)); }
|
||||
[data-cat="encode"]::before { background: linear-gradient(90deg, var(--green), var(--cyan)); }
|
||||
[data-cat="generate"]::before{ background: linear-gradient(90deg, var(--orange), var(--yellow)); }
|
||||
[data-cat="text"]::before { background: linear-gradient(90deg, var(--pink), var(--accent)); }
|
||||
[data-cat="web"]::before { background: linear-gradient(90deg, var(--cyan), var(--green)); }
|
||||
[data-cat="dev"]::before { background: linear-gradient(90deg, var(--red), var(--orange)); }
|
||||
.tag-purple { background: #6c63ff20; color: var(--accent); }
|
||||
.tag-green { background: #22c55e20; color: var(--green); }
|
||||
.tag-red { background: #ef444420; color: var(--red); }
|
||||
.tag-cyan { background: #06b6d420; color: var(--cyan); }
|
||||
.tag-orange { background: #f59e0b20; color: var(--orange); }
|
||||
.tag-pink { background: #ec489920; color: var(--pink); }
|
||||
.tag-yellow { background: #eab30820; color: var(--yellow); }
|
||||
/* ─── Section Header / Back ───────────────────────────── */
|
||||
.section-header { margin-bottom: 24px; }
|
||||
.section-header h2 { font-size: 1.6rem; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 4px; }
|
||||
.section-header p { color: var(--text-secondary); font-size: 0.9rem; }
|
||||
.back-btn {
|
||||
background: transparent; border: 1px solid var(--border);
|
||||
color: var(--text-secondary); padding: 6px 14px; border-radius: var(--radius-sm);
|
||||
cursor: pointer; font-size: 0.8rem; margin-bottom: 16px;
|
||||
transition: var(--transition); font-family: var(--font-sans);
|
||||
}
|
||||
.back-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
/* ─── Form Controls ───────────────────────────────────── */
|
||||
textarea, input[type="text"], input[type="url"], input[type="number"], input[type="password"], select {
|
||||
width: 100%; background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); color: var(--text-primary);
|
||||
padding: 12px 16px; font-family: var(--font-mono); font-size: 0.85rem;
|
||||
transition: var(--transition); outline: none;
|
||||
}
|
||||
textarea:focus, input:focus, select:focus {
|
||||
border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow);
|
||||
}
|
||||
textarea { resize: vertical; min-height: 180px; line-height: 1.6; }
|
||||
input[type="color"] {
|
||||
width: 60px; height: 42px; border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); background: var(--bg-input);
|
||||
cursor: pointer; padding: 4px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 10px 20px; border: none; border-radius: var(--radius-sm);
|
||||
font-family: var(--font-sans); font-size: 0.85rem; font-weight: 600;
|
||||
cursor: pointer; transition: var(--transition); white-space: nowrap;
|
||||
}
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); box-shadow: 0 4px 20px var(--accent-glow); }
|
||||
.btn-secondary { background: var(--bg-input); color: var(--text-secondary); border: 1px solid var(--border); }
|
||||
.btn-secondary:hover { border-color: var(--accent); color: var(--text-primary); }
|
||||
.btn-green { background: var(--green); color: white; }
|
||||
.btn-green:hover { background: #16a34a; }
|
||||
.btn-red { background: var(--red); color: white; }
|
||||
.btn-red:hover { background: #dc2626; }
|
||||
.btn-cyan { background: var(--cyan); color: white; }
|
||||
.btn-cyan:hover { background: #0891b2; }
|
||||
.btn-orange { background: var(--orange); color: #000; }
|
||||
.btn-orange:hover { background: #d97706; }
|
||||
.btn-pink { background: var(--pink); color: white; }
|
||||
.btn-pink:hover { background: #db2777; }
|
||||
.btn-sm { padding: 6px 14px; font-size: 0.78rem; }
|
||||
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0; }
|
||||
/* ─── Panels ──────────────────────────────────────────── */
|
||||
.split-panel { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.panel-label {
|
||||
font-size: 0.72rem; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 1px; color: var(--text-muted); margin-bottom: 8px;
|
||||
}
|
||||
/* ─── Result Cards ────────────────────────────────────── */
|
||||
.result-card {
|
||||
background: var(--bg-card); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 20px; margin-top: 16px;
|
||||
}
|
||||
.result-row {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 10px 14px; background: var(--bg-input);
|
||||
border-radius: var(--radius-sm); margin-bottom: 6px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.result-row .label { font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.result-row .value {
|
||||
font-family: var(--font-mono); font-size: 0.82rem; color: var(--cyan);
|
||||
word-break: break-all; cursor: pointer; text-align: right; max-width: 70%;
|
||||
}
|
||||
.result-row .value:hover { color: var(--accent); }
|
||||
/* ─── Status ──────────────────────────────────────────── */
|
||||
.status {
|
||||
padding: 10px 14px; border-radius: var(--radius-sm); font-size: 0.82rem;
|
||||
margin-top: 10px; display: none; font-family: var(--font-mono);
|
||||
}
|
||||
.status.success { display: block; background: #22c55e12; border: 1px solid #22c55e30; color: var(--green); }
|
||||
.status.error { display: block; background: #ef444412; border: 1px solid #ef444430; color: var(--red); }
|
||||
.status.info { display: block; background: #6c63ff12; border: 1px solid #6c63ff30; color: var(--accent); }
|
||||
.status.warning { display: block; background: #f59e0b12; border: 1px solid #f59e0b30; color: var(--orange); }
|
||||
/* ─── Short URL ───────────────────────────────────────── */
|
||||
.short-url-result { display: none; margin-top: 16px; }
|
||||
.short-url-result.visible { display: block; animation: fadeIn 0.35s ease; }
|
||||
.short-url-display {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: var(--bg-input); border: 1px solid var(--accent);
|
||||
border-radius: var(--radius-sm); padding: 14px 16px; margin: 10px 0;
|
||||
}
|
||||
.short-url-display a { flex: 1; color: var(--accent); font-family: var(--font-mono); font-size: 0.9rem; text-decoration: none; }
|
||||
.short-url-display a:hover { text-decoration: underline; }
|
||||
/* ─── YouTube Card ────────────────────────────────────── */
|
||||
.yt-result { display: none; overflow: hidden; margin-top: 16px; }
|
||||
.yt-result.visible { display: block; animation: fadeIn 0.35s ease; }
|
||||
.yt-thumb { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; border-radius: var(--radius) var(--radius) 0 0; }
|
||||
.yt-info { padding: 20px; background: var(--bg-card); border: 1px solid var(--border); border-top: 0; border-radius: 0 0 var(--radius) var(--radius); }
|
||||
.yt-info h3 { font-size: 1.1rem; margin-bottom: 6px; line-height: 1.4; }
|
||||
.yt-info .author { color: var(--text-secondary); font-size: 0.85rem; margin-bottom: 14px; }
|
||||
.yt-info .author a { color: var(--accent); text-decoration: none; }
|
||||
.yt-links { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
/* ─── Color Preview ───────────────────────────────────── */
|
||||
.color-preview-box {
|
||||
width: 100%; height: 120px; border-radius: var(--radius);
|
||||
border: 1px solid var(--border); margin-bottom: 16px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
/* ─── Checkbox & Toggle ───────────────────────────────── */
|
||||
.checkbox-group { display: flex; flex-wrap: wrap; gap: 16px; margin: 12px 0; }
|
||||
.checkbox-group label {
|
||||
display: flex; align-items: center; gap: 6px; font-size: 0.85rem;
|
||||
color: var(--text-secondary); cursor: pointer;
|
||||
}
|
||||
.checkbox-group input[type="checkbox"] {
|
||||
width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer;
|
||||
}
|
||||
/* ─── Password Display ────────────────────────────────── */
|
||||
.pw-display {
|
||||
font-family: var(--font-mono); font-size: 1.1rem;
|
||||
padding: 16px; background: var(--bg-input);
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
word-break: break-all; cursor: pointer;
|
||||
transition: var(--transition); margin: 12px 0;
|
||||
}
|
||||
.pw-display:hover { border-color: var(--accent); }
|
||||
/* ─── Strength Meter ──────────────────────────────────── */
|
||||
.strength-bar { height: 4px; border-radius: 2px; background: var(--border); margin-top: 8px; overflow: hidden; }
|
||||
.strength-bar-fill { height: 100%; border-radius: 2px; transition: all 0.3s ease; }
|
||||
/* ─── Markdown Preview ────────────────────────────────── */
|
||||
.md-preview {
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); padding: 20px; min-height: 180px;
|
||||
line-height: 1.7; font-size: 0.9rem; overflow-y: auto; max-height: 600px;
|
||||
}
|
||||
.md-preview h1, .md-preview h2, .md-preview h3 { margin: 16px 0 8px; color: var(--text-primary); }
|
||||
.md-preview h1 { font-size: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
|
||||
.md-preview h2 { font-size: 1.25rem; }
|
||||
.md-preview h3 { font-size: 1.1rem; }
|
||||
.md-preview p { margin-bottom: 10px; color: var(--text-secondary); }
|
||||
.md-preview code {
|
||||
background: #1e1e2e; padding: 2px 6px; border-radius: 4px;
|
||||
font-family: var(--font-mono); font-size: 0.82rem; color: var(--cyan);
|
||||
}
|
||||
.md-preview pre { background: #1e1e2e; padding: 14px; border-radius: var(--radius-sm); margin: 10px 0; overflow-x: auto; }
|
||||
.md-preview pre code { background: none; padding: 0; }
|
||||
.md-preview ul, .md-preview ol { padding-left: 24px; margin-bottom: 10px; color: var(--text-secondary); }
|
||||
.md-preview blockquote { border-left: 3px solid var(--accent); padding-left: 14px; margin: 10px 0; color: var(--text-muted); font-style: italic; }
|
||||
.md-preview a { color: var(--accent); }
|
||||
.md-preview strong { color: var(--text-primary); }
|
||||
.md-preview hr { border: none; border-top: 1px solid var(--border); margin: 16px 0; }
|
||||
/* ─── Diff View ───────────────────────────────────────── */
|
||||
.diff-output { font-family: var(--font-mono); font-size: 0.82rem; margin-top: 16px; }
|
||||
.diff-line { padding: 4px 12px; border-radius: 2px; margin: 1px 0; }
|
||||
.diff-add { background: #22c55e10; color: var(--green); }
|
||||
.diff-del { background: #ef444410; color: var(--red); }
|
||||
.diff-same { color: var(--text-muted); }
|
||||
/* ─── Regex Matches ───────────────────────────────────── */
|
||||
.regex-match { background: var(--accent-glow); border: 1px solid var(--accent); border-radius: 3px; padding: 0 2px; }
|
||||
/* ─── IP Info ─────────────────────────────────────────── */
|
||||
.ip-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
|
||||
.ip-card {
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); padding: 14px;
|
||||
}
|
||||
.ip-card .label { font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
|
||||
.ip-card .value { font-family: var(--font-mono); font-size: 0.9rem; color: var(--cyan); }
|
||||
/* ─── QR Code ─────────────────────────────────────────── */
|
||||
#qrCanvas { display: inline-block; }
|
||||
/* ─── Footer ──────────────────────────────────────────── */
|
||||
footer {
|
||||
text-align: center; padding: 28px 24px; color: var(--text-muted);
|
||||
font-size: 0.78rem; border-top: 1px solid var(--border);
|
||||
}
|
||||
footer a { color: var(--accent); text-decoration: none; }
|
||||
/* ─── Scrollbar ───────────────────────────────────────── */
|
||||
::-webkit-scrollbar { width: 8px; }
|
||||
::-webkit-scrollbar-track { background: var(--bg-primary); }
|
||||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
|
||||
::selection { background: var(--accent); color: white; }
|
||||
/* ─── Toast ───────────────────────────────────────────── */
|
||||
.copy-toast {
|
||||
position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%);
|
||||
background: var(--accent); color: white; padding: 10px 24px;
|
||||
border-radius: 30px; font-size: 0.85rem; font-weight: 600;
|
||||
opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 999;
|
||||
}
|
||||
.copy-toast.show { opacity: 1; }
|
||||
/* ─── Responsive ──────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.split-panel { grid-template-columns: 1fr; }
|
||||
.search-bar { display: none; }
|
||||
.tools-grid { grid-template-columns: 1fr; }
|
||||
.ip-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
/* ─── Slider ──────────────────────────────────────────── */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none; width: 100%; height: 6px;
|
||||
background: var(--border); border-radius: 3px; outline: none;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; width: 18px; height: 18px;
|
||||
border-radius: 50%; background: var(--accent); cursor: pointer;
|
||||
box-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
.hidden { display: none !important; }
|
||||
/* ─── API Usage Section ───────────────────────────────── */
|
||||
.api-usage {
|
||||
margin-top: 24px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
.api-usage-toggle {
|
||||
width: 100%;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
background: var(--bg-card);
|
||||
border: none; color: var(--text-secondary);
|
||||
cursor: pointer; font-family: var(--font-sans);
|
||||
font-size: 0.82rem; font-weight: 600;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.api-usage-toggle:hover { color: var(--text-primary); background: var(--bg-input); }
|
||||
.api-usage-toggle i { transition: transform 0.25s ease; }
|
||||
.api-usage-toggle.open i { transform: rotate(180deg); }
|
||||
.api-usage-toggle .badge {
|
||||
background: var(--accent-glow); color: var(--accent);
|
||||
padding: 2px 8px; border-radius: 10px; font-size: 0.68rem;
|
||||
font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.api-usage-body {
|
||||
display: none; padding: 18px;
|
||||
background: var(--bg-primary);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.api-usage-body.open { display: block; animation: fadeIn 0.25s ease; }
|
||||
.api-usage-body .api-endpoint {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.api-usage-body .api-endpoint:last-child { margin-bottom: 0; }
|
||||
.api-method {
|
||||
display: inline-block;
|
||||
padding: 2px 8px; border-radius: 4px;
|
||||
font-size: 0.7rem; font-weight: 700;
|
||||
font-family: var(--font-mono);
|
||||
margin-right: 6px;
|
||||
}
|
||||
.api-method.post { background: #22c55e20; color: var(--green); }
|
||||
.api-method.get { background: #06b6d420; color: var(--cyan); }
|
||||
.api-path {
|
||||
font-family: var(--font-mono); font-size: 0.82rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.api-desc {
|
||||
font-size: 0.78rem; color: var(--text-muted);
|
||||
margin: 4px 0 8px; line-height: 1.4;
|
||||
}
|
||||
.api-code {
|
||||
background: #0d0d14;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px 16px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.76rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.65;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
}
|
||||
.api-code .kw { color: var(--pink); }
|
||||
.api-code .str { color: var(--green); }
|
||||
.api-code .cm { color: var(--text-muted); font-style: italic; }
|
||||
.api-code .fn { color: var(--cyan); }
|
||||
.api-code .var { color: var(--orange); }
|
||||
.api-code-copy {
|
||||
position: absolute; top: 8px; right: 8px;
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
color: var(--text-muted); border-radius: 4px;
|
||||
padding: 3px 8px; font-size: 0.68rem; cursor: pointer;
|
||||
transition: var(--transition); font-family: var(--font-sans);
|
||||
}
|
||||
.api-code-copy:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.api-baseurl-note {
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); padding: 10px 14px;
|
||||
font-size: 0.76rem; color: var(--text-muted);
|
||||
margin-bottom: 14px; line-height: 1.5;
|
||||
}
|
||||
.api-baseurl-note code {
|
||||
background: #0d0d14; padding: 1px 6px; border-radius: 3px;
|
||||
font-family: var(--font-mono); color: var(--accent); font-size: 0.74rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>⚡ WinnieAPI-v2 — Developer Utilities</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-glow"></div>
|
||||
<div class="particles" id="particles"></div>
|
||||
<div class="copy-toast" id="copyToast">Copied to clipboard! ✓</div>
|
||||
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<div class="header-inner">
|
||||
<div class="logo" onclick="showPage('home')">⚡ WinnieAPI-v2</div>
|
||||
<div class="search-bar">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" placeholder="Search tools... (Ctrl+K)" oninput="filterTools(this.value)" />
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button onclick="showPage('home')" title="Home"><i class="fas fa-home"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- ═══════════════ HOME ═══════════════ -->
|
||||
<div class="page active" id="page-home">
|
||||
<div class="hero">
|
||||
<h1>Your <span class="gradient">Developer Toolbox</span></h1>
|
||||
<p>Fast, free, privacy-friendly utilities — right in your browser. No sign-up, no tracking, no nonsense.</p>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="stat-num" id="toolCount">0</div><div class="stat-label">Tools</div></div>
|
||||
<div class="stat"><div class="stat-num">0</div><div class="stat-label">Ads</div></div>
|
||||
<div class="stat"><div class="stat-num">∞</div><div class="stat-label">Usage</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="categories">
|
||||
<button class="cat-btn active" onclick="filterCategory('all', this)">All Tools</button>
|
||||
<button class="cat-btn" onclick="filterCategory('format', this)">Formatters</button>
|
||||
<button class="cat-btn" onclick="filterCategory('encode', this)">Encode / Decode</button>
|
||||
<button class="cat-btn" onclick="filterCategory('generate', this)">Generators</button>
|
||||
<button class="cat-btn" onclick="filterCategory('text', this)">Text</button>
|
||||
<button class="cat-btn" onclick="filterCategory('web', this)">Web</button>
|
||||
<button class="cat-btn" onclick="filterCategory('dev', this)">Dev Tools</button>
|
||||
</div>
|
||||
|
||||
<div class="tools-grid" id="toolsGrid">
|
||||
<!-- JSON -->
|
||||
<div class="tool-card" data-cat="format" data-name="JSON Formatter" onclick="showPage('json')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-code"></i></div>
|
||||
<h3>JSON Formatter</h3>
|
||||
<span class="tag tag-purple">Format</span>
|
||||
</div>
|
||||
<p>Beautify, minify, and validate JSON with configurable indentation.</p>
|
||||
</div>
|
||||
<!-- URL -->
|
||||
<div class="tool-card" data-cat="web" data-name="URL Shortener" onclick="showPage('url')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-link"></i></div>
|
||||
<h3>URL Shortener</h3>
|
||||
<span class="tag tag-green">Web</span>
|
||||
</div>
|
||||
<p>Create short, shareable links instantly with click tracking.</p>
|
||||
</div>
|
||||
<!-- YouTube -->
|
||||
<div class="tool-card" data-cat="web" data-name="YouTube Tool" onclick="showPage('youtube')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-red"><i class="fab fa-youtube"></i></div>
|
||||
<h3>YouTube Tool</h3>
|
||||
<span class="tag tag-red">Video</span>
|
||||
</div>
|
||||
<p>Extract thumbnails, metadata, and embed codes from YouTube videos.</p>
|
||||
</div>
|
||||
<!-- Base64 -->
|
||||
<div class="tool-card" data-cat="encode" data-name="Base64 Encoder Decoder" onclick="showPage('base64')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-exchange-alt"></i></div>
|
||||
<h3>Base64 Encoder</h3>
|
||||
<span class="tag tag-cyan">Encode</span>
|
||||
</div>
|
||||
<p>Encode and decode Base64 strings instantly.</p>
|
||||
</div>
|
||||
<!-- Hash -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Hash Generator MD5 SHA" onclick="showPage('hash')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-fingerprint"></i></div>
|
||||
<h3>Hash Generator</h3>
|
||||
<span class="tag tag-orange">Crypto</span>
|
||||
</div>
|
||||
<p>Generate MD5, SHA-1, SHA-256, and SHA-512 hashes from text.</p>
|
||||
</div>
|
||||
<!-- Color -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Color Converter HEX RGB HSL" onclick="showPage('color')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-palette"></i></div>
|
||||
<h3>Color Converter</h3>
|
||||
<span class="tag tag-pink">Design</span>
|
||||
</div>
|
||||
<p>Convert colors between HEX, RGB, and HSL with live preview.</p>
|
||||
</div>
|
||||
<!-- Password -->
|
||||
<div class="tool-card" data-cat="generate" data-name="Password Generator" onclick="showPage('password')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-red"><i class="fas fa-key"></i></div>
|
||||
<h3>Password Generator</h3>
|
||||
<span class="tag tag-red">Security</span>
|
||||
</div>
|
||||
<p>Generate strong, cryptographically secure passwords with custom rules.</p>
|
||||
</div>
|
||||
<!-- UUID -->
|
||||
<div class="tool-card" data-cat="generate" data-name="UUID Generator" onclick="showPage('uuid')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-hashtag"></i></div>
|
||||
<h3>UUID Generator</h3>
|
||||
<span class="tag tag-purple">Generate</span>
|
||||
</div>
|
||||
<p>Generate random v4 UUIDs. Batch generate up to 100 at once.</p>
|
||||
</div>
|
||||
<!-- Lorem -->
|
||||
<div class="tool-card" data-cat="generate" data-name="Lorem Ipsum Generator" onclick="showPage('lorem')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-yellow"><i class="fas fa-paragraph"></i></div>
|
||||
<h3>Lorem Ipsum</h3>
|
||||
<span class="tag tag-yellow">Text</span>
|
||||
</div>
|
||||
<p>Generate placeholder text with configurable paragraph count.</p>
|
||||
</div>
|
||||
<!-- JWT -->
|
||||
<div class="tool-card" data-cat="dev" data-name="JWT Decoder Token" onclick="showPage('jwt')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-shield-alt"></i></div>
|
||||
<h3>JWT Decoder</h3>
|
||||
<span class="tag tag-orange">Auth</span>
|
||||
</div>
|
||||
<p>Decode JWT tokens and inspect header, payload, and expiry status.</p>
|
||||
</div>
|
||||
<!-- Timestamp -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Unix Timestamp Converter Date" onclick="showPage('timestamp')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-clock"></i></div>
|
||||
<h3>Timestamp Converter</h3>
|
||||
<span class="tag tag-cyan">Time</span>
|
||||
</div>
|
||||
<p>Convert between Unix timestamps, ISO dates, and human-readable formats.</p>
|
||||
</div>
|
||||
<!-- Regex -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Regex Tester Regular Expression" onclick="showPage('regex')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-asterisk"></i></div>
|
||||
<h3>Regex Tester</h3>
|
||||
<span class="tag tag-pink">Dev</span>
|
||||
</div>
|
||||
<p>Test regular expressions with live matching and capture groups.</p>
|
||||
</div>
|
||||
<!-- Markdown -->
|
||||
<div class="tool-card" data-cat="format" data-name="Markdown Preview Renderer" onclick="showPage('markdown')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fab fa-markdown"></i></div>
|
||||
<h3>Markdown Preview</h3>
|
||||
<span class="tag tag-purple">Format</span>
|
||||
</div>
|
||||
<p>Live preview Markdown as you type with full formatting support.</p>
|
||||
</div>
|
||||
<!-- Diff -->
|
||||
<div class="tool-card" data-cat="text" data-name="Text Diff Checker Compare" onclick="showPage('diff')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-columns"></i></div>
|
||||
<h3>Diff Checker</h3>
|
||||
<span class="tag tag-green">Compare</span>
|
||||
</div>
|
||||
<p>Compare two texts side-by-side and find differences line by line.</p>
|
||||
</div>
|
||||
<!-- Word Counter -->
|
||||
<div class="tool-card" data-cat="text" data-name="Word Character Counter Statistics" onclick="showPage('counter')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-calculator"></i></div>
|
||||
<h3>Word Counter</h3>
|
||||
<span class="tag tag-cyan">Text</span>
|
||||
</div>
|
||||
<p>Count words, characters, sentences, and get reading time estimates.</p>
|
||||
</div>
|
||||
<!-- CSS Minifier -->
|
||||
<div class="tool-card" data-cat="format" data-name="CSS Minifier Compressor" onclick="showPage('cssmin')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fab fa-css3-alt"></i></div>
|
||||
<h3>CSS Minifier</h3>
|
||||
<span class="tag tag-orange">Format</span>
|
||||
</div>
|
||||
<p>Minify CSS by removing comments, whitespace, and unnecessary characters.</p>
|
||||
</div>
|
||||
<!-- HTML Entity -->
|
||||
<div class="tool-card" data-cat="encode" data-name="HTML Entity Encoder Decoder" onclick="showPage('htmlent')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fab fa-html5"></i></div>
|
||||
<h3>HTML Entities</h3>
|
||||
<span class="tag tag-green">Encode</span>
|
||||
</div>
|
||||
<p>Encode and decode HTML entities for safe embedding.</p>
|
||||
</div>
|
||||
<!-- Case Converter -->
|
||||
<div class="tool-card" data-cat="text" data-name="Case Converter camelCase snake_case" onclick="showPage('caseconv')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-font"></i></div>
|
||||
<h3>Case Converter</h3>
|
||||
<span class="tag tag-pink">Text</span>
|
||||
</div>
|
||||
<p>Convert text between camelCase, snake_case, UPPER, Title Case, and more.</p>
|
||||
</div>
|
||||
<!-- Number Base -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Number Base Converter Binary Hex" onclick="showPage('numbase')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-yellow"><i class="fas fa-sort-numeric-up"></i></div>
|
||||
<h3>Number Base</h3>
|
||||
<span class="tag tag-yellow">Math</span>
|
||||
</div>
|
||||
<p>Convert numbers between decimal, binary, octal, and hexadecimal.</p>
|
||||
</div>
|
||||
<!-- IP Lookup -->
|
||||
<div class="tool-card" data-cat="web" data-name="IP Address Lookup Geolocation" onclick="showPage('ip')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-globe"></i></div>
|
||||
<h3>IP Lookup</h3>
|
||||
<span class="tag tag-cyan">Network</span>
|
||||
</div>
|
||||
<p>Get geolocation, ISP, and timezone info for any IP address.</p>
|
||||
</div>
|
||||
<!-- QR Code -->
|
||||
<div class="tool-card" data-cat="generate" data-name="QR Code Generator" onclick="showPage('qrcode')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-qrcode"></i></div>
|
||||
<h3>QR Code Generator</h3>
|
||||
<span class="tag tag-purple">Generate</span>
|
||||
</div>
|
||||
<p>Generate QR codes from text, URLs, or any data. Download as PNG.</p>
|
||||
</div>
|
||||
<!-- QR Code Reader -->
|
||||
<div class="tool-card" data-cat="dev" data-name="QR Code Reader Scanner Camera" onclick="showPage('qrreader')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-camera"></i></div>
|
||||
<h3>QR Code Reader</h3>
|
||||
<span class="tag tag-cyan">Scanner</span>
|
||||
</div>
|
||||
<p>Scan QR codes from your camera or an uploaded image file.</p>
|
||||
</div>
|
||||
<!-- String Escape -->
|
||||
<div class="tool-card" data-cat="encode" data-name="String Escape Unescape" onclick="showPage('escape')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-quote-right"></i></div>
|
||||
<h3>String Escape</h3>
|
||||
<span class="tag tag-orange">Encode</span>
|
||||
</div>
|
||||
<p>Escape and unescape strings (backslash sequences) for programming.</p>
|
||||
</div>
|
||||
<!-- Cron Parser -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Cron Expression Parser Scheduler" onclick="showPage('cron')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-stopwatch"></i></div>
|
||||
<h3>Cron Parser</h3>
|
||||
<span class="tag tag-orange">Scheduler</span>
|
||||
</div>
|
||||
<p>Parse cron expressions into human-readable descriptions with next run times.</p>
|
||||
</div>
|
||||
<!-- Placeholder Image -->
|
||||
<div class="tool-card" data-cat="generate" data-name="Placeholder Image Generator" onclick="showPage('placeholder')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-image"></i></div>
|
||||
<h3>Placeholder Image</h3>
|
||||
<span class="tag tag-pink">Design</span>
|
||||
</div>
|
||||
<p>Generate custom placeholder images with text, colors, and dimensions.</p>
|
||||
</div>
|
||||
<!-- JSON CSV Converter -->
|
||||
<div class="tool-card" data-cat="format" data-name="JSON CSV Converter Table Data" onclick="showPage('jsoncsv')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-table"></i></div>
|
||||
<h3>JSON ↔ CSV</h3>
|
||||
<span class="tag tag-green">Convert</span>
|
||||
</div>
|
||||
<p>Convert between JSON arrays and CSV format with download support.</p>
|
||||
</div>
|
||||
<!-- Text Encoder -->
|
||||
<div class="tool-card" data-cat="encode" data-name="Text Encoder ROT13 Binary Morse L33t" onclick="showPage('textenc')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-language"></i></div>
|
||||
<h3>Text Encoder</h3>
|
||||
<span class="tag tag-cyan">Encode</span>
|
||||
</div>
|
||||
<p>Encode text in ROT13, Binary, Morse Code, L33t speak, and more.</p>
|
||||
</div>
|
||||
<!-- HTTP Status Codes -->
|
||||
<div class="tool-card" data-cat="web" data-name="HTTP Status Code Reference API" onclick="showPage('httpstatus')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-server"></i></div>
|
||||
<h3>HTTP Status Codes</h3>
|
||||
<span class="tag tag-green">Reference</span>
|
||||
</div>
|
||||
<p>Quick reference for all HTTP status codes with descriptions and categories.</p>
|
||||
</div>
|
||||
<!-- SQL Formatter -->
|
||||
<div class="tool-card" data-cat="format" data-name="SQL Formatter Beautifier Query" onclick="showPage('sqlformat')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-database"></i></div>
|
||||
<h3>SQL Formatter</h3>
|
||||
<span class="tag tag-purple">Format</span>
|
||||
</div>
|
||||
<p>Beautify and minify SQL queries with keyword formatting.</p>
|
||||
</div>
|
||||
<!-- Byte Size Converter -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Byte Size Converter KB MB GB TB" onclick="showPage('byteconv')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-yellow"><i class="fas fa-weight-hanging"></i></div>
|
||||
<h3>Byte Converter</h3>
|
||||
<span class="tag tag-yellow">Math</span>
|
||||
</div>
|
||||
<p>Convert between bytes, KB, MB, GB, TB with binary and SI modes.</p>
|
||||
</div>
|
||||
<!-- HMAC Generator -->
|
||||
<div class="tool-card" data-cat="dev" data-name="HMAC Generator Signature SHA" onclick="showPage('hmac')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-red"><i class="fas fa-lock"></i></div>
|
||||
<h3>HMAC Generator</h3>
|
||||
<span class="tag tag-red">Security</span>
|
||||
</div>
|
||||
<p>Generate HMAC signatures from a message and secret key.</p>
|
||||
</div>
|
||||
<!-- Slug Generator -->
|
||||
<div class="tool-card" data-cat="text" data-name="Slug Generator URL Friendly" onclick="showPage('slugify')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-link"></i></div>
|
||||
<h3>Slug Generator</h3>
|
||||
<span class="tag tag-green">Text</span>
|
||||
</div>
|
||||
<p>Convert any text into a clean, URL-friendly slug.</p>
|
||||
</div>
|
||||
<!-- Chmod Calculator -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Chmod Calculator Unix Permissions" onclick="showPage('chmod')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-user-shield"></i></div>
|
||||
<h3>Chmod Calculator</h3>
|
||||
<span class="tag tag-orange">Linux</span>
|
||||
</div>
|
||||
<p>Convert between numeric (755) and symbolic (rwxr-xr-x) Unix permissions.</p>
|
||||
</div>
|
||||
<!-- ASCII Art Generator -->
|
||||
<div class="tool-card" data-cat="generate" data-name="ASCII Art Generator Text Banner" onclick="showPage('ascii')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-text-height"></i></div>
|
||||
<h3>ASCII Art</h3>
|
||||
<span class="tag tag-pink">Generate</span>
|
||||
</div>
|
||||
<p>Convert text into ASCII art banners using block characters.</p>
|
||||
</div>
|
||||
<!-- ENV ↔ JSON -->
|
||||
<div class="tool-card" data-cat="format" data-name="ENV JSON Converter Dotenv Config" onclick="showPage('envjson')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-file-code"></i></div>
|
||||
<h3>ENV ↔ JSON</h3>
|
||||
<span class="tag tag-cyan">Convert</span>
|
||||
</div>
|
||||
<p>Convert between .env files and JSON objects instantly.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tool pages are loaded dynamically here -->
|
||||
<div id="tool-pages"></div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Built with ❤️ by <a href="https://winniepat.de" target="_blank">WinniePatGG</a> · <span id="footerToolCount">0</span> tools and counting · 100% free & open source · No data collected
|
||||
</footer>
|
||||
|
||||
<!-- QR Code Library -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
|
||||
<!-- QR Code Reader Library -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
|
||||
|
||||
<!-- Core app JS -->
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
<!-- Load tool HTML partials and JS dynamically -->
|
||||
<script>
|
||||
(async function loadTools() {
|
||||
const tools = [
|
||||
'json', 'url', 'youtube', 'base64', 'hash', 'color',
|
||||
'password', 'uuid', 'lorem', 'jwt', 'timestamp', 'regex',
|
||||
'markdown', 'diff', 'counter', 'cssmin', 'htmlent',
|
||||
'caseconv', 'numbase', 'ip', 'qrcode', 'escape',
|
||||
'cron', 'placeholder', 'jsoncsv', 'textenc', 'httpstatus',
|
||||
'qrreader', 'sqlformat', 'byteconv', 'hmac', 'slugify',
|
||||
'chmod', 'ascii', 'envjson'
|
||||
];
|
||||
const container = document.getElementById('tool-pages');
|
||||
|
||||
// Update tool counts dynamically
|
||||
const countEl = document.getElementById('toolCount');
|
||||
const footerEl = document.getElementById('footerToolCount');
|
||||
if (countEl) countEl.textContent = tools.length;
|
||||
if (footerEl) footerEl.textContent = tools.length;
|
||||
|
||||
// Load all tool HTML partials in parallel
|
||||
const htmlPromises = tools.map(t =>
|
||||
fetch(`tools/${t}.html`).then(r => r.text())
|
||||
);
|
||||
const htmlParts = await Promise.all(htmlPromises);
|
||||
container.innerHTML = htmlParts.join('\n');
|
||||
|
||||
// Load all tool JS files sequentially to ensure DOM is ready
|
||||
for (const t of tools) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `js/tools/${t}.js`;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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>' : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 => ({'<':'<','>':'>','&':'&'}[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>';
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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; }
|
||||
});
|
||||
|
||||
@@ -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 ✓');
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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)));
|
||||
}
|
||||
@@ -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, '<')}</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);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 => ({'<':'<','>':'>','&':'&'}[c]));
|
||||
const safePattern = new RegExp(pattern, flags);
|
||||
highlighted = input.replace(safePattern, m => `<span class="regex-match">${m.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[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 = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -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')); }
|
||||
|
||||
@@ -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(); });
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<!-- ═══════════════ ASCII ART GENERATOR ═══════════════ -->
|
||||
<div class="page" id="page-ascii">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-text-height" style="color:var(--pink)"></i> ASCII Art Generator</h2>
|
||||
<p>Convert text into ASCII art banners using block characters.</p>
|
||||
</div>
|
||||
<div style="max-width:900px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<input type="text" id="asciiInput" placeholder="e.g. HELLO" maxlength="20" value="HELLO" oninput="generateAscii()" />
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generateAscii()"><i class="fas fa-text-height"></i> Generate</button>
|
||||
</div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="asciiOutput" readonly style="min-height:200px;font-size:0.7rem;line-height:1.2;white-space:pre;" placeholder="ASCII art will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('asciiOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div class="status" id="asciiStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/ascii/generate</span>
|
||||
<div class="api-desc">Generate ASCII art text from a string (A-Z, 0-9, common punctuation).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/ascii/generate`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"HI"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "█ █ █████\n█ █ █ \n████ █ \n█ █ █ \n█ █ █████" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ BASE64 ═══════════════ -->
|
||||
<div class="page" id="page-base64">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-exchange-alt" style="color:var(--cyan)"></i> Base64 Encoder / Decoder</h2>
|
||||
<p>Encode or decode Base64 strings.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="b64Input" placeholder="Enter text to encode or Base64 to decode..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="base64Op('encode')"><i class="fas fa-lock"></i> Encode</button>
|
||||
<button class="btn btn-cyan" onclick="base64Op('decode')"><i class="fas fa-unlock"></i> Decode</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="b64Output" readonly placeholder="Result..."></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('b64Output')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="b64Status"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/base64/encode</span>
|
||||
<div class="api-desc">Encode a string to Base64.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/base64/encode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello World"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "SGVsbG8gV29ybGQ=" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/base64/decode</span>
|
||||
<div class="api-desc">Decode a Base64 string back to text.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/base64/decode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"SGVsbG8gV29ybGQ="</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "Hello World" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<!-- ═══════════════ BYTE SIZE CONVERTER ═══════════════ -->
|
||||
<div class="page" id="page-byteconv">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-weight-hanging" style="color:var(--yellow)"></i> Byte Size Converter</h2>
|
||||
<p>Convert between bytes, KB, MB, GB, TB with binary (1024) and SI (1000) modes.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:10px;margin-bottom:12px;">
|
||||
<div>
|
||||
<div class="panel-label">Value</div>
|
||||
<input type="number" id="byteValue" placeholder="e.g. 1048576" value="1048576" oninput="convertBytes()" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Unit</div>
|
||||
<select id="byteUnit" onchange="convertBytes()">
|
||||
<option value="B">Bytes (B)</option>
|
||||
<option value="KB">Kilobytes (KB)</option>
|
||||
<option value="MB" selected>Megabytes (MB)</option>
|
||||
<option value="GB">Gigabytes (GB)</option>
|
||||
<option value="TB">Terabytes (TB)</option>
|
||||
<option value="PB">Petabytes (PB)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-group" style="margin-bottom:16px;">
|
||||
<label><input type="radio" name="byteMode" value="binary" checked onchange="convertBytes()"> Binary (1024)</label>
|
||||
<label><input type="radio" name="byteMode" value="si" onchange="convertBytes()"> SI / Decimal (1000)</label>
|
||||
</div>
|
||||
<div id="byteResults"></div>
|
||||
<div class="status" id="byteStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/bytes/convert</span>
|
||||
<div class="api-desc">Convert a value between byte units. Use <code>mode: "binary"</code> (1024) or <code>"si"</code> (1000).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/bytes/convert`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ value: 1, unit: <span class="str">"GB"</span>, mode: <span class="str">"binary"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, B: 1073741824, KB: 1048576, MB: 1024, GB: 1, TB: 0.000977, PB: 0.00000095 }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<!-- ═══════════════ CASE CONVERTER ═══════════════ -->
|
||||
<div class="page" id="page-caseconv">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-font" style="color:var(--pink)"></i> Case Converter</h2>
|
||||
<p>Convert text between different cases.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="caseInput" placeholder="Enter some text here..." style="min-height:100px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="convertCase()"><i class="fas fa-sync"></i> Convert All</button>
|
||||
</div>
|
||||
<div id="caseResults"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/case</span>
|
||||
<div class="api-desc">Convert text to all case variations at once.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/case`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello World Example"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { 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" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- ═══════════════ CHMOD CALCULATOR ═══════════════ -->
|
||||
<div class="page" id="page-chmod">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-user-shield" style="color:var(--orange)"></i> Chmod Calculator</h2>
|
||||
<p>Convert between numeric (755) and symbolic (rwxr-xr-x) Unix file permissions.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div>
|
||||
<div class="panel-label">Numeric (Octal)</div>
|
||||
<input type="text" id="chmodNumeric" placeholder="e.g. 755" maxlength="4" value="755" oninput="chmodFromNumeric()" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Symbolic</div>
|
||||
<input type="text" id="chmodSymbolic" placeholder="e.g. rwxr-xr-x" value="rwxr-xr-x" oninput="chmodFromSymbolic()" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:20px;">Permission Matrix</div>
|
||||
<div id="chmodMatrix" style="margin-bottom:16px;"></div>
|
||||
<div class="panel-label">Command</div>
|
||||
<div class="result-row" style="cursor:pointer;" onclick="copyText(this.querySelector('.value').textContent)">
|
||||
<div class="label">CHMOD</div>
|
||||
<div class="value" id="chmodCommand">chmod 755 filename</div>
|
||||
</div>
|
||||
<div class="status" id="chmodStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/chmod/calculate</span>
|
||||
<div class="api-desc">Convert between numeric and symbolic chmod. Pass <code>numeric</code> or <code>symbolic</code>.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/chmod/calculate`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ numeric: <span class="str">"755"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, numeric: "755", symbolic: "rwxr-xr-x", owner: {...}, group: {...}, others: {...} }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ COLOR ═══════════════ -->
|
||||
<div class="page" id="page-color">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-palette" style="color:var(--pink)"></i> Color Converter</h2>
|
||||
<p>Convert colors between HEX, RGB, and HSL with a live preview.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div style="display:flex;gap:12px;align-items:flex-end;margin-bottom:16px;">
|
||||
<div style="flex:1;">
|
||||
<div class="panel-label">Color Value</div>
|
||||
<input type="text" id="colorInput" placeholder="#6c63ff or rgb(108,99,255) or hsl(245,100,69)" />
|
||||
</div>
|
||||
<input type="color" id="colorPicker" value="#6c63ff" onchange="document.getElementById('colorInput').value=this.value;convertColor()" />
|
||||
<button class="btn btn-primary" onclick="convertColor()"><i class="fas fa-sync"></i> Convert</button>
|
||||
</div>
|
||||
<div class="color-preview-box" id="colorPreview" style="background:#6c63ff;"></div>
|
||||
<div id="colorResults"></div>
|
||||
<div class="status" id="colorStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/color/convert</span>
|
||||
<div class="api-desc">Convert a color between HEX, RGB, and HSL formats.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/color/convert`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ color: <span class="str">"#6c63ff"</span> })
|
||||
<span class="cm">// Also accepts: "rgb(108,99,255)" or "hsl(245,100,69)"</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, hex: "#6c63ff", rgb: "rgb(108, 99, 255)",
|
||||
// hsl: "hsl(243, 100%, 69%)", r: 108, g: 99, b: 255 }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!-- ═══════════════ WORD COUNTER ═══════════════ -->
|
||||
<div class="page" id="page-counter">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-calculator" style="color:var(--cyan)"></i> Word & Character Counter</h2>
|
||||
<p>Get detailed text statistics as you type.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<textarea id="counterInput" placeholder="Start typing or paste text here..." oninput="updateCounter()" style="min-height:200px;"></textarea>
|
||||
<div id="counterResults" style="margin-top:16px;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/stats</span>
|
||||
<div class="api-desc">Get detailed text statistics: characters, words, sentences, reading time, and top character frequency.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/stats`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello world. This is a test."</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, characters: 28, charactersNoSpaces: 23,
|
||||
// words: 6, sentences: 2, paragraphs: 1, lines: 1,
|
||||
// readingTime: "1 min", topChars: [["l",3], ["s",3], ...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<!-- ═══════════════ CRON PARSER ═══════════════ -->
|
||||
<div class="page" id="page-cron">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-clock" style="color:var(--orange)"></i> Cron Expression Parser</h2>
|
||||
<p>Parse cron expressions into human-readable descriptions and see next run times.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Cron Expression</div>
|
||||
<input type="text" id="cronInput" placeholder="*/5 * * * *" value="*/5 * * * *" oninput="parseCron()" />
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="parseCron()"><i class="fas fa-play"></i> Parse</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('*/5 * * * *')">Every 5 min</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 0 * * *')">Daily midnight</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 9 * * 1-5')">Weekdays 9am</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 0 1 * *')">1st of month</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 */2 * * *')">Every 2 hours</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-card" id="cronResult" style="display:none;">
|
||||
<div class="panel-label">Human-readable</div>
|
||||
<div id="cronDescription" style="font-size:1.1rem;font-weight:600;margin-bottom:16px;color:var(--cyan);"></div>
|
||||
<div class="panel-label">Fields Breakdown</div>
|
||||
<div id="cronFields" style="margin-bottom:16px;"></div>
|
||||
<div class="panel-label">Next 5 Run Times</div>
|
||||
<div id="cronNextRuns"></div>
|
||||
</div>
|
||||
<div class="status" id="cronStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/cron/parse</span>
|
||||
<div class="api-desc">Parse a cron expression into a human-readable description and next run times.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/cron/parse`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ expression: <span class="str">"*/5 * * * *"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, description: "Every 5 minutes", fields: {...}, nextRuns: [...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!-- ═══════════════ CSS MINIFIER ═══════════════ -->
|
||||
<div class="page" id="page-cssmin">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-css3-alt" style="color:var(--orange)"></i> CSS Minifier</h2>
|
||||
<p>Minify CSS by removing comments and whitespace.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input CSS</div>
|
||||
<textarea id="cssInput" placeholder=".container { display: flex; /* comment */ gap: 16px; }"></textarea>
|
||||
<div class="btn-group"><button class="btn btn-primary" onclick="minifyCSS()"><i class="fas fa-compress"></i> Minify</button></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Minified Output</div>
|
||||
<textarea id="cssOutput" readonly placeholder="Minified CSS..."></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('cssOutput')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="cssStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/css/minify</span>
|
||||
<div class="api-desc">Minify CSS by removing comments, whitespace, and unnecessary characters. Returns size savings.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/css/minify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
css: <span class="str">".container {\n display: flex;\n /* comment */\n gap: 16px;\n}"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: ".container{display:flex;gap:16px}",
|
||||
// original: 58, minified: 33, saved: 25, percentage: 43 }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- ═══════════════ DIFF ═══════════════ -->
|
||||
<div class="page" id="page-diff">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-columns" style="color:var(--green)"></i> Diff Checker</h2>
|
||||
<p>Compare two texts and find the differences.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Original</div>
|
||||
<textarea id="diffA" placeholder="Paste original text..."></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Modified</div>
|
||||
<textarea id="diffB" placeholder="Paste modified text..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="computeDiff()"><i class="fas fa-exchange-alt"></i> Compare</button>
|
||||
</div>
|
||||
<div class="diff-output" id="diffOutput"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ Diff Checker runs entirely <strong>client-side</strong>. No server API is needed. Here's the line-by-line comparison logic:</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Simple line-by-line diff in JavaScript.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Client-side diff — no API call needed</span>
|
||||
<span class="kw">function</span> <span class="fn">diffLines</span>(original, modified) {
|
||||
<span class="kw">const</span> a = original.<span class="fn">split</span>(<span class="str">'\n'</span>);
|
||||
<span class="kw">const</span> b = modified.<span class="fn">split</span>(<span class="str">'\n'</span>);
|
||||
<span class="kw">const</span> result = [];
|
||||
<span class="kw">const</span> max = Math.<span class="fn">max</span>(a.length, b.length);
|
||||
<span class="kw">for</span> (<span class="kw">let</span> i = 0; i < max; i++) {
|
||||
<span class="kw">if</span> (a[i] === undefined) result.<span class="fn">push</span>({ type: <span class="str">'add'</span>, line: b[i] });
|
||||
<span class="kw">else if</span> (b[i] === undefined) result.<span class="fn">push</span>({ type: <span class="str">'del'</span>, line: a[i] });
|
||||
<span class="kw">else if</span> (a[i] === b[i]) result.<span class="fn">push</span>({ type: <span class="str">'same'</span>, line: a[i] });
|
||||
<span class="kw">else</span> {
|
||||
result.<span class="fn">push</span>({ type: <span class="str">'del'</span>, line: a[i] });
|
||||
result.<span class="fn">push</span>({ type: <span class="str">'add'</span>, line: b[i] });
|
||||
}
|
||||
}
|
||||
<span class="kw">return</span> result;
|
||||
}
|
||||
|
||||
<span class="kw">const</span> diff = <span class="fn">diffLines</span>(<span class="str">"hello\nworld"</span>, <span class="str">"hello\nearth"</span>);
|
||||
<span class="cm">// → [{ type: "same", line: "hello" }, { type: "del", line: "world" }, { type: "add", line: "earth" }]</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<!-- ═══════════════ ENV ↔ JSON CONVERTER ═══════════════ -->
|
||||
<div class="page" id="page-envjson">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-file-code" style="color:var(--cyan)"></i> ENV ↔ JSON Converter</h2>
|
||||
<p>Convert between .env files and JSON objects.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">.env Input</div>
|
||||
<textarea id="envInput" placeholder="DB_HOST=localhost DB_PORT=5432 DB_NAME=myapp SECRET_KEY=abc123"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="envToJson()"><i class="fas fa-arrow-right"></i> ENV → JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">JSON Input</div>
|
||||
<textarea id="envJsonInput" placeholder='{"DB_HOST":"localhost","DB_PORT":"5432"}'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="jsonToEnv()"><i class="fas fa-arrow-left"></i> JSON → ENV</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:20px;">Output</div>
|
||||
<textarea id="envJsonOutput" readonly placeholder="Converted output will appear here..." style="min-height:140px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('envJsonOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div class="status" id="envJsonStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/env-to-json</span>
|
||||
<div class="api-desc">Convert .env format text to a JSON object.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/env-to-json`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ env: <span class="str">"DB_HOST=localhost\nDB_PORT=5432"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: { DB_HOST: "localhost", DB_PORT: "5432" } }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/json-to-env</span>
|
||||
<div class="api-desc">Convert a flat JSON object to .env format text.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/json-to-env`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'{"DB_HOST":"localhost","DB_PORT":"5432"}'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "DB_HOST=localhost\nDB_PORT=5432" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ STRING ESCAPE ═══════════════ -->
|
||||
<div class="page" id="page-escape">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-quote-right" style="color:var(--orange)"></i> String Escape / Unescape</h2>
|
||||
<p>Escape and unescape special characters in strings.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="escInput" placeholder='Hello "World"\nNew line\tTab'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="escapeOp('escape')"><i class="fas fa-lock"></i> Escape</button>
|
||||
<button class="btn btn-cyan" onclick="escapeOp('unescape')"><i class="fas fa-unlock"></i> Unescape</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="escOutput" readonly></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('escOutput')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="escStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/escape</span>
|
||||
<div class="api-desc">Escape special characters in a string (backslash sequences like \n, \t, \", etc).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/escape`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">'Hello "World"\nNew line'</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "Hello \\\"World\\\"\\nNew line" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/unescape</span>
|
||||
<div class="api-desc">Unescape backslash sequences back to their original characters.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/unescape`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">'Hello \\\"World\\\"\\nNew line'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: 'Hello "World"\nNew line' }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- ═══════════════ HASH ═══════════════ -->
|
||||
<div class="page" id="page-hash">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-fingerprint" style="color:var(--orange)"></i> Hash Generator</h2>
|
||||
<p>Generate cryptographic hashes from any text.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="hashInput" placeholder="Enter text to hash..." style="min-height:100px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generateHash()"><i class="fas fa-fingerprint"></i> Generate Hashes</button>
|
||||
</div>
|
||||
<div id="hashResults"></div>
|
||||
<div class="status" id="hashStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/hash</span>
|
||||
<div class="api-desc">Generate MD5, SHA-1, SHA-256, and SHA-512 hashes. Optionally specify a single algorithm.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="cm">// Get all hashes at once:</span>
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/hash`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"hello world"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, hashes: { md5: "5eb6...", sha1: "2aae...", sha256: "b94d...", sha512: "309e..." } }</span>
|
||||
|
||||
<span class="cm">// Or get a single algorithm:</span>
|
||||
<span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/hash`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"hello"</span>, algorithm: <span class="str">"sha256"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, hashes: { sha256: "2cf2..." } }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- ═══════════════ HMAC GENERATOR ═══════════════ -->
|
||||
<div class="page" id="page-hmac">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-lock" style="color:var(--red)"></i> HMAC Generator</h2>
|
||||
<p>Generate HMAC signatures from a message and secret key.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Message</div>
|
||||
<textarea id="hmacMessage" placeholder="Enter message to sign..." style="min-height:80px;"></textarea>
|
||||
<div class="panel-label" style="margin-top:12px;">Secret Key</div>
|
||||
<input type="text" id="hmacSecret" placeholder="Enter your secret key..." />
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-top:10px;">
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);">Algorithm:</label>
|
||||
<select id="hmacAlgo" style="width:140px;padding:6px 10px;">
|
||||
<option value="sha256" selected>SHA-256</option>
|
||||
<option value="sha512">SHA-512</option>
|
||||
<option value="sha1">SHA-1</option>
|
||||
<option value="md5">MD5</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generateHmac()"><i class="fas fa-lock"></i> Generate HMAC</button>
|
||||
</div>
|
||||
<div id="hmacResults"></div>
|
||||
<div class="status" id="hmacStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/hmac</span>
|
||||
<div class="api-desc">Generate an HMAC signature. Supports sha256, sha512, sha1, md5.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/hmac`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
message: <span class="str">"hello world"</span>,
|
||||
secret: <span class="str">"my-secret-key"</span>,
|
||||
algorithm: <span class="str">"sha256"</span>
|
||||
})
|
||||
});
|
||||
<span class="cm">// → { success: true, hmac: "734c...", algorithm: "sha256" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- ═══════════════ HTML ENTITIES ═══════════════ -->
|
||||
<div class="page" id="page-htmlent">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-html5" style="color:var(--green)"></i> HTML Entity Encoder / Decoder</h2>
|
||||
<p>Convert special characters to HTML entities and back.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="htmlEntInput" placeholder='<div class="test">& more</div>'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="htmlEntOp('encode')"><i class="fas fa-lock"></i> Encode</button>
|
||||
<button class="btn btn-cyan" onclick="htmlEntOp('decode')"><i class="fas fa-unlock"></i> Decode</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="htmlEntOutput" readonly></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('htmlEntOutput')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="htmlEntStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/html/encode</span>
|
||||
<div class="api-desc">Convert special characters to HTML entities.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/html/encode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">'<div class="test">&</div>'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "&lt;div class=&quot;test&quot;&gt;..." }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/html/decode</span>
|
||||
<div class="api-desc">Decode HTML entities back to characters.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/html/decode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"&lt;p&gt;Hello&lt;/p&gt;"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "<p>Hello</p>" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ HTTP STATUS CODES ═══════════════ -->
|
||||
<div class="page" id="page-httpstatus">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-server" style="color:var(--green)"></i> HTTP Status Codes</h2>
|
||||
<p>Quick reference for all HTTP status codes with descriptions and categories.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Search / Lookup</div>
|
||||
<input type="text" id="httpStatusInput" placeholder="Search by code (e.g. 404) or keyword (e.g. not found)..." oninput="filterHttpStatus()" />
|
||||
<div class="btn-group" id="httpCatBtns">
|
||||
<button class="btn btn-sm btn-primary" onclick="filterHttpCat('all', this)">All</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('1xx', this)">1xx Info</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('2xx', this)">2xx Success</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('3xx', this)">3xx Redirect</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('4xx', this)">4xx Client Error</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('5xx', this)">5xx Server Error</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="httpStatusList" style="margin-top:16px;"></div>
|
||||
<div class="status" id="httpStatusStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/http-status</span>
|
||||
<div class="api-desc">Get all HTTP status codes with their text and category.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/http-status`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, statuses: [{ code: 200, text: "OK", category: "2xx" }, ...] }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/http-status/:code</span>
|
||||
<div class="api-desc">Look up a specific HTTP status code.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/http-status/404`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, code: 404, text: "Not Found", category: "4xx" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ IP LOOKUP ═══════════════ -->
|
||||
<div class="page" id="page-ip">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-globe" style="color:var(--cyan)"></i> IP Address Lookup</h2>
|
||||
<p>Get geolocation and network info for any IP address.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:flex;gap:10px;margin-bottom:16px;">
|
||||
<input type="text" id="ipInput" placeholder="Leave blank for your IP, or enter an IP..." />
|
||||
<button class="btn btn-primary" onclick="lookupIP()"><i class="fas fa-search"></i> Lookup</button>
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('ipInput').value='';lookupIP()">My IP</button>
|
||||
</div>
|
||||
<div class="ip-grid" id="ipResults"></div>
|
||||
<div class="status" id="ipStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/ip</span>
|
||||
<div class="api-desc">Get geolocation info for the server's public IP.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/ip`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, query: "203.0.113.1", country: "United States",
|
||||
// regionName: "California", city: "San Jose", timezone: "America/Los_Angeles",
|
||||
// isp: "Example ISP", ... }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/ip/:ip</span>
|
||||
<div class="api-desc">Lookup a specific IP address.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/ip/8.8.8.8`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, query: "8.8.8.8", country: "United States",
|
||||
// city: "Ashburn", isp: "Google LLC", ... }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<!-- ═══════════════ JSON FORMATTER ═══════════════ -->
|
||||
<div class="page" id="page-json">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-code" style="color:var(--accent)"></i> JSON Formatter</h2>
|
||||
<p>Paste your JSON below to beautify, minify, or validate it.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="jsonInput" placeholder='{"name":"WinnieAPI-v2","version":1,"tools":["json","url","hash"]}'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="formatJSON()"><i class="fas fa-magic"></i> Beautify</button>
|
||||
<button class="btn btn-secondary" onclick="minifyJSON()"><i class="fas fa-compress"></i> Minify</button>
|
||||
<button class="btn btn-secondary" onclick="validateJSON()"><i class="fas fa-check"></i> Validate</button>
|
||||
<button class="btn btn-secondary" onclick="clearJSON()"><i class="fas fa-trash"></i> Clear</button>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-top:6px;">
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);">Indent:</label>
|
||||
<select id="jsonIndent" style="width:100px;padding:6px 10px;">
|
||||
<option value="2" selected>2 spaces</option><option value="4">4 spaces</option><option value="\t">Tab</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="jsonOutput" readonly placeholder="Formatted output will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('jsonOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="jsonStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/json/format</span>
|
||||
<div class="api-desc">Beautify JSON with configurable indentation.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/json/format`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
json: <span class="str">'{"name":"WinnieAPI-v2","version":1}'</span>,
|
||||
indent: 2 <span class="cm">// 2, 4, or "\t"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "{\n \"name\": \"WinnieAPI-v2\",\n ...}" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/json/minify</span>
|
||||
<div class="api-desc">Minify JSON to a single line.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/json/minify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'{ "a" : 1 , "b" : 2 }'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: '{"a":1,"b":2}' }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/json/validate</span>
|
||||
<div class="api-desc">Check if a string is valid JSON.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/json/validate`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'{"valid": true}'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, valid: true, message: "Valid JSON ✓" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- ═══════════════ JSON TO CSV ═══════════════ -->
|
||||
<div class="page" id="page-jsoncsv">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-table" style="color:var(--green)"></i> JSON ↔ CSV Converter</h2>
|
||||
<p>Convert between JSON arrays and CSV format.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">JSON Input</div>
|
||||
<textarea id="jsoncsvJsonInput" placeholder='[{"name":"Alice","age":30},{"name":"Bob","age":25}]'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="jsonToCsv()"><i class="fas fa-arrow-right"></i> JSON → CSV</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">CSV Input</div>
|
||||
<textarea id="jsoncsvCsvInput" placeholder='name,age Alice,30 Bob,25'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="csvToJson()"><i class="fas fa-arrow-left"></i> CSV → JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:20px;">Output</div>
|
||||
<textarea id="jsoncsvOutput" readonly placeholder="Converted output will appear here..." style="min-height:160px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('jsoncsvOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
<button class="btn btn-secondary" onclick="downloadJsoncsvOutput()"><i class="fas fa-download"></i> Download</button>
|
||||
</div>
|
||||
<div class="status" id="jsoncsvStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/json-to-csv</span>
|
||||
<div class="api-desc">Convert a JSON array to CSV format.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/json-to-csv`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'[{"name":"Alice","age":30}]'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "name,age\nAlice,30" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/csv-to-json</span>
|
||||
<div class="api-desc">Convert CSV text to a JSON array.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/csv-to-json`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ csv: <span class="str">"name,age\nAlice,30"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: [{"name":"Alice","age":"30"}] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<!-- ═══════════════ JWT ═══════════════ -->
|
||||
<div class="page" id="page-jwt">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-shield-alt" style="color:var(--orange)"></i> JWT Decoder</h2>
|
||||
<p>Decode and inspect JSON Web Tokens.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">JWT Token</div>
|
||||
<textarea id="jwtInput" placeholder="Paste your JWT token here (eyJ...)" style="min-height:100px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="decodeJWT()"><i class="fas fa-unlock"></i> Decode</button>
|
||||
</div>
|
||||
<div id="jwtResults"></div>
|
||||
<div class="status" id="jwtStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/jwt/decode</span>
|
||||
<div class="api-desc">Decode a JWT token and inspect its header, payload, and expiry.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/jwt/decode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
token: <span class="str">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, header: { alg: "HS256", typ: "JWT" },
|
||||
// payload: { sub: "1234567890" }, expired: null, signature: "..." }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- ═══════════════ LOREM ═══════════════ -->
|
||||
<div class="page" id="page-lorem">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-paragraph" style="color:var(--yellow)"></i> Lorem Ipsum Generator</h2>
|
||||
<p>Generate placeholder text.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:flex;gap:10px;align-items:center;margin-bottom:16px;">
|
||||
<label style="font-size:0.85rem;color:var(--text-secondary);">Paragraphs:</label>
|
||||
<input type="number" id="loremCount" value="3" min="1" max="20" style="width:80px;" />
|
||||
<button class="btn btn-primary" onclick="generateLorem()"><i class="fas fa-feather"></i> Generate</button>
|
||||
<button class="btn btn-green" onclick="copyOutput('loremOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<textarea id="loremOutput" readonly placeholder="Generated text will appear here..." style="min-height:300px;"></textarea>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/lorem</span>
|
||||
<div class="api-desc">Generate lorem ipsum placeholder text.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/lorem`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ paragraphs: 3 }) <span class="cm">// 1–20</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "Lorem ipsum dolor sit amet..." }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- ═══════════════ MARKDOWN ═══════════════ -->
|
||||
<div class="page" id="page-markdown">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-markdown" style="color:var(--accent)"></i> Markdown Preview</h2>
|
||||
<p>Write Markdown and see a live preview.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Markdown</div>
|
||||
<textarea id="mdInput" oninput="renderMarkdown()" placeholder="# Hello World Write your **markdown** here... - Item 1 - Item 2 `code` and more" style="min-height:400px;"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Preview</div>
|
||||
<div class="md-preview" id="mdPreview"><span style="color:var(--text-muted);">Preview will appear here...</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ Markdown Preview runs entirely <strong>client-side</strong> using regex-based rendering. No server API is needed. Here's how to use a similar approach in your own code:</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Simple Markdown to HTML conversion in JavaScript.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Client-side Markdown rendering — no API call needed</span>
|
||||
<span class="kw">function</span> <span class="fn">renderMarkdown</span>(md) {
|
||||
<span class="kw">return</span> md
|
||||
.<span class="fn">replace</span>(/^### (.+)$/gm, <span class="str">'<h3>$1</h3>'</span>)
|
||||
.<span class="fn">replace</span>(/^## (.+)$/gm, <span class="str">'<h2>$1</h2>'</span>)
|
||||
.<span class="fn">replace</span>(/^# (.+)$/gm, <span class="str">'<h1>$1</h1>'</span>)
|
||||
.<span class="fn">replace</span>(/\*\*(.+?)\*\*/g, <span class="str">'<strong>$1</strong>'</span>)
|
||||
.<span class="fn">replace</span>(/\*(.+?)\*/g, <span class="str">'<em>$1</em>'</span>)
|
||||
.<span class="fn">replace</span>(/`([^`]+)`/g, <span class="str">'<code>$1</code>'</span>)
|
||||
.<span class="fn">replace</span>(/\n\n/g, <span class="str">'</p><p>'</span>);
|
||||
}
|
||||
|
||||
<span class="kw">const</span> html = <span class="fn">renderMarkdown</span>(<span class="str">"# Hello\n\n**Bold** and *italic*"</span>);
|
||||
<span class="cm">// → "<h1>Hello</h1></p><p><strong>Bold</strong> and <em>italic</em>"</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- ═══════════════ NUMBER BASE ═══════════════ -->
|
||||
<div class="page" id="page-numbase">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-sort-numeric-up" style="color:var(--yellow)"></i> Number Base Converter</h2>
|
||||
<p>Convert numbers between different bases.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div style="display:flex;gap:10px;align-items:flex-end;">
|
||||
<div style="flex:1;">
|
||||
<div class="panel-label">Number</div>
|
||||
<input type="text" id="numInput" placeholder="255" />
|
||||
</div>
|
||||
<div style="width:120px;">
|
||||
<div class="panel-label">From Base</div>
|
||||
<select id="numBase">
|
||||
<option value="10" selected>Decimal (10)</option>
|
||||
<option value="2">Binary (2)</option>
|
||||
<option value="8">Octal (8)</option>
|
||||
<option value="16">Hex (16)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="convertNumber()"><i class="fas fa-sync"></i></button>
|
||||
</div>
|
||||
<div id="numResults" style="margin-top:16px;"></div>
|
||||
<div class="status" id="numStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/number/convert</span>
|
||||
<div class="api-desc">Convert a number between decimal, binary, octal, and hex bases.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/number/convert`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
value: <span class="str">"255"</span>,
|
||||
fromBase: 10 <span class="cm">// 2, 8, 10, or 16</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, decimal: "255", binary: "11111111",
|
||||
// octal: "377", hex: "FF" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ PASSWORD ═══════════════ -->
|
||||
<div class="page" id="page-password">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-key" style="color:var(--red)"></i> Password Generator</h2>
|
||||
<p>Generate cryptographically secure passwords.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="pw-display" id="pwDisplay" onclick="copyText(this.textContent)">Click "Generate" to create a password</div>
|
||||
<div class="strength-bar"><div class="strength-bar-fill" id="pwStrength"></div></div>
|
||||
<div style="display:flex;align-items:center;gap:12px;margin:16px 0;">
|
||||
<label style="font-size:0.85rem;color:var(--text-secondary);white-space:nowrap;">Length: <strong id="pwLenLabel">16</strong></label>
|
||||
<input type="range" id="pwLength" min="4" max="128" value="16" oninput="document.getElementById('pwLenLabel').textContent=this.value" />
|
||||
</div>
|
||||
<div class="checkbox-group">
|
||||
<label><input type="checkbox" id="pwUpper" checked /> Uppercase (A-Z)</label>
|
||||
<label><input type="checkbox" id="pwLower" checked /> Lowercase (a-z)</label>
|
||||
<label><input type="checkbox" id="pwNumbers" checked /> Numbers (0-9)</label>
|
||||
<label><input type="checkbox" id="pwSymbols" /> Symbols (!@#$%)</label>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generatePassword()"><i class="fas fa-sync"></i> Generate</button>
|
||||
<button class="btn btn-green" onclick="copyText(document.getElementById('pwDisplay').textContent)"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div id="pwBatch" style="margin-top:16px;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/password</span>
|
||||
<div class="api-desc">Generate cryptographically secure passwords with configurable rules.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/password`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
length: 24, <span class="cm">// 4–128 (default: 16)</span>
|
||||
uppercase: true, <span class="cm">// include A-Z</span>
|
||||
lowercase: true, <span class="cm">// include a-z</span>
|
||||
numbers: true, <span class="cm">// include 0-9</span>
|
||||
symbols: true, <span class="cm">// include !@#$%...</span>
|
||||
count: 5 <span class="cm">// 1–20 passwords at once</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, passwords: ["xK#9mL...", "Qw!7pR...", ...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<!-- ═══════════════ PLACEHOLDER IMAGE ═══════════════ -->
|
||||
<div class="page" id="page-placeholder">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-image" style="color:var(--pink)"></i> Placeholder Image Generator</h2>
|
||||
<p>Generate custom placeholder images with text, colors, and dimensions.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Settings</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px;">
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Width (px)</label>
|
||||
<input type="number" id="phWidth" value="400" min="10" max="2000" oninput="generatePlaceholder()" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Height (px)</label>
|
||||
<input type="number" id="phHeight" value="300" min="10" max="2000" oninput="generatePlaceholder()" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Text (leave empty for dimensions)</label>
|
||||
<input type="text" id="phText" placeholder="e.g. Hero Image" oninput="generatePlaceholder()" />
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px;">
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Background</label>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<input type="color" id="phBg" value="#2a2a3a" oninput="generatePlaceholder()" />
|
||||
<input type="text" id="phBgText" value="#2a2a3a" style="flex:1;" oninput="document.getElementById('phBg').value=this.value;generatePlaceholder()" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Text Color</label>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<input type="color" id="phFg" value="#8888a0" oninput="generatePlaceholder()" />
|
||||
<input type="text" id="phFgText" value="#8888a0" style="flex:1;" oninput="document.getElementById('phFg').value=this.value;generatePlaceholder()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Font Size (px)</label>
|
||||
<input type="range" id="phFontSize" min="10" max="120" value="28" oninput="generatePlaceholder()" />
|
||||
<span id="phFontSizeVal" style="font-size:0.8rem;color:var(--text-muted);">28px</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="downloadPlaceholder()"><i class="fas fa-download"></i> Download PNG</button>
|
||||
<button class="btn btn-secondary" onclick="copyPlaceholderDataUrl()"><i class="fas fa-copy"></i> Copy Data URL</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Preview</div>
|
||||
<canvas id="phCanvas" style="width:100%;border-radius:var(--radius);border:1px solid var(--border);"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="phStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/placeholder/:width/:height</span>
|
||||
<div class="api-desc">Generate a placeholder image (SVG). Supports query params: <code>bg</code>, <code>fg</code>, <code>text</code>, <code>fontSize</code>.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Returns an SVG image directly — use as an <img> src</span>
|
||||
|
||||
<span class="cm">// Basic usage:</span>
|
||||
<img src=<span class="str">"${BASE_URL}/api/placeholder/400/300"</span> />
|
||||
|
||||
<span class="cm">// Custom colors & text:</span>
|
||||
<img src=<span class="str">"${BASE_URL}/api/placeholder/800/400?bg=1a1a25&fg=6c63ff&text=Hero+Image&fontSize=36"</span> />
|
||||
|
||||
<span class="cm">// Fetch as SVG text:</span>
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/placeholder/200/200?text=Avatar`</span>);
|
||||
<span class="kw">const</span> svg = <span class="kw">await</span> res.<span class="fn">text</span>();
|
||||
<span class="cm">// → <svg xmlns=...>...</svg></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ QR CODE ═══════════════ -->
|
||||
<div class="page" id="page-qrcode">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-qrcode" style="color:var(--accent)"></i> QR Code Generator</h2>
|
||||
<p>Generate QR codes from any text or URL.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="panel-label">Data</div>
|
||||
<textarea id="qrInput" placeholder="Enter text or URL..." style="min-height:80px;" oninput="generateQR()"></textarea>
|
||||
<div style="text-align:center;margin:20px 0;">
|
||||
<div id="qrCanvas" style="display:none;"></div>
|
||||
<div id="qrPlaceholder" style="color:var(--text-muted);font-size:0.85rem;">Type something above to generate a QR code</div>
|
||||
</div>
|
||||
<div class="btn-group" style="justify-content:center;">
|
||||
<button class="btn btn-primary" onclick="downloadQR()"><i class="fas fa-download"></i> Download PNG</button>
|
||||
</div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ QR Code generation runs entirely <strong>client-side</strong> using the <code>qrcode-generator</code> library. No server API is needed.</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Generate QR codes using the qrcode-generator package (works in Node.js and browsers).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Install: npm install qrcode-generator</span>
|
||||
<span class="cm">// Browser CDN: https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js</span>
|
||||
|
||||
<span class="kw">const</span> qr = <span class="fn">qrcode</span>(0, <span class="str">'M'</span>);
|
||||
qr.<span class="fn">addData</span>(<span class="str">"https://example.com"</span>);
|
||||
qr.<span class="fn">make</span>();
|
||||
|
||||
<span class="cm">// Render as an <img> tag:</span>
|
||||
document.<span class="fn">getElementById</span>(<span class="str">'container'</span>).innerHTML =
|
||||
qr.<span class="fn">createImgTag</span>(4, 8);
|
||||
|
||||
<span class="cm">// Or get a data URL:</span>
|
||||
<span class="kw">const</span> dataUrl = qr.<span class="fn">createDataURL</span>(4, 8);
|
||||
<span class="cm">// → "data:image/gif;base64,..."</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<!-- ═══════════════ QR CODE READER ═══════════════ -->
|
||||
<div class="page" id="page-qrreader">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-camera" style="color:var(--cyan)"></i> QR Code Reader</h2>
|
||||
<p>Scan QR codes using your camera or upload an image file.</p>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
|
||||
<!-- Left: Input -->
|
||||
<div>
|
||||
<div class="panel-label">Scan Method</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" id="qrCameraBtn" onclick="startQrCamera()"><i class="fas fa-video"></i> Use Camera</button>
|
||||
<button class="btn btn-secondary" id="qrStopBtn" onclick="stopQrCamera()" style="display:none;"><i class="fas fa-stop"></i> Stop Camera</button>
|
||||
<label class="btn btn-secondary" style="cursor:pointer;">
|
||||
<i class="fas fa-upload"></i> Upload Image
|
||||
<input type="file" id="qrFileInput" accept="image/*" onchange="scanQrFromFile(event)" style="display:none;" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Camera feed -->
|
||||
<div id="qrCameraContainer" style="display:none;margin-top:16px;">
|
||||
<video id="qrVideo" style="width:100%;border-radius:var(--radius);border:1px solid var(--border);background:#000;" autoplay playsinline muted></video>
|
||||
<canvas id="qrScanCanvas" style="display:none;"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Image preview -->
|
||||
<div id="qrImagePreview" style="display:none;margin-top:16px;">
|
||||
<img id="qrPreviewImg" alt="Uploaded QR image" src="" style="width:100%;border-radius:var(--radius);border:1px solid var(--border);" />
|
||||
<canvas id="qrImgCanvas" style="display:none;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Result -->
|
||||
<div>
|
||||
<div class="panel-label">Scan Result</div>
|
||||
<div id="qrReaderPlaceholder" style="color:var(--text-muted);font-size:0.85rem;padding:40px 20px;text-align:center;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);">
|
||||
<i class="fas fa-qrcode" style="font-size:3rem;margin-bottom:12px;display:block;opacity:0.3;"></i>
|
||||
Point your camera at a QR code or upload an image to scan.
|
||||
</div>
|
||||
<div id="qrReaderResult" style="display:none;">
|
||||
<div class="result-card">
|
||||
<div class="panel-label">Decoded Content</div>
|
||||
<div id="qrDecodedText" style="font-family:var(--font-mono);font-size:0.9rem;padding:14px;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-sm);word-break:break-all;margin-bottom:12px;color:var(--cyan);"></div>
|
||||
<div class="panel-label">Type</div>
|
||||
<div id="qrDecodedType" style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:12px;"></div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyText(document.getElementById('qrDecodedText').textContent)"><i class="fas fa-copy"></i> Copy</button>
|
||||
<button class="btn btn-secondary" id="qrOpenLinkBtn" style="display:none;" onclick="window.open(document.getElementById('qrDecodedText').textContent,'_blank')"><i class="fas fa-external-link-alt"></i> Open Link</button>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:12px;">Scan History</div>
|
||||
<div id="qrScanHistory"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="qrReaderStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ QR Code reading runs entirely <strong>client-side</strong> using the <code>jsQR</code> library. No server API is needed.</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Decode QR codes from image data using the jsQR library (works in Node.js and browsers).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Install: npm install jsqr</span>
|
||||
<span class="cm">// Browser CDN: https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js</span>
|
||||
|
||||
<span class="cm">// Read from a canvas element:</span>
|
||||
<span class="kw">const</span> canvas = document.<span class="fn">getElementById</span>(<span class="str">'myCanvas'</span>);
|
||||
<span class="kw">const</span> ctx = canvas.<span class="fn">getContext</span>(<span class="str">'2d'</span>);
|
||||
<span class="kw">const</span> imageData = ctx.<span class="fn">getImageData</span>(0, 0, canvas.width, canvas.height);
|
||||
|
||||
<span class="kw">const</span> code = <span class="fn">jsQR</span>(imageData.data, imageData.width, imageData.height);
|
||||
<span class="kw">if</span> (code) {
|
||||
console.<span class="fn">log</span>(<span class="str">"Decoded:"</span>, code.data);
|
||||
<span class="cm">// → "https://example.com"</span>
|
||||
}
|
||||
|
||||
<span class="cm">// Read from camera (MediaDevices API):</span>
|
||||
<span class="kw">const</span> stream = <span class="kw">await</span> navigator.mediaDevices.<span class="fn">getUserMedia</span>({ video: { facingMode: <span class="str">'environment'</span> } });
|
||||
<span class="kw">const</span> video = document.<span class="fn">createElement</span>(<span class="str">'video'</span>);
|
||||
video.srcObject = stream;
|
||||
video.<span class="fn">play</span>();
|
||||
<span class="cm">// Then draw video frames to canvas and scan with jsQR</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<!-- ═══════════════ REGEX ═══════════════ -->
|
||||
<div class="page" id="page-regex">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-asterisk" style="color:var(--pink)"></i> Regex Tester</h2>
|
||||
<p>Test regular expressions with live matching.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:flex;gap:10px;margin-bottom:12px;">
|
||||
<div style="flex:1;">
|
||||
<div class="panel-label">Pattern</div>
|
||||
<input type="text" id="regexPattern" placeholder="[a-z]+@[a-z]+\.[a-z]+" oninput="testRegex()" />
|
||||
</div>
|
||||
<div style="width:80px;">
|
||||
<div class="panel-label">Flags</div>
|
||||
<input type="text" id="regexFlags" value="gi" placeholder="gi" oninput="testRegex()" style="width:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label">Test String</div>
|
||||
<textarea id="regexInput" placeholder="Enter text to test against..." oninput="testRegex()" style="min-height:120px;"></textarea>
|
||||
<div class="panel-label" style="margin-top:14px;">Matches</div>
|
||||
<div id="regexResults" style="background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px;font-family:var(--font-mono);font-size:0.85rem;min-height:60px;line-height:1.8;word-break:break-all;"></div>
|
||||
<div id="regexMatchList" style="margin-top:12px;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ Regex Tester runs entirely <strong>client-side</strong> using JavaScript's native <code>RegExp</code>. No server API is needed. Here's how to use the same logic in your own code:</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Test a regex pattern against a string in JavaScript.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Client-side regex testing — no API call needed</span>
|
||||
<span class="kw">const</span> pattern = <span class="str">"[a-z]+@[a-z]+\\.[a-z]+"</span>;
|
||||
<span class="kw">const</span> flags = <span class="str">"gi"</span>;
|
||||
<span class="kw">const</span> testString = <span class="str">"Contact us at hello@example.com or info@test.org"</span>;
|
||||
|
||||
<span class="kw">const</span> regex = <span class="kw">new</span> <span class="fn">RegExp</span>(pattern, flags);
|
||||
<span class="kw">const</span> matches = [...testString.<span class="fn">matchAll</span>(regex)];
|
||||
|
||||
matches.<span class="fn">forEach</span>((m, i) => {
|
||||
console.<span class="fn">log</span>(<span class="str">`Match ${i+1}: ${m[0]} (index: ${m.index})`</span>);
|
||||
});
|
||||
<span class="cm">// → Match 1: hello@example.com (index: 14)
|
||||
// → Match 2: info@test.org (index: 36)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- ═══════════════ SLUG GENERATOR ═══════════════ -->
|
||||
<div class="page" id="page-slugify">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-link" style="color:var(--green)"></i> Slug Generator</h2>
|
||||
<p>Convert any text into a clean, URL-friendly slug.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="slugInput" placeholder="e.g. Hello World! This is My Blog Post #1" style="min-height:80px;" oninput="generateSlug()"></textarea>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-top:10px;">
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);">Separator:</label>
|
||||
<select id="slugSeparator" style="width:120px;padding:6px 10px;" onchange="generateSlug()">
|
||||
<option value="-" selected>Hyphen (-)</option>
|
||||
<option value="_">Underscore (_)</option>
|
||||
<option value=".">Dot (.)</option>
|
||||
</select>
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);margin-left:10px;">
|
||||
<input type="checkbox" id="slugLower" checked onchange="generateSlug()"> Lowercase
|
||||
</label>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:16px;">Slug Output</div>
|
||||
<div class="result-row" id="slugResult" style="cursor:pointer;">
|
||||
<div class="label">SLUG</div>
|
||||
<div class="value" id="slugOutput" onclick="copyText(this.textContent)" title="Click to copy">—</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyText(document.getElementById('slugOutput').textContent)"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div class="status" id="slugStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/slugify</span>
|
||||
<div class="api-desc">Convert text to a URL-friendly slug. Optional: <code>separator</code> (<code>-</code>, <code>_</code>, <code>.</code>) and <code>lowercase</code> (boolean).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/slugify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
text: <span class="str">"Hello World! My Blog Post #1"</span>,
|
||||
separator: <span class="str">"-"</span>,
|
||||
lowercase: <span class="kw">true</span>
|
||||
})
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "hello-world-my-blog-post-1" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ SQL FORMATTER ═══════════════ -->
|
||||
<div class="page" id="page-sqlformat">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-database" style="color:var(--accent)"></i> SQL Formatter</h2>
|
||||
<p>Beautify and minify SQL queries with keyword highlighting.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input SQL</div>
|
||||
<textarea id="sqlInput" placeholder="SELECT * FROM users WHERE id = 1 AND active = true ORDER BY name"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="formatSQL()"><i class="fas fa-magic"></i> Beautify</button>
|
||||
<button class="btn btn-secondary" onclick="minifySQL()"><i class="fas fa-compress"></i> Minify</button>
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('sqlInput').value='';document.getElementById('sqlOutput').value='';document.getElementById('sqlStatus').className='status';"><i class="fas fa-trash"></i> Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="sqlOutput" readonly placeholder="Formatted SQL will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('sqlOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="sqlStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/sql/format</span>
|
||||
<div class="api-desc">Beautify a SQL query with indentation.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/sql/format`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ sql: <span class="str">"SELECT * FROM users WHERE active = 1"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "SELECT\n *\nFROM\n users\nWHERE\n active = 1" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/sql/minify</span>
|
||||
<div class="api-desc">Minify a SQL query to a single line.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/sql/minify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ sql: <span class="str">"SELECT\n *\nFROM\n users"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "SELECT * FROM users" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!-- ═══════════════ TEXT ENCODER ═══════════════ -->
|
||||
<div class="page" id="page-textenc">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-language" style="color:var(--cyan)"></i> Text Encoder / Decoder</h2>
|
||||
<p>Encode and decode text in ROT13, Binary, Morse Code, and more.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="textencInput" placeholder="Type or paste text here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="encodeText('rot13')"><i class="fas fa-sync"></i> ROT13</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('binary')"><i class="fas fa-microchip"></i> Binary</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('morse')"><i class="fas fa-broadcast-tower"></i> Morse</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('reverse')"><i class="fas fa-undo"></i> Reverse</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('leetspeak')"><i class="fas fa-skull"></i> L33t</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('upside_down')"><i class="fas fa-arrows-alt-v"></i> Upside Down</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:16px;">Output</div>
|
||||
<textarea id="textencOutput" readonly placeholder="Encoded output will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('textencOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
<button class="btn btn-secondary" onclick="swapTextEnc()"><i class="fas fa-exchange-alt"></i> Swap</button>
|
||||
</div>
|
||||
<div class="status" id="textencStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/encode</span>
|
||||
<div class="api-desc">Encode text with the specified method (rot13, binary, morse, reverse, leetspeak, upside_down).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/encode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello World"</span>, method: <span class="str">"morse"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: ".... . .-.. .-.. --- / .-- --- .-. .-.. -.." }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- ═══════════════ TIMESTAMP ═══════════════ -->
|
||||
<div class="page" id="page-timestamp">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-clock" style="color:var(--cyan)"></i> Timestamp Converter</h2>
|
||||
<p>Convert between Unix timestamps and human-readable dates.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="panel-label">Input (Unix timestamp, ISO date, or "now")</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="tsInput" placeholder="1709683200 or 2024-03-06 or now" />
|
||||
<button class="btn btn-primary" onclick="convertTimestamp()"><i class="fas fa-sync"></i> Convert</button>
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('tsInput').value='now';convertTimestamp()">Now</button>
|
||||
</div>
|
||||
<div id="tsResults" style="margin-top:16px;"></div>
|
||||
<div class="status" id="tsStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/timestamp</span>
|
||||
<div class="api-desc">Convert between Unix timestamps, ISO dates, and relative time. Accepts 10-digit (seconds), 13-digit (ms), ISO strings, or "now".</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="cm">// From Unix timestamp:</span>
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/timestamp`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ value: <span class="str">"1709683200"</span> })
|
||||
<span class="cm">// Also: "now", "2024-03-06", or "1709683200000" (ms)</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, unix: 1709683200, unixMs: 1709683200000,
|
||||
// iso: "2024-03-06T00:00:00.000Z", utc: "Wed, 06 Mar 2024...",
|
||||
// local: "...", relative: "1y ago" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<!-- ═══════════════ URL SHORTENER ═══════════════ -->
|
||||
<div class="page" id="page-url">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-link" style="color:var(--green)"></i> URL Shortener</h2>
|
||||
<p>Paste a long URL and get a short, shareable link.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="panel-label">Long URL</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="url" id="urlInput" placeholder="https://example.com/very/long/url" />
|
||||
<button class="btn btn-primary" onclick="shortenURL()"><i class="fas fa-bolt"></i> Shorten</button>
|
||||
</div>
|
||||
<div class="status" id="urlStatus"></div>
|
||||
<div class="short-url-result" id="urlResult">
|
||||
<div class="result-card">
|
||||
<div class="panel-label">Your Short URL</div>
|
||||
<div class="short-url-display">
|
||||
<a id="shortUrlLink" href="#" target="_blank"></a>
|
||||
<button class="btn btn-sm btn-secondary" onclick="copyShortUrl()"><i class="fas fa-copy"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:28px;"><div class="panel-label">Recent Links</div><div id="urlHistory" style="color:var(--text-muted);font-size:0.85rem;">No links shortened yet.</div></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/url/shorten</span>
|
||||
<div class="api-desc">Create a shortened URL.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/url/shorten`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ url: <span class="str">"https://github.com/long/repo/path"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, shortUrl: "http://localhost:3000/s/abc1234", id: "abc1234" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/url/stats/:id</span>
|
||||
<div class="api-desc">Get click stats for a shortened URL.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/url/stats/abc1234`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, url: "https://...", clicks: 42, createdAt: "..." }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/s/:id</span>
|
||||
<div class="api-desc">Redirect to the original URL (use in browser).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Simply open in browser or use as a link:</span>
|
||||
window.<span class="fn">open</span>(<span class="var">`${BASE_URL}/s/abc1234`</span>);
|
||||
<span class="cm">// → 302 redirect to the original URL</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- ═══════════════ UUID ═══════════════ -->
|
||||
<div class="page" id="page-uuid">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-hashtag" style="color:var(--accent)"></i> UUID Generator</h2>
|
||||
<p>Generate random v4 UUIDs.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div style="display:flex;gap:10px;align-items:center;margin-bottom:16px;">
|
||||
<label style="font-size:0.85rem;color:var(--text-secondary);">Count:</label>
|
||||
<input type="number" id="uuidCount" value="5" min="1" max="100" style="width:80px;" />
|
||||
<button class="btn btn-primary" onclick="generateUUIDs()"><i class="fas fa-dice"></i> Generate</button>
|
||||
<button class="btn btn-green" onclick="copyUUIDs()"><i class="fas fa-copy"></i> Copy All</button>
|
||||
</div>
|
||||
<div id="uuidResults" style="font-family:var(--font-mono);font-size:0.85rem;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/uuid</span>
|
||||
<div class="api-desc">Generate random v4 UUIDs. Batch up to 100.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/uuid`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ count: 5 }) <span class="cm">// 1–100</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, uuids: ["550e8400-e29b-41d4-...", ...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- ═══════════════ YOUTUBE ═══════════════ -->
|
||||
<div class="page" id="page-youtube">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-youtube" style="color:var(--red)"></i> YouTube Tool</h2>
|
||||
<p>Extract metadata, thumbnails, and embed codes from any YouTube video.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">YouTube URL</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="url" id="ytInput" placeholder="https://www.youtube.com/watch?v=..." />
|
||||
<button class="btn btn-red" onclick="fetchYouTube()"><i class="fas fa-search"></i> Fetch</button>
|
||||
</div>
|
||||
<div class="status" id="ytStatus"></div>
|
||||
<div class="yt-result" id="ytResult">
|
||||
<img class="yt-thumb" id="ytThumb" src="" alt="Thumbnail" />
|
||||
<div class="yt-info">
|
||||
<h3 id="ytTitle"></h3>
|
||||
<div class="author">by <a id="ytAuthor" href="#" target="_blank"></a></div>
|
||||
<div class="panel-label">Thumbnail URLs (click to copy)</div>
|
||||
<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:14px;">
|
||||
<code id="ytThumbMax" style="font-size:0.75rem;color:var(--cyan);word-break:break-all;cursor:pointer;" onclick="copyText(this.textContent)"></code>
|
||||
<code id="ytThumbHQ" style="font-size:0.75rem;color:var(--cyan);word-break:break-all;cursor:pointer;" onclick="copyText(this.textContent)"></code>
|
||||
</div>
|
||||
<div class="panel-label">Embed Code</div>
|
||||
<textarea id="ytEmbed" readonly style="min-height:50px;font-size:0.75rem;margin-bottom:12px;"></textarea>
|
||||
<div class="yt-links">
|
||||
<button class="btn btn-primary btn-sm" onclick="copyOutput('ytEmbed')"><i class="fas fa-copy"></i> Copy Embed</button>
|
||||
<a id="ytWatch" class="btn btn-secondary btn-sm" href="#" target="_blank" style="text-decoration:none;"><i class="fas fa-external-link-alt"></i> YouTube</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/youtube/info</span>
|
||||
<div class="api-desc">Extract metadata, thumbnails, and embed info from a YouTube video.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/youtube/info`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
url: <span class="str">"https://www.youtube.com/watch?v=dQw4w9WgXcQ"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, videoId: "dQw4w9WgXcQ", title: "...",
|
||||
// author: "...", thumbnail: "https://img.youtube.com/...",
|
||||
// embedUrl: "https://youtube.com/embed/...", ... }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -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, '"').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 = `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}">
|
||||
<rect width="100%" height="100%" fill="#${bg}"/>
|
||||
<line x1="0" y1="0" x2="${w}" y2="${h}" stroke="#${fg}" stroke-opacity="0.12" stroke-width="1"/>
|
||||
<line x1="${w}" y1="0" x2="0" y2="${h}" stroke="#${fg}" stroke-opacity="0.12" stroke-width="1"/>
|
||||
<text x="50%" y="50%" dominant-baseline="central" text-anchor="middle" font-family="sans-serif" font-size="${fontSize}" fill="#${fg}">${text.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>')}</text>
|
||||
</svg>`;
|
||||
|
||||
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`);
|
||||
});
|
||||
Reference in New Issue
Block a user