first commit
This commit is contained in:
@@ -0,0 +1,482 @@
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
:root {
|
||||
--bg-primary: #0a0a0f;
|
||||
--bg-secondary: #12121a;
|
||||
--bg-card: #16161f;
|
||||
--bg-input: #1a1a25;
|
||||
--border: #2a2a3a;
|
||||
--border-glow: #6c63ff40;
|
||||
--text-primary: #e8e8f0;
|
||||
--text-secondary: #8888a0;
|
||||
--text-muted: #555570;
|
||||
--accent: #6c63ff;
|
||||
--accent-hover: #7f78ff;
|
||||
--accent-glow: #6c63ff30;
|
||||
--green: #22c55e;
|
||||
--red: #ef4444;
|
||||
--orange: #f59e0b;
|
||||
--cyan: #06b6d4;
|
||||
--pink: #ec4899;
|
||||
--yellow: #eab308;
|
||||
--radius: 12px;
|
||||
--radius-sm: 8px;
|
||||
--font-mono: 'SF Mono', 'Cascadia Code', 'Fira Code', 'JetBrains Mono', monospace;
|
||||
--font-sans: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
||||
--transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
background: var(--bg-primary);
|
||||
color: var(--text-primary);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
/* ─── Background Glow ─────────────────────────────────── */
|
||||
.bg-glow { position: fixed; inset: 0; z-index: -1; overflow: hidden; }
|
||||
.bg-glow::before, .bg-glow::after {
|
||||
content: ''; position: absolute; width: 600px; height: 600px;
|
||||
border-radius: 50%; filter: blur(120px); opacity: 0.07;
|
||||
animation: float 20s ease-in-out infinite;
|
||||
}
|
||||
.bg-glow::before { background: var(--accent); top: -200px; left: -100px; }
|
||||
.bg-glow::after { background: var(--pink); bottom: -200px; right: -100px; animation-delay: -10s; }
|
||||
@keyframes float {
|
||||
0%, 100% { transform: translate(0, 0) scale(1); }
|
||||
33% { transform: translate(100px, 50px) scale(1.1); }
|
||||
66% { transform: translate(-50px, -30px) scale(0.95); }
|
||||
}
|
||||
/* ─── Particles ─────────────────────────────────────── */
|
||||
.particles { position: fixed; inset: 0; z-index: -1; pointer-events: none; }
|
||||
.particle {
|
||||
position: absolute; width: 2px; height: 2px;
|
||||
background: var(--accent); border-radius: 50%; opacity: 0;
|
||||
animation: particle-float 15s linear infinite;
|
||||
}
|
||||
@keyframes particle-float {
|
||||
0% { opacity: 0; transform: translateY(100vh) scale(0); }
|
||||
10% { opacity: 0.6; }
|
||||
90% { opacity: 0.6; }
|
||||
100% { opacity: 0; transform: translateY(-10vh) scale(1); }
|
||||
}
|
||||
/* ─── Header ──────────────────────────────────────────── */
|
||||
header {
|
||||
position: sticky; top: 0; z-index: 100;
|
||||
background: rgba(10, 10, 15, 0.85);
|
||||
backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(20px);
|
||||
border-bottom: 1px solid var(--border);
|
||||
padding: 0 24px;
|
||||
}
|
||||
.header-inner {
|
||||
max-width: 1400px; margin: 0 auto;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
height: 64px; gap: 16px;
|
||||
}
|
||||
.logo {
|
||||
font-size: 1.35rem; font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--accent), var(--pink));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
||||
cursor: pointer; letter-spacing: -0.5px; white-space: nowrap;
|
||||
}
|
||||
.logo span { opacity: 0.5; font-weight: 400; }
|
||||
.search-bar {
|
||||
flex: 1; max-width: 400px; position: relative;
|
||||
}
|
||||
.search-bar input {
|
||||
width: 100%; background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: 24px; color: var(--text-primary);
|
||||
padding: 8px 16px 8px 36px; font-size: 0.85rem;
|
||||
transition: var(--transition); outline: none; font-family: var(--font-sans);
|
||||
}
|
||||
.search-bar input:focus { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow); }
|
||||
.search-bar i { position: absolute; left: 12px; top: 50%; transform: translateY(-50%); color: var(--text-muted); font-size: 0.8rem; }
|
||||
.header-actions { display: flex; gap: 8px; align-items: center; }
|
||||
.header-actions button {
|
||||
background: var(--bg-input); border: 1px solid var(--border); color: var(--text-secondary);
|
||||
width: 36px; height: 36px; border-radius: 50%; cursor: pointer;
|
||||
transition: var(--transition); display: flex; align-items: center; justify-content: center;
|
||||
font-size: 0.85rem;
|
||||
}
|
||||
.header-actions button:hover { border-color: var(--accent); color: var(--accent); }
|
||||
/* ─── Main ────────────────────────────────────────────── */
|
||||
main { max-width: 1400px; margin: 0 auto; padding: 32px 24px 80px; }
|
||||
.page { display: none; animation: fadeIn 0.35s ease; }
|
||||
.page.active { display: block; }
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
/* ─── Hero ────────────────────────────────────────────── */
|
||||
.hero { text-align: center; padding: 60px 0 48px; }
|
||||
.hero h1 {
|
||||
font-size: clamp(2.2rem, 5vw, 3.5rem);
|
||||
font-weight: 800; letter-spacing: -1.5px; line-height: 1.1; margin-bottom: 14px;
|
||||
}
|
||||
.hero h1 .gradient {
|
||||
background: linear-gradient(135deg, var(--accent), var(--cyan), var(--pink));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;
|
||||
}
|
||||
.hero p { color: var(--text-secondary); font-size: 1.1rem; max-width: 520px; margin: 0 auto 12px; line-height: 1.6; }
|
||||
.hero .stats {
|
||||
display: flex; gap: 32px; justify-content: center; margin-top: 20px;
|
||||
}
|
||||
.hero .stat { text-align: center; }
|
||||
.hero .stat-num { font-size: 1.8rem; font-weight: 800; color: var(--accent); }
|
||||
.hero .stat-label { font-size: 0.75rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 1px; }
|
||||
/* ─── Category Tabs ───────────────────────────────────── */
|
||||
.categories { display: flex; gap: 6px; flex-wrap: wrap; justify-content: center; margin-bottom: 28px; }
|
||||
.cat-btn {
|
||||
background: var(--bg-input); border: 1px solid var(--border); color: var(--text-secondary);
|
||||
padding: 6px 16px; border-radius: 20px; font-size: 0.8rem; font-weight: 500;
|
||||
cursor: pointer; transition: var(--transition); font-family: var(--font-sans);
|
||||
}
|
||||
.cat-btn:hover { border-color: var(--accent); color: var(--text-primary); }
|
||||
.cat-btn.active { background: var(--accent-glow); border-color: var(--accent); color: var(--accent); }
|
||||
/* ─── Tools Grid ──────────────────────────────────────── */
|
||||
.tools-grid {
|
||||
display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
.tool-card {
|
||||
background: var(--bg-card); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 24px; cursor: pointer;
|
||||
transition: var(--transition); position: relative; overflow: hidden;
|
||||
}
|
||||
.tool-card::before {
|
||||
content: ''; position: absolute; top: 0; left: 0; right: 0; height: 3px;
|
||||
opacity: 0; transition: var(--transition);
|
||||
}
|
||||
.tool-card:hover {
|
||||
border-color: var(--border-glow); transform: translateY(-3px);
|
||||
box-shadow: 0 16px 32px rgba(0,0,0,0.3), 0 0 20px var(--accent-glow);
|
||||
}
|
||||
.tool-card:hover::before { opacity: 1; }
|
||||
.tool-card .tc-top { display: flex; align-items: center; gap: 12px; margin-bottom: 10px; }
|
||||
.tool-card .icon {
|
||||
width: 42px; height: 42px; border-radius: var(--radius-sm);
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
font-size: 1.1rem; flex-shrink: 0;
|
||||
}
|
||||
.tool-card h3 { font-size: 1rem; font-weight: 700; }
|
||||
.tool-card p { color: var(--text-secondary); font-size: 0.82rem; line-height: 1.5; }
|
||||
.tool-card .tag {
|
||||
display: inline-block; padding: 2px 8px; border-radius: 20px;
|
||||
font-size: 0.65rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;
|
||||
margin-left: auto;
|
||||
}
|
||||
/* Icon colors */
|
||||
.ic-purple { background: #6c63ff15; color: var(--accent); }
|
||||
.ic-green { background: #22c55e15; color: var(--green); }
|
||||
.ic-red { background: #ef444415; color: var(--red); }
|
||||
.ic-cyan { background: #06b6d415; color: var(--cyan); }
|
||||
.ic-orange { background: #f59e0b15; color: var(--orange); }
|
||||
.ic-pink { background: #ec489915; color: var(--pink); }
|
||||
.ic-yellow { background: #eab30815; color: var(--yellow); }
|
||||
/* Gradient bars */
|
||||
[data-cat="format"]::before { background: linear-gradient(90deg, var(--accent), var(--cyan)); }
|
||||
[data-cat="encode"]::before { background: linear-gradient(90deg, var(--green), var(--cyan)); }
|
||||
[data-cat="generate"]::before{ background: linear-gradient(90deg, var(--orange), var(--yellow)); }
|
||||
[data-cat="text"]::before { background: linear-gradient(90deg, var(--pink), var(--accent)); }
|
||||
[data-cat="web"]::before { background: linear-gradient(90deg, var(--cyan), var(--green)); }
|
||||
[data-cat="dev"]::before { background: linear-gradient(90deg, var(--red), var(--orange)); }
|
||||
.tag-purple { background: #6c63ff20; color: var(--accent); }
|
||||
.tag-green { background: #22c55e20; color: var(--green); }
|
||||
.tag-red { background: #ef444420; color: var(--red); }
|
||||
.tag-cyan { background: #06b6d420; color: var(--cyan); }
|
||||
.tag-orange { background: #f59e0b20; color: var(--orange); }
|
||||
.tag-pink { background: #ec489920; color: var(--pink); }
|
||||
.tag-yellow { background: #eab30820; color: var(--yellow); }
|
||||
/* ─── Section Header / Back ───────────────────────────── */
|
||||
.section-header { margin-bottom: 24px; }
|
||||
.section-header h2 { font-size: 1.6rem; font-weight: 800; letter-spacing: -0.5px; margin-bottom: 4px; }
|
||||
.section-header p { color: var(--text-secondary); font-size: 0.9rem; }
|
||||
.back-btn {
|
||||
background: transparent; border: 1px solid var(--border);
|
||||
color: var(--text-secondary); padding: 6px 14px; border-radius: var(--radius-sm);
|
||||
cursor: pointer; font-size: 0.8rem; margin-bottom: 16px;
|
||||
transition: var(--transition); font-family: var(--font-sans);
|
||||
}
|
||||
.back-btn:hover { border-color: var(--accent); color: var(--accent); }
|
||||
/* ─── Form Controls ───────────────────────────────────── */
|
||||
textarea, input[type="text"], input[type="url"], input[type="number"], input[type="password"], select {
|
||||
width: 100%; background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); color: var(--text-primary);
|
||||
padding: 12px 16px; font-family: var(--font-mono); font-size: 0.85rem;
|
||||
transition: var(--transition); outline: none;
|
||||
}
|
||||
textarea:focus, input:focus, select:focus {
|
||||
border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-glow);
|
||||
}
|
||||
textarea { resize: vertical; min-height: 180px; line-height: 1.6; }
|
||||
input[type="color"] {
|
||||
width: 60px; height: 42px; border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); background: var(--bg-input);
|
||||
cursor: pointer; padding: 4px;
|
||||
}
|
||||
.btn {
|
||||
display: inline-flex; align-items: center; gap: 8px;
|
||||
padding: 10px 20px; border: none; border-radius: var(--radius-sm);
|
||||
font-family: var(--font-sans); font-size: 0.85rem; font-weight: 600;
|
||||
cursor: pointer; transition: var(--transition); white-space: nowrap;
|
||||
}
|
||||
.btn-primary { background: var(--accent); color: white; }
|
||||
.btn-primary:hover { background: var(--accent-hover); box-shadow: 0 4px 20px var(--accent-glow); }
|
||||
.btn-secondary { background: var(--bg-input); color: var(--text-secondary); border: 1px solid var(--border); }
|
||||
.btn-secondary:hover { border-color: var(--accent); color: var(--text-primary); }
|
||||
.btn-green { background: var(--green); color: white; }
|
||||
.btn-green:hover { background: #16a34a; }
|
||||
.btn-red { background: var(--red); color: white; }
|
||||
.btn-red:hover { background: #dc2626; }
|
||||
.btn-cyan { background: var(--cyan); color: white; }
|
||||
.btn-cyan:hover { background: #0891b2; }
|
||||
.btn-orange { background: var(--orange); color: #000; }
|
||||
.btn-orange:hover { background: #d97706; }
|
||||
.btn-pink { background: var(--pink); color: white; }
|
||||
.btn-pink:hover { background: #db2777; }
|
||||
.btn-sm { padding: 6px 14px; font-size: 0.78rem; }
|
||||
.btn-group { display: flex; gap: 8px; flex-wrap: wrap; margin: 14px 0; }
|
||||
/* ─── Panels ──────────────────────────────────────────── */
|
||||
.split-panel { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; }
|
||||
.panel-label {
|
||||
font-size: 0.72rem; font-weight: 600; text-transform: uppercase;
|
||||
letter-spacing: 1px; color: var(--text-muted); margin-bottom: 8px;
|
||||
}
|
||||
/* ─── Result Cards ────────────────────────────────────── */
|
||||
.result-card {
|
||||
background: var(--bg-card); border: 1px solid var(--border);
|
||||
border-radius: var(--radius); padding: 20px; margin-top: 16px;
|
||||
}
|
||||
.result-row {
|
||||
display: flex; justify-content: space-between; align-items: center;
|
||||
padding: 10px 14px; background: var(--bg-input);
|
||||
border-radius: var(--radius-sm); margin-bottom: 6px;
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.result-row .label { font-size: 0.72rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
.result-row .value {
|
||||
font-family: var(--font-mono); font-size: 0.82rem; color: var(--cyan);
|
||||
word-break: break-all; cursor: pointer; text-align: right; max-width: 70%;
|
||||
}
|
||||
.result-row .value:hover { color: var(--accent); }
|
||||
/* ─── Status ──────────────────────────────────────────── */
|
||||
.status {
|
||||
padding: 10px 14px; border-radius: var(--radius-sm); font-size: 0.82rem;
|
||||
margin-top: 10px; display: none; font-family: var(--font-mono);
|
||||
}
|
||||
.status.success { display: block; background: #22c55e12; border: 1px solid #22c55e30; color: var(--green); }
|
||||
.status.error { display: block; background: #ef444412; border: 1px solid #ef444430; color: var(--red); }
|
||||
.status.info { display: block; background: #6c63ff12; border: 1px solid #6c63ff30; color: var(--accent); }
|
||||
.status.warning { display: block; background: #f59e0b12; border: 1px solid #f59e0b30; color: var(--orange); }
|
||||
/* ─── Short URL ───────────────────────────────────────── */
|
||||
.short-url-result { display: none; margin-top: 16px; }
|
||||
.short-url-result.visible { display: block; animation: fadeIn 0.35s ease; }
|
||||
.short-url-display {
|
||||
display: flex; align-items: center; gap: 12px;
|
||||
background: var(--bg-input); border: 1px solid var(--accent);
|
||||
border-radius: var(--radius-sm); padding: 14px 16px; margin: 10px 0;
|
||||
}
|
||||
.short-url-display a { flex: 1; color: var(--accent); font-family: var(--font-mono); font-size: 0.9rem; text-decoration: none; }
|
||||
.short-url-display a:hover { text-decoration: underline; }
|
||||
/* ─── YouTube Card ────────────────────────────────────── */
|
||||
.yt-result { display: none; overflow: hidden; margin-top: 16px; }
|
||||
.yt-result.visible { display: block; animation: fadeIn 0.35s ease; }
|
||||
.yt-thumb { width: 100%; aspect-ratio: 16/9; object-fit: cover; display: block; border-radius: var(--radius) var(--radius) 0 0; }
|
||||
.yt-info { padding: 20px; background: var(--bg-card); border: 1px solid var(--border); border-top: 0; border-radius: 0 0 var(--radius) var(--radius); }
|
||||
.yt-info h3 { font-size: 1.1rem; margin-bottom: 6px; line-height: 1.4; }
|
||||
.yt-info .author { color: var(--text-secondary); font-size: 0.85rem; margin-bottom: 14px; }
|
||||
.yt-info .author a { color: var(--accent); text-decoration: none; }
|
||||
.yt-links { display: flex; gap: 8px; flex-wrap: wrap; }
|
||||
/* ─── Color Preview ───────────────────────────────────── */
|
||||
.color-preview-box {
|
||||
width: 100%; height: 120px; border-radius: var(--radius);
|
||||
border: 1px solid var(--border); margin-bottom: 16px;
|
||||
transition: background 0.3s ease;
|
||||
}
|
||||
/* ─── Checkbox & Toggle ───────────────────────────────── */
|
||||
.checkbox-group { display: flex; flex-wrap: wrap; gap: 16px; margin: 12px 0; }
|
||||
.checkbox-group label {
|
||||
display: flex; align-items: center; gap: 6px; font-size: 0.85rem;
|
||||
color: var(--text-secondary); cursor: pointer;
|
||||
}
|
||||
.checkbox-group input[type="checkbox"] {
|
||||
width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer;
|
||||
}
|
||||
/* ─── Password Display ────────────────────────────────── */
|
||||
.pw-display {
|
||||
font-family: var(--font-mono); font-size: 1.1rem;
|
||||
padding: 16px; background: var(--bg-input);
|
||||
border: 1px solid var(--border); border-radius: var(--radius-sm);
|
||||
word-break: break-all; cursor: pointer;
|
||||
transition: var(--transition); margin: 12px 0;
|
||||
}
|
||||
.pw-display:hover { border-color: var(--accent); }
|
||||
/* ─── Strength Meter ──────────────────────────────────── */
|
||||
.strength-bar { height: 4px; border-radius: 2px; background: var(--border); margin-top: 8px; overflow: hidden; }
|
||||
.strength-bar-fill { height: 100%; border-radius: 2px; transition: all 0.3s ease; }
|
||||
/* ─── Markdown Preview ────────────────────────────────── */
|
||||
.md-preview {
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); padding: 20px; min-height: 180px;
|
||||
line-height: 1.7; font-size: 0.9rem; overflow-y: auto; max-height: 600px;
|
||||
}
|
||||
.md-preview h1, .md-preview h2, .md-preview h3 { margin: 16px 0 8px; color: var(--text-primary); }
|
||||
.md-preview h1 { font-size: 1.5rem; border-bottom: 1px solid var(--border); padding-bottom: 8px; }
|
||||
.md-preview h2 { font-size: 1.25rem; }
|
||||
.md-preview h3 { font-size: 1.1rem; }
|
||||
.md-preview p { margin-bottom: 10px; color: var(--text-secondary); }
|
||||
.md-preview code {
|
||||
background: #1e1e2e; padding: 2px 6px; border-radius: 4px;
|
||||
font-family: var(--font-mono); font-size: 0.82rem; color: var(--cyan);
|
||||
}
|
||||
.md-preview pre { background: #1e1e2e; padding: 14px; border-radius: var(--radius-sm); margin: 10px 0; overflow-x: auto; }
|
||||
.md-preview pre code { background: none; padding: 0; }
|
||||
.md-preview ul, .md-preview ol { padding-left: 24px; margin-bottom: 10px; color: var(--text-secondary); }
|
||||
.md-preview blockquote { border-left: 3px solid var(--accent); padding-left: 14px; margin: 10px 0; color: var(--text-muted); font-style: italic; }
|
||||
.md-preview a { color: var(--accent); }
|
||||
.md-preview strong { color: var(--text-primary); }
|
||||
.md-preview hr { border: none; border-top: 1px solid var(--border); margin: 16px 0; }
|
||||
/* ─── Diff View ───────────────────────────────────────── */
|
||||
.diff-output { font-family: var(--font-mono); font-size: 0.82rem; margin-top: 16px; }
|
||||
.diff-line { padding: 4px 12px; border-radius: 2px; margin: 1px 0; }
|
||||
.diff-add { background: #22c55e10; color: var(--green); }
|
||||
.diff-del { background: #ef444410; color: var(--red); }
|
||||
.diff-same { color: var(--text-muted); }
|
||||
/* ─── Regex Matches ───────────────────────────────────── */
|
||||
.regex-match { background: var(--accent-glow); border: 1px solid var(--accent); border-radius: 3px; padding: 0 2px; }
|
||||
/* ─── IP Info ─────────────────────────────────────────── */
|
||||
.ip-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 10px; }
|
||||
.ip-card {
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); padding: 14px;
|
||||
}
|
||||
.ip-card .label { font-size: 0.7rem; color: var(--text-muted); text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 4px; }
|
||||
.ip-card .value { font-family: var(--font-mono); font-size: 0.9rem; color: var(--cyan); }
|
||||
/* ─── QR Code ─────────────────────────────────────────── */
|
||||
#qrCanvas { display: inline-block; }
|
||||
/* ─── Footer ──────────────────────────────────────────── */
|
||||
footer {
|
||||
text-align: center; padding: 28px 24px; color: var(--text-muted);
|
||||
font-size: 0.78rem; border-top: 1px solid var(--border);
|
||||
}
|
||||
footer a { color: var(--accent); text-decoration: none; }
|
||||
/* ─── Scrollbar ───────────────────────────────────────── */
|
||||
::-webkit-scrollbar { width: 8px; }
|
||||
::-webkit-scrollbar-track { background: var(--bg-primary); }
|
||||
::-webkit-scrollbar-thumb { background: var(--border); border-radius: 4px; }
|
||||
::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
|
||||
::selection { background: var(--accent); color: white; }
|
||||
/* ─── Toast ───────────────────────────────────────────── */
|
||||
.copy-toast {
|
||||
position: fixed; bottom: 30px; left: 50%; transform: translateX(-50%);
|
||||
background: var(--accent); color: white; padding: 10px 24px;
|
||||
border-radius: 30px; font-size: 0.85rem; font-weight: 600;
|
||||
opacity: 0; pointer-events: none; transition: opacity 0.3s ease; z-index: 999;
|
||||
}
|
||||
.copy-toast.show { opacity: 1; }
|
||||
/* ─── Responsive ──────────────────────────────────────── */
|
||||
@media (max-width: 768px) {
|
||||
.split-panel { grid-template-columns: 1fr; }
|
||||
.search-bar { display: none; }
|
||||
.tools-grid { grid-template-columns: 1fr; }
|
||||
.ip-grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
/* ─── Slider ──────────────────────────────────────────── */
|
||||
input[type="range"] {
|
||||
-webkit-appearance: none; width: 100%; height: 6px;
|
||||
background: var(--border); border-radius: 3px; outline: none;
|
||||
}
|
||||
input[type="range"]::-webkit-slider-thumb {
|
||||
-webkit-appearance: none; width: 18px; height: 18px;
|
||||
border-radius: 50%; background: var(--accent); cursor: pointer;
|
||||
box-shadow: 0 0 8px var(--accent-glow);
|
||||
}
|
||||
.hidden { display: none !important; }
|
||||
/* ─── API Usage Section ───────────────────────────────── */
|
||||
.api-usage {
|
||||
margin-top: 24px;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius);
|
||||
overflow: hidden;
|
||||
}
|
||||
.api-usage-toggle {
|
||||
width: 100%;
|
||||
display: flex; align-items: center; justify-content: space-between;
|
||||
padding: 14px 18px;
|
||||
background: var(--bg-card);
|
||||
border: none; color: var(--text-secondary);
|
||||
cursor: pointer; font-family: var(--font-sans);
|
||||
font-size: 0.82rem; font-weight: 600;
|
||||
transition: var(--transition);
|
||||
}
|
||||
.api-usage-toggle:hover { color: var(--text-primary); background: var(--bg-input); }
|
||||
.api-usage-toggle i { transition: transform 0.25s ease; }
|
||||
.api-usage-toggle.open i { transform: rotate(180deg); }
|
||||
.api-usage-toggle .badge {
|
||||
background: var(--accent-glow); color: var(--accent);
|
||||
padding: 2px 8px; border-radius: 10px; font-size: 0.68rem;
|
||||
font-weight: 700; text-transform: uppercase; letter-spacing: 0.5px;
|
||||
margin-left: 8px;
|
||||
}
|
||||
.api-usage-body {
|
||||
display: none; padding: 18px;
|
||||
background: var(--bg-primary);
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.api-usage-body.open { display: block; animation: fadeIn 0.25s ease; }
|
||||
.api-usage-body .api-endpoint {
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
.api-usage-body .api-endpoint:last-child { margin-bottom: 0; }
|
||||
.api-method {
|
||||
display: inline-block;
|
||||
padding: 2px 8px; border-radius: 4px;
|
||||
font-size: 0.7rem; font-weight: 700;
|
||||
font-family: var(--font-mono);
|
||||
margin-right: 6px;
|
||||
}
|
||||
.api-method.post { background: #22c55e20; color: var(--green); }
|
||||
.api-method.get { background: #06b6d420; color: var(--cyan); }
|
||||
.api-path {
|
||||
font-family: var(--font-mono); font-size: 0.82rem;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
.api-desc {
|
||||
font-size: 0.78rem; color: var(--text-muted);
|
||||
margin: 4px 0 8px; line-height: 1.4;
|
||||
}
|
||||
.api-code {
|
||||
background: #0d0d14;
|
||||
border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm);
|
||||
padding: 14px 16px;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.76rem;
|
||||
color: var(--text-secondary);
|
||||
line-height: 1.65;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
position: relative;
|
||||
}
|
||||
.api-code .kw { color: var(--pink); }
|
||||
.api-code .str { color: var(--green); }
|
||||
.api-code .cm { color: var(--text-muted); font-style: italic; }
|
||||
.api-code .fn { color: var(--cyan); }
|
||||
.api-code .var { color: var(--orange); }
|
||||
.api-code-copy {
|
||||
position: absolute; top: 8px; right: 8px;
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
color: var(--text-muted); border-radius: 4px;
|
||||
padding: 3px 8px; font-size: 0.68rem; cursor: pointer;
|
||||
transition: var(--transition); font-family: var(--font-sans);
|
||||
}
|
||||
.api-code-copy:hover { color: var(--accent); border-color: var(--accent); }
|
||||
.api-baseurl-note {
|
||||
background: var(--bg-input); border: 1px solid var(--border);
|
||||
border-radius: var(--radius-sm); padding: 10px 14px;
|
||||
font-size: 0.76rem; color: var(--text-muted);
|
||||
margin-bottom: 14px; line-height: 1.5;
|
||||
}
|
||||
.api-baseurl-note code {
|
||||
background: #0d0d14; padding: 1px 6px; border-radius: 3px;
|
||||
font-family: var(--font-mono); color: var(--accent); font-size: 0.74rem;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,429 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>⚡ WinnieAPI-v2 — Developer Utilities</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>⚡</text></svg>">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" />
|
||||
<link rel="stylesheet" href="css/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="bg-glow"></div>
|
||||
<div class="particles" id="particles"></div>
|
||||
<div class="copy-toast" id="copyToast">Copied to clipboard! ✓</div>
|
||||
|
||||
<!-- Header -->
|
||||
<header>
|
||||
<div class="header-inner">
|
||||
<div class="logo" onclick="showPage('home')">⚡ WinnieAPI-v2</div>
|
||||
<div class="search-bar">
|
||||
<i class="fas fa-search"></i>
|
||||
<input type="text" id="searchInput" placeholder="Search tools... (Ctrl+K)" oninput="filterTools(this.value)" />
|
||||
</div>
|
||||
<div class="header-actions">
|
||||
<button onclick="showPage('home')" title="Home"><i class="fas fa-home"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<!-- ═══════════════ HOME ═══════════════ -->
|
||||
<div class="page active" id="page-home">
|
||||
<div class="hero">
|
||||
<h1>Your <span class="gradient">Developer Toolbox</span></h1>
|
||||
<p>Fast, free, privacy-friendly utilities — right in your browser. No sign-up, no tracking, no nonsense.</p>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="stat-num" id="toolCount">0</div><div class="stat-label">Tools</div></div>
|
||||
<div class="stat"><div class="stat-num">0</div><div class="stat-label">Ads</div></div>
|
||||
<div class="stat"><div class="stat-num">∞</div><div class="stat-label">Usage</div></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="categories">
|
||||
<button class="cat-btn active" onclick="filterCategory('all', this)">All Tools</button>
|
||||
<button class="cat-btn" onclick="filterCategory('format', this)">Formatters</button>
|
||||
<button class="cat-btn" onclick="filterCategory('encode', this)">Encode / Decode</button>
|
||||
<button class="cat-btn" onclick="filterCategory('generate', this)">Generators</button>
|
||||
<button class="cat-btn" onclick="filterCategory('text', this)">Text</button>
|
||||
<button class="cat-btn" onclick="filterCategory('web', this)">Web</button>
|
||||
<button class="cat-btn" onclick="filterCategory('dev', this)">Dev Tools</button>
|
||||
</div>
|
||||
|
||||
<div class="tools-grid" id="toolsGrid">
|
||||
<!-- JSON -->
|
||||
<div class="tool-card" data-cat="format" data-name="JSON Formatter" onclick="showPage('json')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-code"></i></div>
|
||||
<h3>JSON Formatter</h3>
|
||||
<span class="tag tag-purple">Format</span>
|
||||
</div>
|
||||
<p>Beautify, minify, and validate JSON with configurable indentation.</p>
|
||||
</div>
|
||||
<!-- URL -->
|
||||
<div class="tool-card" data-cat="web" data-name="URL Shortener" onclick="showPage('url')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-link"></i></div>
|
||||
<h3>URL Shortener</h3>
|
||||
<span class="tag tag-green">Web</span>
|
||||
</div>
|
||||
<p>Create short, shareable links instantly with click tracking.</p>
|
||||
</div>
|
||||
<!-- YouTube -->
|
||||
<div class="tool-card" data-cat="web" data-name="YouTube Tool" onclick="showPage('youtube')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-red"><i class="fab fa-youtube"></i></div>
|
||||
<h3>YouTube Tool</h3>
|
||||
<span class="tag tag-red">Video</span>
|
||||
</div>
|
||||
<p>Extract thumbnails, metadata, and embed codes from YouTube videos.</p>
|
||||
</div>
|
||||
<!-- Base64 -->
|
||||
<div class="tool-card" data-cat="encode" data-name="Base64 Encoder Decoder" onclick="showPage('base64')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-exchange-alt"></i></div>
|
||||
<h3>Base64 Encoder</h3>
|
||||
<span class="tag tag-cyan">Encode</span>
|
||||
</div>
|
||||
<p>Encode and decode Base64 strings instantly.</p>
|
||||
</div>
|
||||
<!-- Hash -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Hash Generator MD5 SHA" onclick="showPage('hash')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-fingerprint"></i></div>
|
||||
<h3>Hash Generator</h3>
|
||||
<span class="tag tag-orange">Crypto</span>
|
||||
</div>
|
||||
<p>Generate MD5, SHA-1, SHA-256, and SHA-512 hashes from text.</p>
|
||||
</div>
|
||||
<!-- Color -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Color Converter HEX RGB HSL" onclick="showPage('color')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-palette"></i></div>
|
||||
<h3>Color Converter</h3>
|
||||
<span class="tag tag-pink">Design</span>
|
||||
</div>
|
||||
<p>Convert colors between HEX, RGB, and HSL with live preview.</p>
|
||||
</div>
|
||||
<!-- Password -->
|
||||
<div class="tool-card" data-cat="generate" data-name="Password Generator" onclick="showPage('password')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-red"><i class="fas fa-key"></i></div>
|
||||
<h3>Password Generator</h3>
|
||||
<span class="tag tag-red">Security</span>
|
||||
</div>
|
||||
<p>Generate strong, cryptographically secure passwords with custom rules.</p>
|
||||
</div>
|
||||
<!-- UUID -->
|
||||
<div class="tool-card" data-cat="generate" data-name="UUID Generator" onclick="showPage('uuid')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-hashtag"></i></div>
|
||||
<h3>UUID Generator</h3>
|
||||
<span class="tag tag-purple">Generate</span>
|
||||
</div>
|
||||
<p>Generate random v4 UUIDs. Batch generate up to 100 at once.</p>
|
||||
</div>
|
||||
<!-- Lorem -->
|
||||
<div class="tool-card" data-cat="generate" data-name="Lorem Ipsum Generator" onclick="showPage('lorem')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-yellow"><i class="fas fa-paragraph"></i></div>
|
||||
<h3>Lorem Ipsum</h3>
|
||||
<span class="tag tag-yellow">Text</span>
|
||||
</div>
|
||||
<p>Generate placeholder text with configurable paragraph count.</p>
|
||||
</div>
|
||||
<!-- JWT -->
|
||||
<div class="tool-card" data-cat="dev" data-name="JWT Decoder Token" onclick="showPage('jwt')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-shield-alt"></i></div>
|
||||
<h3>JWT Decoder</h3>
|
||||
<span class="tag tag-orange">Auth</span>
|
||||
</div>
|
||||
<p>Decode JWT tokens and inspect header, payload, and expiry status.</p>
|
||||
</div>
|
||||
<!-- Timestamp -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Unix Timestamp Converter Date" onclick="showPage('timestamp')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-clock"></i></div>
|
||||
<h3>Timestamp Converter</h3>
|
||||
<span class="tag tag-cyan">Time</span>
|
||||
</div>
|
||||
<p>Convert between Unix timestamps, ISO dates, and human-readable formats.</p>
|
||||
</div>
|
||||
<!-- Regex -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Regex Tester Regular Expression" onclick="showPage('regex')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-asterisk"></i></div>
|
||||
<h3>Regex Tester</h3>
|
||||
<span class="tag tag-pink">Dev</span>
|
||||
</div>
|
||||
<p>Test regular expressions with live matching and capture groups.</p>
|
||||
</div>
|
||||
<!-- Markdown -->
|
||||
<div class="tool-card" data-cat="format" data-name="Markdown Preview Renderer" onclick="showPage('markdown')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fab fa-markdown"></i></div>
|
||||
<h3>Markdown Preview</h3>
|
||||
<span class="tag tag-purple">Format</span>
|
||||
</div>
|
||||
<p>Live preview Markdown as you type with full formatting support.</p>
|
||||
</div>
|
||||
<!-- Diff -->
|
||||
<div class="tool-card" data-cat="text" data-name="Text Diff Checker Compare" onclick="showPage('diff')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-columns"></i></div>
|
||||
<h3>Diff Checker</h3>
|
||||
<span class="tag tag-green">Compare</span>
|
||||
</div>
|
||||
<p>Compare two texts side-by-side and find differences line by line.</p>
|
||||
</div>
|
||||
<!-- Word Counter -->
|
||||
<div class="tool-card" data-cat="text" data-name="Word Character Counter Statistics" onclick="showPage('counter')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-calculator"></i></div>
|
||||
<h3>Word Counter</h3>
|
||||
<span class="tag tag-cyan">Text</span>
|
||||
</div>
|
||||
<p>Count words, characters, sentences, and get reading time estimates.</p>
|
||||
</div>
|
||||
<!-- CSS Minifier -->
|
||||
<div class="tool-card" data-cat="format" data-name="CSS Minifier Compressor" onclick="showPage('cssmin')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fab fa-css3-alt"></i></div>
|
||||
<h3>CSS Minifier</h3>
|
||||
<span class="tag tag-orange">Format</span>
|
||||
</div>
|
||||
<p>Minify CSS by removing comments, whitespace, and unnecessary characters.</p>
|
||||
</div>
|
||||
<!-- HTML Entity -->
|
||||
<div class="tool-card" data-cat="encode" data-name="HTML Entity Encoder Decoder" onclick="showPage('htmlent')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fab fa-html5"></i></div>
|
||||
<h3>HTML Entities</h3>
|
||||
<span class="tag tag-green">Encode</span>
|
||||
</div>
|
||||
<p>Encode and decode HTML entities for safe embedding.</p>
|
||||
</div>
|
||||
<!-- Case Converter -->
|
||||
<div class="tool-card" data-cat="text" data-name="Case Converter camelCase snake_case" onclick="showPage('caseconv')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-font"></i></div>
|
||||
<h3>Case Converter</h3>
|
||||
<span class="tag tag-pink">Text</span>
|
||||
</div>
|
||||
<p>Convert text between camelCase, snake_case, UPPER, Title Case, and more.</p>
|
||||
</div>
|
||||
<!-- Number Base -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Number Base Converter Binary Hex" onclick="showPage('numbase')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-yellow"><i class="fas fa-sort-numeric-up"></i></div>
|
||||
<h3>Number Base</h3>
|
||||
<span class="tag tag-yellow">Math</span>
|
||||
</div>
|
||||
<p>Convert numbers between decimal, binary, octal, and hexadecimal.</p>
|
||||
</div>
|
||||
<!-- IP Lookup -->
|
||||
<div class="tool-card" data-cat="web" data-name="IP Address Lookup Geolocation" onclick="showPage('ip')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-globe"></i></div>
|
||||
<h3>IP Lookup</h3>
|
||||
<span class="tag tag-cyan">Network</span>
|
||||
</div>
|
||||
<p>Get geolocation, ISP, and timezone info for any IP address.</p>
|
||||
</div>
|
||||
<!-- QR Code -->
|
||||
<div class="tool-card" data-cat="generate" data-name="QR Code Generator" onclick="showPage('qrcode')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-qrcode"></i></div>
|
||||
<h3>QR Code Generator</h3>
|
||||
<span class="tag tag-purple">Generate</span>
|
||||
</div>
|
||||
<p>Generate QR codes from text, URLs, or any data. Download as PNG.</p>
|
||||
</div>
|
||||
<!-- QR Code Reader -->
|
||||
<div class="tool-card" data-cat="dev" data-name="QR Code Reader Scanner Camera" onclick="showPage('qrreader')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-camera"></i></div>
|
||||
<h3>QR Code Reader</h3>
|
||||
<span class="tag tag-cyan">Scanner</span>
|
||||
</div>
|
||||
<p>Scan QR codes from your camera or an uploaded image file.</p>
|
||||
</div>
|
||||
<!-- String Escape -->
|
||||
<div class="tool-card" data-cat="encode" data-name="String Escape Unescape" onclick="showPage('escape')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-quote-right"></i></div>
|
||||
<h3>String Escape</h3>
|
||||
<span class="tag tag-orange">Encode</span>
|
||||
</div>
|
||||
<p>Escape and unescape strings (backslash sequences) for programming.</p>
|
||||
</div>
|
||||
<!-- Cron Parser -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Cron Expression Parser Scheduler" onclick="showPage('cron')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-stopwatch"></i></div>
|
||||
<h3>Cron Parser</h3>
|
||||
<span class="tag tag-orange">Scheduler</span>
|
||||
</div>
|
||||
<p>Parse cron expressions into human-readable descriptions with next run times.</p>
|
||||
</div>
|
||||
<!-- Placeholder Image -->
|
||||
<div class="tool-card" data-cat="generate" data-name="Placeholder Image Generator" onclick="showPage('placeholder')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-image"></i></div>
|
||||
<h3>Placeholder Image</h3>
|
||||
<span class="tag tag-pink">Design</span>
|
||||
</div>
|
||||
<p>Generate custom placeholder images with text, colors, and dimensions.</p>
|
||||
</div>
|
||||
<!-- JSON CSV Converter -->
|
||||
<div class="tool-card" data-cat="format" data-name="JSON CSV Converter Table Data" onclick="showPage('jsoncsv')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-table"></i></div>
|
||||
<h3>JSON ↔ CSV</h3>
|
||||
<span class="tag tag-green">Convert</span>
|
||||
</div>
|
||||
<p>Convert between JSON arrays and CSV format with download support.</p>
|
||||
</div>
|
||||
<!-- Text Encoder -->
|
||||
<div class="tool-card" data-cat="encode" data-name="Text Encoder ROT13 Binary Morse L33t" onclick="showPage('textenc')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-language"></i></div>
|
||||
<h3>Text Encoder</h3>
|
||||
<span class="tag tag-cyan">Encode</span>
|
||||
</div>
|
||||
<p>Encode text in ROT13, Binary, Morse Code, L33t speak, and more.</p>
|
||||
</div>
|
||||
<!-- HTTP Status Codes -->
|
||||
<div class="tool-card" data-cat="web" data-name="HTTP Status Code Reference API" onclick="showPage('httpstatus')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-server"></i></div>
|
||||
<h3>HTTP Status Codes</h3>
|
||||
<span class="tag tag-green">Reference</span>
|
||||
</div>
|
||||
<p>Quick reference for all HTTP status codes with descriptions and categories.</p>
|
||||
</div>
|
||||
<!-- SQL Formatter -->
|
||||
<div class="tool-card" data-cat="format" data-name="SQL Formatter Beautifier Query" onclick="showPage('sqlformat')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-purple"><i class="fas fa-database"></i></div>
|
||||
<h3>SQL Formatter</h3>
|
||||
<span class="tag tag-purple">Format</span>
|
||||
</div>
|
||||
<p>Beautify and minify SQL queries with keyword formatting.</p>
|
||||
</div>
|
||||
<!-- Byte Size Converter -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Byte Size Converter KB MB GB TB" onclick="showPage('byteconv')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-yellow"><i class="fas fa-weight-hanging"></i></div>
|
||||
<h3>Byte Converter</h3>
|
||||
<span class="tag tag-yellow">Math</span>
|
||||
</div>
|
||||
<p>Convert between bytes, KB, MB, GB, TB with binary and SI modes.</p>
|
||||
</div>
|
||||
<!-- HMAC Generator -->
|
||||
<div class="tool-card" data-cat="dev" data-name="HMAC Generator Signature SHA" onclick="showPage('hmac')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-red"><i class="fas fa-lock"></i></div>
|
||||
<h3>HMAC Generator</h3>
|
||||
<span class="tag tag-red">Security</span>
|
||||
</div>
|
||||
<p>Generate HMAC signatures from a message and secret key.</p>
|
||||
</div>
|
||||
<!-- Slug Generator -->
|
||||
<div class="tool-card" data-cat="text" data-name="Slug Generator URL Friendly" onclick="showPage('slugify')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-green"><i class="fas fa-link"></i></div>
|
||||
<h3>Slug Generator</h3>
|
||||
<span class="tag tag-green">Text</span>
|
||||
</div>
|
||||
<p>Convert any text into a clean, URL-friendly slug.</p>
|
||||
</div>
|
||||
<!-- Chmod Calculator -->
|
||||
<div class="tool-card" data-cat="dev" data-name="Chmod Calculator Unix Permissions" onclick="showPage('chmod')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-orange"><i class="fas fa-user-shield"></i></div>
|
||||
<h3>Chmod Calculator</h3>
|
||||
<span class="tag tag-orange">Linux</span>
|
||||
</div>
|
||||
<p>Convert between numeric (755) and symbolic (rwxr-xr-x) Unix permissions.</p>
|
||||
</div>
|
||||
<!-- ASCII Art Generator -->
|
||||
<div class="tool-card" data-cat="generate" data-name="ASCII Art Generator Text Banner" onclick="showPage('ascii')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-pink"><i class="fas fa-text-height"></i></div>
|
||||
<h3>ASCII Art</h3>
|
||||
<span class="tag tag-pink">Generate</span>
|
||||
</div>
|
||||
<p>Convert text into ASCII art banners using block characters.</p>
|
||||
</div>
|
||||
<!-- ENV ↔ JSON -->
|
||||
<div class="tool-card" data-cat="format" data-name="ENV JSON Converter Dotenv Config" onclick="showPage('envjson')">
|
||||
<div class="tc-top">
|
||||
<div class="icon ic-cyan"><i class="fas fa-file-code"></i></div>
|
||||
<h3>ENV ↔ JSON</h3>
|
||||
<span class="tag tag-cyan">Convert</span>
|
||||
</div>
|
||||
<p>Convert between .env files and JSON objects instantly.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Tool pages are loaded dynamically here -->
|
||||
<div id="tool-pages"></div>
|
||||
</main>
|
||||
|
||||
<footer>
|
||||
Built with ❤️ by <a href="https://winniepat.de" target="_blank">WinniePatGG</a> · <span id="footerToolCount">0</span> tools and counting · 100% free & open source · No data collected
|
||||
</footer>
|
||||
|
||||
<!-- QR Code Library -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js"></script>
|
||||
<!-- QR Code Reader Library -->
|
||||
<script src="https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js"></script>
|
||||
|
||||
<!-- Core app JS -->
|
||||
<script src="js/app.js"></script>
|
||||
|
||||
<!-- Load tool HTML partials and JS dynamically -->
|
||||
<script>
|
||||
(async function loadTools() {
|
||||
const tools = [
|
||||
'json', 'url', 'youtube', 'base64', 'hash', 'color',
|
||||
'password', 'uuid', 'lorem', 'jwt', 'timestamp', 'regex',
|
||||
'markdown', 'diff', 'counter', 'cssmin', 'htmlent',
|
||||
'caseconv', 'numbase', 'ip', 'qrcode', 'escape',
|
||||
'cron', 'placeholder', 'jsoncsv', 'textenc', 'httpstatus',
|
||||
'qrreader', 'sqlformat', 'byteconv', 'hmac', 'slugify',
|
||||
'chmod', 'ascii', 'envjson'
|
||||
];
|
||||
const container = document.getElementById('tool-pages');
|
||||
|
||||
// Update tool counts dynamically
|
||||
const countEl = document.getElementById('toolCount');
|
||||
const footerEl = document.getElementById('footerToolCount');
|
||||
if (countEl) countEl.textContent = tools.length;
|
||||
if (footerEl) footerEl.textContent = tools.length;
|
||||
|
||||
// Load all tool HTML partials in parallel
|
||||
const htmlPromises = tools.map(t =>
|
||||
fetch(`tools/${t}.html`).then(r => r.text())
|
||||
);
|
||||
const htmlParts = await Promise.all(htmlPromises);
|
||||
container.innerHTML = htmlParts.join('\n');
|
||||
|
||||
// Load all tool JS files sequentially to ensure DOM is ready
|
||||
for (const t of tools) {
|
||||
await new Promise((resolve, reject) => {
|
||||
const script = document.createElement('script');
|
||||
script.src = `js/tools/${t}.js`;
|
||||
script.onload = resolve;
|
||||
script.onerror = reject;
|
||||
document.body.appendChild(script);
|
||||
});
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -0,0 +1,88 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Particles
|
||||
// ═══════════════════════════════════════════════════════
|
||||
(function initParticles() {
|
||||
const c = document.getElementById('particles');
|
||||
for (let i = 0; i < 30; i++) {
|
||||
const p = document.createElement('div');
|
||||
p.className = 'particle';
|
||||
p.style.left = Math.random() * 100 + '%';
|
||||
p.style.animationDelay = Math.random() * 15 + 's';
|
||||
p.style.animationDuration = (10 + Math.random() * 20) + 's';
|
||||
p.style.width = p.style.height = (1 + Math.random() * 3) + 'px';
|
||||
c.appendChild(p);
|
||||
}
|
||||
})();
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Navigation
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function showPage(name) {
|
||||
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
||||
document.getElementById('page-' + name).classList.add('active');
|
||||
window.scrollTo({ top: 0, behavior: 'smooth' });
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Search & Filter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function filterTools(query) {
|
||||
const q = query.toLowerCase();
|
||||
document.querySelectorAll('.tool-card').forEach(card => {
|
||||
const name = card.dataset.name.toLowerCase();
|
||||
card.style.display = name.includes(q) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
function filterCategory(cat, btn) {
|
||||
document.querySelectorAll('.cat-btn').forEach(b => b.classList.remove('active'));
|
||||
if (btn) btn.classList.add('active');
|
||||
document.querySelectorAll('.tool-card').forEach(card => {
|
||||
card.style.display = (cat === 'all' || card.dataset.cat === cat) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
// Ctrl+K to focus search
|
||||
document.addEventListener('keydown', e => {
|
||||
if ((e.ctrlKey || e.metaKey) && e.key === 'k') {
|
||||
e.preventDefault();
|
||||
const el = document.getElementById('searchInput');
|
||||
if (el) el.focus();
|
||||
}
|
||||
});
|
||||
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Helpers
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// ✦ Change this for production deployment:
|
||||
const BASE_URL = window.location.origin; // e.g. "https://winnieapi-v2.yourdomain.com"
|
||||
|
||||
function copyText(text) {
|
||||
navigator.clipboard.writeText(text).then(() => {
|
||||
const t = document.getElementById('copyToast');
|
||||
t.classList.add('show');
|
||||
setTimeout(() => t.classList.remove('show'), 1500);
|
||||
});
|
||||
}
|
||||
function copyOutput(id) { copyText(document.getElementById(id).value); }
|
||||
function setStatus(id, type, msg) {
|
||||
const el = document.getElementById(id);
|
||||
el.className = 'status ' + type;
|
||||
el.textContent = msg;
|
||||
}
|
||||
async function apiPost(url, body) {
|
||||
const r = await fetch(BASE_URL + url, { method:'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify(body) });
|
||||
return r.json();
|
||||
}
|
||||
async function apiGet(url) {
|
||||
const r = await fetch(BASE_URL + url);
|
||||
return r.json();
|
||||
}
|
||||
function toggleApiUsage(btn) {
|
||||
btn.classList.toggle('open');
|
||||
const body = btn.nextElementSibling;
|
||||
body.classList.toggle('open');
|
||||
}
|
||||
function copyApiCode(btn) {
|
||||
const code = btn.parentElement.textContent.replace('Copy', '').trim();
|
||||
copyText(code);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// ASCII Art Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateAscii() {
|
||||
const text = document.getElementById('asciiInput').value.trim();
|
||||
if (!text) { document.getElementById('asciiOutput').value = ''; return; }
|
||||
const d = await apiPost('/api/ascii/generate', { text });
|
||||
if (d.success) {
|
||||
document.getElementById('asciiOutput').value = d.result;
|
||||
setStatus('asciiStatus', 'success', 'Generated ✓');
|
||||
} else setStatus('asciiStatus', 'error', d.error);
|
||||
}
|
||||
setTimeout(generateAscii, 200);
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Base64
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function base64Op(op) {
|
||||
const text = document.getElementById('b64Input').value;
|
||||
if (!text) return setStatus('b64Status','error','Enter some text.');
|
||||
const d = await apiPost('/api/base64/' + op, { text });
|
||||
if (d.success) { document.getElementById('b64Output').value = d.result; setStatus('b64Status','success', op === 'encode' ? 'Encoded ✓' : 'Decoded ✓'); }
|
||||
else setStatus('b64Status','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Byte Size Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertBytes() {
|
||||
const value = parseFloat(document.getElementById('byteValue').value);
|
||||
if (isNaN(value)) return;
|
||||
const unit = document.getElementById('byteUnit').value;
|
||||
const mode = document.querySelector('input[name="byteMode"]:checked').value;
|
||||
const d = await apiPost('/api/bytes/convert', { value, unit, mode });
|
||||
if (d.success) {
|
||||
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB'];
|
||||
document.getElementById('byteResults').innerHTML = units.map(u =>
|
||||
`<div class="result-row">
|
||||
<div class="label">${u}</div>
|
||||
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${d[u]}</div>
|
||||
</div>`
|
||||
).join('');
|
||||
setStatus('byteStatus', 'success', `Converted (${mode === 'binary' ? '1024' : '1000'} base) ✓`);
|
||||
} else setStatus('byteStatus', 'error', d.error);
|
||||
}
|
||||
setTimeout(convertBytes, 200);
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Case Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertCase() {
|
||||
const text = document.getElementById('caseInput').value;
|
||||
if (!text) return;
|
||||
const d = await apiPost('/api/text/case', { text });
|
||||
if (d.success) {
|
||||
document.getElementById('caseResults').innerHTML = [
|
||||
['UPPERCASE', d.uppercase], ['lowercase', d.lowercase], ['Title Case', d.titleCase],
|
||||
['camelCase', d.camelCase], ['snake_case', d.snakeCase], ['kebab-case', d.kebabCase],
|
||||
['dot.case', d.dotCase], ['desreveR', d.reversed]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Chmod Calculator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function chmodFromNumeric() {
|
||||
const val = document.getElementById('chmodNumeric').value.trim();
|
||||
if (!val || !/^[0-7]{3,4}$/.test(val)) return;
|
||||
const d = await apiPost('/api/chmod/calculate', { numeric: val });
|
||||
if (d.success) updateChmodUI(d);
|
||||
}
|
||||
async function chmodFromSymbolic() {
|
||||
const val = document.getElementById('chmodSymbolic').value.trim();
|
||||
if (!val || val.length < 9) return;
|
||||
const d = await apiPost('/api/chmod/calculate', { symbolic: val });
|
||||
if (d.success) updateChmodUI(d);
|
||||
}
|
||||
function updateChmodUI(d) {
|
||||
document.getElementById('chmodNumeric').value = d.numeric;
|
||||
document.getElementById('chmodSymbolic').value = d.symbolic;
|
||||
document.getElementById('chmodCommand').textContent = `chmod ${d.numeric} filename`;
|
||||
const roles = ['owner', 'group', 'others'];
|
||||
const perms = ['read', 'write', 'execute'];
|
||||
let html = '<div style="display:grid;grid-template-columns:100px repeat(3,1fr);gap:6px;font-size:0.82rem;">';
|
||||
html += '<div></div>' + perms.map(p => `<div style="text-align:center;color:var(--text-muted);text-transform:uppercase;font-size:0.7rem;font-weight:600;">${p}</div>`).join('');
|
||||
for (const role of roles) {
|
||||
html += `<div style="color:var(--text-secondary);font-weight:600;text-transform:capitalize;">${role}</div>`;
|
||||
for (const perm of perms) {
|
||||
const on = d[role][perm];
|
||||
html += `<div style="text-align:center;padding:6px;background:${on ? 'var(--green)' : 'var(--bg-input)'};color:${on ? '#fff' : 'var(--text-muted)'};border-radius:var(--radius-sm);font-weight:600;border:1px solid var(--border);">${on ? '✓' : '—'}</div>`;
|
||||
}
|
||||
}
|
||||
html += '</div>';
|
||||
document.getElementById('chmodMatrix').innerHTML = html;
|
||||
setStatus('chmodStatus', 'success', `${d.numeric} = ${d.symbolic} ✓`);
|
||||
}
|
||||
setTimeout(chmodFromNumeric, 200);
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Color Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertColor() {
|
||||
const color = document.getElementById('colorInput').value.trim();
|
||||
if (!color) return setStatus('colorStatus','error','Enter a color.');
|
||||
const d = await apiPost('/api/color/convert', { color });
|
||||
if (d.success) {
|
||||
document.getElementById('colorPreview').style.background = d.hex;
|
||||
document.getElementById('colorPicker').value = d.hex;
|
||||
const c = document.getElementById('colorResults');
|
||||
c.innerHTML = [
|
||||
['HEX', d.hex], ['RGB', d.rgb], ['HSL', d.hsl]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
setStatus('colorStatus','success','Converted ✓');
|
||||
} else setStatus('colorStatus','error', d.error);
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('colorInput').addEventListener('keydown', e => { if(e.key==='Enter') convertColor(); });
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Word Counter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function updateCounter() {
|
||||
const text = document.getElementById('counterInput').value;
|
||||
const d = await apiPost('/api/text/stats', { text });
|
||||
if (d.success) {
|
||||
document.getElementById('counterResults').innerHTML = `
|
||||
<div class="ip-grid">
|
||||
<div class="ip-card"><div class="label">Characters</div><div class="value">${d.characters}</div></div>
|
||||
<div class="ip-card"><div class="label">No Spaces</div><div class="value">${d.charactersNoSpaces}</div></div>
|
||||
<div class="ip-card"><div class="label">Words</div><div class="value">${d.words}</div></div>
|
||||
<div class="ip-card"><div class="label">Sentences</div><div class="value">${d.sentences}</div></div>
|
||||
<div class="ip-card"><div class="label">Paragraphs</div><div class="value">${d.paragraphs}</div></div>
|
||||
<div class="ip-card"><div class="label">Lines</div><div class="value">${d.lines}</div></div>
|
||||
<div class="ip-card"><div class="label">Reading Time</div><div class="value">${d.readingTime}</div></div>
|
||||
</div>
|
||||
${d.topChars.length ? '<div class="panel-label" style="margin-top:14px;">Top Characters</div><div style="display:flex;gap:6px;flex-wrap:wrap;">' + d.topChars.map(([ch,n]) => `<span style="background:var(--bg-input);border:1px solid var(--border);padding:4px 10px;border-radius:var(--radius-sm);font-family:var(--font-mono);font-size:0.8rem;"><strong style="color:var(--accent);">${ch}</strong> <span style="color:var(--text-muted);">×${n}</span></span>`).join('') + '</div>' : ''}`;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Cron Parser
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function setCronPreset(expr) {
|
||||
document.getElementById('cronInput').value = expr;
|
||||
parseCron();
|
||||
}
|
||||
|
||||
async function parseCron() {
|
||||
const expr = document.getElementById('cronInput').value.trim();
|
||||
if (!expr) return setStatus('cronStatus', 'error', 'Enter a cron expression.');
|
||||
const d = await apiPost('/api/cron/parse', { expression: expr });
|
||||
if (d.success) {
|
||||
document.getElementById('cronResult').style.display = 'block';
|
||||
document.getElementById('cronDescription').textContent = d.description;
|
||||
document.getElementById('cronFields').innerHTML = Object.entries(d.fields).map(([k, v]) =>
|
||||
`<div class="result-row"><div class="label">${k}</div><div class="value">${v}</div></div>`
|
||||
).join('');
|
||||
document.getElementById('cronNextRuns').innerHTML = d.nextRuns.map((t, i) =>
|
||||
`<div class="result-row"><div class="label">Run #${i + 1}</div><div class="value" onclick="copyText(this.textContent)" title="Click to copy">${t}</div></div>`
|
||||
).join('');
|
||||
setStatus('cronStatus', 'success', 'Parsed ✓');
|
||||
} else {
|
||||
document.getElementById('cronResult').style.display = 'none';
|
||||
setStatus('cronStatus', 'error', d.error);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// CSS Minifier
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function minifyCSS() {
|
||||
const css = document.getElementById('cssInput').value;
|
||||
if (!css) return setStatus('cssStatus','error','Enter some CSS.');
|
||||
const d = await apiPost('/api/css/minify', { css });
|
||||
if (d.success) {
|
||||
document.getElementById('cssOutput').value = d.result;
|
||||
setStatus('cssStatus','success', `Minified ✓ — saved ${d.saved} chars (${d.percentage}% smaller)`);
|
||||
} else setStatus('cssStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Diff Checker
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function computeDiff() {
|
||||
const a = document.getElementById('diffA').value.split('\n');
|
||||
const b = document.getElementById('diffB').value.split('\n');
|
||||
const max = Math.max(a.length, b.length);
|
||||
let html = '';
|
||||
for (let i = 0; i < max; i++) {
|
||||
const la = a[i] !== undefined ? a[i] : null;
|
||||
const lb = b[i] !== undefined ? b[i] : null;
|
||||
const esc = s => s.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]));
|
||||
if (la === null) html += `<div class="diff-line diff-add">+ ${esc(lb)}</div>`;
|
||||
else if (lb === null) html += `<div class="diff-line diff-del">- ${esc(la)}</div>`;
|
||||
else if (la === lb) html += `<div class="diff-line diff-same"> ${esc(la)}</div>`;
|
||||
else {
|
||||
html += `<div class="diff-line diff-del">- ${esc(la)}</div>`;
|
||||
html += `<div class="diff-line diff-add">+ ${esc(lb)}</div>`;
|
||||
}
|
||||
}
|
||||
document.getElementById('diffOutput').innerHTML = html || '<span style="color:var(--text-muted);">Texts are identical.</span>';
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// ENV ↔ JSON Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function envToJson() {
|
||||
const input = document.getElementById('envInput').value.trim();
|
||||
if (!input) return setStatus('envJsonStatus', 'error', 'Paste some .env content first.');
|
||||
const d = await apiPost('/api/convert/env-to-json', { env: input });
|
||||
if (d.success) {
|
||||
document.getElementById('envJsonOutput').value = JSON.stringify(d.result, null, 2);
|
||||
setStatus('envJsonStatus', 'success', `Converted ✓ — ${Object.keys(d.result).length} variables`);
|
||||
} else setStatus('envJsonStatus', 'error', d.error);
|
||||
}
|
||||
async function jsonToEnv() {
|
||||
const input = document.getElementById('envJsonInput').value.trim();
|
||||
if (!input) return setStatus('envJsonStatus', 'error', 'Paste some JSON first.');
|
||||
const d = await apiPost('/api/convert/json-to-env', { json: input });
|
||||
if (d.success) {
|
||||
document.getElementById('envJsonOutput').value = d.result;
|
||||
setStatus('envJsonStatus', 'success', `Converted ✓ — ${d.count} variables`);
|
||||
} else setStatus('envJsonStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// String Escape
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function escapeOp(op) {
|
||||
const text = document.getElementById('escInput').value;
|
||||
if (!text) return setStatus('escStatus','error','Enter text.');
|
||||
const d = await apiPost('/api/' + op, { text });
|
||||
if (d.success) { document.getElementById('escOutput').value = d.result; setStatus('escStatus','success', op === 'escape' ? 'Escaped ✓' : 'Unescaped ✓'); }
|
||||
else setStatus('escStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Hash
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateHash() {
|
||||
const text = document.getElementById('hashInput').value;
|
||||
if (!text) return setStatus('hashStatus','error','Enter text to hash.');
|
||||
const d = await apiPost('/api/hash', { text });
|
||||
if (d.success) {
|
||||
const c = document.getElementById('hashResults');
|
||||
c.innerHTML = Object.entries(d.hashes).map(([algo, hash]) => `
|
||||
<div class="result-row">
|
||||
<div class="label">${algo.toUpperCase()}</div>
|
||||
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${hash}</div>
|
||||
</div>`).join('');
|
||||
setStatus('hashStatus','success','Generated ✓');
|
||||
} else setStatus('hashStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// HMAC Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateHmac() {
|
||||
const message = document.getElementById('hmacMessage').value;
|
||||
const secret = document.getElementById('hmacSecret').value;
|
||||
const algorithm = document.getElementById('hmacAlgo').value;
|
||||
if (!message) return setStatus('hmacStatus', 'error', 'Enter a message.');
|
||||
if (!secret) return setStatus('hmacStatus', 'error', 'Enter a secret key.');
|
||||
const d = await apiPost('/api/hmac', { message, secret, algorithm });
|
||||
if (d.success) {
|
||||
document.getElementById('hmacResults').innerHTML = `
|
||||
<div class="result-row">
|
||||
<div class="label">${d.algorithm.toUpperCase()} HMAC</div>
|
||||
<div class="value" onclick="copyText(this.textContent)" title="Click to copy">${d.hmac}</div>
|
||||
</div>`;
|
||||
setStatus('hmacStatus', 'success', 'Generated ✓');
|
||||
} else setStatus('hmacStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// HTML Entities
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function htmlEntOp(op) {
|
||||
const text = document.getElementById('htmlEntInput').value;
|
||||
if (!text) return setStatus('htmlEntStatus','error','Enter text.');
|
||||
const d = await apiPost('/api/html/' + op, { text });
|
||||
if (d.success) { document.getElementById('htmlEntOutput').value = d.result; setStatus('htmlEntStatus','success', op === 'encode' ? 'Encoded ✓' : 'Decoded ✓'); }
|
||||
else setStatus('htmlEntStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// HTTP Status Codes Reference
|
||||
// ═══════════════════════════════════════════════════════
|
||||
const HTTP_CODES = [
|
||||
// 1xx
|
||||
{ code: 100, text: 'Continue', desc: 'The server has received the request headers, and the client should proceed to send the request body.', cat: '1xx' },
|
||||
{ code: 101, text: 'Switching Protocols', desc: 'The server is switching protocols as requested by the client (e.g., to WebSocket).', cat: '1xx' },
|
||||
{ code: 102, text: 'Processing', desc: 'The server has received and is processing the request, but no response is available yet.', cat: '1xx' },
|
||||
{ code: 103, text: 'Early Hints', desc: 'Used to return some response headers before the final HTTP message.', cat: '1xx' },
|
||||
// 2xx
|
||||
{ code: 200, text: 'OK', desc: 'The request has succeeded. The meaning depends on the HTTP method used.', cat: '2xx' },
|
||||
{ code: 201, text: 'Created', desc: 'The request has been fulfilled and a new resource has been created.', cat: '2xx' },
|
||||
{ code: 202, text: 'Accepted', desc: 'The request has been accepted for processing, but processing is not complete.', cat: '2xx' },
|
||||
{ code: 203, text: 'Non-Authoritative Information', desc: 'The returned meta-information is from a local or third-party copy.', cat: '2xx' },
|
||||
{ code: 204, text: 'No Content', desc: 'The server has fulfilled the request but does not need to return an entity-body.', cat: '2xx' },
|
||||
{ code: 205, text: 'Reset Content', desc: 'The server has fulfilled the request and the user agent should reset the document view.', cat: '2xx' },
|
||||
{ code: 206, text: 'Partial Content', desc: 'The server is delivering only part of the resource due to a range header sent by the client.', cat: '2xx' },
|
||||
{ code: 207, text: 'Multi-Status', desc: 'A Multi-Status response conveys information about multiple resources (WebDAV).', cat: '2xx' },
|
||||
{ code: 208, text: 'Already Reported', desc: 'Members of a DAV binding have already been enumerated in a previous reply.', cat: '2xx' },
|
||||
{ code: 226, text: 'IM Used', desc: 'The server has fulfilled a GET request for the resource with instance manipulations applied.', cat: '2xx' },
|
||||
// 3xx
|
||||
{ code: 300, text: 'Multiple Choices', desc: 'There are multiple options for the resource, each with specific attributes.', cat: '3xx' },
|
||||
{ code: 301, text: 'Moved Permanently', desc: 'This and all future requests should be directed to the given URI.', cat: '3xx' },
|
||||
{ code: 302, text: 'Found', desc: 'The resource was found at a different URI temporarily.', cat: '3xx' },
|
||||
{ code: 303, text: 'See Other', desc: 'The response can be found at another URI using a GET method.', cat: '3xx' },
|
||||
{ code: 304, text: 'Not Modified', desc: 'The resource has not been modified since the last request.', cat: '3xx' },
|
||||
{ code: 307, text: 'Temporary Redirect', desc: 'The request should be repeated with another URI, but future requests should still use the original URI.', cat: '3xx' },
|
||||
{ code: 308, text: 'Permanent Redirect', desc: 'This and all future requests should be directed to the given URI (no method change).', cat: '3xx' },
|
||||
// 4xx
|
||||
{ code: 400, text: 'Bad Request', desc: 'The server cannot process the request due to a client error (e.g., malformed request syntax).', cat: '4xx' },
|
||||
{ code: 401, text: 'Unauthorized', desc: 'Authentication is required and has failed or has not been provided.', cat: '4xx' },
|
||||
{ code: 402, text: 'Payment Required', desc: 'Reserved for future use. Some APIs use this for rate limiting or paid features.', cat: '4xx' },
|
||||
{ code: 403, text: 'Forbidden', desc: 'The server understood the request but refuses to authorize it.', cat: '4xx' },
|
||||
{ code: 404, text: 'Not Found', desc: 'The requested resource could not be found on this server.', cat: '4xx' },
|
||||
{ code: 405, text: 'Method Not Allowed', desc: 'The request method is not supported for the requested resource.', cat: '4xx' },
|
||||
{ code: 406, text: 'Not Acceptable', desc: 'The requested resource can only generate content not acceptable per the Accept headers.', cat: '4xx' },
|
||||
{ code: 407, text: 'Proxy Authentication Required', desc: 'The client must authenticate itself with the proxy.', cat: '4xx' },
|
||||
{ code: 408, text: 'Request Timeout', desc: 'The server timed out waiting for the request.', cat: '4xx' },
|
||||
{ code: 409, text: 'Conflict', desc: 'The request could not be processed because of conflict in the current state of the resource.', cat: '4xx' },
|
||||
{ code: 410, text: 'Gone', desc: 'The resource requested is no longer available and will not be available again.', cat: '4xx' },
|
||||
{ code: 411, text: 'Length Required', desc: 'The request did not specify the length of its content, which is required.', cat: '4xx' },
|
||||
{ code: 412, text: 'Precondition Failed', desc: 'The server does not meet one of the preconditions set by the requester.', cat: '4xx' },
|
||||
{ code: 413, text: 'Payload Too Large', desc: 'The request is larger than the server is willing or able to process.', cat: '4xx' },
|
||||
{ code: 414, text: 'URI Too Long', desc: 'The URI provided was too long for the server to process.', cat: '4xx' },
|
||||
{ code: 415, text: 'Unsupported Media Type', desc: 'The request entity has a media type which the server does not support.', cat: '4xx' },
|
||||
{ code: 416, text: 'Range Not Satisfiable', desc: 'The client has asked for a portion of the file that the server cannot supply.', cat: '4xx' },
|
||||
{ code: 418, text: "I'm a Teapot", desc: "The server refuses to brew coffee because it is, permanently, a teapot. (RFC 2324)", cat: '4xx' },
|
||||
{ code: 422, text: 'Unprocessable Entity', desc: 'The request was well-formed but was unable to be followed due to semantic errors.', cat: '4xx' },
|
||||
{ code: 425, text: 'Too Early', desc: 'The server is unwilling to risk processing a request that might be replayed.', cat: '4xx' },
|
||||
{ code: 429, text: 'Too Many Requests', desc: 'The user has sent too many requests in a given amount of time (rate limiting).', cat: '4xx' },
|
||||
{ code: 451, text: 'Unavailable For Legal Reasons', desc: 'The resource is unavailable due to legal demands (e.g., censorship or government order).', cat: '4xx' },
|
||||
// 5xx
|
||||
{ code: 500, text: 'Internal Server Error', desc: 'An unexpected condition was encountered by the server.', cat: '5xx' },
|
||||
{ code: 501, text: 'Not Implemented', desc: 'The server does not support the functionality required to fulfill the request.', cat: '5xx' },
|
||||
{ code: 502, text: 'Bad Gateway', desc: 'The server received an invalid response from an upstream server.', cat: '5xx' },
|
||||
{ code: 503, text: 'Service Unavailable', desc: 'The server is currently unable to handle the request (overloaded or maintenance).', cat: '5xx' },
|
||||
{ code: 504, text: 'Gateway Timeout', desc: 'The server did not receive a timely response from an upstream server.', cat: '5xx' },
|
||||
{ code: 505, text: 'HTTP Version Not Supported', desc: 'The server does not support the HTTP protocol version used in the request.', cat: '5xx' },
|
||||
{ code: 507, text: 'Insufficient Storage', desc: 'The server is unable to store the representation needed to complete the request.', cat: '5xx' },
|
||||
{ code: 508, text: 'Loop Detected', desc: 'The server detected an infinite loop while processing the request.', cat: '5xx' },
|
||||
{ code: 511, text: 'Network Authentication Required', desc: 'The client needs to authenticate to gain network access (captive portal).', cat: '5xx' },
|
||||
];
|
||||
|
||||
const catColors = { '1xx': 'var(--accent)', '2xx': 'var(--green)', '3xx': 'var(--cyan)', '4xx': 'var(--orange)', '5xx': 'var(--red)' };
|
||||
let currentHttpCat = 'all';
|
||||
|
||||
function renderHttpStatus(filtered) {
|
||||
const list = document.getElementById('httpStatusList');
|
||||
if (filtered.length === 0) {
|
||||
list.innerHTML = '<div style="text-align:center;color:var(--text-muted);padding:32px;">No matching status codes found.</div>';
|
||||
return;
|
||||
}
|
||||
list.innerHTML = filtered.map(c => `
|
||||
<div class="result-row" style="flex-direction:column;align-items:flex-start;gap:6px;margin-bottom:8px;cursor:pointer;" onclick="copyText('${c.code} ${c.text}')">
|
||||
<div style="display:flex;align-items:center;gap:10px;width:100%;">
|
||||
<span style="font-family:var(--font-mono);font-size:1.1rem;font-weight:800;color:${catColors[c.cat]};">${c.code}</span>
|
||||
<span style="font-weight:600;color:var(--text-primary);">${c.text}</span>
|
||||
<span class="tag" style="background:${catColors[c.cat]}20;color:${catColors[c.cat]};margin-left:auto;">${c.cat}</span>
|
||||
</div>
|
||||
<div style="font-size:0.8rem;color:var(--text-secondary);line-height:1.5;">${c.desc}</div>
|
||||
</div>
|
||||
`).join('');
|
||||
}
|
||||
|
||||
function filterHttpStatus() {
|
||||
const q = document.getElementById('httpStatusInput').value.toLowerCase();
|
||||
let filtered = HTTP_CODES;
|
||||
if (currentHttpCat !== 'all') filtered = filtered.filter(c => c.cat === currentHttpCat);
|
||||
if (q) filtered = filtered.filter(c => String(c.code).includes(q) || c.text.toLowerCase().includes(q) || c.desc.toLowerCase().includes(q));
|
||||
renderHttpStatus(filtered);
|
||||
}
|
||||
|
||||
function filterHttpCat(cat, btn) {
|
||||
currentHttpCat = cat;
|
||||
document.querySelectorAll('#httpCatBtns .btn').forEach(b => {
|
||||
b.className = b === btn ? 'btn btn-sm btn-primary' : 'btn btn-sm btn-secondary';
|
||||
});
|
||||
filterHttpStatus();
|
||||
}
|
||||
|
||||
// Initialize
|
||||
setTimeout(() => renderHttpStatus(HTTP_CODES), 100);
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// IP Lookup
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function lookupIP() {
|
||||
const ip = document.getElementById('ipInput').value.trim();
|
||||
setStatus('ipStatus','info','Looking up...');
|
||||
const d = await apiGet(ip ? '/api/ip/' + ip : '/api/ip');
|
||||
if (d.success && d.status !== 'fail') {
|
||||
const fields = [
|
||||
['IP Address', d.query], ['Country', d.country], ['Region', d.regionName],
|
||||
['City', d.city], ['ZIP', d.zip], ['Latitude', d.lat],
|
||||
['Longitude', d.lon], ['Timezone', d.timezone], ['ISP', d.isp],
|
||||
['Organization', d.org], ['AS', d.as]
|
||||
];
|
||||
document.getElementById('ipResults').innerHTML = fields.map(([l, v]) =>
|
||||
`<div class="ip-card"><div class="label">${l}</div><div class="value">${v || '—'}</div></div>`).join('');
|
||||
setStatus('ipStatus','success','Lookup complete ✓');
|
||||
} else setStatus('ipStatus','error', d.message || d.error || 'Lookup failed');
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('ipInput').addEventListener('keydown', e => { if(e.key==='Enter') lookupIP(); });
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// JSON Formatter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function formatJSON() {
|
||||
const input = document.getElementById('jsonInput').value.trim();
|
||||
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
|
||||
const iv = document.getElementById('jsonIndent').value;
|
||||
const indent = iv === '\\t' ? '\t' : parseInt(iv);
|
||||
const d = await apiPost('/api/json/format', { json: input, indent });
|
||||
if (d.success) { document.getElementById('jsonOutput').value = d.result; setStatus('jsonStatus','success','Formatted ✓'); }
|
||||
else setStatus('jsonStatus','error', d.error);
|
||||
}
|
||||
async function minifyJSON() {
|
||||
const input = document.getElementById('jsonInput').value.trim();
|
||||
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
|
||||
const d = await apiPost('/api/json/minify', { json: input });
|
||||
if (d.success) { document.getElementById('jsonOutput').value = d.result; setStatus('jsonStatus','success','Minified ✓'); }
|
||||
else setStatus('jsonStatus','error', d.error);
|
||||
}
|
||||
async function validateJSON() {
|
||||
const input = document.getElementById('jsonInput').value.trim();
|
||||
if (!input) return setStatus('jsonStatus','error','Paste some JSON first.');
|
||||
const d = await apiPost('/api/json/validate', { json: input });
|
||||
setStatus('jsonStatus', d.valid ? 'success' : 'error', d.message);
|
||||
}
|
||||
function clearJSON() {
|
||||
document.getElementById('jsonInput').value=''; document.getElementById('jsonOutput').value='';
|
||||
document.getElementById('jsonStatus').className='status';
|
||||
}
|
||||
|
||||
// Keyboard shortcuts
|
||||
document.getElementById('jsonInput').addEventListener('keydown', e => {
|
||||
if(e.key==='Enter' && (e.ctrlKey||e.metaKey)) formatJSON();
|
||||
if(e.key==='Tab') { e.preventDefault(); const t=e.target,s=t.selectionStart,en=t.selectionEnd; t.value=t.value.substring(0,s)+' '+t.value.substring(en); t.selectionStart=t.selectionEnd=s+2; }
|
||||
});
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// JSON ↔ CSV Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
let lastJsoncsvType = 'csv'; // track last conversion type for download
|
||||
|
||||
async function jsonToCsv() {
|
||||
const input = document.getElementById('jsoncsvJsonInput').value.trim();
|
||||
if (!input) return setStatus('jsoncsvStatus', 'error', 'Paste some JSON first.');
|
||||
const d = await apiPost('/api/convert/json-to-csv', { json: input });
|
||||
if (d.success) {
|
||||
document.getElementById('jsoncsvOutput').value = d.result;
|
||||
lastJsoncsvType = 'csv';
|
||||
setStatus('jsoncsvStatus', 'success', `Converted ✓ — ${d.rows} rows, ${d.columns} columns`);
|
||||
} else setStatus('jsoncsvStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
async function csvToJson() {
|
||||
const input = document.getElementById('jsoncsvCsvInput').value.trim();
|
||||
if (!input) return setStatus('jsoncsvStatus', 'error', 'Paste some CSV first.');
|
||||
const d = await apiPost('/api/convert/csv-to-json', { csv: input });
|
||||
if (d.success) {
|
||||
document.getElementById('jsoncsvOutput').value = JSON.stringify(d.result, null, 2);
|
||||
lastJsoncsvType = 'json';
|
||||
setStatus('jsoncsvStatus', 'success', `Converted ✓ — ${d.result.length} records`);
|
||||
} else setStatus('jsoncsvStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
function downloadJsoncsvOutput() {
|
||||
const output = document.getElementById('jsoncsvOutput').value;
|
||||
if (!output) return setStatus('jsoncsvStatus', 'error', 'Nothing to download.');
|
||||
const ext = lastJsoncsvType === 'csv' ? 'csv' : 'json';
|
||||
const mime = lastJsoncsvType === 'csv' ? 'text/csv' : 'application/json';
|
||||
const blob = new Blob([output], { type: mime });
|
||||
const a = document.createElement('a');
|
||||
a.href = URL.createObjectURL(blob);
|
||||
a.download = `converted.${ext}`;
|
||||
a.click();
|
||||
URL.revokeObjectURL(a.href);
|
||||
setStatus('jsoncsvStatus', 'success', 'Downloaded ✓');
|
||||
}
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// JWT Decoder
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function decodeJWT() {
|
||||
const token = document.getElementById('jwtInput').value.trim();
|
||||
if (!token) return setStatus('jwtStatus','error','Paste a JWT.');
|
||||
const d = await apiPost('/api/jwt/decode', { token });
|
||||
if (d.success) {
|
||||
const expStr = d.expired === null ? '—' : d.expired ? '<span style="color:var(--red)">EXPIRED ✗</span>' : '<span style="color:var(--green)">VALID ✓</span>';
|
||||
document.getElementById('jwtResults').innerHTML = `
|
||||
<div class="result-card">
|
||||
<div class="panel-label">Header</div>
|
||||
<pre style="background:var(--bg-input);padding:12px;border-radius:var(--radius-sm);font-size:0.82rem;color:var(--cyan);overflow-x:auto;">${JSON.stringify(d.header, null, 2)}</pre>
|
||||
<div class="panel-label" style="margin-top:14px;">Payload</div>
|
||||
<pre style="background:var(--bg-input);padding:12px;border-radius:var(--radius-sm);font-size:0.82rem;color:var(--green);overflow-x:auto;">${JSON.stringify(d.payload, null, 2)}</pre>
|
||||
<div class="panel-label" style="margin-top:14px;">Expiry</div>
|
||||
<div style="font-size:0.9rem;">${expStr}</div>
|
||||
</div>`;
|
||||
setStatus('jwtStatus','success','Decoded ✓');
|
||||
} else setStatus('jwtStatus','error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Lorem Ipsum
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateLorem() {
|
||||
const d = await apiPost('/api/lorem', { paragraphs: parseInt(document.getElementById('loremCount').value) });
|
||||
if (d.success) document.getElementById('loremOutput').value = d.result;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Markdown Preview
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function renderMarkdown() {
|
||||
const md = document.getElementById('mdInput').value;
|
||||
if (!md) { document.getElementById('mdPreview').innerHTML = '<span style="color:var(--text-muted)">Preview will appear here...</span>'; return; }
|
||||
let html = md
|
||||
.replace(/^### (.+)$/gm, '<h3>$1</h3>')
|
||||
.replace(/^## (.+)$/gm, '<h2>$1</h2>')
|
||||
.replace(/^# (.+)$/gm, '<h1>$1</h1>')
|
||||
.replace(/^---$/gm, '<hr>')
|
||||
.replace(/```([\s\S]*?)```/g, '<pre><code>$1</code></pre>')
|
||||
.replace(/`([^`]+)`/g, '<code>$1</code>')
|
||||
.replace(/\*\*(.+?)\*\*/g, '<strong>$1</strong>')
|
||||
.replace(/\*(.+?)\*/g, '<em>$1</em>')
|
||||
.replace(/~~(.+?)~~/g, '<del>$1</del>')
|
||||
.replace(/^\> (.+)$/gm, '<blockquote>$1</blockquote>')
|
||||
.replace(/^\- (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^\* (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/^\d+\. (.+)$/gm, '<li>$1</li>')
|
||||
.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank">$1</a>')
|
||||
.replace(/!\[([^\]]*)\]\(([^)]+)\)/g, '<img src="$2" alt="$1" style="max-width:100%;border-radius:8px;">')
|
||||
.replace(/\n\n/g, '</p><p>')
|
||||
.replace(/\n/g, '<br>');
|
||||
html = '<p>' + html + '</p>';
|
||||
// Wrap consecutive li in ul
|
||||
html = html.replace(/(<li>.*?<\/li>)+/gs, '<ul>$&</ul>');
|
||||
document.getElementById('mdPreview').innerHTML = html;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Number Base Converter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertNumber() {
|
||||
const v = document.getElementById('numInput').value.trim();
|
||||
if (!v) return setStatus('numStatus','error','Enter a number.');
|
||||
const d = await apiPost('/api/number/convert', { value: v, fromBase: document.getElementById('numBase').value });
|
||||
if (d.success) {
|
||||
document.getElementById('numResults').innerHTML = [
|
||||
['Decimal', d.decimal], ['Binary', d.binary], ['Octal', d.octal], ['Hexadecimal', d.hex]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
setStatus('numStatus','success','Converted ✓');
|
||||
} else setStatus('numStatus','error', d.error);
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('numInput').addEventListener('keydown', e => { if(e.key==='Enter') convertNumber(); });
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Password Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generatePassword() {
|
||||
const d = await apiPost('/api/password', {
|
||||
length: parseInt(document.getElementById('pwLength').value),
|
||||
uppercase: document.getElementById('pwUpper').checked,
|
||||
lowercase: document.getElementById('pwLower').checked,
|
||||
numbers: document.getElementById('pwNumbers').checked,
|
||||
symbols: document.getElementById('pwSymbols').checked,
|
||||
count: 5
|
||||
});
|
||||
if (d.success) {
|
||||
document.getElementById('pwDisplay').textContent = d.passwords[0];
|
||||
// Strength
|
||||
const len = d.passwords[0].length;
|
||||
const str = len >= 20 ? 100 : len >= 12 ? 75 : len >= 8 ? 50 : 25;
|
||||
const colors = { 25: 'var(--red)', 50: 'var(--orange)', 75: 'var(--yellow)', 100: 'var(--green)' };
|
||||
const fill = document.getElementById('pwStrength');
|
||||
fill.style.width = str + '%';
|
||||
fill.style.background = colors[str];
|
||||
// Batch
|
||||
document.getElementById('pwBatch').innerHTML = '<div class="panel-label">More Passwords</div>' +
|
||||
d.passwords.slice(1).map(pw => `<div class="result-row"><div class="value" onclick="copyText(this.textContent)" style="max-width:100%;text-align:left;">${pw}</div></div>`).join('');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Placeholder Image Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function generatePlaceholder() {
|
||||
const w = parseInt(document.getElementById('phWidth').value) || 400;
|
||||
const h = parseInt(document.getElementById('phHeight').value) || 300;
|
||||
const text = document.getElementById('phText').value || `${w} × ${h}`;
|
||||
const bg = document.getElementById('phBg').value;
|
||||
const fg = document.getElementById('phFg').value;
|
||||
const fontSize = parseInt(document.getElementById('phFontSize').value) || 28;
|
||||
|
||||
document.getElementById('phBgText').value = bg;
|
||||
document.getElementById('phFgText').value = fg;
|
||||
document.getElementById('phFontSizeVal').textContent = fontSize + 'px';
|
||||
|
||||
const canvas = document.getElementById('phCanvas');
|
||||
canvas.width = w;
|
||||
canvas.height = h;
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
||||
// Background
|
||||
ctx.fillStyle = bg;
|
||||
ctx.fillRect(0, 0, w, h);
|
||||
|
||||
// Cross lines
|
||||
ctx.strokeStyle = fg + '20';
|
||||
ctx.lineWidth = 1;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(0, 0); ctx.lineTo(w, h);
|
||||
ctx.moveTo(w, 0); ctx.lineTo(0, h);
|
||||
ctx.stroke();
|
||||
|
||||
// Text
|
||||
ctx.fillStyle = fg;
|
||||
ctx.font = `${fontSize}px -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif`;
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'middle';
|
||||
ctx.fillText(text, w / 2, h / 2);
|
||||
}
|
||||
|
||||
function downloadPlaceholder() {
|
||||
const canvas = document.getElementById('phCanvas');
|
||||
const link = document.createElement('a');
|
||||
const w = document.getElementById('phWidth').value || 400;
|
||||
const h = document.getElementById('phHeight').value || 300;
|
||||
link.download = `placeholder-${w}x${h}.png`;
|
||||
link.href = canvas.toDataURL('image/png');
|
||||
link.click();
|
||||
setStatus('phStatus', 'success', 'Downloaded ✓');
|
||||
}
|
||||
|
||||
function copyPlaceholderDataUrl() {
|
||||
const canvas = document.getElementById('phCanvas');
|
||||
copyText(canvas.toDataURL('image/png'));
|
||||
setStatus('phStatus', 'success', 'Data URL copied ✓');
|
||||
}
|
||||
|
||||
// Initialize on load
|
||||
setTimeout(generatePlaceholder, 100);
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// QR Code (using qrcode-generator library)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function generateQR() {
|
||||
const data = document.getElementById('qrInput').value.trim();
|
||||
const container = document.getElementById('qrCanvas');
|
||||
const ph = document.getElementById('qrPlaceholder');
|
||||
if (!data) { container.style.display = 'none'; ph.style.display = ''; return; }
|
||||
if (typeof qrcode === 'undefined') { ph.textContent = 'Loading QR library...'; return; }
|
||||
|
||||
try {
|
||||
const qr = qrcode(0, 'M');
|
||||
qr.addData(data);
|
||||
qr.make();
|
||||
container.innerHTML = qr.createSvgTag({ cellSize: 4, margin: 8, scalable: true });
|
||||
// Style the SVG
|
||||
const svg = container.querySelector('svg');
|
||||
if (svg) {
|
||||
svg.style.width = '256px';
|
||||
svg.style.height = '256px';
|
||||
svg.style.background = '#fff';
|
||||
svg.style.borderRadius = '12px';
|
||||
}
|
||||
container.style.display = '';
|
||||
ph.style.display = 'none';
|
||||
} catch (err) {
|
||||
ph.textContent = err.message;
|
||||
container.style.display = 'none';
|
||||
ph.style.display = '';
|
||||
}
|
||||
}
|
||||
|
||||
function downloadQR() {
|
||||
const container = document.getElementById('qrCanvas');
|
||||
if (container.style.display === 'none') return;
|
||||
const svg = container.querySelector('svg');
|
||||
if (!svg) return;
|
||||
|
||||
// Convert SVG to PNG via canvas
|
||||
const svgData = new XMLSerializer().serializeToString(svg);
|
||||
const img = new Image();
|
||||
img.onload = function () {
|
||||
const canvas = document.createElement('canvas');
|
||||
canvas.width = 512;
|
||||
canvas.height = 512;
|
||||
const ctx = canvas.getContext('2d');
|
||||
ctx.fillStyle = '#ffffff';
|
||||
ctx.fillRect(0, 0, 512, 512);
|
||||
ctx.drawImage(img, 0, 0, 512, 512);
|
||||
const a = document.createElement('a');
|
||||
a.href = canvas.toDataURL('image/png');
|
||||
a.download = 'qrcode.png';
|
||||
a.click();
|
||||
};
|
||||
img.src = 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svgData)));
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// QR Code Reader (using jsQR library)
|
||||
// ═══════════════════════════════════════════════════════
|
||||
let qrCameraStream = null;
|
||||
let qrScanInterval = null;
|
||||
let qrScanHistoryList = [];
|
||||
|
||||
function startQrCamera() {
|
||||
if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) {
|
||||
return setStatus('qrReaderStatus', 'error', 'Camera API not supported in this browser.');
|
||||
}
|
||||
|
||||
navigator.mediaDevices.getUserMedia({ video: { facingMode: 'environment' } })
|
||||
.then(stream => {
|
||||
qrCameraStream = stream;
|
||||
const video = document.getElementById('qrVideo');
|
||||
video.srcObject = stream;
|
||||
video.play();
|
||||
|
||||
document.getElementById('qrCameraContainer').style.display = 'block';
|
||||
document.getElementById('qrCameraBtn').style.display = 'none';
|
||||
document.getElementById('qrStopBtn').style.display = '';
|
||||
document.getElementById('qrImagePreview').style.display = 'none';
|
||||
|
||||
setStatus('qrReaderStatus', 'info', 'Camera active — scanning for QR codes...');
|
||||
|
||||
// Start scanning frames
|
||||
const canvas = document.getElementById('qrScanCanvas');
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
|
||||
qrScanInterval = setInterval(() => {
|
||||
if (video.readyState === video.HAVE_ENOUGH_DATA) {
|
||||
canvas.width = video.videoWidth;
|
||||
canvas.height = video.videoHeight;
|
||||
ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (typeof jsQR !== 'undefined') {
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'dontInvert' });
|
||||
if (code) {
|
||||
showQrResult(code.data);
|
||||
setStatus('qrReaderStatus', 'success', 'QR code detected ✓');
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 250);
|
||||
})
|
||||
.catch(err => {
|
||||
setStatus('qrReaderStatus', 'error', 'Camera access denied: ' + err.message);
|
||||
});
|
||||
}
|
||||
|
||||
function stopQrCamera() {
|
||||
if (qrScanInterval) { clearInterval(qrScanInterval); qrScanInterval = null; }
|
||||
if (qrCameraStream) {
|
||||
qrCameraStream.getTracks().forEach(t => t.stop());
|
||||
qrCameraStream = null;
|
||||
}
|
||||
const video = document.getElementById('qrVideo');
|
||||
video.srcObject = null;
|
||||
|
||||
document.getElementById('qrCameraContainer').style.display = 'none';
|
||||
document.getElementById('qrCameraBtn').style.display = '';
|
||||
document.getElementById('qrStopBtn').style.display = 'none';
|
||||
setStatus('qrReaderStatus', 'info', 'Camera stopped.');
|
||||
}
|
||||
|
||||
function scanQrFromFile(event) {
|
||||
const file = event.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
// Stop camera if running
|
||||
stopQrCamera();
|
||||
|
||||
const img = document.getElementById('qrPreviewImg');
|
||||
const reader = new FileReader();
|
||||
reader.onload = function (e) {
|
||||
img.src = e.target.result;
|
||||
document.getElementById('qrImagePreview').style.display = 'block';
|
||||
|
||||
const tempImg = new Image();
|
||||
tempImg.onload = function () {
|
||||
const canvas = document.getElementById('qrImgCanvas');
|
||||
const ctx = canvas.getContext('2d', { willReadFrequently: true });
|
||||
canvas.width = tempImg.width;
|
||||
canvas.height = tempImg.height;
|
||||
ctx.drawImage(tempImg, 0, 0);
|
||||
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
|
||||
|
||||
if (typeof jsQR !== 'undefined') {
|
||||
const code = jsQR(imageData.data, imageData.width, imageData.height, { inversionAttempts: 'attemptBoth' });
|
||||
if (code) {
|
||||
showQrResult(code.data);
|
||||
setStatus('qrReaderStatus', 'success', 'QR code detected in image ✓');
|
||||
} else {
|
||||
setStatus('qrReaderStatus', 'error', 'No QR code found in this image.');
|
||||
}
|
||||
} else {
|
||||
setStatus('qrReaderStatus', 'error', 'QR scanning library not loaded.');
|
||||
}
|
||||
};
|
||||
tempImg.src = e.target.result;
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
|
||||
// Reset file input so same file can be re-selected
|
||||
event.target.value = '';
|
||||
}
|
||||
|
||||
function showQrResult(data) {
|
||||
document.getElementById('qrReaderPlaceholder').style.display = 'none';
|
||||
document.getElementById('qrReaderResult').style.display = 'block';
|
||||
document.getElementById('qrDecodedText').textContent = data;
|
||||
|
||||
// Detect type
|
||||
let type = 'Plain Text';
|
||||
const openBtn = document.getElementById('qrOpenLinkBtn');
|
||||
openBtn.style.display = 'none';
|
||||
|
||||
if (/^https?:\/\//i.test(data)) {
|
||||
type = '🔗 URL';
|
||||
openBtn.style.display = '';
|
||||
} else if (/^mailto:/i.test(data)) {
|
||||
type = '📧 Email';
|
||||
openBtn.style.display = '';
|
||||
} else if (/^tel:/i.test(data)) {
|
||||
type = '📞 Phone Number';
|
||||
} else if (/^BEGIN:VCARD/i.test(data)) {
|
||||
type = '👤 vCard Contact';
|
||||
} else if (/^BEGIN:VEVENT/i.test(data)) {
|
||||
type = '📅 Calendar Event';
|
||||
} else if (/^WIFI:/i.test(data)) {
|
||||
type = '📶 Wi-Fi Network';
|
||||
} else if (/^smsto:/i.test(data)) {
|
||||
type = '💬 SMS';
|
||||
} else if (/^geo:/i.test(data)) {
|
||||
type = '📍 Geolocation';
|
||||
}
|
||||
|
||||
document.getElementById('qrDecodedType').textContent = type;
|
||||
|
||||
// Add to history (keep last 10)
|
||||
const now = new Date().toLocaleTimeString();
|
||||
qrScanHistoryList.unshift({ data: data.length > 80 ? data.slice(0, 80) + '...' : data, time: now, full: data });
|
||||
if (qrScanHistoryList.length > 10) qrScanHistoryList.pop();
|
||||
|
||||
document.getElementById('qrScanHistory').innerHTML = qrScanHistoryList.map(h =>
|
||||
`<div class="result-row" style="cursor:pointer;" onclick="copyText('${h.full.replace(/'/g, "\\'")}')">
|
||||
<div class="label">${h.time}</div>
|
||||
<div class="value" style="font-size:0.78rem;">${h.data.replace(/</g, '<')}</div>
|
||||
</div>`
|
||||
).join('');
|
||||
}
|
||||
|
||||
// Stop camera when navigating away
|
||||
const origShowPage = window.showPage;
|
||||
if (origShowPage) {
|
||||
window.showPage = function (name) {
|
||||
if (name !== 'qrreader' && qrCameraStream) stopQrCamera();
|
||||
origShowPage(name);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Regex Tester
|
||||
// ═══════════════════════════════════════════════════════
|
||||
function testRegex() {
|
||||
const pattern = document.getElementById('regexPattern').value;
|
||||
const flags = document.getElementById('regexFlags').value;
|
||||
const input = document.getElementById('regexInput').value;
|
||||
const resEl = document.getElementById('regexResults');
|
||||
const listEl = document.getElementById('regexMatchList');
|
||||
if (!pattern || !input) { resEl.innerHTML = '<span style="color:var(--text-muted)">Enter a pattern and test string...</span>'; listEl.innerHTML=''; return; }
|
||||
try {
|
||||
const re = new RegExp(pattern, flags);
|
||||
// Highlight matches
|
||||
let highlighted = input.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]));
|
||||
const safePattern = new RegExp(pattern, flags);
|
||||
highlighted = input.replace(safePattern, m => `<span class="regex-match">${m.replace(/[<>&]/g, c => ({'<':'<','>':'>','&':'&'}[c]))}</span>`);
|
||||
resEl.innerHTML = highlighted || '<span style="color:var(--text-muted)">No matches</span>';
|
||||
// Match list
|
||||
const matches = [...input.matchAll(new RegExp(pattern, flags.includes('g') ? flags : flags + 'g'))];
|
||||
if (matches.length) {
|
||||
listEl.innerHTML = '<div class="panel-label">Matches (' + matches.length + ')</div>' +
|
||||
matches.map((m, i) => `<div class="result-row"><div class="label">Match ${i+1}</div><div class="value" onclick="copyText(this.textContent)">${m[0]}</div></div>`).join('');
|
||||
} else listEl.innerHTML = '';
|
||||
} catch (e) {
|
||||
resEl.innerHTML = `<span style="color:var(--red)">${e.message}</span>`;
|
||||
listEl.innerHTML = '';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Slug Generator
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function generateSlug() {
|
||||
const text = document.getElementById('slugInput').value;
|
||||
if (!text.trim()) { document.getElementById('slugOutput').textContent = '—'; return; }
|
||||
const separator = document.getElementById('slugSeparator').value;
|
||||
const lowercase = document.getElementById('slugLower').checked;
|
||||
const d = await apiPost('/api/text/slugify', { text, separator, lowercase });
|
||||
if (d.success) {
|
||||
document.getElementById('slugOutput').textContent = d.result;
|
||||
setStatus('slugStatus', 'success', 'Generated ✓');
|
||||
} else setStatus('slugStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// SQL Formatter
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function formatSQL() {
|
||||
const input = document.getElementById('sqlInput').value.trim();
|
||||
if (!input) return setStatus('sqlStatus', 'error', 'Paste some SQL first.');
|
||||
const d = await apiPost('/api/sql/format', { sql: input });
|
||||
if (d.success) { document.getElementById('sqlOutput').value = d.result; setStatus('sqlStatus', 'success', 'Formatted ✓'); }
|
||||
else setStatus('sqlStatus', 'error', d.error);
|
||||
}
|
||||
async function minifySQL() {
|
||||
const input = document.getElementById('sqlInput').value.trim();
|
||||
if (!input) return setStatus('sqlStatus', 'error', 'Paste some SQL first.');
|
||||
const d = await apiPost('/api/sql/minify', { sql: input });
|
||||
if (d.success) { document.getElementById('sqlOutput').value = d.result; setStatus('sqlStatus', 'success', 'Minified ✓'); }
|
||||
else setStatus('sqlStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Text Encoder / Decoder
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function encodeText(method) {
|
||||
const text = document.getElementById('textencInput').value;
|
||||
if (!text) return setStatus('textencStatus', 'error', 'Enter some text first.');
|
||||
const d = await apiPost('/api/text/encode', { text, method });
|
||||
if (d.success) {
|
||||
document.getElementById('textencOutput').value = d.result;
|
||||
setStatus('textencStatus', 'success', `Encoded with ${method} ✓`);
|
||||
} else setStatus('textencStatus', 'error', d.error);
|
||||
}
|
||||
|
||||
function swapTextEnc() {
|
||||
const input = document.getElementById('textencInput');
|
||||
const output = document.getElementById('textencOutput');
|
||||
const tmp = input.value;
|
||||
input.value = output.value;
|
||||
output.value = tmp;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// Timestamp
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function convertTimestamp() {
|
||||
const v = document.getElementById('tsInput').value.trim();
|
||||
const d = await apiPost('/api/timestamp', { value: v || 'now' });
|
||||
if (d.success) {
|
||||
document.getElementById('tsResults').innerHTML = [
|
||||
['Unix (s)', d.unix], ['Unix (ms)', d.unixMs], ['ISO 8601', d.iso],
|
||||
['UTC', d.utc], ['Local', d.local], ['Relative', d.relative]
|
||||
].map(([l, v]) => `<div class="result-row"><div class="label">${l}</div><div class="value" onclick="copyText(this.textContent)">${v}</div></div>`).join('');
|
||||
setStatus('tsStatus','success','Converted ✓');
|
||||
} else setStatus('tsStatus','error', d.error);
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('tsInput').addEventListener('keydown', e => { if(e.key==='Enter') convertTimestamp(); });
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// URL Shortener
|
||||
// ═══════════════════════════════════════════════════════
|
||||
const urlHist = [];
|
||||
async function shortenURL() {
|
||||
const url = document.getElementById('urlInput').value.trim();
|
||||
if (!url) return setStatus('urlStatus','error','Enter a URL.');
|
||||
const d = await apiPost('/api/url/shorten', { url });
|
||||
if (d.success) {
|
||||
document.getElementById('shortUrlLink').href = d.shortUrl;
|
||||
document.getElementById('shortUrlLink').textContent = d.shortUrl;
|
||||
document.getElementById('urlResult').classList.add('visible');
|
||||
setStatus('urlStatus','success','Shortened ✓');
|
||||
urlHist.unshift({ short: d.shortUrl, original: url, time: new Date().toLocaleTimeString() });
|
||||
renderUrlHistory();
|
||||
} else setStatus('urlStatus','error', d.error);
|
||||
}
|
||||
function copyShortUrl() { copyText(document.getElementById('shortUrlLink').textContent); }
|
||||
function renderUrlHistory() {
|
||||
const c = document.getElementById('urlHistory');
|
||||
if (!urlHist.length) { c.textContent = 'No links shortened yet.'; return; }
|
||||
c.innerHTML = urlHist.slice(0,10).map(h => `
|
||||
<div class="result-row">
|
||||
<div style="overflow:hidden;flex:1;"><a href="${h.short}" target="_blank" style="color:var(--accent);font-family:var(--font-mono);font-size:0.8rem;text-decoration:none;">${h.short}</a>
|
||||
<div style="font-size:0.7rem;color:var(--text-muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">${h.original}</div></div>
|
||||
<span style="font-size:0.68rem;color:var(--text-muted);margin-left:12px;">${h.time}</span>
|
||||
</div>`).join('');
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('urlInput').addEventListener('keydown', e => { if(e.key==='Enter') shortenURL(); });
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// UUID
|
||||
// ═══════════════════════════════════════════════════════
|
||||
let lastUUIDs = [];
|
||||
async function generateUUIDs() {
|
||||
const d = await apiPost('/api/uuid', { count: parseInt(document.getElementById('uuidCount').value) });
|
||||
if (d.success) {
|
||||
lastUUIDs = d.uuids;
|
||||
document.getElementById('uuidResults').innerHTML = d.uuids.map(u =>
|
||||
`<div class="result-row"><div class="value" onclick="copyText(this.textContent)" style="max-width:100%;text-align:left;">${u}</div></div>`).join('');
|
||||
}
|
||||
}
|
||||
function copyUUIDs() { if (lastUUIDs.length) copyText(lastUUIDs.join('\n')); }
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
// ═══════════════════════════════════════════════════════
|
||||
// YouTube
|
||||
// ═══════════════════════════════════════════════════════
|
||||
async function fetchYouTube() {
|
||||
const url = document.getElementById('ytInput').value.trim();
|
||||
if (!url) return setStatus('ytStatus','error','Enter a YouTube URL.');
|
||||
setStatus('ytStatus','info','Fetching...');
|
||||
const d = await apiPost('/api/youtube/info', { url });
|
||||
if (d.success) {
|
||||
document.getElementById('ytThumb').src = d.thumbnail;
|
||||
document.getElementById('ytThumb').onerror = function() { this.src = d.thumbnailHQ; };
|
||||
document.getElementById('ytTitle').textContent = d.title;
|
||||
document.getElementById('ytAuthor').textContent = d.author;
|
||||
document.getElementById('ytAuthor').href = d.authorUrl;
|
||||
document.getElementById('ytThumbMax').textContent = d.thumbnail;
|
||||
document.getElementById('ytThumbHQ').textContent = d.thumbnailHQ;
|
||||
document.getElementById('ytEmbed').value = `<iframe width="560" height="315" src="${d.embedUrl}" frameborder="0" allowfullscreen></iframe>`;
|
||||
document.getElementById('ytWatch').href = d.watchUrl;
|
||||
document.getElementById('ytResult').classList.add('visible');
|
||||
setStatus('ytStatus','success','Fetched ✓');
|
||||
} else { document.getElementById('ytResult').classList.remove('visible'); setStatus('ytStatus','error', d.error); }
|
||||
}
|
||||
|
||||
// Keyboard shortcut
|
||||
document.getElementById('ytInput').addEventListener('keydown', e => { if(e.key==='Enter') fetchYouTube(); });
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
<!-- ═══════════════ ASCII ART GENERATOR ═══════════════ -->
|
||||
<div class="page" id="page-ascii">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-text-height" style="color:var(--pink)"></i> ASCII Art Generator</h2>
|
||||
<p>Convert text into ASCII art banners using block characters.</p>
|
||||
</div>
|
||||
<div style="max-width:900px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<input type="text" id="asciiInput" placeholder="e.g. HELLO" maxlength="20" value="HELLO" oninput="generateAscii()" />
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generateAscii()"><i class="fas fa-text-height"></i> Generate</button>
|
||||
</div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="asciiOutput" readonly style="min-height:200px;font-size:0.7rem;line-height:1.2;white-space:pre;" placeholder="ASCII art will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('asciiOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div class="status" id="asciiStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/ascii/generate</span>
|
||||
<div class="api-desc">Generate ASCII art text from a string (A-Z, 0-9, common punctuation).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/ascii/generate`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"HI"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "█ █ █████\n█ █ █ \n████ █ \n█ █ █ \n█ █ █████" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ BASE64 ═══════════════ -->
|
||||
<div class="page" id="page-base64">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-exchange-alt" style="color:var(--cyan)"></i> Base64 Encoder / Decoder</h2>
|
||||
<p>Encode or decode Base64 strings.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="b64Input" placeholder="Enter text to encode or Base64 to decode..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="base64Op('encode')"><i class="fas fa-lock"></i> Encode</button>
|
||||
<button class="btn btn-cyan" onclick="base64Op('decode')"><i class="fas fa-unlock"></i> Decode</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="b64Output" readonly placeholder="Result..."></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('b64Output')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="b64Status"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/base64/encode</span>
|
||||
<div class="api-desc">Encode a string to Base64.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/base64/encode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello World"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "SGVsbG8gV29ybGQ=" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/base64/decode</span>
|
||||
<div class="api-desc">Decode a Base64 string back to text.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/base64/decode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"SGVsbG8gV29ybGQ="</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "Hello World" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
<!-- ═══════════════ BYTE SIZE CONVERTER ═══════════════ -->
|
||||
<div class="page" id="page-byteconv">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-weight-hanging" style="color:var(--yellow)"></i> Byte Size Converter</h2>
|
||||
<p>Convert between bytes, KB, MB, GB, TB with binary (1024) and SI (1000) modes.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:grid;grid-template-columns:2fr 1fr;gap:10px;margin-bottom:12px;">
|
||||
<div>
|
||||
<div class="panel-label">Value</div>
|
||||
<input type="number" id="byteValue" placeholder="e.g. 1048576" value="1048576" oninput="convertBytes()" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Unit</div>
|
||||
<select id="byteUnit" onchange="convertBytes()">
|
||||
<option value="B">Bytes (B)</option>
|
||||
<option value="KB">Kilobytes (KB)</option>
|
||||
<option value="MB" selected>Megabytes (MB)</option>
|
||||
<option value="GB">Gigabytes (GB)</option>
|
||||
<option value="TB">Terabytes (TB)</option>
|
||||
<option value="PB">Petabytes (PB)</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="checkbox-group" style="margin-bottom:16px;">
|
||||
<label><input type="radio" name="byteMode" value="binary" checked onchange="convertBytes()"> Binary (1024)</label>
|
||||
<label><input type="radio" name="byteMode" value="si" onchange="convertBytes()"> SI / Decimal (1000)</label>
|
||||
</div>
|
||||
<div id="byteResults"></div>
|
||||
<div class="status" id="byteStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/bytes/convert</span>
|
||||
<div class="api-desc">Convert a value between byte units. Use <code>mode: "binary"</code> (1024) or <code>"si"</code> (1000).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/bytes/convert`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ value: 1, unit: <span class="str">"GB"</span>, mode: <span class="str">"binary"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, B: 1073741824, KB: 1048576, MB: 1024, GB: 1, TB: 0.000977, PB: 0.00000095 }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<!-- ═══════════════ CASE CONVERTER ═══════════════ -->
|
||||
<div class="page" id="page-caseconv">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-font" style="color:var(--pink)"></i> Case Converter</h2>
|
||||
<p>Convert text between different cases.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="caseInput" placeholder="Enter some text here..." style="min-height:100px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="convertCase()"><i class="fas fa-sync"></i> Convert All</button>
|
||||
</div>
|
||||
<div id="caseResults"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/case</span>
|
||||
<div class="api-desc">Convert text to all case variations at once.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/case`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello World Example"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, uppercase: "HELLO WORLD EXAMPLE",
|
||||
// lowercase: "hello world example", titleCase: "Hello World Example",
|
||||
// camelCase: "helloWorldExample", snakeCase: "hello_world_example",
|
||||
// kebabCase: "hello-world-example", dotCase: "hello.world.example",
|
||||
// reversed: "elpmaxE dlroW olleH" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- ═══════════════ CHMOD CALCULATOR ═══════════════ -->
|
||||
<div class="page" id="page-chmod">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-user-shield" style="color:var(--orange)"></i> Chmod Calculator</h2>
|
||||
<p>Convert between numeric (755) and symbolic (rwxr-xr-x) Unix file permissions.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:16px;">
|
||||
<div>
|
||||
<div class="panel-label">Numeric (Octal)</div>
|
||||
<input type="text" id="chmodNumeric" placeholder="e.g. 755" maxlength="4" value="755" oninput="chmodFromNumeric()" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Symbolic</div>
|
||||
<input type="text" id="chmodSymbolic" placeholder="e.g. rwxr-xr-x" value="rwxr-xr-x" oninput="chmodFromSymbolic()" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:20px;">Permission Matrix</div>
|
||||
<div id="chmodMatrix" style="margin-bottom:16px;"></div>
|
||||
<div class="panel-label">Command</div>
|
||||
<div class="result-row" style="cursor:pointer;" onclick="copyText(this.querySelector('.value').textContent)">
|
||||
<div class="label">CHMOD</div>
|
||||
<div class="value" id="chmodCommand">chmod 755 filename</div>
|
||||
</div>
|
||||
<div class="status" id="chmodStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/chmod/calculate</span>
|
||||
<div class="api-desc">Convert between numeric and symbolic chmod. Pass <code>numeric</code> or <code>symbolic</code>.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/chmod/calculate`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ numeric: <span class="str">"755"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, numeric: "755", symbolic: "rwxr-xr-x", owner: {...}, group: {...}, others: {...} }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ COLOR ═══════════════ -->
|
||||
<div class="page" id="page-color">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-palette" style="color:var(--pink)"></i> Color Converter</h2>
|
||||
<p>Convert colors between HEX, RGB, and HSL with a live preview.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div style="display:flex;gap:12px;align-items:flex-end;margin-bottom:16px;">
|
||||
<div style="flex:1;">
|
||||
<div class="panel-label">Color Value</div>
|
||||
<input type="text" id="colorInput" placeholder="#6c63ff or rgb(108,99,255) or hsl(245,100,69)" />
|
||||
</div>
|
||||
<input type="color" id="colorPicker" value="#6c63ff" onchange="document.getElementById('colorInput').value=this.value;convertColor()" />
|
||||
<button class="btn btn-primary" onclick="convertColor()"><i class="fas fa-sync"></i> Convert</button>
|
||||
</div>
|
||||
<div class="color-preview-box" id="colorPreview" style="background:#6c63ff;"></div>
|
||||
<div id="colorResults"></div>
|
||||
<div class="status" id="colorStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/color/convert</span>
|
||||
<div class="api-desc">Convert a color between HEX, RGB, and HSL formats.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/color/convert`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ color: <span class="str">"#6c63ff"</span> })
|
||||
<span class="cm">// Also accepts: "rgb(108,99,255)" or "hsl(245,100,69)"</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, hex: "#6c63ff", rgb: "rgb(108, 99, 255)",
|
||||
// hsl: "hsl(243, 100%, 69%)", r: 108, g: 99, b: 255 }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
<!-- ═══════════════ WORD COUNTER ═══════════════ -->
|
||||
<div class="page" id="page-counter">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-calculator" style="color:var(--cyan)"></i> Word & Character Counter</h2>
|
||||
<p>Get detailed text statistics as you type.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<textarea id="counterInput" placeholder="Start typing or paste text here..." oninput="updateCounter()" style="min-height:200px;"></textarea>
|
||||
<div id="counterResults" style="margin-top:16px;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/stats</span>
|
||||
<div class="api-desc">Get detailed text statistics: characters, words, sentences, reading time, and top character frequency.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/stats`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello world. This is a test."</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, characters: 28, charactersNoSpaces: 23,
|
||||
// words: 6, sentences: 2, paragraphs: 1, lines: 1,
|
||||
// readingTime: "1 min", topChars: [["l",3], ["s",3], ...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
<!-- ═══════════════ CRON PARSER ═══════════════ -->
|
||||
<div class="page" id="page-cron">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-clock" style="color:var(--orange)"></i> Cron Expression Parser</h2>
|
||||
<p>Parse cron expressions into human-readable descriptions and see next run times.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Cron Expression</div>
|
||||
<input type="text" id="cronInput" placeholder="*/5 * * * *" value="*/5 * * * *" oninput="parseCron()" />
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="parseCron()"><i class="fas fa-play"></i> Parse</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('*/5 * * * *')">Every 5 min</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 0 * * *')">Daily midnight</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 9 * * 1-5')">Weekdays 9am</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 0 1 * *')">1st of month</button>
|
||||
<button class="btn btn-secondary" onclick="setCronPreset('0 */2 * * *')">Every 2 hours</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="result-card" id="cronResult" style="display:none;">
|
||||
<div class="panel-label">Human-readable</div>
|
||||
<div id="cronDescription" style="font-size:1.1rem;font-weight:600;margin-bottom:16px;color:var(--cyan);"></div>
|
||||
<div class="panel-label">Fields Breakdown</div>
|
||||
<div id="cronFields" style="margin-bottom:16px;"></div>
|
||||
<div class="panel-label">Next 5 Run Times</div>
|
||||
<div id="cronNextRuns"></div>
|
||||
</div>
|
||||
<div class="status" id="cronStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/cron/parse</span>
|
||||
<div class="api-desc">Parse a cron expression into a human-readable description and next run times.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/cron/parse`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ expression: <span class="str">"*/5 * * * *"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, description: "Every 5 minutes", fields: {...}, nextRuns: [...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!-- ═══════════════ CSS MINIFIER ═══════════════ -->
|
||||
<div class="page" id="page-cssmin">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-css3-alt" style="color:var(--orange)"></i> CSS Minifier</h2>
|
||||
<p>Minify CSS by removing comments and whitespace.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input CSS</div>
|
||||
<textarea id="cssInput" placeholder=".container { display: flex; /* comment */ gap: 16px; }"></textarea>
|
||||
<div class="btn-group"><button class="btn btn-primary" onclick="minifyCSS()"><i class="fas fa-compress"></i> Minify</button></div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Minified Output</div>
|
||||
<textarea id="cssOutput" readonly placeholder="Minified CSS..."></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('cssOutput')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="cssStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/css/minify</span>
|
||||
<div class="api-desc">Minify CSS by removing comments, whitespace, and unnecessary characters. Returns size savings.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/css/minify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
css: <span class="str">".container {\n display: flex;\n /* comment */\n gap: 16px;\n}"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: ".container{display:flex;gap:16px}",
|
||||
// original: 58, minified: 33, saved: 25, percentage: 43 }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- ═══════════════ DIFF ═══════════════ -->
|
||||
<div class="page" id="page-diff">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-columns" style="color:var(--green)"></i> Diff Checker</h2>
|
||||
<p>Compare two texts and find the differences.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Original</div>
|
||||
<textarea id="diffA" placeholder="Paste original text..."></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Modified</div>
|
||||
<textarea id="diffB" placeholder="Paste modified text..."></textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="computeDiff()"><i class="fas fa-exchange-alt"></i> Compare</button>
|
||||
</div>
|
||||
<div class="diff-output" id="diffOutput"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ Diff Checker runs entirely <strong>client-side</strong>. No server API is needed. Here's the line-by-line comparison logic:</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Simple line-by-line diff in JavaScript.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Client-side diff — no API call needed</span>
|
||||
<span class="kw">function</span> <span class="fn">diffLines</span>(original, modified) {
|
||||
<span class="kw">const</span> a = original.<span class="fn">split</span>(<span class="str">'\n'</span>);
|
||||
<span class="kw">const</span> b = modified.<span class="fn">split</span>(<span class="str">'\n'</span>);
|
||||
<span class="kw">const</span> result = [];
|
||||
<span class="kw">const</span> max = Math.<span class="fn">max</span>(a.length, b.length);
|
||||
<span class="kw">for</span> (<span class="kw">let</span> i = 0; i < max; i++) {
|
||||
<span class="kw">if</span> (a[i] === undefined) result.<span class="fn">push</span>({ type: <span class="str">'add'</span>, line: b[i] });
|
||||
<span class="kw">else if</span> (b[i] === undefined) result.<span class="fn">push</span>({ type: <span class="str">'del'</span>, line: a[i] });
|
||||
<span class="kw">else if</span> (a[i] === b[i]) result.<span class="fn">push</span>({ type: <span class="str">'same'</span>, line: a[i] });
|
||||
<span class="kw">else</span> {
|
||||
result.<span class="fn">push</span>({ type: <span class="str">'del'</span>, line: a[i] });
|
||||
result.<span class="fn">push</span>({ type: <span class="str">'add'</span>, line: b[i] });
|
||||
}
|
||||
}
|
||||
<span class="kw">return</span> result;
|
||||
}
|
||||
|
||||
<span class="kw">const</span> diff = <span class="fn">diffLines</span>(<span class="str">"hello\nworld"</span>, <span class="str">"hello\nearth"</span>);
|
||||
<span class="cm">// → [{ type: "same", line: "hello" }, { type: "del", line: "world" }, { type: "add", line: "earth" }]</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
<!-- ═══════════════ ENV ↔ JSON CONVERTER ═══════════════ -->
|
||||
<div class="page" id="page-envjson">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-file-code" style="color:var(--cyan)"></i> ENV ↔ JSON Converter</h2>
|
||||
<p>Convert between .env files and JSON objects.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">.env Input</div>
|
||||
<textarea id="envInput" placeholder="DB_HOST=localhost DB_PORT=5432 DB_NAME=myapp SECRET_KEY=abc123"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="envToJson()"><i class="fas fa-arrow-right"></i> ENV → JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">JSON Input</div>
|
||||
<textarea id="envJsonInput" placeholder='{"DB_HOST":"localhost","DB_PORT":"5432"}'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="jsonToEnv()"><i class="fas fa-arrow-left"></i> JSON → ENV</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:20px;">Output</div>
|
||||
<textarea id="envJsonOutput" readonly placeholder="Converted output will appear here..." style="min-height:140px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('envJsonOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div class="status" id="envJsonStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/env-to-json</span>
|
||||
<div class="api-desc">Convert .env format text to a JSON object.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/env-to-json`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ env: <span class="str">"DB_HOST=localhost\nDB_PORT=5432"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: { DB_HOST: "localhost", DB_PORT: "5432" } }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/json-to-env</span>
|
||||
<div class="api-desc">Convert a flat JSON object to .env format text.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/json-to-env`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'{"DB_HOST":"localhost","DB_PORT":"5432"}'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "DB_HOST=localhost\nDB_PORT=5432" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ STRING ESCAPE ═══════════════ -->
|
||||
<div class="page" id="page-escape">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-quote-right" style="color:var(--orange)"></i> String Escape / Unescape</h2>
|
||||
<p>Escape and unescape special characters in strings.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="escInput" placeholder='Hello "World"\nNew line\tTab'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="escapeOp('escape')"><i class="fas fa-lock"></i> Escape</button>
|
||||
<button class="btn btn-cyan" onclick="escapeOp('unescape')"><i class="fas fa-unlock"></i> Unescape</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="escOutput" readonly></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('escOutput')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="escStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/escape</span>
|
||||
<div class="api-desc">Escape special characters in a string (backslash sequences like \n, \t, \", etc).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/escape`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">'Hello "World"\nNew line'</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "Hello \\\"World\\\"\\nNew line" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/unescape</span>
|
||||
<div class="api-desc">Unescape backslash sequences back to their original characters.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/unescape`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">'Hello \\\"World\\\"\\nNew line'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: 'Hello "World"\nNew line' }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
<!-- ═══════════════ HASH ═══════════════ -->
|
||||
<div class="page" id="page-hash">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-fingerprint" style="color:var(--orange)"></i> Hash Generator</h2>
|
||||
<p>Generate cryptographic hashes from any text.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="hashInput" placeholder="Enter text to hash..." style="min-height:100px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generateHash()"><i class="fas fa-fingerprint"></i> Generate Hashes</button>
|
||||
</div>
|
||||
<div id="hashResults"></div>
|
||||
<div class="status" id="hashStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/hash</span>
|
||||
<div class="api-desc">Generate MD5, SHA-1, SHA-256, and SHA-512 hashes. Optionally specify a single algorithm.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="cm">// Get all hashes at once:</span>
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/hash`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"hello world"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, hashes: { md5: "5eb6...", sha1: "2aae...", sha256: "b94d...", sha512: "309e..." } }</span>
|
||||
|
||||
<span class="cm">// Or get a single algorithm:</span>
|
||||
<span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/hash`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"hello"</span>, algorithm: <span class="str">"sha256"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, hashes: { sha256: "2cf2..." } }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<!-- ═══════════════ HMAC GENERATOR ═══════════════ -->
|
||||
<div class="page" id="page-hmac">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-lock" style="color:var(--red)"></i> HMAC Generator</h2>
|
||||
<p>Generate HMAC signatures from a message and secret key.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Message</div>
|
||||
<textarea id="hmacMessage" placeholder="Enter message to sign..." style="min-height:80px;"></textarea>
|
||||
<div class="panel-label" style="margin-top:12px;">Secret Key</div>
|
||||
<input type="text" id="hmacSecret" placeholder="Enter your secret key..." />
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-top:10px;">
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);">Algorithm:</label>
|
||||
<select id="hmacAlgo" style="width:140px;padding:6px 10px;">
|
||||
<option value="sha256" selected>SHA-256</option>
|
||||
<option value="sha512">SHA-512</option>
|
||||
<option value="sha1">SHA-1</option>
|
||||
<option value="md5">MD5</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generateHmac()"><i class="fas fa-lock"></i> Generate HMAC</button>
|
||||
</div>
|
||||
<div id="hmacResults"></div>
|
||||
<div class="status" id="hmacStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/hmac</span>
|
||||
<div class="api-desc">Generate an HMAC signature. Supports sha256, sha512, sha1, md5.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/hmac`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
message: <span class="str">"hello world"</span>,
|
||||
secret: <span class="str">"my-secret-key"</span>,
|
||||
algorithm: <span class="str">"sha256"</span>
|
||||
})
|
||||
});
|
||||
<span class="cm">// → { success: true, hmac: "734c...", algorithm: "sha256" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- ═══════════════ HTML ENTITIES ═══════════════ -->
|
||||
<div class="page" id="page-htmlent">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-html5" style="color:var(--green)"></i> HTML Entity Encoder / Decoder</h2>
|
||||
<p>Convert special characters to HTML entities and back.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="htmlEntInput" placeholder='<div class="test">& more</div>'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="htmlEntOp('encode')"><i class="fas fa-lock"></i> Encode</button>
|
||||
<button class="btn btn-cyan" onclick="htmlEntOp('decode')"><i class="fas fa-unlock"></i> Decode</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="htmlEntOutput" readonly></textarea>
|
||||
<div class="btn-group"><button class="btn btn-green btn-sm" onclick="copyOutput('htmlEntOutput')"><i class="fas fa-copy"></i> Copy</button></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="htmlEntStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/html/encode</span>
|
||||
<div class="api-desc">Convert special characters to HTML entities.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/html/encode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">'<div class="test">&</div>'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "&lt;div class=&quot;test&quot;&gt;..." }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/html/decode</span>
|
||||
<div class="api-desc">Decode HTML entities back to characters.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/html/decode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"&lt;p&gt;Hello&lt;/p&gt;"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "<p>Hello</p>" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ HTTP STATUS CODES ═══════════════ -->
|
||||
<div class="page" id="page-httpstatus">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-server" style="color:var(--green)"></i> HTTP Status Codes</h2>
|
||||
<p>Quick reference for all HTTP status codes with descriptions and categories.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Search / Lookup</div>
|
||||
<input type="text" id="httpStatusInput" placeholder="Search by code (e.g. 404) or keyword (e.g. not found)..." oninput="filterHttpStatus()" />
|
||||
<div class="btn-group" id="httpCatBtns">
|
||||
<button class="btn btn-sm btn-primary" onclick="filterHttpCat('all', this)">All</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('1xx', this)">1xx Info</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('2xx', this)">2xx Success</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('3xx', this)">3xx Redirect</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('4xx', this)">4xx Client Error</button>
|
||||
<button class="btn btn-sm btn-secondary" onclick="filterHttpCat('5xx', this)">5xx Server Error</button>
|
||||
</div>
|
||||
</div>
|
||||
<div id="httpStatusList" style="margin-top:16px;"></div>
|
||||
<div class="status" id="httpStatusStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/http-status</span>
|
||||
<div class="api-desc">Get all HTTP status codes with their text and category.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/http-status`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, statuses: [{ code: 200, text: "OK", category: "2xx" }, ...] }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/http-status/:code</span>
|
||||
<div class="api-desc">Look up a specific HTTP status code.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/http-status/404`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, code: 404, text: "Not Found", category: "4xx" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ IP LOOKUP ═══════════════ -->
|
||||
<div class="page" id="page-ip">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-globe" style="color:var(--cyan)"></i> IP Address Lookup</h2>
|
||||
<p>Get geolocation and network info for any IP address.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:flex;gap:10px;margin-bottom:16px;">
|
||||
<input type="text" id="ipInput" placeholder="Leave blank for your IP, or enter an IP..." />
|
||||
<button class="btn btn-primary" onclick="lookupIP()"><i class="fas fa-search"></i> Lookup</button>
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('ipInput').value='';lookupIP()">My IP</button>
|
||||
</div>
|
||||
<div class="ip-grid" id="ipResults"></div>
|
||||
<div class="status" id="ipStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/ip</span>
|
||||
<div class="api-desc">Get geolocation info for the server's public IP.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/ip`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, query: "203.0.113.1", country: "United States",
|
||||
// regionName: "California", city: "San Jose", timezone: "America/Los_Angeles",
|
||||
// isp: "Example ISP", ... }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/ip/:ip</span>
|
||||
<div class="api-desc">Lookup a specific IP address.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/ip/8.8.8.8`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, query: "8.8.8.8", country: "United States",
|
||||
// city: "Ashburn", isp: "Google LLC", ... }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
<!-- ═══════════════ JSON FORMATTER ═══════════════ -->
|
||||
<div class="page" id="page-json">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-code" style="color:var(--accent)"></i> JSON Formatter</h2>
|
||||
<p>Paste your JSON below to beautify, minify, or validate it.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input</div>
|
||||
<textarea id="jsonInput" placeholder='{"name":"WinnieAPI-v2","version":1,"tools":["json","url","hash"]}'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="formatJSON()"><i class="fas fa-magic"></i> Beautify</button>
|
||||
<button class="btn btn-secondary" onclick="minifyJSON()"><i class="fas fa-compress"></i> Minify</button>
|
||||
<button class="btn btn-secondary" onclick="validateJSON()"><i class="fas fa-check"></i> Validate</button>
|
||||
<button class="btn btn-secondary" onclick="clearJSON()"><i class="fas fa-trash"></i> Clear</button>
|
||||
</div>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-top:6px;">
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);">Indent:</label>
|
||||
<select id="jsonIndent" style="width:100px;padding:6px 10px;">
|
||||
<option value="2" selected>2 spaces</option><option value="4">4 spaces</option><option value="\t">Tab</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="jsonOutput" readonly placeholder="Formatted output will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('jsonOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="jsonStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/json/format</span>
|
||||
<div class="api-desc">Beautify JSON with configurable indentation.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/json/format`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
json: <span class="str">'{"name":"WinnieAPI-v2","version":1}'</span>,
|
||||
indent: 2 <span class="cm">// 2, 4, or "\t"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "{\n \"name\": \"WinnieAPI-v2\",\n ...}" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/json/minify</span>
|
||||
<div class="api-desc">Minify JSON to a single line.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/json/minify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'{ "a" : 1 , "b" : 2 }'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: '{"a":1,"b":2}' }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/json/validate</span>
|
||||
<div class="api-desc">Check if a string is valid JSON.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/json/validate`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'{"valid": true}'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, valid: true, message: "Valid JSON ✓" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- ═══════════════ JSON TO CSV ═══════════════ -->
|
||||
<div class="page" id="page-jsoncsv">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-table" style="color:var(--green)"></i> JSON ↔ CSV Converter</h2>
|
||||
<p>Convert between JSON arrays and CSV format.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">JSON Input</div>
|
||||
<textarea id="jsoncsvJsonInput" placeholder='[{"name":"Alice","age":30},{"name":"Bob","age":25}]'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="jsonToCsv()"><i class="fas fa-arrow-right"></i> JSON → CSV</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">CSV Input</div>
|
||||
<textarea id="jsoncsvCsvInput" placeholder='name,age Alice,30 Bob,25'></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="csvToJson()"><i class="fas fa-arrow-left"></i> CSV → JSON</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:20px;">Output</div>
|
||||
<textarea id="jsoncsvOutput" readonly placeholder="Converted output will appear here..." style="min-height:160px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('jsoncsvOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
<button class="btn btn-secondary" onclick="downloadJsoncsvOutput()"><i class="fas fa-download"></i> Download</button>
|
||||
</div>
|
||||
<div class="status" id="jsoncsvStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/json-to-csv</span>
|
||||
<div class="api-desc">Convert a JSON array to CSV format.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/json-to-csv`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ json: <span class="str">'[{"name":"Alice","age":30}]'</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "name,age\nAlice,30" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/convert/csv-to-json</span>
|
||||
<div class="api-desc">Convert CSV text to a JSON array.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/convert/csv-to-json`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ csv: <span class="str">"name,age\nAlice,30"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: [{"name":"Alice","age":"30"}] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
<!-- ═══════════════ JWT ═══════════════ -->
|
||||
<div class="page" id="page-jwt">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-shield-alt" style="color:var(--orange)"></i> JWT Decoder</h2>
|
||||
<p>Decode and inspect JSON Web Tokens.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">JWT Token</div>
|
||||
<textarea id="jwtInput" placeholder="Paste your JWT token here (eyJ...)" style="min-height:100px;"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="decodeJWT()"><i class="fas fa-unlock"></i> Decode</button>
|
||||
</div>
|
||||
<div id="jwtResults"></div>
|
||||
<div class="status" id="jwtStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/jwt/decode</span>
|
||||
<div class="api-desc">Decode a JWT token and inspect its header, payload, and expiry.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/jwt/decode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
token: <span class="str">"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, header: { alg: "HS256", typ: "JWT" },
|
||||
// payload: { sub: "1234567890" }, expired: null, signature: "..." }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- ═══════════════ LOREM ═══════════════ -->
|
||||
<div class="page" id="page-lorem">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-paragraph" style="color:var(--yellow)"></i> Lorem Ipsum Generator</h2>
|
||||
<p>Generate placeholder text.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:flex;gap:10px;align-items:center;margin-bottom:16px;">
|
||||
<label style="font-size:0.85rem;color:var(--text-secondary);">Paragraphs:</label>
|
||||
<input type="number" id="loremCount" value="3" min="1" max="20" style="width:80px;" />
|
||||
<button class="btn btn-primary" onclick="generateLorem()"><i class="fas fa-feather"></i> Generate</button>
|
||||
<button class="btn btn-green" onclick="copyOutput('loremOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<textarea id="loremOutput" readonly placeholder="Generated text will appear here..." style="min-height:300px;"></textarea>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/lorem</span>
|
||||
<div class="api-desc">Generate lorem ipsum placeholder text.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/lorem`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ paragraphs: 3 }) <span class="cm">// 1–20</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, result: "Lorem ipsum dolor sit amet..." }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- ═══════════════ MARKDOWN ═══════════════ -->
|
||||
<div class="page" id="page-markdown">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-markdown" style="color:var(--accent)"></i> Markdown Preview</h2>
|
||||
<p>Write Markdown and see a live preview.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Markdown</div>
|
||||
<textarea id="mdInput" oninput="renderMarkdown()" placeholder="# Hello World Write your **markdown** here... - Item 1 - Item 2 `code` and more" style="min-height:400px;"></textarea>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Preview</div>
|
||||
<div class="md-preview" id="mdPreview"><span style="color:var(--text-muted);">Preview will appear here...</span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ Markdown Preview runs entirely <strong>client-side</strong> using regex-based rendering. No server API is needed. Here's how to use a similar approach in your own code:</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Simple Markdown to HTML conversion in JavaScript.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Client-side Markdown rendering — no API call needed</span>
|
||||
<span class="kw">function</span> <span class="fn">renderMarkdown</span>(md) {
|
||||
<span class="kw">return</span> md
|
||||
.<span class="fn">replace</span>(/^### (.+)$/gm, <span class="str">'<h3>$1</h3>'</span>)
|
||||
.<span class="fn">replace</span>(/^## (.+)$/gm, <span class="str">'<h2>$1</h2>'</span>)
|
||||
.<span class="fn">replace</span>(/^# (.+)$/gm, <span class="str">'<h1>$1</h1>'</span>)
|
||||
.<span class="fn">replace</span>(/\*\*(.+?)\*\*/g, <span class="str">'<strong>$1</strong>'</span>)
|
||||
.<span class="fn">replace</span>(/\*(.+?)\*/g, <span class="str">'<em>$1</em>'</span>)
|
||||
.<span class="fn">replace</span>(/`([^`]+)`/g, <span class="str">'<code>$1</code>'</span>)
|
||||
.<span class="fn">replace</span>(/\n\n/g, <span class="str">'</p><p>'</span>);
|
||||
}
|
||||
|
||||
<span class="kw">const</span> html = <span class="fn">renderMarkdown</span>(<span class="str">"# Hello\n\n**Bold** and *italic*"</span>);
|
||||
<span class="cm">// → "<h1>Hello</h1></p><p><strong>Bold</strong> and <em>italic</em>"</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<!-- ═══════════════ NUMBER BASE ═══════════════ -->
|
||||
<div class="page" id="page-numbase">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-sort-numeric-up" style="color:var(--yellow)"></i> Number Base Converter</h2>
|
||||
<p>Convert numbers between different bases.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div style="display:flex;gap:10px;align-items:flex-end;">
|
||||
<div style="flex:1;">
|
||||
<div class="panel-label">Number</div>
|
||||
<input type="text" id="numInput" placeholder="255" />
|
||||
</div>
|
||||
<div style="width:120px;">
|
||||
<div class="panel-label">From Base</div>
|
||||
<select id="numBase">
|
||||
<option value="10" selected>Decimal (10)</option>
|
||||
<option value="2">Binary (2)</option>
|
||||
<option value="8">Octal (8)</option>
|
||||
<option value="16">Hex (16)</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" onclick="convertNumber()"><i class="fas fa-sync"></i></button>
|
||||
</div>
|
||||
<div id="numResults" style="margin-top:16px;"></div>
|
||||
<div class="status" id="numStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/number/convert</span>
|
||||
<div class="api-desc">Convert a number between decimal, binary, octal, and hex bases.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/number/convert`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
value: <span class="str">"255"</span>,
|
||||
fromBase: 10 <span class="cm">// 2, 8, 10, or 16</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, decimal: "255", binary: "11111111",
|
||||
// octal: "377", hex: "FF" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ PASSWORD ═══════════════ -->
|
||||
<div class="page" id="page-password">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-key" style="color:var(--red)"></i> Password Generator</h2>
|
||||
<p>Generate cryptographically secure passwords.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="pw-display" id="pwDisplay" onclick="copyText(this.textContent)">Click "Generate" to create a password</div>
|
||||
<div class="strength-bar"><div class="strength-bar-fill" id="pwStrength"></div></div>
|
||||
<div style="display:flex;align-items:center;gap:12px;margin:16px 0;">
|
||||
<label style="font-size:0.85rem;color:var(--text-secondary);white-space:nowrap;">Length: <strong id="pwLenLabel">16</strong></label>
|
||||
<input type="range" id="pwLength" min="4" max="128" value="16" oninput="document.getElementById('pwLenLabel').textContent=this.value" />
|
||||
</div>
|
||||
<div class="checkbox-group">
|
||||
<label><input type="checkbox" id="pwUpper" checked /> Uppercase (A-Z)</label>
|
||||
<label><input type="checkbox" id="pwLower" checked /> Lowercase (a-z)</label>
|
||||
<label><input type="checkbox" id="pwNumbers" checked /> Numbers (0-9)</label>
|
||||
<label><input type="checkbox" id="pwSymbols" /> Symbols (!@#$%)</label>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="generatePassword()"><i class="fas fa-sync"></i> Generate</button>
|
||||
<button class="btn btn-green" onclick="copyText(document.getElementById('pwDisplay').textContent)"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div id="pwBatch" style="margin-top:16px;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/password</span>
|
||||
<div class="api-desc">Generate cryptographically secure passwords with configurable rules.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/password`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
length: 24, <span class="cm">// 4–128 (default: 16)</span>
|
||||
uppercase: true, <span class="cm">// include A-Z</span>
|
||||
lowercase: true, <span class="cm">// include a-z</span>
|
||||
numbers: true, <span class="cm">// include 0-9</span>
|
||||
symbols: true, <span class="cm">// include !@#$%...</span>
|
||||
count: 5 <span class="cm">// 1–20 passwords at once</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, passwords: ["xK#9mL...", "Qw!7pR...", ...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
<!-- ═══════════════ PLACEHOLDER IMAGE ═══════════════ -->
|
||||
<div class="page" id="page-placeholder">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-image" style="color:var(--pink)"></i> Placeholder Image Generator</h2>
|
||||
<p>Generate custom placeholder images with text, colors, and dimensions.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Settings</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px;">
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Width (px)</label>
|
||||
<input type="number" id="phWidth" value="400" min="10" max="2000" oninput="generatePlaceholder()" />
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Height (px)</label>
|
||||
<input type="number" id="phHeight" value="300" min="10" max="2000" oninput="generatePlaceholder()" />
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Text (leave empty for dimensions)</label>
|
||||
<input type="text" id="phText" placeholder="e.g. Hero Image" oninput="generatePlaceholder()" />
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:10px;margin-bottom:12px;">
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Background</label>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<input type="color" id="phBg" value="#2a2a3a" oninput="generatePlaceholder()" />
|
||||
<input type="text" id="phBgText" value="#2a2a3a" style="flex:1;" oninput="document.getElementById('phBg').value=this.value;generatePlaceholder()" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Text Color</label>
|
||||
<div style="display:flex;align-items:center;gap:8px;">
|
||||
<input type="color" id="phFg" value="#8888a0" oninput="generatePlaceholder()" />
|
||||
<input type="text" id="phFgText" value="#8888a0" style="flex:1;" oninput="document.getElementById('phFg').value=this.value;generatePlaceholder()" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-bottom:12px;">
|
||||
<label style="font-size:0.78rem;color:var(--text-muted);display:block;margin-bottom:4px;">Font Size (px)</label>
|
||||
<input type="range" id="phFontSize" min="10" max="120" value="28" oninput="generatePlaceholder()" />
|
||||
<span id="phFontSizeVal" style="font-size:0.8rem;color:var(--text-muted);">28px</span>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="downloadPlaceholder()"><i class="fas fa-download"></i> Download PNG</button>
|
||||
<button class="btn btn-secondary" onclick="copyPlaceholderDataUrl()"><i class="fas fa-copy"></i> Copy Data URL</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Preview</div>
|
||||
<canvas id="phCanvas" style="width:100%;border-radius:var(--radius);border:1px solid var(--border);"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="phStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/placeholder/:width/:height</span>
|
||||
<div class="api-desc">Generate a placeholder image (SVG). Supports query params: <code>bg</code>, <code>fg</code>, <code>text</code>, <code>fontSize</code>.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Returns an SVG image directly — use as an <img> src</span>
|
||||
|
||||
<span class="cm">// Basic usage:</span>
|
||||
<img src=<span class="str">"${BASE_URL}/api/placeholder/400/300"</span> />
|
||||
|
||||
<span class="cm">// Custom colors & text:</span>
|
||||
<img src=<span class="str">"${BASE_URL}/api/placeholder/800/400?bg=1a1a25&fg=6c63ff&text=Hero+Image&fontSize=36"</span> />
|
||||
|
||||
<span class="cm">// Fetch as SVG text:</span>
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/placeholder/200/200?text=Avatar`</span>);
|
||||
<span class="kw">const</span> svg = <span class="kw">await</span> res.<span class="fn">text</span>();
|
||||
<span class="cm">// → <svg xmlns=...>...</svg></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<!-- ═══════════════ QR CODE ═══════════════ -->
|
||||
<div class="page" id="page-qrcode">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-qrcode" style="color:var(--accent)"></i> QR Code Generator</h2>
|
||||
<p>Generate QR codes from any text or URL.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="panel-label">Data</div>
|
||||
<textarea id="qrInput" placeholder="Enter text or URL..." style="min-height:80px;" oninput="generateQR()"></textarea>
|
||||
<div style="text-align:center;margin:20px 0;">
|
||||
<div id="qrCanvas" style="display:none;"></div>
|
||||
<div id="qrPlaceholder" style="color:var(--text-muted);font-size:0.85rem;">Type something above to generate a QR code</div>
|
||||
</div>
|
||||
<div class="btn-group" style="justify-content:center;">
|
||||
<button class="btn btn-primary" onclick="downloadQR()"><i class="fas fa-download"></i> Download PNG</button>
|
||||
</div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ QR Code generation runs entirely <strong>client-side</strong> using the <code>qrcode-generator</code> library. No server API is needed.</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Generate QR codes using the qrcode-generator package (works in Node.js and browsers).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Install: npm install qrcode-generator</span>
|
||||
<span class="cm">// Browser CDN: https://cdn.jsdelivr.net/npm/qrcode-generator@1.4.4/qrcode.min.js</span>
|
||||
|
||||
<span class="kw">const</span> qr = <span class="fn">qrcode</span>(0, <span class="str">'M'</span>);
|
||||
qr.<span class="fn">addData</span>(<span class="str">"https://example.com"</span>);
|
||||
qr.<span class="fn">make</span>();
|
||||
|
||||
<span class="cm">// Render as an <img> tag:</span>
|
||||
document.<span class="fn">getElementById</span>(<span class="str">'container'</span>).innerHTML =
|
||||
qr.<span class="fn">createImgTag</span>(4, 8);
|
||||
|
||||
<span class="cm">// Or get a data URL:</span>
|
||||
<span class="kw">const</span> dataUrl = qr.<span class="fn">createDataURL</span>(4, 8);
|
||||
<span class="cm">// → "data:image/gif;base64,..."</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
<!-- ═══════════════ QR CODE READER ═══════════════ -->
|
||||
<div class="page" id="page-qrreader">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-camera" style="color:var(--cyan)"></i> QR Code Reader</h2>
|
||||
<p>Scan QR codes using your camera or upload an image file.</p>
|
||||
</div>
|
||||
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr;gap:20px;">
|
||||
<!-- Left: Input -->
|
||||
<div>
|
||||
<div class="panel-label">Scan Method</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" id="qrCameraBtn" onclick="startQrCamera()"><i class="fas fa-video"></i> Use Camera</button>
|
||||
<button class="btn btn-secondary" id="qrStopBtn" onclick="stopQrCamera()" style="display:none;"><i class="fas fa-stop"></i> Stop Camera</button>
|
||||
<label class="btn btn-secondary" style="cursor:pointer;">
|
||||
<i class="fas fa-upload"></i> Upload Image
|
||||
<input type="file" id="qrFileInput" accept="image/*" onchange="scanQrFromFile(event)" style="display:none;" />
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<!-- Camera feed -->
|
||||
<div id="qrCameraContainer" style="display:none;margin-top:16px;">
|
||||
<video id="qrVideo" style="width:100%;border-radius:var(--radius);border:1px solid var(--border);background:#000;" autoplay playsinline muted></video>
|
||||
<canvas id="qrScanCanvas" style="display:none;"></canvas>
|
||||
</div>
|
||||
|
||||
<!-- Image preview -->
|
||||
<div id="qrImagePreview" style="display:none;margin-top:16px;">
|
||||
<img id="qrPreviewImg" alt="Uploaded QR image" src="" style="width:100%;border-radius:var(--radius);border:1px solid var(--border);" />
|
||||
<canvas id="qrImgCanvas" style="display:none;"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right: Result -->
|
||||
<div>
|
||||
<div class="panel-label">Scan Result</div>
|
||||
<div id="qrReaderPlaceholder" style="color:var(--text-muted);font-size:0.85rem;padding:40px 20px;text-align:center;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius);">
|
||||
<i class="fas fa-qrcode" style="font-size:3rem;margin-bottom:12px;display:block;opacity:0.3;"></i>
|
||||
Point your camera at a QR code or upload an image to scan.
|
||||
</div>
|
||||
<div id="qrReaderResult" style="display:none;">
|
||||
<div class="result-card">
|
||||
<div class="panel-label">Decoded Content</div>
|
||||
<div id="qrDecodedText" style="font-family:var(--font-mono);font-size:0.9rem;padding:14px;background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-sm);word-break:break-all;margin-bottom:12px;color:var(--cyan);"></div>
|
||||
<div class="panel-label">Type</div>
|
||||
<div id="qrDecodedType" style="font-size:0.85rem;color:var(--text-secondary);margin-bottom:12px;"></div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyText(document.getElementById('qrDecodedText').textContent)"><i class="fas fa-copy"></i> Copy</button>
|
||||
<button class="btn btn-secondary" id="qrOpenLinkBtn" style="display:none;" onclick="window.open(document.getElementById('qrDecodedText').textContent,'_blank')"><i class="fas fa-external-link-alt"></i> Open Link</button>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:12px;">Scan History</div>
|
||||
<div id="qrScanHistory"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status" id="qrReaderStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ QR Code reading runs entirely <strong>client-side</strong> using the <code>jsQR</code> library. No server API is needed.</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Decode QR codes from image data using the jsQR library (works in Node.js and browsers).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Install: npm install jsqr</span>
|
||||
<span class="cm">// Browser CDN: https://cdn.jsdelivr.net/npm/jsqr@1.4.0/dist/jsQR.min.js</span>
|
||||
|
||||
<span class="cm">// Read from a canvas element:</span>
|
||||
<span class="kw">const</span> canvas = document.<span class="fn">getElementById</span>(<span class="str">'myCanvas'</span>);
|
||||
<span class="kw">const</span> ctx = canvas.<span class="fn">getContext</span>(<span class="str">'2d'</span>);
|
||||
<span class="kw">const</span> imageData = ctx.<span class="fn">getImageData</span>(0, 0, canvas.width, canvas.height);
|
||||
|
||||
<span class="kw">const</span> code = <span class="fn">jsQR</span>(imageData.data, imageData.width, imageData.height);
|
||||
<span class="kw">if</span> (code) {
|
||||
console.<span class="fn">log</span>(<span class="str">"Decoded:"</span>, code.data);
|
||||
<span class="cm">// → "https://example.com"</span>
|
||||
}
|
||||
|
||||
<span class="cm">// Read from camera (MediaDevices API):</span>
|
||||
<span class="kw">const</span> stream = <span class="kw">await</span> navigator.mediaDevices.<span class="fn">getUserMedia</span>({ video: { facingMode: <span class="str">'environment'</span> } });
|
||||
<span class="kw">const</span> video = document.<span class="fn">createElement</span>(<span class="str">'video'</span>);
|
||||
video.srcObject = stream;
|
||||
video.<span class="fn">play</span>();
|
||||
<span class="cm">// Then draw video frames to canvas and scan with jsQR</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
<!-- ═══════════════ REGEX ═══════════════ -->
|
||||
<div class="page" id="page-regex">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-asterisk" style="color:var(--pink)"></i> Regex Tester</h2>
|
||||
<p>Test regular expressions with live matching.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div style="display:flex;gap:10px;margin-bottom:12px;">
|
||||
<div style="flex:1;">
|
||||
<div class="panel-label">Pattern</div>
|
||||
<input type="text" id="regexPattern" placeholder="[a-z]+@[a-z]+\.[a-z]+" oninput="testRegex()" />
|
||||
</div>
|
||||
<div style="width:80px;">
|
||||
<div class="panel-label">Flags</div>
|
||||
<input type="text" id="regexFlags" value="gi" placeholder="gi" oninput="testRegex()" style="width:100%;" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label">Test String</div>
|
||||
<textarea id="regexInput" placeholder="Enter text to test against..." oninput="testRegex()" style="min-height:120px;"></textarea>
|
||||
<div class="panel-label" style="margin-top:14px;">Matches</div>
|
||||
<div id="regexResults" style="background:var(--bg-input);border:1px solid var(--border);border-radius:var(--radius-sm);padding:14px;font-family:var(--font-mono);font-size:0.85rem;min-height:60px;line-height:1.8;word-break:break-all;"></div>
|
||||
<div id="regexMatchList" style="margin-top:12px;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">Client-side</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">ℹ️ Regex Tester runs entirely <strong>client-side</strong> using JavaScript's native <code>RegExp</code>. No server API is needed. Here's how to use the same logic in your own code:</div>
|
||||
<div class="api-endpoint">
|
||||
<div class="api-desc">Test a regex pattern against a string in JavaScript.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Client-side regex testing — no API call needed</span>
|
||||
<span class="kw">const</span> pattern = <span class="str">"[a-z]+@[a-z]+\\.[a-z]+"</span>;
|
||||
<span class="kw">const</span> flags = <span class="str">"gi"</span>;
|
||||
<span class="kw">const</span> testString = <span class="str">"Contact us at hello@example.com or info@test.org"</span>;
|
||||
|
||||
<span class="kw">const</span> regex = <span class="kw">new</span> <span class="fn">RegExp</span>(pattern, flags);
|
||||
<span class="kw">const</span> matches = [...testString.<span class="fn">matchAll</span>(regex)];
|
||||
|
||||
matches.<span class="fn">forEach</span>((m, i) => {
|
||||
console.<span class="fn">log</span>(<span class="str">`Match ${i+1}: ${m[0]} (index: ${m.index})`</span>);
|
||||
});
|
||||
<span class="cm">// → Match 1: hello@example.com (index: 14)
|
||||
// → Match 2: info@test.org (index: 36)</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
<!-- ═══════════════ SLUG GENERATOR ═══════════════ -->
|
||||
<div class="page" id="page-slugify">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-link" style="color:var(--green)"></i> Slug Generator</h2>
|
||||
<p>Convert any text into a clean, URL-friendly slug.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="slugInput" placeholder="e.g. Hello World! This is My Blog Post #1" style="min-height:80px;" oninput="generateSlug()"></textarea>
|
||||
<div style="display:flex;align-items:center;gap:10px;margin-top:10px;">
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);">Separator:</label>
|
||||
<select id="slugSeparator" style="width:120px;padding:6px 10px;" onchange="generateSlug()">
|
||||
<option value="-" selected>Hyphen (-)</option>
|
||||
<option value="_">Underscore (_)</option>
|
||||
<option value=".">Dot (.)</option>
|
||||
</select>
|
||||
<label style="font-size:0.8rem;color:var(--text-secondary);margin-left:10px;">
|
||||
<input type="checkbox" id="slugLower" checked onchange="generateSlug()"> Lowercase
|
||||
</label>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:16px;">Slug Output</div>
|
||||
<div class="result-row" id="slugResult" style="cursor:pointer;">
|
||||
<div class="label">SLUG</div>
|
||||
<div class="value" id="slugOutput" onclick="copyText(this.textContent)" title="Click to copy">—</div>
|
||||
</div>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyText(document.getElementById('slugOutput').textContent)"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
<div class="status" id="slugStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/slugify</span>
|
||||
<div class="api-desc">Convert text to a URL-friendly slug. Optional: <code>separator</code> (<code>-</code>, <code>_</code>, <code>.</code>) and <code>lowercase</code> (boolean).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/slugify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
text: <span class="str">"Hello World! My Blog Post #1"</span>,
|
||||
separator: <span class="str">"-"</span>,
|
||||
lowercase: <span class="kw">true</span>
|
||||
})
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "hello-world-my-blog-post-1" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
<!-- ═══════════════ SQL FORMATTER ═══════════════ -->
|
||||
<div class="page" id="page-sqlformat">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-database" style="color:var(--accent)"></i> SQL Formatter</h2>
|
||||
<p>Beautify and minify SQL queries with keyword highlighting.</p>
|
||||
</div>
|
||||
<div class="split-panel">
|
||||
<div>
|
||||
<div class="panel-label">Input SQL</div>
|
||||
<textarea id="sqlInput" placeholder="SELECT * FROM users WHERE id = 1 AND active = true ORDER BY name"></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="formatSQL()"><i class="fas fa-magic"></i> Beautify</button>
|
||||
<button class="btn btn-secondary" onclick="minifySQL()"><i class="fas fa-compress"></i> Minify</button>
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('sqlInput').value='';document.getElementById('sqlOutput').value='';document.getElementById('sqlStatus').className='status';"><i class="fas fa-trash"></i> Clear</button>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Output</div>
|
||||
<textarea id="sqlOutput" readonly placeholder="Formatted SQL will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('sqlOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="status" id="sqlStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/sql/format</span>
|
||||
<div class="api-desc">Beautify a SQL query with indentation.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/sql/format`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ sql: <span class="str">"SELECT * FROM users WHERE active = 1"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "SELECT\n *\nFROM\n users\nWHERE\n active = 1" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/sql/minify</span>
|
||||
<div class="api-desc">Minify a SQL query to a single line.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/sql/minify`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ sql: <span class="str">"SELECT\n *\nFROM\n users"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: "SELECT * FROM users" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
<!-- ═══════════════ TEXT ENCODER ═══════════════ -->
|
||||
<div class="page" id="page-textenc">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-language" style="color:var(--cyan)"></i> Text Encoder / Decoder</h2>
|
||||
<p>Encode and decode text in ROT13, Binary, Morse Code, and more.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="panel-label">Input Text</div>
|
||||
<textarea id="textencInput" placeholder="Type or paste text here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-primary" onclick="encodeText('rot13')"><i class="fas fa-sync"></i> ROT13</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('binary')"><i class="fas fa-microchip"></i> Binary</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('morse')"><i class="fas fa-broadcast-tower"></i> Morse</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('reverse')"><i class="fas fa-undo"></i> Reverse</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('leetspeak')"><i class="fas fa-skull"></i> L33t</button>
|
||||
<button class="btn btn-secondary" onclick="encodeText('upside_down')"><i class="fas fa-arrows-alt-v"></i> Upside Down</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="panel-label" style="margin-top:16px;">Output</div>
|
||||
<textarea id="textencOutput" readonly placeholder="Encoded output will appear here..."></textarea>
|
||||
<div class="btn-group">
|
||||
<button class="btn btn-green" onclick="copyOutput('textencOutput')"><i class="fas fa-copy"></i> Copy</button>
|
||||
<button class="btn btn-secondary" onclick="swapTextEnc()"><i class="fas fa-exchange-alt"></i> Swap</button>
|
||||
</div>
|
||||
<div class="status" id="textencStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL, e.g. <code>https://winnieapi-v2.yourdomain.com</code></div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/text/encode</span>
|
||||
<div class="api-desc">Encode text with the specified method (rot13, binary, morse, reverse, leetspeak, upside_down).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/text/encode`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ text: <span class="str">"Hello World"</span>, method: <span class="str">"morse"</span> })
|
||||
});
|
||||
<span class="cm">// → { success: true, result: ".... . .-.. .-.. --- / .-- --- .-. .-.. -.." }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- ═══════════════ TIMESTAMP ═══════════════ -->
|
||||
<div class="page" id="page-timestamp">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-clock" style="color:var(--cyan)"></i> Timestamp Converter</h2>
|
||||
<p>Convert between Unix timestamps and human-readable dates.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="panel-label">Input (Unix timestamp, ISO date, or "now")</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="text" id="tsInput" placeholder="1709683200 or 2024-03-06 or now" />
|
||||
<button class="btn btn-primary" onclick="convertTimestamp()"><i class="fas fa-sync"></i> Convert</button>
|
||||
<button class="btn btn-secondary" onclick="document.getElementById('tsInput').value='now';convertTimestamp()">Now</button>
|
||||
</div>
|
||||
<div id="tsResults" style="margin-top:16px;"></div>
|
||||
<div class="status" id="tsStatus"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/timestamp</span>
|
||||
<div class="api-desc">Convert between Unix timestamps, ISO dates, and relative time. Accepts 10-digit (seconds), 13-digit (ms), ISO strings, or "now".</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="cm">// From Unix timestamp:</span>
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/timestamp`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ value: <span class="str">"1709683200"</span> })
|
||||
<span class="cm">// Also: "now", "2024-03-06", or "1709683200000" (ms)</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, unix: 1709683200, unixMs: 1709683200000,
|
||||
// iso: "2024-03-06T00:00:00.000Z", utc: "Wed, 06 Mar 2024...",
|
||||
// local: "...", relative: "1y ago" }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<!-- ═══════════════ URL SHORTENER ═══════════════ -->
|
||||
<div class="page" id="page-url">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-link" style="color:var(--green)"></i> URL Shortener</h2>
|
||||
<p>Paste a long URL and get a short, shareable link.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div class="panel-label">Long URL</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="url" id="urlInput" placeholder="https://example.com/very/long/url" />
|
||||
<button class="btn btn-primary" onclick="shortenURL()"><i class="fas fa-bolt"></i> Shorten</button>
|
||||
</div>
|
||||
<div class="status" id="urlStatus"></div>
|
||||
<div class="short-url-result" id="urlResult">
|
||||
<div class="result-card">
|
||||
<div class="panel-label">Your Short URL</div>
|
||||
<div class="short-url-display">
|
||||
<a id="shortUrlLink" href="#" target="_blank"></a>
|
||||
<button class="btn btn-sm btn-secondary" onclick="copyShortUrl()"><i class="fas fa-copy"></i></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="margin-top:28px;"><div class="panel-label">Recent Links</div><div id="urlHistory" style="color:var(--text-muted);font-size:0.85rem;">No links shortened yet.</div></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/url/shorten</span>
|
||||
<div class="api-desc">Create a shortened URL.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/url/shorten`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ url: <span class="str">"https://github.com/long/repo/path"</span> })
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, shortUrl: "http://localhost:3000/s/abc1234", id: "abc1234" }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/api/url/stats/:id</span>
|
||||
<div class="api-desc">Get click stats for a shortened URL.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/url/stats/abc1234`</span>);
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, url: "https://...", clicks: 42, createdAt: "..." }</span></div>
|
||||
</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method get">GET</span><span class="api-path">/s/:id</span>
|
||||
<div class="api-desc">Redirect to the original URL (use in browser).</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="cm">// Simply open in browser or use as a link:</span>
|
||||
window.<span class="fn">open</span>(<span class="var">`${BASE_URL}/s/abc1234`</span>);
|
||||
<span class="cm">// → 302 redirect to the original URL</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<!-- ═══════════════ UUID ═══════════════ -->
|
||||
<div class="page" id="page-uuid">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fas fa-hashtag" style="color:var(--accent)"></i> UUID Generator</h2>
|
||||
<p>Generate random v4 UUIDs.</p>
|
||||
</div>
|
||||
<div style="max-width:640px;">
|
||||
<div style="display:flex;gap:10px;align-items:center;margin-bottom:16px;">
|
||||
<label style="font-size:0.85rem;color:var(--text-secondary);">Count:</label>
|
||||
<input type="number" id="uuidCount" value="5" min="1" max="100" style="width:80px;" />
|
||||
<button class="btn btn-primary" onclick="generateUUIDs()"><i class="fas fa-dice"></i> Generate</button>
|
||||
<button class="btn btn-green" onclick="copyUUIDs()"><i class="fas fa-copy"></i> Copy All</button>
|
||||
</div>
|
||||
<div id="uuidResults" style="font-family:var(--font-mono);font-size:0.85rem;"></div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/uuid</span>
|
||||
<div class="api-desc">Generate random v4 UUIDs. Batch up to 100.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/uuid`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({ count: 5 }) <span class="cm">// 1–100</span>
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, uuids: ["550e8400-e29b-41d4-...", ...] }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
<!-- ═══════════════ YOUTUBE ═══════════════ -->
|
||||
<div class="page" id="page-youtube">
|
||||
<button class="back-btn" onclick="showPage('home')">← Back to Tools</button>
|
||||
<div class="section-header">
|
||||
<h2><i class="fab fa-youtube" style="color:var(--red)"></i> YouTube Tool</h2>
|
||||
<p>Extract metadata, thumbnails, and embed codes from any YouTube video.</p>
|
||||
</div>
|
||||
<div style="max-width:720px;">
|
||||
<div class="panel-label">YouTube URL</div>
|
||||
<div style="display:flex;gap:10px;">
|
||||
<input type="url" id="ytInput" placeholder="https://www.youtube.com/watch?v=..." />
|
||||
<button class="btn btn-red" onclick="fetchYouTube()"><i class="fas fa-search"></i> Fetch</button>
|
||||
</div>
|
||||
<div class="status" id="ytStatus"></div>
|
||||
<div class="yt-result" id="ytResult">
|
||||
<img class="yt-thumb" id="ytThumb" src="" alt="Thumbnail" />
|
||||
<div class="yt-info">
|
||||
<h3 id="ytTitle"></h3>
|
||||
<div class="author">by <a id="ytAuthor" href="#" target="_blank"></a></div>
|
||||
<div class="panel-label">Thumbnail URLs (click to copy)</div>
|
||||
<div style="display:flex;flex-direction:column;gap:4px;margin-bottom:14px;">
|
||||
<code id="ytThumbMax" style="font-size:0.75rem;color:var(--cyan);word-break:break-all;cursor:pointer;" onclick="copyText(this.textContent)"></code>
|
||||
<code id="ytThumbHQ" style="font-size:0.75rem;color:var(--cyan);word-break:break-all;cursor:pointer;" onclick="copyText(this.textContent)"></code>
|
||||
</div>
|
||||
<div class="panel-label">Embed Code</div>
|
||||
<textarea id="ytEmbed" readonly style="min-height:50px;font-size:0.75rem;margin-bottom:12px;"></textarea>
|
||||
<div class="yt-links">
|
||||
<button class="btn btn-primary btn-sm" onclick="copyOutput('ytEmbed')"><i class="fas fa-copy"></i> Copy Embed</button>
|
||||
<a id="ytWatch" class="btn btn-secondary btn-sm" href="#" target="_blank" style="text-decoration:none;"><i class="fas fa-external-link-alt"></i> YouTube</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="api-usage">
|
||||
<button class="api-usage-toggle" onclick="toggleApiUsage(this)"><span><i class="fas fa-terminal"></i> API Usage <span class="badge">REST</span></span><i class="fas fa-chevron-down"></i></button>
|
||||
<div class="api-usage-body">
|
||||
<div class="api-baseurl-note">All examples use <code>BASE_URL</code> — set it to your deployment URL.</div>
|
||||
<div class="api-endpoint">
|
||||
<span class="api-method post">POST</span><span class="api-path">/api/youtube/info</span>
|
||||
<div class="api-desc">Extract metadata, thumbnails, and embed info from a YouTube video.</div>
|
||||
<div class="api-code"><button class="api-code-copy" onclick="copyApiCode(this)">Copy</button><span class="kw">const</span> <span class="var">BASE_URL</span> = <span class="str">"http://localhost:3000"</span>;
|
||||
|
||||
<span class="kw">const</span> res = <span class="kw">await</span> <span class="fn">fetch</span>(<span class="var">`${BASE_URL}/api/youtube/info`</span>, {
|
||||
method: <span class="str">"POST"</span>,
|
||||
headers: { <span class="str">"Content-Type"</span>: <span class="str">"application/json"</span> },
|
||||
body: JSON.<span class="fn">stringify</span>({
|
||||
url: <span class="str">"https://www.youtube.com/watch?v=dQw4w9WgXcQ"</span>
|
||||
})
|
||||
});
|
||||
<span class="kw">const</span> data = <span class="kw">await</span> res.<span class="fn">json</span>();
|
||||
<span class="cm">// → { success: true, videoId: "dQw4w9WgXcQ", title: "...",
|
||||
// author: "...", thumbnail: "https://img.youtube.com/...",
|
||||
// embedUrl: "https://youtube.com/embed/...", ... }</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user