Думаю, многие сталкивались с этим: нужно быстро объединить PDF, проверить подсеть или декодировать JWT. Открываешь первый попавшийся сервис, загружаешь файл и надеешься что данные никуда не сохраняются.
Главный принцип простой: если задачу можно выполнить прямо в браузере, зачем вообще нужен сервер?
Так появились два проекта. Каждый — один HTML файл.

Как это работает
Зависимости подгружаются с CDN по требованию — только когда инструмент реально открыт
async function loadLibrary(url) { return new Promise((resolve, reject) => { const script = document.createElement('script'); script.src = url; script.onload = resolve; script.onerror = reject; document.head.appendChild(script); });}
Это позволяет держать начальный размер маленьким и не грузить то что не нужно. localStorage сохраняет состояние между сессиями автоматически
input.addEventListener('input', () => { localStorage.setItem(`tool_${toolId}_input`, input.value);});input.value = localStorage.getItem(`tool_${toolId}_input`) || '';
DarkenAmber IT Tools
17 инструментов для сети и разработки. 194KB, ноль зависимостей в начальной загрузке.


Хэши через Web Crypto API без внешних библиотек
const buffer = await crypto.subtle.digest( 'SHA-256', new TextEncoder().encode(input));const hash = Array.from(new Uint8Array(buffer)) .map(b => b.toString(16).padStart(2, '0')) .join('');
JWT декодируется без библиотек — base64url отличается от стандартного base64
const [header, payload] = token.split('.') .slice(0, 2) .map(part => JSON.parse(atob( part.replace(/-/g, '+').replace(/_/g, '/') )));
IP калькулятор считает через побитовые операции
function calculateSubnet(ip, prefix) { const mask = ~(0xFFFFFFFF >>> prefix) >>> 0; const network = (ipToInt(ip) & mask) >>> 0; const broadcast = (network | ~mask) >>> 0; return { network: intToIp(network), broadcast: intToIp(broadcast), hosts: Math.pow(2, 32 - prefix) - 2 };}

GitHub: https://github.com/DarkenAmber/DarkenAmber-it-tools
Live: https://darkenamber.github.io/DarkenAmber-it-tools
ZeroOffice
PDF, изображения, текст, графики, AI ассистент. 480KB, работает офлайн.

PDF манипуляции через pdf-lib
async function mergePdfs(files) { const merged = await PDFLib.PDFDocument.create(); for (const file of files) { const bytes = await file.arrayBuffer(); const doc = await PDFLib.PDFDocument.load(bytes); const pages = await merged.copyPages(doc, doc.getPageIndices()); pages.forEach(p => merged.addPage(p)); } return merged.save();}
Рендеринг PDF для предпросмотра через PDF.js
const page = await pdf.getPage(1);const viewport = page.getViewport({ scale: 1.5 });await page.render({ canvasContext: canvas.getContext('2d'), viewport}).promise;


Chart Builder строит графики через Chart.js и экспортирует в PNG, SVG и Excel через ExcelJS — всё локально.
AI инструменты работают через Claude API напрямую из браузера — ZeroOffice не посередине:
const response = await fetch('https://api.anthropic.com/v1/messages', { headers: { 'x-api-key': localStorage.getItem('claude_api_key'), 'anthropic-version': '2023-06-01', }, body: JSON.stringify({ model: 'claude-3-haiku-20240307', ... })});
GitHub: https://github.com/DarkenAmber/ZeroOffice
Live: https://darkenamber.github.io/ZeroOffice
Практика показала что большинство повседневных задач с файлами и данными можно решить без сервера. Браузер получил достаточно API — Web Crypto, Canvas, File System Access, Web Workers — чтобы делать полноценные инструменты локально.
Оба проекта ещё сырые и дорабатываются. В планах новые инструменты, локализация и улучшение мобильной версии. Буду рад замечаниям и идеям в комментариях.
ссылка на оригинал статьи https://habr.com/ru/articles/1050748/