From b7b1fd306aeefde977665e65b844381ff8061239 Mon Sep 17 00:00:00 2001 From: pawka Date: Sun, 15 Mar 2026 16:34:31 -0300 Subject: [PATCH] init commit --- .gitignore | 23 + .npmrc | 1 + .nvmrc | 1 + .vscode/extensions.json | 3 + CLAUDE.md | 104 ++ Dockerfile | 16 + README.md | 86 ++ docker-compose.yml | 10 + jsconfig.json | 19 + package-lock.json | 1854 +++++++++++++++++++++++++++++ package.json | 23 + src/app.d.ts | 13 + src/app.html | 16 + src/lib/clipboard.js | 6 + src/lib/images/svelte-logo.svg | 1 + src/lib/md5.js | 66 + src/routes/+layout.svelte | 134 +++ src/routes/+page.js | 3 + src/routes/+page.svelte | 71 ++ src/routes/api-key/+page.svelte | 286 +++++ src/routes/base/+page.svelte | 317 +++++ src/routes/cron/+page.svelte | 370 ++++++ src/routes/hash/+page.svelte | 136 +++ src/routes/json/+page.svelte | 253 ++++ src/routes/layout.css | 132 ++ src/routes/object-id/+page.svelte | 130 ++ src/routes/string/+page.svelte | 217 ++++ src/routes/uuid/+page.svelte | 213 ++++ static/favicon.svg | 1 + static/robots.txt | 3 + svelte.config.js | 13 + vite.config.js | 6 + 32 files changed, 4527 insertions(+) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .nvmrc create mode 100644 .vscode/extensions.json create mode 100644 CLAUDE.md create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 jsconfig.json create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/app.d.ts create mode 100644 src/app.html create mode 100644 src/lib/clipboard.js create mode 100644 src/lib/images/svelte-logo.svg create mode 100644 src/lib/md5.js create mode 100644 src/routes/+layout.svelte create mode 100644 src/routes/+page.js create mode 100644 src/routes/+page.svelte create mode 100644 src/routes/api-key/+page.svelte create mode 100644 src/routes/base/+page.svelte create mode 100644 src/routes/cron/+page.svelte create mode 100644 src/routes/hash/+page.svelte create mode 100644 src/routes/json/+page.svelte create mode 100644 src/routes/layout.css create mode 100644 src/routes/object-id/+page.svelte create mode 100644 src/routes/string/+page.svelte create mode 100644 src/routes/uuid/+page.svelte create mode 100644 static/favicon.svg create mode 100644 static/robots.txt create mode 100644 svelte.config.js create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b462cb --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +node_modules + +# Output +.output +.vercel +.netlify +.wrangler +/.svelte-kit +/build + +# OS +.DS_Store +Thumbs.db + +# Env +.env +.env.* +!.env.example +!.env.test + +# Vite +vite.config.js.timestamp-* +vite.config.ts.timestamp-* diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b6f27f1 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +engine-strict=true diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..01cee70 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +v25 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..28d1e67 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["svelte.svelte-vscode"] +} diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..38e31e6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,104 @@ +# CLAUDE.md — toolkit + +## Project + +SvelteKit 2 + Svelte 5 developer toolkit. All tools run client-side — no server-side logic. + +## Language + +**JavaScript only** (`lang="js"` in Svelte files). No TypeScript. Use JSDoc for type annotations. + +```svelte + + %sveltekit.head% + + +
%sveltekit.body%
+ + diff --git a/src/lib/clipboard.js b/src/lib/clipboard.js new file mode 100644 index 0000000..b86f5c5 --- /dev/null +++ b/src/lib/clipboard.js @@ -0,0 +1,6 @@ +/** + * @param {string} text + * @returns {void} + */ +export const copy = (text) => void navigator.clipboard.writeText(text); + diff --git a/src/lib/images/svelte-logo.svg b/src/lib/images/svelte-logo.svg new file mode 100644 index 0000000..49492a8 --- /dev/null +++ b/src/lib/images/svelte-logo.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/src/lib/md5.js b/src/lib/md5.js new file mode 100644 index 0000000..0e7c7db --- /dev/null +++ b/src/lib/md5.js @@ -0,0 +1,66 @@ +/** + * Compact MD5 (no dependencies). Accepts a UTF-8 string or raw bytes. + * @param {string | Uint8Array} input + * @returns {string} hex digest + */ + +/** @param {number} x @param {number} y @returns {number} */ +function add(x, y) { + const lsw = (x & 0xffff) + (y & 0xffff); + return (((x >> 16) + (y >> 16) + (lsw >> 16)) << 16) | (lsw & 0xffff); +} + +/** @param {number} n @param {number} s @returns {number} */ +const rol = (n, s) => (n << s) | (n >>> (32 - s)); + +// Per-round shift amounts (4 rounds × 4 positions) +const S = [7, 12, 17, 22, 5, 9, 14, 20, 4, 11, 16, 23, 6, 10, 15, 21]; + +// Round constants: floor(abs(sin(i+1)) * 2^32) +const K = new Int32Array([ + -680876936, -389564586, 606105819, -1044525330, -176418897, 1200080426, -1473231341, -45705983, + 1770035416,-1958414417, -42063, -1990404162, 1804603682, -40341101, -1502002290, 1236535329, + -165796510,-1069501632, 643717713, -373897302, -701558691, 38016083, -660478335, -405537848, + 568446438,-1019803690, -187363961, 1163531501,-1444681467, -51403784, 1735328473, -1926607734, + -378558,-2022574463, 1839030562, -35309556,-1530992060, 1272893353, -155497632, -1094730640, + 681279174, -358537222, -722521979, 76029189, -640364487, -421815835, 530742520, -995338651, + -198630844, 1126891415,-1416354905, -57434055, 1700485571,-1894986606, -1051523, -2054922799, + 1873313359, -30611744,-1560198380, 1309151649, -145523070,-1120210379, 718787259, -343485551, +]); + +/** @param {string | Uint8Array} input @returns {string} */ +export function md5(input) { + const bytes = input instanceof Uint8Array ? input : new TextEncoder().encode(input); + const len = bytes.length; + + // Pad to 512-bit blocks: append 0x80, zeros, then 64-bit LE bit-length + const padded = new Uint8Array((((len + 8) >> 6) + 1) << 6); + padded.set(bytes); + padded[len] = 0x80; + new DataView(padded.buffer).setUint32(padded.length - 8, len * 8, true); + + let a = 0x67452301, b = 0xefcdab89, c = 0x98badcfe, d = 0x10325476; + + for (let off = 0; off < padded.length; off += 64) { + const M = new Int32Array(padded.buffer, off, 16); + const oa = a, ob = b, oc = c, od = d; + + for (let j = 0; j < 64; j++) { + let f, g; + if (j < 16) { f = (b & c) | (~b & d); g = j; } + else if (j < 32) { f = (b & d) | (c & ~d); g = (5 * j + 1) & 15; } + else if (j < 48) { f = b ^ c ^ d; g = (3 * j + 5) & 15; } + else { f = c ^ (b | ~d); g = (7 * j) & 15; } + + const tmp = add(add(a, f), add(M[g], K[j])); + a = d; d = c; c = b; + b = add(b, rol(tmp, S[j >> 4 << 2 | j & 3])); + } + + a = add(a, oa); b = add(b, ob); c = add(c, oc); d = add(d, od); + } + + return [a, b, c, d] + .map(n => Array.from({ length: 4 }, (_, j) => ((n >>> (j * 8)) & 0xff).toString(16).padStart(2, '0')).join('')) + .join(''); +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte new file mode 100644 index 0000000..d567af4 --- /dev/null +++ b/src/routes/+layout.svelte @@ -0,0 +1,134 @@ + + +
+ +
+ {@render children()} +
+
+ + diff --git a/src/routes/+page.js b/src/routes/+page.js new file mode 100644 index 0000000..a72419a --- /dev/null +++ b/src/routes/+page.js @@ -0,0 +1,3 @@ +// since there's no dynamic data here, we can prerender +// it so that it gets served as a static asset in production +export const prerender = true; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte new file mode 100644 index 0000000..77fa584 --- /dev/null +++ b/src/routes/+page.svelte @@ -0,0 +1,71 @@ + + Toolkit + + + + + +

