import { useState, useRef, useEffect, useMemo } from “react”; import { ImageIcon, Shrink, RefreshCw, FileStack, FileImage, FileSearch, Receipt, ClipboardList, QrCode, Type, CaseSensitive, AlignLeft, ArrowUpRight, Search, Check, Copy, Download, X, Plus, Printer, Upload, ChevronRight, Shield, Zap, Lock, ArrowRight } from “lucide-react”; // ============================================================ // DESIGN TOKENS — Linear/Stripe-inspired precision // ============================================================ const TOOLS = [ { n: “01”, category: “Image”, slug: “image-resizer”, name: “Image Resizer”, desc: “Resize to exact pixel dimensions”, Icon: ImageIcon }, { n: “02”, category: “Image”, slug: “image-compressor”, name: “Image Compressor”, desc: “Reduce file size, keep quality”, Icon: Shrink }, { n: “03”, category: “Image”, slug: “image-converter”, name: “Image Converter”, desc: “Convert JPG, PNG, and WebP”, Icon: RefreshCw }, { n: “04”, category: “PDF”, slug: “pdf-merge”, name: “PDF Merger”, desc: “Combine PDFs into a single file”, Icon: FileStack }, { n: “05”, category: “PDF”, slug: “pdf-image-to-pdf”, name: “Image to PDF”, desc: “Convert images into PDF pages”, Icon: FileImage }, { n: “06”, category: “PDF”, slug: “pdf-info”, name: “PDF Info Viewer”, desc: “Inspect metadata and properties”, Icon: FileSearch }, { n: “07”, category: “Business”, slug: “invoice-generator”, name: “Invoice Generator”, desc: “Professional invoices, print-ready”, Icon: Receipt }, { n: “08”, category: “Business”, slug: “quote-maker”, name: “Quote & Estimate Maker”, desc: “Quotes with live preview”, Icon: ClipboardList }, { n: “09”, category: “Business”, slug: “qr-code”, name: “QR Code Generator”, desc: “Custom QR codes, your colors”, Icon: QrCode }, { n: “10”, category: “Text”, slug: “word-counter”, name: “Word Counter”, desc: “Words, chars, reading time”, Icon: Type }, { n: “11”, category: “Text”, slug: “case-converter”, name: “Case Converter”, desc: “8 case styles, instant output”, Icon: CaseSensitive }, { n: “12”, category: “Text”, slug: “lorem-ipsum”, name: “Lorem Ipsum Generator”, desc: “Placeholder text on demand”, Icon: AlignLeft }, ]; const CATEGORIES = [“Image”, “PDF”, “Business”, “Text”]; // ============================================================ // GLOBAL STYLES // ============================================================ const GlobalStyles = () => ( ); // ============================================================ // SHARED COMPONENTS // ============================================================ function BrandMark() { return (
av/
); } function TopBar({ onNavigate, page }) { return (
onNavigate(“home”)}> Nexora / Tools
{CATEGORIES.map(c => ( ))}
); } function Breadcrumb({ tool, onNavigate }) { return (
onNavigate(“home”)}>Home onNavigate(“home”)}>{tool.category} Tools {tool.name}
); } function ToolHero({ tool }) { const { Icon } = tool; return (
{tool.n} / {tool.category.toUpperCase()}

{tool.name}

{tool.desc}. Runs entirely in your browser — no uploads, no accounts, no limits.

); } function HowTo({ steps, toolName }) { return (

How to use {toolName}

{steps.map((s, i) => (
{String(i + 1).padStart(2, “0”)}
{s}
))}
); } function FAQ({ items }) { const [open, setOpen] = useState(0); return (

Questions & answers

{items.map((item, i) => (
setOpen(open === i ? -1 : i)}> {item.q}
{open === i &&
{item.a}
}
))}
); } function NativeAd() { return (
Sponsored Ad placement — replace with AdSense, Ezoic, or direct sponsor
); } function CopyButton({ text }) { const [copied, setCopied] = useState(false); const handle = () => { navigator.clipboard.writeText(text).catch(() => {}); setCopied(true); setTimeout(() => setCopied(false), 1500); }; return ( ); } function Footer({ onNavigate }) { return (

Nexora Tools

Twelve precision tools for images, PDFs, business documents, and text. Everything browser-native. Zero uploads.
{CATEGORIES.map(cat => (
{cat}
{TOOLS.filter(t => t.category === cat).map(t => ( onNavigate(t.slug)}> {t.name} ))}
))}
© 2025 AV Nexora · tools.avnexora.com Built privacy-first · no tracking · no uploads
); } // ============================================================ // HOMEPAGE // ============================================================ function HomePage({ onNavigate }) { const [q, setQ] = useState(“”); const filtered = q ? TOOLS.filter(t => t.name.toLowerCase().includes(q.toLowerCase()) || t.desc.toLowerCase().includes(q.toLowerCase()) || t.category.toLowerCase().includes(q.toLowerCase()) ) : null; return ( <>
Browser-native · 12 tools · 2025

