# План начальной реализации: минимальный рабочий MVP Сопутствующие документы: [roadmap.md](./roadmap.md) — общий план по фазам, [analysis.md](./analysis.md) — перечень утилит. ## 0. Зафиксированные решения - **Объём MVP:** только одиночные инструменты «загрузил → настроил → скачал». Пайплайн-workspace (`/workspace`) — сразу следующим шагом после checkpoint'а, не внутри MVP. - **Набор инструментов первого среза:** ~11 базовых EASY (см. §3). Достаточно, чтобы проверить насквозь ядро → реестр → универсальную страницу. - **Стили:** обычный CSS без фреймворка — design-токены на CSS custom properties, ноль зависимостей. - **Стек:** SvelteKit + `@sveltejs/adapter-static` + TypeScript, пакетный менеджер pnpm (уже зафиксирован в корневом `package.json`). Весь код сайта — в `web/`. - **Ядро без DOM:** все пиксельные операции — чистые функции над `PixelImage`; DOM/canvas допускается только в модуле IO и компонентах превью. --- ## 1. Шаг 0 — Каркас проекта 1. Создать SvelteKit-приложение в `web/`: `npx sv create web` (шаблон minimal, TypeScript, без лишних аддонов). 2. `pnpm install` внутри `web/`. 3. `svelte.config.js`: заменить адаптер на `@sveltejs/adapter-static`. 4. `src/routes/+layout.ts`: `export const prerender = true;` — весь сайт prerenderится в статику. 5. `src/app.css`: дизайн-токены (палитра, типографика, отступы, радиусы через CSS custom properties) + минимальный сброс стилей. 6. `src/routes/+layout.svelte`: шапка с названием сайта и навигацией по категориям инструментов (категории — константа из реестра), футер. **Проверка:** `pnpm dev` открывает пустой каркас; `pnpm build && pnpm preview` отдаёт статический экспорт. --- ## 2. Шаг 1 — Ядро: типы и ввод-вывод Файлы в `web/src/lib/core/`: - `types.ts` ```ts export type PixelImage = { width: number; height: number; data: Uint8ClampedArray }; // RGBA, как ImageData ``` - `io.ts` — единственное место ядра, где разрешён canvas/DOM: - `decodeFile(file: File): Promise` — `createImageBitmap(file)` → отрисовка на canvas → `getImageData`; - `encode(img: PixelImage, mime: 'image/png' | 'image/jpeg' | 'image/webp', quality?: number): Promise` — `putImageData` → `canvas.toBlob`; - `downloadBlob(blob: Blob, filename: string): void`. **Проверка:** юнит-смоук — декодировать фикстурный PNG, закодировать обратно, размеры совпадают. --- ## 3. Шаг 2 — Операции ядра (чистые функции) Файлы в `web/src/lib/core/`, каждая функция: `(img, params) => new PixelImage`, без мутации входа: - `color.ts`: `grayscale(img)` (luma), `invert(img)`, `brightnessContrast(img, brightness −100..100, contrast −100..100)`. - `geometry.ts`: `flip(img, axis)`, `rotate90(img, turns)`, `crop(img, x, y, w, h)` — индексная математика; `resize(img, w, h)` — билинейная интерполяция на TS (не `drawImage`, чтобы ядро осталось DOM-free и воспроизводимым бит-в-бит). - `alpha.ts`: `removeColorToAlpha(img, hex, tolerancePercent)` — цветовое расстояние → альфа 0; остальное — позже. - `analyze.ts`: `imageInfo(img)` → `{ width, height, hasAlpha, colorCount }`. **Проверка:** vitest на маленьких матрицах пикселей (2×2, 3×3): flip, rotate, crop, grayscale — ожидаемые значения руками. --- ## 4. Шаг 3 — Реестр инструментов `web/src/lib/registry.ts` — источник истины для страниц, форм и навигации: ```ts export type ParamDef = | { id: string; label: string; type: "number"; min?: number; max?: number; step?: number; default: number } | { id: string; label: string; type: "select"; options: { value: string; label: string }[]; default: string } | { id: string; label: string; type: "checkbox"; default: boolean } | { id: string; label: string; type: "color"; default: string }; export type ToolEntry = { id: string; // slug маршрута title: string; description: string; category: CategoryId; params: ParamDef[]; run: (img: PixelImage, params: Record) => Promise | PixelImage; }; ``` Категории (константа там же): `convert`, `alpha`, `color`, `geometry`, `analyze`. ### Состав первого среза — 11 инструментов | # | id | Категория | Параметры | | --- | -------------------------------- | --------- | ----------------------------------------------------- | | 1 | `resize-png` | geometry | width, height, checkbox «сохранять пропорции» | | 2 | `crop-png` | geometry | x, y, width, height | | 3 | `rotate-png` | geometry | select: 90 / 180 / 270 | | 4 | `flip-png` | geometry | select: horizontal / vertical | | 5 | `grayscale-png` | color | — | | 6 | `invert-colors-png` | color | — | | 7 | `adjust-brightness-contrast-png` | color | brightness −100..100, contrast −100..100 | | 8 | `convert-png-to-jpg` | convert | background color (подложка под альфу), quality 0..100 | | 9 | `convert-png-to-webp` | convert | quality 0..100 | | 10 | `remove-color-from-png` | alpha | hex color, tolerance % | | 11 | `png-info` | analyze | — особый случай: не картинка, а текстовая сводка | Для `png-info` в записи реестра добавляется флаг `resultType: 'info'`; остальные — `'image'`. **Проверка:** импорт реестра в тесте — все id уникальны, у каждого `params` имеют дефолты, `run` определён. --- ## 5. Шаг 4 — Универсальные UI-компоненты `web/src/lib/components/`: - `DropZone.svelte` — drag&drop + клик; эмитит `File`; проверка MIME (PNG/JPEG/WebP/GIF/BMP). - `Preview.svelte` — рендер `PixelImage` на canvas с fit-масштабированием; шахматный фон под прозрачность. - `ParamForm.svelte` — генерирует контролы из `ParamDef[]`, двусторонний `bind:values`. - `DownloadButton.svelte` — encode выбранного формата + `downloadBlob`, имя `.`. - `InfoPanel.svelte` — текстовый вывод для `resultType: 'info'`. - `ToolPage.svelte` — оркестратор страницы инструмента; состояния `idle → loaded → processing → done | error`; раскладка: слева DropZone, справа превью + форма параметров + кнопки «Применить» / «Скачать». **Правило:** новый инструмент = новая запись в реестре (+ функция операции). Компоненты и маршрут не трогаем. --- ## 6. Шаг 5 — Маршруты - `/` (`src/routes/+page.svelte`) — сетка карточек инструментов, сгруппированных по категориям; данные из реестра. - `/tools/[id]` — универсальная страница: - `+page.ts`: `entries()` возвращает все `id` из реестра (обязательное условие prerender при adapter-static); - `load()` возвращает запись реестра по `id`, неизвестный id → 404; - `+page.svelte` — тонкая обёртка над `ToolPage`. **Проверка:** `pnpm build` — в `build/tools/` лежат все 11 страниц; главная содержит ссылки на каждую. --- ## 7. Шаг 6 — Ошибки и минимальная полировка - Обработка ошибок: неподдерживаемый формат, ошибка декода, некорректные параметры crop (выход за границы — клампы или понятная ошибка). - Состояния: «перетащите файл», спиннер во время обработки, disabled-кнопки до загрузки. - Тексты описаний для всех 11 инструментов (title/description уже в реестре). --- ## 8. Шаг 7 — Верификация и checkpoint Команды: ```bash cd web && pnpm dev # разработка cd web && pnpm build # статический экспорт должен проходить без ошибок cd web && pnpm test # vitest: ядро (геометрия/цвет) + целостность реестра ``` Ручной smoke-тест каждого из 11 инструментов: загрузка PNG → применить → скачать → открыть результат. **Checkpoint MVP:** любой из 11 инструментов доступен по прямому URL с главной страницы; PNG загружается drag&drop; параметры применяются; результат скачивается; всё работает офлайн со статического хостинга; ни одной серверной зависимости. --- ## 9. Сразу после MVP (не входит в план) По roadmap.md §Фаза 1, в порядке следования: 1. Пайплайн-workspace `/workspace` (шаги, превью шагов, localStorage, экспорт/импорт JSON). 2. Расширение набора до ~40 топовых EASY из analysis.md §6.2. 3. Web Worker для тяжёлых операций, полировка UX, SEO-описания. Что сознательно отложено: wasm/Rust, эталонные файлы, Playwright, CLI — фазы 2+ из roadmap.md.