From 53c61dfd18b19f3abed4dc5351a8812d73da14be Mon Sep 17 00:00:00 2001 From: Suraj Verma <5441349+surajverma@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:58:10 +0530 Subject: [PATCH 01/42] refactor: centralize tool metadata and privacy contract --- src/components/PrivacyBadge.tsx | 12 ++++++++ src/components/ToolPage.tsx | 16 +++++++++++ src/config/tools.ts | 49 +++++++++++++++++++++++++++++++++ src/types/tool.ts | 24 ++++++++++++++++ 4 files changed, 101 insertions(+) create mode 100644 src/components/PrivacyBadge.tsx create mode 100644 src/components/ToolPage.tsx create mode 100644 src/config/tools.ts create mode 100644 src/types/tool.ts diff --git a/src/components/PrivacyBadge.tsx b/src/components/PrivacyBadge.tsx new file mode 100644 index 0000000..0095e95 --- /dev/null +++ b/src/components/PrivacyBadge.tsx @@ -0,0 +1,12 @@ +export default function PrivacyBadge() { + return ( +
+ + Processed locally + + No upload + + No tracking +
+ ); +} diff --git a/src/components/ToolPage.tsx b/src/components/ToolPage.tsx new file mode 100644 index 0000000..23eea1a --- /dev/null +++ b/src/components/ToolPage.tsx @@ -0,0 +1,16 @@ +import Link from 'next/link'; +import PrivacyBadge from './PrivacyBadge'; + +export default function ToolPage({ title, description, children }: { title: string; description: string; children: React.ReactNode }) { + return ( +
+
+

{title}

+

{description}

+
+
+ {children} +
← Back to All Tools
+
+ ); +} diff --git a/src/config/tools.ts b/src/config/tools.ts new file mode 100644 index 0000000..0914941 --- /dev/null +++ b/src/config/tools.ts @@ -0,0 +1,49 @@ +import type { Tool } from '@/types/tool'; + +export const TOOLS: Tool[] = [ + ['image-compressor','Image Compressor','Reduce image file sizes locally.','Images',['image','compress','optimize','media']], + ['regex-tester','Regex Tester & Explainer','Test regular expressions and understand their matches.','Developer',['text','developer','regex','code']], + ['text-diff','Text Diff Checker','Compare two pieces of text and highlight differences.','Text & Data',['text','compare','diff']], + ['color-converter','Color Code Converter','Convert HEX, RGB and HSL color values.','Converters',['color','design','developer','converter']], + ['placeholder-generator','Placeholder Image Generator','Create custom placeholder images locally.','Images',['image','design','placeholder']], + ['text-analyzer','Text Statistics / Analyzer','Count words, characters, sentences and more.','Text & Data',['text','stats','analyzer','counter']], + ['image-metadata','Image Metadata Remover','View and remove EXIF metadata from JPEG images.','Privacy & Security',['image','privacy','exif','metadata','security']], + ['markdown-editor','Markdown Editor','Edit Markdown with a live local preview.','Text & Data',['text','markdown','editor','writing']], + ['csv-converter','CSV to JSON/XML Converter','Convert CSV data to JSON or XML locally.','Converters',['csv','json','xml','converter','data','developer']], + ['contrast-checker','Text Contrast Checker','Check color contrast against accessibility guidance.','Accessibility',['accessibility','color','design','wcag']], + ['image-resizer','Image Resizer','Resize images to specific dimensions locally.','Images',['image','resize','dimensions','optimize']], + ['image-cropper','Image Cropper','Crop images locally in your browser.','Images',['image','crop','edit','dimensions']], + ['lorem-ipsum-generator','Lorem Ipsum Generator','Generate placeholder text.','Generators',['text','generator','placeholder','design']], + ['regex-generator','Easy Regex Generator','Build common regular expressions without memorizing syntax.','Developer',['regex','generator','developer','text','code']], + ['find-replace','Find & Replace in Text','Find and replace text with optional regex support.','Text & Data',['text','find','replace','regex']], + ['data-anonymizer','Data Anonymizer','Detect and redact common sensitive data locally.','Privacy & Security',['text','privacy','security','anonymize','redact']], + ['css-specificity-calculator','CSS Specificity Calculator','Calculate and explain CSS selector specificity.','Developer',['css','developer','specificity','design']], + ['unit-converter','Unit Converter','Convert common measurement units.','Converters',['converter','units','measurement','math']], + ['http-status-codes','HTTP Status Code Explainer','Look up HTTP status codes and meanings.','Developer',['http','developer','reference','network']], + ['list-cleaner','List Cleaner & Formatter','Sort, deduplicate and format text lists.','Text & Data',['text','list','format','cleaner','sort']], + ['html-tag-explainer','HTML Tag Explainer','Reference standard HTML tags and their uses.','Developer',['html','developer','reference','code']], + ['file-hash','File Hash & Checksum','Generate SHA-256, SHA-384 and SHA-512 checksums without uploading files.','Privacy & Security',['privacy','hash','checksum','file','security']], + ['json-toolbox','JSON Toolbox','Format, minify, validate and sort JSON locally.','Developer',['json','format','validate','developer','data']], + ['url-cleaner','Tracking URL Cleaner','Remove common tracking parameters and inspect URL parts locally.','Privacy & Security',['privacy','url','tracking','utm','cleaner']], + ['password-generator','Password & Passphrase Generator','Generate cryptographically secure passwords and passphrases locally.','Privacy & Security',['password','security','privacy','generator']], + ['encoder-decoder','Encoder / Decoder','Encode and decode Base64, URLs, HTML entities and Unicode text.','Developer',['base64','url','html','unicode','encode','decode']], + ['jwt-inspector','JWT Inspector','Decode JWT headers and payloads locally without sending tokens anywhere.','Privacy & Security',['jwt','token','developer','privacy','security']], + ['timestamp-tool','Timestamp & Date Toolkit','Convert Unix timestamps, ISO dates, UTC and local time.','Converters',['date','time','timestamp','unix','converter']], + ['uuid-generator','UUID Generator','Generate and validate UUIDs locally.','Generators',['uuid','generator','developer','identifier']], + ['browser-privacy','Browser Privacy Inspector','See what your browser exposes to websites without transmitting the results.','Privacy & Security',['privacy','browser','fingerprint','security']], + ['text-privacy-cleaner','Text Privacy Cleaner','Reveal or remove hidden Unicode, control and zero-width characters.','Privacy & Security',['privacy','text','unicode','cleaner','security']], + ['image-converter','Image Format Converter','Convert supported images to PNG, JPEG or WebP locally.','Images',['image','convert','png','jpeg','webp']], +].map(([slug,name,description,category,tags], index) => ({ + id: String(index + 1), + slug: `/${slug}`, + name, + description, + category, + tags, + processing: 'local', + networkRequired: false, + storesData: false, +})) as Tool[]; + +export const TOOL_BY_SLUG = new Map(TOOLS.map((tool) => [tool.slug, tool])); +export const TOOL_CATEGORIES = [...new Set(TOOLS.map((tool) => tool.category))]; diff --git a/src/types/tool.ts b/src/types/tool.ts new file mode 100644 index 0000000..707c9a8 --- /dev/null +++ b/src/types/tool.ts @@ -0,0 +1,24 @@ +export type ToolCategory = + | 'Privacy & Security' + | 'PDF & Documents' + | 'Images' + | 'Text & Data' + | 'Developer' + | 'Accessibility' + | 'Converters' + | 'Generators'; + +export interface Tool { + id: string; + name: string; + description: string; + slug: string; + category: ToolCategory; + tags: string[]; + processing: 'local'; + networkRequired: false; + storesData: false; + icon?: string; + featured?: boolean; + new?: boolean; +} From b5e169bb18f0d72fdfe3d0d8885a9ac1fa2aab6d Mon Sep 17 00:00:00 2001 From: Suraj Verma <5441349+surajverma@users.noreply.github.com> Date: Fri, 4 Sep 2026 17:59:10 +0530 Subject: [PATCH 02/42] feat: add first local-only privacy tools --- src/app/file-hash/page.tsx | 13 +++++++++++++ src/app/json-toolbox/page.tsx | 16 ++++++++++++++++ src/app/password-generator/page.tsx | 9 +++++++++ src/app/url-cleaner/page.tsx | 6 ++++++ src/components/LocalToolLayout.tsx | 8 ++++++++ 5 files changed, 52 insertions(+) create mode 100644 src/app/file-hash/page.tsx create mode 100644 src/app/json-toolbox/page.tsx create mode 100644 src/app/password-generator/page.tsx create mode 100644 src/app/url-cleaner/page.tsx create mode 100644 src/components/LocalToolLayout.tsx diff --git a/src/app/file-hash/page.tsx b/src/app/file-hash/page.tsx new file mode 100644 index 0000000..c6929bb --- /dev/null +++ b/src/app/file-hash/page.tsx @@ -0,0 +1,13 @@ +'use client'; +import { useState } from 'react'; +import LocalToolLayout from '@/components/LocalToolLayout'; + +type Hashes = Record; +const hex = (buffer: ArrayBuffer) => Array.from(new Uint8Array(buffer)).map(b=>b.toString(16).padStart(2,'0')).join(''); + +export default function FileHashPage(){ + const [file,setFile]=useState(null); const [hashes,setHashes]=useState({}); const [busy,setBusy]=useState(false); const [expected,setExpected]=useState(''); + const run=async(f:File)=>{setFile(f);setBusy(true);setHashes({});const data=await f.arrayBuffer();const out:Hashes={};for(const alg of ['SHA-256','SHA-384','SHA-512']) out[alg]=hex(await crypto.subtle.digest(alg,data));setHashes(out);setBusy(false)}; + const normalized=expected.trim().toLowerCase(); const match=normalized ? Object.values(hashes).some(h=>h===normalized) : null; + return
e.target.files?.[0]&&run(e.target.files[0])} className='block w-full rounded border p-3'/>{file&&

{file.name} · {(file.size/1024).toFixed(1)} KB

}{busy&&

Calculating hashes...

}
{Object.entries(hashes).map(([alg,value])=>
)}
{Object.keys(hashes).length>0&&
setExpected(e.target.value)} placeholder='Paste expected SHA checksum' className='mt-1 w-full rounded border p-2 font-mono text-xs'/>{match!==null&&

{match?'Checksum matches.':'No generated checksum matches.'}

}
}
+} diff --git a/src/app/json-toolbox/page.tsx b/src/app/json-toolbox/page.tsx new file mode 100644 index 0000000..0887bbe --- /dev/null +++ b/src/app/json-toolbox/page.tsx @@ -0,0 +1,16 @@ +'use client'; +import { useMemo, useState } from 'react'; +import LocalToolLayout from '@/components/LocalToolLayout'; + +function sortObject(value: unknown): unknown { + if (Array.isArray(value)) return value.map(sortObject); + if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value as Record).sort(([a],[b]) => a.localeCompare(b)).map(([k,v]) => [k, sortObject(v)])); + return value; +} + +export default function JsonToolboxPage() { + const [input, setInput] = useState('{"name":"ToolStack","privacy":true,"count":32}'); + const [mode, setMode] = useState<'pretty'|'minify'|'sort'>('pretty'); + const result = useMemo(() => { try { const parsed = JSON.parse(input); return { output: mode === 'minify' ? JSON.stringify(parsed) : JSON.stringify(mode === 'sort' ? sortObject(parsed) : parsed, null, 2), error: '' }; } catch (e) { return { output: '', error: (e as Error).message }; } }, [input, mode]); + return
{(['pretty','minify','sort'] as const).map(x => )}