Everyday tools,
done in the browser.

Resize, compress, merge, invoice, generate. Twelve focused utilities for images, PDFs, business docs, and text. Nothing uploads anywhere — your files never leave this tab.

Session status
LIVE
Files uploaded
0 bytes
Server processing
Disabled
Compute
Local browser
Account required
None
setQ(e.target.value)} /> ⌘K
{filtered ? (
{String(filtered.length).padStart(2, “0”)}

Search results

for “{q}”
{filtered.length === 0 ? (
No tools match “{q}”. Try “image”, “pdf”, or “text”.
) : filtered.map(t => )}
) : ( CATEGORIES.map((cat, i) => { const items = TOOLS.filter(t => t.category === cat); return (
{String(i + 1).padStart(2, “0”)}

{cat}

{items.length} tools
{items.map(t => )}
); }) )}
); } function ToolTile({ tool, onNavigate }) { const { Icon } = tool; return ( ); } // ============================================================ // IMAGE RESIZER // ============================================================ function ImageResizer({ onNavigate }) { const tool = TOOLS.find(t => t.slug === “image-resizer”); const [img, setImg] = useState(null); const [orig, setOrig] = useState(null); const [w, setW] = useState(“”); const [h, setH] = useState(“”); const [lock, setLock] = useState(true); const [ratio, setRatio] = useState(1); const [result, setResult] = useState(null); const canvasRef = useRef(); const onFile = e => { const file = e.target.files[0]; if (!file) return; const image = new Image(); image.onload = () => { setImg(image); setOrig({ w: image.width, h: image.height, size: (file.size / 1024).toFixed(1) }); setRatio(image.width / image.height); setW(String(image.width)); setH(String(image.height)); setResult(null); }; image.src = URL.createObjectURL(file); }; const handleW = v => { setW(v); if (lock && v) setH(Math.round(parseInt(v) / ratio).toString()); }; const handleH = v => { setH(v); if (lock && v) setW(Math.round(parseInt(v) * ratio).toString()); }; const resize = () => { if (!img) return; const canvas = canvasRef.current; canvas.width = parseInt(w) || orig.w; canvas.height = parseInt(h) || orig.h; canvas.getContext(“2d”).drawImage(img, 0, 0, canvas.width, canvas.height); canvas.toBlob(blob => setResult({ url: URL.createObjectURL(blob), w: canvas.width, h: canvas.height, size: (blob.size / 1024).toFixed(1) }), “image/jpeg”, 0.92); }; return (
{!img ? ( ) : ( <>
{orig.w} × {orig.h}px {orig.size} KB
Width PX
handleW(e.target.value)} />
Height PX
handleH(e.target.value)} />
Lock aspect ratio
{result && ( <>
Resized to {result.w} × {result.h}px · {result.size} KB
Download )} )}
); } // ============================================================ // IMAGE COMPRESSOR // ============================================================ function ImageCompressor({ onNavigate }) { const tool = TOOLS.find(t => t.slug === “image-compressor”); const [img, setImg] = useState(null); const [origSize, setOrigSize] = useState(0); const [quality, setQuality] = useState(75); const [result, setResult] = useState(null); const canvasRef = useRef(); const onFile = e => { const file = e.target.files[0]; if (!file) return; setOrigSize(file.size); const image = new Image(); image.onload = () => { setImg(image); setResult(null); }; image.src = URL.createObjectURL(file); }; const compress = () => { if (!img) return; const canvas = canvasRef.current; canvas.width = img.width; canvas.height = img.height; canvas.getContext(“2d”).drawImage(img, 0, 0); canvas.toBlob(blob => setResult({ url: URL.createObjectURL(blob), size: blob.size }), “image/jpeg”, quality / 100); }; const saved = result ? Math.round((1 – result.size / origSize) * 100) : 0; return (
{!img ? ( ) : ( <>
Quality {quality}%
setQuality(+e.target.value)} />
SMALLERHIGHER QUALITY
{result && ( <>
Saved {saved}% · {(origSize / 1024).toFixed(1)} KB → {(result.size / 1024).toFixed(1)} KB
Download compressed )} )}
); } // ============================================================ // IMAGE CONVERTER // ============================================================ function ImageConverter({ onNavigate }) { const tool = TOOLS.find(t => t.slug === “image-converter”); const [img, setImg] = useState(null); const [format, setFormat] = useState(“image/png”); const [result, setResult] = useState(null); const canvasRef = useRef(); const ext = { “image/jpeg”: “jpg”, “image/png”: “png”, “image/webp”: “webp” }; const onFile = e => { const file = e.target.files[0]; if (!file) return; const image = new Image(); image.onload = () => { setImg(image); setResult(null); }; image.src = URL.createObjectURL(file); }; const convert = () => { if (!img) return; const canvas = canvasRef.current; canvas.width = img.width; canvas.height = img.height; canvas.getContext(“2d”).drawImage(img, 0, 0); canvas.toBlob(blob => setResult({ url: URL.createObjectURL(blob), size: (blob.size / 1024).toFixed(1) }), format, 0.92); }; return (
{!img ? ( ) : ( <>
Output format
{[[“image/jpeg”, “JPG”], [“image/png”, “PNG”], [“image/webp”, “WebP”]].map(([v, l]) => ( ))}
{result && ( <>
Converted · {result.size} KB
Download .{ext[format]} )} )}
); } // ============================================================ // PDF INFO VIEWER // ============================================================ function PDFInfo({ onNavigate }) { const tool = TOOLS.find(t => t.slug === “pdf-info”); const [info, setInfo] = useState(null); const [loading, setLoading] = useState(false); const [fileName, setFileName] = useState(“”); const [fileSize, setFileSize] = useState(0); const onFile = async e => { const file = e.target.files[0]; if (!file) return; setLoading(true); setFileName(file.name); setFileSize(file.size); try { const { PDFDocument } = await import(“https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.esm.min.js”); const buf = await file.arrayBuffer(); const pdf = await PDFDocument.load(buf, { ignoreEncryption: true }); setInfo({ pages: pdf.getPageCount(), title: pdf.getTitle() || “—”, author: pdf.getAuthor() || “—”, subject: pdf.getSubject() || “—”, creator: pdf.getCreator() || “—”, producer: pdf.getProducer() || “—”, created: pdf.getCreationDate()?.toLocaleDateString() || “—”, modified: pdf.getModificationDate()?.toLocaleDateString() || “—”, }); } catch { setInfo({ error: “Could not read metadata. The file may be encrypted or corrupted.” }); } setLoading(false); }; const rows = info && !info.error ? [ [“File name”, fileName], [“File size”, `${(fileSize / 1024).toFixed(1)} KB`], [“Pages”, info.pages], [“Title”, info.title], [“Author”, info.author], [“Subject”, info.subject], [“Creator”, info.creator], [“Producer”, info.producer], [“Created”, info.created], [“Modified”, info.modified], ] : []; return (
{loading &&
Reading PDF…
} {info && !info.error && (
{rows.map(([k, v], i) => (
{k} {v}
))}
)} {info?.error &&
{info.error}
}
); } // ============================================================ // PDF MERGER // ============================================================ function PDFMerger({ onNavigate }) { const tool = TOOLS.find(t => t.slug === “pdf-merge”); const [files, setFiles] = useState([]); const [merged, setMerged] = useState(null); const [loading, setLoading] = useState(false); const [err, setErr] = useState(“”); const onFiles = e => { const newFiles = Array.from(e.target.files).map(f => ({ file: f, id: Math.random() })); setFiles(p => […p, …newFiles]); setMerged(null); setErr(“”); }; const remove = id => setFiles(f => f.filter(x => x.id !== id)); const merge = async () => { if (files.length < 2) return; setLoading(true); setErr(""); try { const { PDFDocument } = await import("https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.esm.min.js"); const out = await PDFDocument.create(); for (const { file } of files) { const buf = await file.arrayBuffer(); const pdf = await PDFDocument.load(buf, { ignoreEncryption: true }); const pages = await out.copyPages(pdf, pdf.getPageIndices()); pages.forEach(p => out.addPage(p)); } const bytes = await out.save(); setMerged(URL.createObjectURL(new Blob([bytes], { type: “application/pdf” }))); } catch { setErr(“Could not merge. Ensure all files are unencrypted PDFs.”); } setLoading(false); }; return (
{files.length > 0 && (
{files.map(({ file, id }, i) => (
{String(i + 1).padStart(2, “0”)} {file.name} {(file.size / 1024).toFixed(0)} KB
))}
)} {err &&
{err}
} {merged && (
Merged successfully Download
)}
); } // ============================================================ // IMAGE TO PDF // ============================================================ function ImageToPDF({ onNavigate }) { const tool = TOOLS.find(t => t.slug === “pdf-image-to-pdf”); const [files, setFiles] = useState([]); const [result, setResult] = useState(null); const [loading, setLoading] = useState(false); const onFiles = e => { const newFiles = Array.from(e.target.files).map(f => ({ file: f, id: Math.random(), preview: URL.createObjectURL(f) })); setFiles(p => […p, …newFiles]); setResult(null); }; const remove = id => setFiles(f => f.filter(x => x.id !== id)); const convert = async () => { if (!files.length) return; setLoading(true); try { const { PDFDocument } = await import(“https://cdn.jsdelivr.net/npm/pdf-lib@1.17.1/dist/pdf-lib.esm.min.js”); const pdf = await PDFDocument.create(); for (const { file } of files) { const buf = await file.arrayBuffer(); const img = file.type === “image/jpeg” ? await pdf.embedJpg(buf) : await pdf.embedPng(buf); const page = pdf.addPage([img.width, img.height]); page.drawImage(img, { x: 0, y: 0, width: img.width, height: img.height }); } const bytes = await pdf.save(); setResult(URL.createObjectURL(new Blob([bytes], { type: “application/pdf” }))); } catch { alert(“Conversion failed. Use valid JPG or PNG files.”); } setLoading(false); }; return (
{files.length > 0 && (
{files.map(({ id, preview }) => (
))}
)} {result && (
PDF created with {files.length} page{files.length !== 1 ? “s” : “”} Download
)}
); } // ============================================================ // INVOICE / QUOTE GENERATOR // ============================================================ function InvoiceGenerator({ onNavigate, isQuote = false }) { const slug = isQuote ? “quote-maker” : “invoice-generator”; const tool = TOOLS.find(t => t.slug === slug); const label = isQuote ? “QUOTE” : “INVOICE”; const [biz, setBiz] = useState({ name: “Your Business”, address: “123 Main St, City”, email: “hello@business.com” }); const [client, setClient] = useState({ name: “Client Name”, address: “456 Client Ave, City” }); const [meta, setMeta] = useState({ number: isQuote ? “QUO-001” : “INV-001”, date: new Date().toISOString().split(“T”)[0] }); const [items, setItems] = useState([{ desc: “Service / Product”, qty: 1, rate: 100 }]); const [tax, setTax] = useState(0); const [notes, setNotes] = useState(“Thank you for your business.”); const addItem = () => setItems(p => […p, { desc: “”, qty: 1, rate: 0 }]); const removeItem = i => setItems(p => p.filter((_, idx) => idx !== i)); const updateItem = (i, key, val) => setItems(p => p.map((x, idx) => idx === i ? { …x, [key]: val } : x)); const subtotal = items.reduce((s, x) => s + (parseFloat(x.qty) || 0) * (parseFloat(x.rate) || 0), 0); const taxAmt = subtotal * (parseFloat(tax) || 0) / 100; const total = subtotal + taxAmt; return (
Business name
setBiz(p => ({ …p, name: e.target.value }))} />
Address
setBiz(p => ({ …p, address: e.target.value }))} />
Email
setBiz(p => ({ …p, email: e.target.value }))} />
Client name
setClient(p => ({ …p, name: e.target.value }))} />
Client address
setClient(p => ({ …p, address: e.target.value }))} />
{label} #
setMeta(p => ({ …p, number: e.target.value }))} />
Date
setMeta(p => ({ …p, date: e.target.value }))} />
{items.map((item, i) => (
updateItem(i, “desc”, e.target.value)} style={{ height: 36, fontSize: 13 }} /> updateItem(i, “qty”, e.target.value)} style={{ height: 36, fontSize: 13 }} /> updateItem(i, “rate”, e.target.value)} style={{ height: 36, fontSize: 13 }} />
))}
Tax %
setTax(e.target.value)} />
Notes