Думаю, многие сталкивались с этим: нужно быстро объединить 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 - чтобы делать полноценные инструменты локально.
Оба проекта ещё сырые и дорабатываются. В планах новые инструменты, локализация и улучшение мобильной версии. Буду рад замечаниям и идеям в комментариях.






