About

+ +

+ A collection of browser-based tools for developers.
+ No server, no tracking, no external dependencies - everything runs locally in your browser. +

+ +

Tools

+ + + + {#each tools as tool} + + + + + {/each} + +
{tool.label}{tool.desc}
+ +

Stack

+

+ SvelteKit 2 + + Svelte 5 · + JavaScript with JSDoc · + No runtime dependencies +

+ + diff --git a/src/routes/api-key/+page.svelte b/src/routes/api-key/+page.svelte new file mode 100644 index 0000000..19dde29 --- /dev/null +++ b/src/routes/api-key/+page.svelte @@ -0,0 +1,286 @@ + + API Key Generator + + + + +

API Key Generator

+ +

Format

+ + + {#each formats as f} + + + + + {/each} + +
+ + {f.desc}
+ +{#if selectedFormat === 'custom'} +
+ +
+ +
+
+ {#each Object.entries(ALPHABETS) as [name, abc]} + + {/each} +
+
+{/if} + +

Options

+

+ +   + +

+ +

Keys ~{currentEntropy} bits of entropy

+
+ + +
+ + + {#each keys as k, i} + + + + + + + {/each} + +
{i + 1}{k.key} + +
+ + diff --git a/src/routes/base/+page.svelte b/src/routes/base/+page.svelte new file mode 100644 index 0000000..690de88 --- /dev/null +++ b/src/routes/base/+page.svelte @@ -0,0 +1,317 @@ + + Base Encoder / Decoder + + + + +

Base Encoding

+ +
+
+ {#each ['base64', 'base64url', 'base32', 'base58', 'base16'] as enc} + + {/each} +
+
+ + +
+
+ +{#if tab === 'encode'} +
+
+ Plain text +
+ + +
+
+ + {#if plainInput} + {#if encodeResult.ok && encodeResult.value} +
+ {encodeResult.value} +
+ {:else if !encodeResult.ok} +
{encodeResult.error}
+ {/if} + {/if} +
+{:else} +
+
+ Encoded +
+ + +
+
+ + {#if encodedInput} + {#if decodeResult.ok && decodeResult.value} +
+ {decodeResult.value} +
+ {:else if !decodeResult.ok} +
{decodeResult.error}
+ {/if} + {/if} +
+{/if} + + diff --git a/src/routes/cron/+page.svelte b/src/routes/cron/+page.svelte new file mode 100644 index 0000000..f0a2037 --- /dev/null +++ b/src/routes/cron/+page.svelte @@ -0,0 +1,370 @@ + + Crontab Generator + + + + +

Cron

+ +

Quick presets

+
+ {#each quickPresets as p} + + {/each} +
+ +

Builder

+ + + + {#each fields as f} + + {/each} + + + + + {#each fields as f} + + {/each} + + + {#each fields as f} + + {/each} + + +
{f.label}
+ set(f.name, /** @type {HTMLInputElement} */ (e.target).value)} + size="6" + spellcheck="false" + /> +
+ {#each f.presets as p} + + {/each} +
+ +

Expression

+

+ {expression} + +

+

{description}

+ +

Paste & parse

+

+ parseExpression(manualInput)} + /> + +

+ +

Reference

+ + + + + + + + + + + +
FieldRangeSpecial
Minute0–59* any   , list   - range   / step
Hour0–23
Day (month)1–31
Month1–12
Day (week)0–7 (0,7=Sun)
+ + diff --git a/src/routes/hash/+page.svelte b/src/routes/hash/+page.svelte new file mode 100644 index 0000000..56ee9f6 --- /dev/null +++ b/src/routes/hash/+page.svelte @@ -0,0 +1,136 @@ + + Hash Generator + + + + +

Hash Generator

+ +
+
+
+ Plain text +
+ +
+
+ +
+ + + + {#each hashes as hash} + + + + + + {/each} + +
{hash.label}{hash.value}
+
+ + diff --git a/src/routes/json/+page.svelte b/src/routes/json/+page.svelte new file mode 100644 index 0000000..fd20611 --- /dev/null +++ b/src/routes/json/+page.svelte @@ -0,0 +1,253 @@ + + JSON formatter + + + + +

JSON

+ +
+
+
+ Input +
+ + +
+
+ +
+ +
+
+ + + + {#if mode === 'pretty'} + + {/if} +
+
+ {#if input.trim() && !parsed.ok && parsed.error} + {parsed.error} + {:else if parsed.ok && stats} + + type: {stats.type} + compact: {stats.size} B + pretty: {stats.prettySize} B + + {/if} +
+
+ +
+
+ Output +
+ +
+
+
{parsed.ok ? output : (parsed.error ? '— invalid JSON —' : '')}
+
+
+ + diff --git a/src/routes/layout.css b/src/routes/layout.css new file mode 100644 index 0000000..2406fd7 --- /dev/null +++ b/src/routes/layout.css @@ -0,0 +1,132 @@ +:root { + /* Gruvbox Light Hard */ + --bg: #f9f5d7; + --bg-panel: #fbf1c7; + --bg-raised: #ebdbb2; + --bg-raised-hover: #d5c4a1; + --fg: #3c3836; + --fg-dim: #665c54; + --fg-faint: #928374; + --border: #a89984; + --border-faint: #bdae93; + --active-bg: #3c3836; + --active-fg: #f9f5d7; + --link: #458588; + --link-vis: #b16286; + --link-hover: #9d0006; + --err: #9d0006; + --err-bg: #f9e5e5; + --err-border: #cc241d; + --green: #98971a; + --blue: #458588; + --red: #cc241d; + --bar-bg: #3c3836; + --bar-fg: #f9f5d7; + --bar-dim: #928374; +} + +[data-theme="dark"] { + /* Gruvbox Dark Hard */ + --bg: #1d2021; + --bg-panel: #282828; + --bg-raised: #3c3836; + --bg-raised-hover: #504945; + --fg: #ebdbb2; + --fg-dim: #bdae93; + --fg-faint: #928374; + --border: #665c54; + --border-faint: #504945; + --active-bg: #d5c4a1; + --active-fg: #1d2021; + --link: #83a598; + --link-vis: #d3869b; + --link-hover: #fb4934; + --err: #fb4934; + --err-bg: #3b2020; + --err-border: #cc241d; + --green: #b8bb26; + --blue: #83a598; + --red: #fb4934; + --bar-bg: #282828; + --bar-fg: #ebdbb2; + --bar-dim: #928374; +} + +body { + margin: 0; + background: var(--bg); + color: var(--fg); + font-family: Arial, Helvetica, sans-serif; +} + +a { + color: var(--link); + text-decoration: underline; +} + +a:visited { + color: var(--link-vis); +} + +a:hover { + color: var(--link-hover); +} + +h1 { + margin: 0 0 0.5em 0; + border-bottom: 1px solid var(--border); + padding-bottom: 2px; +} + +h2 { + margin: 0.8em 0 0.3em 0; +} + +input, button, textarea, select { + font-family: Arial, Helvetica, sans-serif; +} + +button { + background: var(--bg-raised); + border: 1px solid var(--border); + padding: 2px 8px; + cursor: pointer; + color: var(--fg); +} + +button:hover { + background: var(--bg-raised-hover); +} + +input[type="text"], +input[type="number"] { + border: 1px solid var(--border); + padding: 2px 4px; + background: var(--bg); + color: var(--fg); +} + +select { + border: 1px solid var(--border); + background: var(--bg); + color: var(--fg); +} + +code { + font-family: monospace; +} + +table { + border-collapse: collapse; +} + +td, th { + border: 1px solid var(--border); + padding: 3px 6px; +} + +hr { + border: none; + border-top: 1px solid var(--border); + margin: 1em 0; +} diff --git a/src/routes/object-id/+page.svelte b/src/routes/object-id/+page.svelte new file mode 100644 index 0000000..c90d99d --- /dev/null +++ b/src/routes/object-id/+page.svelte @@ -0,0 +1,130 @@ + + ObjectId Tools + + + + +

ObjectId

+ +

Generate

+

+ + {generated.slice(0, 8)}{generated.slice(8, 18)}{generated.slice(18, 24)} + +   + + +

+

+ ■ timestamp + ■ random + ■ counter +  —  + Generated: {new Date(parseInt(generated.slice(0, 8), 16) * 1000).toLocaleString()} +

+ +
+ +

Parse

+

+ +

+ +{#if parseError} +

{parseError}

+{/if} + +{#if parsed} + + + + + + + + +
Timestamp (unix){parsed.timestamp}
Date (UTC){parsed.date}
Date (local){parsed.dateLocal}
Random part{parsed.randomPart}
Counter{parsed.counterPart} ({parseInt(parsed.counterPart, 16)})
+{/if} + + diff --git a/src/routes/string/+page.svelte b/src/routes/string/+page.svelte new file mode 100644 index 0000000..1ab228e --- /dev/null +++ b/src/routes/string/+page.svelte @@ -0,0 +1,217 @@ + + String Transform + + + + +

String

+ +
+
+
+ Input +
+ + +
+
+ +
+ +
+
+ {#each buttons as b} + + {/each} +
+ + {#if stats} + + {stats.chars} chars + {stats.words} words + {stats.lines} lines + + {/if} +
+ + {#if output} +
+
+ Result{op ? ` - ${buttons.find(b => b.id === op)?.label}` : ''} +
+ +
+
+
{output}
+
+ {/if} +
+ + diff --git a/src/routes/uuid/+page.svelte b/src/routes/uuid/+page.svelte new file mode 100644 index 0000000..f5f339a --- /dev/null +++ b/src/routes/uuid/+page.svelte @@ -0,0 +1,213 @@ + + UUID Tools + + + + +

UUID

+ +

Generate

+ + + {#each generators as g} + + + + + + + {/each} + +
{g.label}{g.uuid}
+ +
+ +

Parse / Inspect

+

+ +

+ +{#if parseError} +

{parseError}

+{/if} + +{#if parsed} + + + + + {#if parsed.date} + + + + {/if} + {#if parsed.randA} + + + {/if} + {#if parsed.clockSeq} + + + {/if} + +
Version{parsed.version}
Variant{parsed.variant}
Timestamp (ms){parsed.timestamp}
Date (UTC){parsed.date}
Date (local){parsed.dateLocal}
rand_a (12 bits){parsed.randA}
rand_b (62 bits){parsed.randB}
Clock seq{parsed.clockSeq}
Node{parsed.node}
+{/if} + + diff --git a/static/favicon.svg b/static/favicon.svg new file mode 100644 index 0000000..cc5dc66 --- /dev/null +++ b/static/favicon.svg @@ -0,0 +1 @@ +svelte-logo \ No newline at end of file diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..b6dd667 --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,3 @@ +# allow crawling everything by default +User-agent: * +Disallow: diff --git a/svelte.config.js b/svelte.config.js new file mode 100644 index 0000000..0e52b35 --- /dev/null +++ b/svelte.config.js @@ -0,0 +1,13 @@ +import adapter from '@sveltejs/adapter-node'; + +/** @type {import('@sveltejs/kit').Config} */ +const config = { + kit: { + // adapter-auto only supports some environments, see https://svelte.dev/docs/kit/adapter-auto for a list. + // If your environment is not supported, or you settled on a specific environment, switch out the adapter. + // See https://svelte.dev/docs/kit/adapters for more information about adapters. + adapter: adapter() + } +}; + +export default config; diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..bbf8c7d --- /dev/null +++ b/vite.config.js @@ -0,0 +1,6 @@ +import { sveltekit } from '@sveltejs/kit/vite'; +import { defineConfig } from 'vite'; + +export default defineConfig({ + plugins: [sveltekit()] +});