first commit

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