diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000..8c2fb74 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,5 @@ +# Allowlist: prettier в корне форматирует только markdown docs/. +# web/ и refs/ имеют свои тулинги, refs-html/ — генерат: всё остальное +# в корне (включая появившееся позже) игнорируется целиком. +/* +!/docs diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 0000000..2397d28 --- /dev/null +++ b/.prettierrc @@ -0,0 +1,3 @@ +{ + "proseWrap": "always" +} diff --git a/AGENTS.md b/AGENTS.md index 5be3433..fdffd8d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,9 +11,13 @@ - Сборка: `pnpm --dir web build`, проверка типов: `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json`, тесты: `pnpm --dir web test` (Vitest), e2e: `pnpm --dir web test:e2e` (Playwright, `web/e2e/`, свой webServer на `pnpm build` + `scripts/serve-static.mjs`, порт 4173). -- Форматирование: `pnpm --dir web format` (Prettier + `prettier-plugin-svelte`, +- Форматирование: два независимых прогона. Корневой prettier форматирует + только markdown `docs/` (`.prettierrc` + allowlist `.prettierignore` в корне, + `pnpm format:docs`, проверка — `pnpm check:docs`). Код `web/` форматируется + отдельно: `pnpm --dir web format` (Prettier + `prettier-plugin-svelte`, конфиг `web/.prettierrc`, игнор `web/.prettierignore`). Проверка без записи: - `pnpm --dir web exec prettier --check .`. + `pnpm --dir web exec prettier --check .`. Оба прогона сразу: `pnpm format` + в корне. - Линтинг: `pnpm --dir web lint` (ESLint, flat-конфиг `web/eslint.config.js`). Устроен инкрементально: - На **весь код** — парсинг TS/Svelte + правило diff --git a/README.md b/README.md index 34fc57b..cb46b15 100644 --- a/README.md +++ b/README.md @@ -51,7 +51,8 @@ Static output is written to `web/build/`. | `pnpm --dir web test` | Run unit tests (Vitest) | | `pnpm --dir web check` | Type-check (svelte-check) | | `pnpm --dir web lint` | Lint (ESLint) | -| `pnpm --dir web format` | Format (Prettier) | +| `pnpm --dir web format` | Format code (Prettier) | +| `pnpm format:docs` | Format docs (Prettier) | | `pnpm --dir web preview` | Preview production build | ## Project Structure diff --git a/docs/analysis.md b/docs/analysis.md index 37d08a5..a874ee3 100644 --- a/docs/analysis.md +++ b/docs/analysis.md @@ -1,9 +1,13 @@ # Анализ: набор PNG-утилит для браузера -Проект: **easy-png-tools** — набор PNG-утилит, работающий полностью в браузере. Вдохновлён идеей онлайн-сервисов «все PNG-операции в одном месте»: никаких загрузок на сервер, всё считается локально. -Стек: **SvelteKit + @sveltejs/adapter-static** (статический экспорт, никакого сервера). Все данные обрабатываются на клиенте, ничего не уходит в сеть. +Проект: **easy-png-tools** — набор PNG-утилит, работающий полностью в браузере. +Вдохновлён идеей онлайн-сервисов «все PNG-операции в одном месте»: никаких +загрузок на сервер, всё считается локально. Стек: **SvelteKit + +@sveltejs/adapter-static** (статический экспорт, никакого сервера). Все данные +обрабатываются на клиенте, ничего не уходит в сеть. -Источник идеи: типовой перечень утилит, который встречается в онлайн-наборах PNG-инструментов (~296 операций). +Источник идеи: типовой перечень утилит, который встречается в онлайн-наборах +PNG-инструментов (~296 операций). --- @@ -21,63 +25,121 @@ | Тяжёлая математика без блокировки UI | Web Workers, SharedArrayBuffer, WASM (zlib, квантование, ML) | | Экономия памяти при больших изображениях | `ImageBitmap`, работа тайлами, downscale до декорирования | -**Вывод для архитектуры:** ~90% утилит — это одна и та же связка «декодировать → прогнать через пиксельную функцию → закодировать». Разумно сделать общий ядро (`core/pipeline.ts` + набор операций над `ImageData`) и тонькие страницы-обёртки в SvelteKit. Каждая утилита = отдельный маршрут со своим UI (drag&drop слева, превью, параметры, скачивание справа). +**Вывод для архитектуры:** ~90% утилит — это одна и та же связка «декодировать → +прогнать через пиксельную функцию → закодировать». Разумно сделать общий ядро +(`core/pipeline.ts` + набор операций над `ImageData`) и тонькие страницы-обёртки +в SvelteKit. Каждая утилита = отдельный маршрут со своим UI (drag&drop слева, +превью, параметры, скачивание справа). --- ## 2. Уровень EASY — canvas + ImageData, простые попиксельные операции и композиция -Реализация: загрузка в canvas → `getImageData()` → цикл по пикселям / перекомпоновка / отрисовка на выходной canvas → `toBlob('image/png')`. Без сложных алгоритмов, каждая утилита — часы работы. +Реализация: загрузка в canvas → `getImageData()` → цикл по пикселям / +перекомпоновка / отрисовка на выходной canvas → `toBlob('image/png')`. Без +сложных алгоритмов, каждая утилита — часы работы. ### 2.1 Конвертация форматов и текстовые представления -- **convert-png-to-jpg / convert-jpg-to-png / convert-webp-to-png / convert-png-to-webp / convert-svg-to-png** — рисование на canvas + `toBlob()` нужного типа -- **convert-bmp-to-png / convert-ico-to-png / convert-gif-to-png** (первый кадр) — нативное декодирование браузером +- **convert-png-to-jpg / convert-jpg-to-png / convert-webp-to-png / + convert-png-to-webp / convert-svg-to-png** — рисование на canvas + `toBlob()` + нужного типа +- **convert-bmp-to-png / convert-ico-to-png / convert-gif-to-png** (первый кадр) + — нативное декодирование браузером - **convert-png-to-bmp** — BMP-формат простой, ~30 строк энкодера -- **convert-png-to-base64 / convert-base64-to-png / data-uri (в обе стороны)** — `FileReader.readAsDataURL` + строка -- **convert-png-to-bytes / convert-bytes-to-png / convert-png-to-hex / convert-hex-to-png / convert-png-to-rgb-values / convert-rgb-values-to-png** — чтение/запись `ImageData` в текст +- **convert-png-to-base64 / convert-base64-to-png / data-uri (в обе стороны)** — + `FileReader.readAsDataURL` + строка +- **convert-png-to-bytes / convert-bytes-to-png / convert-png-to-hex / + convert-hex-to-png / convert-png-to-rgb-values / convert-rgb-values-to-png** — + чтение/запись `ImageData` в текст - **convert-png-to-gif** (один кадр) — `gifenc`, тривиально ### 2.2 Прозрачность и альфа-канал -- **create-transparent-png, remove-color-from-png(простая версия), transparent-logo-maker, remove-logo/icon-background (по цвету)** — замена цвета на прозрачность по порогу близости -- **remove-alpha-channel-from-png, fill-alpha-channel-in-png, replace-alpha-channel-in-png, extract-alpha-channel-from-png, invert-png-alpha-channel** — манипуляции только с A-компонентой -- **generate-png-alpha-mask, show-transparent-png-areas, fill-transparent-png-pixels, convert-semi-transparent-png-to-opaque** — пороги и заливки по альфе +- **create-transparent-png, remove-color-from-png(простая версия), + transparent-logo-maker, remove-logo/icon-background (по цвету)** — замена + цвета на прозрачность по порогу близости +- **remove-alpha-channel-from-png, fill-alpha-channel-in-png, + replace-alpha-channel-in-png, extract-alpha-channel-from-png, + invert-png-alpha-channel** — манипуляции только с A-компонентой +- **generate-png-alpha-mask, show-transparent-png-areas, + fill-transparent-png-pixels, convert-semi-transparent-png-to-opaque** — пороги + и заливки по альфе - **harden-png-edges** — бинаризация альфы по порогу ### 2.3 Цвет: замена, тон, каналы, цветовые пространства -- **change-png-color, change-png-color-tone, change-png-opacity, swap-png-colors, change-png-brightness, change-png-contrast, convert-png-to-grayscale, add-sepia-tone-to-png, change-png-hue, invert-png-colors, reverse-png-colors, convert-png-to-two-colors, convert-png-to-black-and-white, create-monochrome-png, generate-single-color-png** — попиксельные арифметики/преобразования -- **extract-png-color-channels, swap-rgba-color-channels, split-png-into-rgb-components** — перестановка/выделение RGBA-каналов -- **convert-png-to-hsl/hsv/hsi/cmyk/ycbcr/lab-colors** — известные формулы конвертации (Lab — чуть больше математики, но детерминировано) -- **generate-\*-color-palette (complementary/monochromatic/analogous/triadic/tetradic), generate-similar-color-shades, color-wheel-generator, mix-multiple-colors, calculate-average-color, simulate-paint-colors, blend-two-colors, step-between-two-colors, convert-color-names-to-png** — генераторы палитр/смешение без работы с изображением -- **posterize-png, create-color-spectrum-png, create-multi-color-gradient-png, generate-png-gradient-palette** — простые градиенты/квантование уровней +- **change-png-color, change-png-color-tone, change-png-opacity, + swap-png-colors, change-png-brightness, change-png-contrast, + convert-png-to-grayscale, add-sepia-tone-to-png, change-png-hue, + invert-png-colors, reverse-png-colors, convert-png-to-two-colors, + convert-png-to-black-and-white, create-monochrome-png, + generate-single-color-png** — попиксельные арифметики/преобразования +- **extract-png-color-channels, swap-rgba-color-channels, + split-png-into-rgb-components** — перестановка/выделение RGBA-каналов +- **convert-png-to-hsl/hsv/hsi/cmyk/ycbcr/lab-colors** — известные формулы + конвертации (Lab — чуть больше математики, но детерминировано) +- **generate-\*-color-palette + (complementary/monochromatic/analogous/triadic/tetradic), + generate-similar-color-shades, color-wheel-generator, mix-multiple-colors, + calculate-average-color, simulate-paint-colors, blend-two-colors, + step-between-two-colors, convert-color-names-to-png** — генераторы + палитр/смешение без работы с изображением +- **posterize-png, create-color-spectrum-png, create-multi-color-gradient-png, + generate-png-gradient-palette** — простые градиенты/квантование уровней ### 2.4 Геометрия, композиция, холст -- **resize-png, zoom-png, crop-png, cut-png, rotate-png, skew-png, shift-png, change-png-canvas-size, change-png-aspect-ratio, fit-png-in-rectangle, convert-landscape/portrait-png-to-portrait/landscape-png** — операции через `CanvasRenderingContext2D` (`drawImage`, `rotate`, `setTransform`) -- **flip-png-horizontally/vertically, mirror-png, reverse-png** — отражение/поворот на 180° -- **center-png, center-logo, center-icon** — центровка по bounding box альфа-канала -- **duplicate-png, clone-png, multiply-png, create-png-tile, create-symmetric-png, split-png-into-parts** — тайлинг/копирование -- **create-round-png, create-square-png, create-star-shaped-png, create-wavy-shaped-png, round-png-corners** — маски-«штампы» на альфе -- **add-png-border, add-padding-to-png, add-space-around-png, remove-padding-from-png, remove-space-around-png, remove-png-padding** — расширение/усадка холста -- **add-png-background, add-signature/logo/icon/stamp-background** — подложка под прозрачность +- **resize-png, zoom-png, crop-png, cut-png, rotate-png, skew-png, shift-png, + change-png-canvas-size, change-png-aspect-ratio, fit-png-in-rectangle, + convert-landscape/portrait-png-to-portrait/landscape-png** — операции через + `CanvasRenderingContext2D` (`drawImage`, `rotate`, `setTransform`) +- **flip-png-horizontally/vertically, mirror-png, reverse-png** — + отражение/поворот на 180° +- **center-png, center-logo, center-icon** — центровка по bounding box + альфа-канала +- **duplicate-png, clone-png, multiply-png, create-png-tile, + create-symmetric-png, split-png-into-parts** — тайлинг/копирование +- **create-round-png, create-square-png, create-star-shaped-png, + create-wavy-shaped-png, round-png-corners** — маски-«штампы» на альфе +- **add-png-border, add-padding-to-png, add-space-around-png, + remove-padding-from-png, remove-space-around-png, remove-png-padding** — + расширение/усадка холста +- **add-png-background, add-signature/logo/icon/stamp-background** — подложка + под прозрачность ### 2.5 Текст, рамки, простые эффекты -- **add-text-to-png, convert-text-to-png, create-emoji-png, add-png-watermark, wrap-png-in-polaroid, create-logo-from-text, create-icon-from-text/emoji, create-custom-stamp, create-\*-stamp (round/rect/green/red/number/date-time/monogram/from-text/emoji), create-placeholder-png, create-empty-png, create-custom-png, generate-colorful-png, generate-random-png, generate-white-noise-png, generate-1x1-png, draw-png-grid** — рисование примитивов/случайных данных -- **add-noise-to-png, create-glitch-png, pixelate-png, censor-png, erase-part-of-png, randomize-png-pixels** — шум/порча байтов/блоки/кисть +- **add-text-to-png, convert-text-to-png, create-emoji-png, add-png-watermark, + wrap-png-in-polaroid, create-logo-from-text, create-icon-from-text/emoji, + create-custom-stamp, create-\*-stamp + (round/rect/green/red/number/date-time/monogram/from-text/emoji), + create-placeholder-png, create-empty-png, create-custom-png, + generate-colorful-png, generate-random-png, generate-white-noise-png, + generate-1x1-png, draw-png-grid** — рисование примитивов/случайных данных +- **add-noise-to-png, create-glitch-png, pixelate-png, censor-png, + erase-part-of-png, randomize-png-pixels** — шум/порча байтов/блоки/кисть - **add-shadow-to-png** — смещение + лёгкое размытие + композиция - **add-artifacts-to-png** — простой вариант: JPEG-раундтрип через canvas ### 2.6 Анализ и проверки -- **analyze-png, find-png-file-size, find-png-dimensions, check-if-png-is-landscape-or-portrait, find-png-color-count, check-if-png-is-grayscale, check-if-png-is-transparent, verify-if-image-is-png, pick-png-color, pick-logo/icon-color, png-viewer, test-png, preview-png-on-colorful-background, test-png-on-multiple-backgrounds** — чтение заголовков/ImageData/гистограмм, «пипетка» по клику +- **analyze-png, find-png-file-size, find-png-dimensions, + check-if-png-is-landscape-or-portrait, find-png-color-count, + check-if-png-is-grayscale, check-if-png-is-transparent, + verify-if-image-is-png, pick-png-color, pick-logo/icon-color, png-viewer, + test-png, preview-png-on-colorful-background, + test-png-on-multiple-backgrounds** — чтение заголовков/ImageData/гистограмм, + «пипетка» по клику - **sort-colors** — сортировка списка палитры (не изображения) ### 2.7 Семейства signature/logo/icon/stamp — простые вариации -Почти все «-maker/-color/-resize/-crop/-rotate/-bg/-space/-padding/-grayscale/-single-color/-white/-black/-semi-transparent/-shadow» для signature/logo/icon/stamp — это переиспользование операций из разделов выше с одним цветом/боксом. Отдельно перечислены только те, что требуют алгоритмов (см. MEDIUM/HARD). +Почти все +«-maker/-color/-resize/-crop/-rotate/-bg/-space/-padding/-grayscale/-single-color/-white/-black/-semi-transparent/-shadow» +для signature/logo/icon/stamp — это переиспользование операций из разделов выше +с одним цветом/боксом. Отдельно перечислены только те, что требуют алгоритмов +(см. MEDIUM/HARD). > Примерный объём: **≈180–190 утилит уровня EASY.** @@ -85,54 +147,96 @@ ## 3. Уровень MEDIUM — нужен настоящий алгоритм (свёртки, морфология, квантование, анимация) -Реализация: честные алгоритмы на `ImageData`, часто в Web Worker, при необходимости — обвязка вокруг готовых JS-библиотек. +Реализация: честные алгоритмы на `ImageData`, часто в Web Worker, при +необходимости — обвязка вокруг готовых JS-библиотек. ### 3.1 Фильтры и свёртки -- **blur-png, sharpen-png** — box/gaussian blur, ядро свёртки (упрощённо можно `ctx.filter`, но качественно — своя свёртка) -- **refine-png-edges, feather-png-edges, clean-png-edges** — работа с полупрозрачными краями: размытие альфы, де-контуринг, suppression полупрозрачности +- **blur-png, sharpen-png** — box/gaussian blur, ядро свёртки (упрощённо можно + `ctx.filter`, но качественно — своя свёртка) +- **refine-png-edges, feather-png-edges, clean-png-edges** — работа с + полупрозрачными краями: размытие альфы, де-контуринг, suppression + полупрозрачности - **add-glow-effect-to-png** — расширение альфы + размытие + цветное наложение ### 3.2 Морфология и контуры (операции над альфа-маской) -- **add-stroke-to-png, add-outline-to-png, remove-stroke-from-png, remove-outline-from-png, find-png-outline** — dilation/erosion по альфе, извлечение контура -- **make-signature-thicker / make-signature-thinner, make-icon-thicker / make-icon-thinner** — dilation/erosion -- **convert-outlined-icon-to-filled-icon** — flood-fill замкнутых областей; **convert-filled-icon-to-outlined-icon** — эрозия -- **remove-png-border, remove-border-around-icon, remove-stamp-border** — детект однотонной/декоративной рамки и вырезание +- **add-stroke-to-png, add-outline-to-png, remove-stroke-from-png, + remove-outline-from-png, find-png-outline** — dilation/erosion по альфе, + извлечение контура +- **make-signature-thicker / make-signature-thinner, make-icon-thicker / + make-icon-thinner** — dilation/erosion +- **convert-outlined-icon-to-filled-icon** — flood-fill замкнутых областей; + **convert-filled-icon-to-outlined-icon** — эрозия +- **remove-png-border, remove-border-around-icon, remove-stamp-border** — детект + однотонной/декоративной рамки и вырезание ### 3.3 Квантование и цветовая кластеризация -- **quantize-png, decrease-png-color-count, set-custom-png-color-palette, add-dithering-to-png** — median cut / octree / Wu + Floyd–Steinberg; готовые JS-библиотеки (image-q и т.п.) -- **change-png-quality, create-low-quality-png** — симуляция потери качества через квантование + JPEG-раундтрип -- **convert-png-to-color-blocks** — мультипликативный блок-эффект/квантование по блокам -- **extract-png-color-palette, find-logo-color-scheme, extract-icon-colors** — частотная гистограмма + выбор топ-N / кластеризация -- **create-unique/popular/multi-color/outlier-color-mask** — статистика распределения цветов и выделение по маске +- **quantize-png, decrease-png-color-count, set-custom-png-color-palette, + add-dithering-to-png** — median cut / octree / Wu + Floyd–Steinberg; готовые + JS-библиотеки (image-q и т.п.) +- **change-png-quality, create-low-quality-png** — симуляция потери качества + через квантование + JPEG-раундтрип +- **convert-png-to-color-blocks** — мультипликативный блок-эффект/квантование по + блокам +- **extract-png-color-palette, find-logo-color-scheme, extract-icon-colors** — + частотная гистограмма + выбор топ-N / кластеризация +- **create-unique/popular/multi-color/outlier-color-mask** — статистика + распределения цветов и выделение по маске ### 3.4 Выделение объектов по цвету / фону -- **remove-png-chroma-key** — порог по цвету + деспилл (убрать зелёную окантовку) -- **remove-color-from-png (качественная версия), extract-color-from-png, separate-png-colors, separate-logo-colors, separate-icon-colors** — сегментация по близости цвета -- **convert-png-to-silhouette, convert-png-to-stencil** — порог + заливка силуэта -- **extract-stamp-from-image (по цвету печати)** — хромакей/частотный фильтр по цвету чернил -- **remove-png-background (вариант без ИИ)** — проверенный цветовой подход, знакомый по популярным онлайн-наборам PNG-утилит. Работает хорошо на однотонном/градиентном фоне и в ряде случаев удобнее ИИ. Опции и алгоритм: - - **Background Color** — выбор удаляемого цвета: пипеткой по изображению или hex/RGB - - **Percentage (похожесть цвета)** — slider; 0% = убрать только точный цвет, >0% = убрать соседние оттенки в окрестности цвета (в цветовом пространстве) - - **Delete Outer Areas (галочка)** — режим «только внешние пиксели»: удаляются только области, связанные с краями изображения (flood-fill/region-growing от границ). Выключена = удалять по всему холсту. Именно эта опция позволяет убрать фон, не задевая «пятна» того же цвета внутри объекта - - **Smooth Edge Line + Radius** — постобработка: полупрозрачная полоса толщиной N px по границе удаления (сгладить переход) - - **Preview Deleted Background** — двухцветный предпросмотр маски (что удаляется/что остаётся) - - Реализация: выбор цвета → порог по цветовому расстоянию → (опц.) flood-fill от краёв → сглаживание краёв → маска поверх превью. Всё на `ImageData`, без ML. +- **remove-png-chroma-key** — порог по цвету + деспилл (убрать зелёную + окантовку) +- **remove-color-from-png (качественная версия), extract-color-from-png, + separate-png-colors, separate-logo-colors, separate-icon-colors** — + сегментация по близости цвета +- **convert-png-to-silhouette, convert-png-to-stencil** — порог + заливка + силуэта +- **extract-stamp-from-image (по цвету печати)** — хромакей/частотный фильтр по + цвету чернил +- **remove-png-background (вариант без ИИ)** — проверенный цветовой подход, + знакомый по популярным онлайн-наборам PNG-утилит. Работает хорошо на + однотонном/градиентном фоне и в ряде случаев удобнее ИИ. Опции и алгоритм: + - **Background Color** — выбор удаляемого цвета: пипеткой по изображению или + hex/RGB + - **Percentage (похожесть цвета)** — slider; 0% = убрать только точный + цвет, >0% = убрать соседние оттенки в окрестности цвета (в цветовом + пространстве) + - **Delete Outer Areas (галочка)** — режим «только внешние пиксели»: удаляются + только области, связанные с краями изображения (flood-fill/region-growing от + границ). Выключена = удалять по всему холсту. Именно эта опция позволяет + убрать фон, не задевая «пятна» того же цвета внутри объекта + - **Smooth Edge Line + Radius** — постобработка: полупрозрачная полоса + толщиной N px по границе удаления (сгладить переход) + - **Preview Deleted Background** — двухцветный предпросмотр маски (что + удаляется/что остаётся) + - Реализация: выбор цвета → порог по цветовому расстоянию → (опц.) flood-fill + от краёв → сглаживание краёв → маска поверх превью. Всё на `ImageData`, без + ML. ### 3.5 Геометрические искажения и пиксель-сортировка - **add-whirl-to-png** — свирл/вихрь: обратное отображение координат -- **sort-png-pixels** — pixel sorting (сортировка строк/столбцов по яркости/цвету) -- **upscale-png / downscale-png** — качественный ресемплинг (Lanczos, mipmap) поверх базового canvas-resize -- **remove-signature-background, transparent-png-signature-maker, clean-up-signature-edges, improve-low-quality-signature, clean-logo-edges, clean-icon-edges, fix-low-quality-stamp, convert-rubber-stamp-to-digital-stamp** — комбинация «цвет→прозрачность + де-шум + морфология краёв» -- **convert-image/png/jpg/icon/logo/signature-to-stamp** — стилизация в двухцветную «печать» (порог + зернистость + края) +- **sort-png-pixels** — pixel sorting (сортировка строк/столбцов по + яркости/цвету) +- **upscale-png / downscale-png** — качественный ресемплинг (Lanczos, mipmap) + поверх базового canvas-resize +- **remove-signature-background, transparent-png-signature-maker, + clean-up-signature-edges, improve-low-quality-signature, clean-logo-edges, + clean-icon-edges, fix-low-quality-stamp, + convert-rubber-stamp-to-digital-stamp** — комбинация «цвет→прозрачность + + де-шум + морфология краёв» +- **convert-image/png/jpg/icon/logo/signature-to-stamp** — стилизация в + двухцветную «печать» (порог + зернистость + края) ### 3.6 Анимации и GIF-энкодинг -- **convert-png-to-gif, slowly-reveal-png, create-disappearing-png, fade-in-png, fade-out-png, create-scrolling-png, create-png-sequence-from-gif** — декомпозиция кадров GIF / генерация последовательности кадров + `gifenc`/`UPNG` (APNG) +- **convert-png-to-gif, slowly-reveal-png, create-disappearing-png, fade-in-png, + fade-out-png, create-scrolling-png, create-png-sequence-from-gif** — + декомпозиция кадров GIF / генерация последовательности кадров + + `gifenc`/`UPNG` (APNG) > Примерный объём: **≈71 утилита уровня MEDIUM.** @@ -140,35 +244,65 @@ ## 4. Уровень HARD — WASM, ML, длительная обработка, тонкая подгонка алгоритма -Реализация: WASM-модули, ML-модели в браузере, длительная обработка с прогресс-барами. +Реализация: WASM-модули, ML-модели в браузере, длительная обработка с +прогресс-барами. -- **compress-png / reduce-png-file-size / optimize-png** — сжатие на уровне формата: нужен качественный PNG-энкодер с контролем zlib-фильтров/битовой глубины и оптимизаторы. Решение: **pngquant / oxipng / zopfli (WASM)** + подгонка под «качество→размер» (это главный кандидат на HARD — именно здесь тюнинг бесконечен) -- **remove-png-background (вариант с ИИ)** — опциональное улучшение к MEDIUM-версии: ML-сегментация (Rembg/DeepLab в WASM или ONNX) для произвольных фото, где цветовой матчинг бессилен. Начинать можно без него — не-ИИ версия уже даёт рабочий инструмент -- **extract-signature-from-image** — детект подписи на скане/фото с произвольным фоном: де-шум, контраст, ML-детекция -- **convert-handwritten-signature-to-digital-signature** — очистка и «оцифровка» почерка (ML, денойзинг, выравнивание) -- **extract-barcode-from-image** — декодирование штрих-кодов/QR: готовая библиотека **zxing-wasm**, сопряжение с камерой/фото +- **compress-png / reduce-png-file-size / optimize-png** — сжатие на уровне + формата: нужен качественный PNG-энкодер с контролем zlib-фильтров/битовой + глубины и оптимизаторы. Решение: **pngquant / oxipng / zopfli (WASM)** + + подгонка под «качество→размер» (это главный кандидат на HARD — именно здесь + тюнинг бесконечен) +- **remove-png-background (вариант с ИИ)** — опциональное улучшение к + MEDIUM-версии: ML-сегментация (Rembg/DeepLab в WASM или ONNX) для произвольных + фото, где цветовой матчинг бессилен. Начинать можно без него — не-ИИ версия + уже даёт рабочий инструмент +- **extract-signature-from-image** — детект подписи на скане/фото с произвольным + фоном: де-шум, контраст, ML-детекция +- **convert-handwritten-signature-to-digital-signature** — очистка и «оцифровка» + почерка (ML, денойзинг, выравнивание) +- **extract-barcode-from-image** — декодирование штрих-кодов/QR: готовая + библиотека **zxing-wasm**, сопряжение с камерой/фото -> Итого **6 утилит уровня HARD** (все — опциональные улучшения/надстройки). Всё остальное (≈290) — EASY/MEDIUM. +> Итого **6 утилит уровня HARD** (все — опциональные улучшения/надстройки). Всё +> остальное (≈290) — EASY/MEDIUM. --- ## 5. Итоговая сводка -**EASY — ~185 утилит.** canvas + ImageData. -Примеры: crop, resize, rotate, конвертации, альфа-канал, палитры, текст, рамки, анализ. +**EASY — ~185 утилит.** canvas + ImageData. Примеры: crop, resize, rotate, +конвертации, альфа-канал, палитры, текст, рамки, анализ. -**MEDIUM — ~71 утилита.** Алгоритмы, Web Workers, gifenc. -Примеры: blur, sharpen, морфология, квантование, дизеринг, pixel sorting, анимации GIF, remove-background (без ИИ). +**MEDIUM — ~71 утилита.** Алгоритмы, Web Workers, gifenc. Примеры: blur, +sharpen, морфология, квантование, дизеринг, pixel sorting, анимации GIF, +remove-background (без ИИ). -**HARD — 6 утилит.** WASM (pngquant/oxipng/zxing), ML (ONNX/Rembg). -Примеры: compress/optimize, remove-background (ИИ), extract-signature, barcode. +**HARD — 6 утилит.** WASM (pngquant/oxipng/zxing), ML (ONNX/Rembg). Примеры: +compress/optimize, remove-background (ИИ), extract-signature, barcode. --- ## 6. Рекомендации по реализации -1. **Единое ядро обработки** — `src/lib/core/`: загрузка файла → `ImageData`, библиотека операций (`color.ts`, `alpha.ts`, `geometry.ts`, `filters.ts`, `morphology.ts`, `quantize.ts`, `channels.ts`), экспорт через `toBlob`. Каждая утилита — тонкая SvelteKit-страница + общие компоненты (DropZone, Preview, Download). -2. **Порядок внедрения:** MVP = ~40 топовых EASY (crop, resize, rotate, convert, alpha, grayscale, brightness, палитры, watermark, add-text, add-background, pick-color, analyze). Затем остальные EASY → MEDIUM пакетами по темам (фильтры, морфология, квантование, анимации). `remove-png-background` (без ИИ) — отличная «витринная» MEDIUM-фича ранних этапов: не требует ML и переиспользует `remove-color` + flood-fill + сглаживание краёв. HARD — в последнюю очередь. -3. **Производительность:** вся пиксельная работа и тяжёлые алгоритмы — в Web Worker; большие изображения декодировать сразу в downscale для превью; прогресс-бары для MEDIUM/HARD. -4. **SEO/структура:** SvelteKit + static adapter — каждая утилита отдельный маршрут (`/crop-png`, `/rotate-png`), что даёт удобные адреса и повторяет распространённую структуру наборов PNG-инструментов. -5. **Семейства logo/icon/stamp/signature** — переиспользуют общие операции; в коде это конфиг-опции той же страницы (например `/resize-png` и `/resize-icon` = один компонент с разными подписями/дефолтами). Не плодить дублирующий код. +1. **Единое ядро обработки** — `src/lib/core/`: загрузка файла → `ImageData`, + библиотека операций (`color.ts`, `alpha.ts`, `geometry.ts`, `filters.ts`, + `morphology.ts`, `quantize.ts`, `channels.ts`), экспорт через `toBlob`. + Каждая утилита — тонкая SvelteKit-страница + общие компоненты (DropZone, + Preview, Download). +2. **Порядок внедрения:** MVP = ~40 топовых EASY (crop, resize, rotate, convert, + alpha, grayscale, brightness, палитры, watermark, add-text, add-background, + pick-color, analyze). Затем остальные EASY → MEDIUM пакетами по темам + (фильтры, морфология, квантование, анимации). `remove-png-background` (без + ИИ) — отличная «витринная» MEDIUM-фича ранних этапов: не требует ML и + переиспользует `remove-color` + flood-fill + сглаживание краёв. HARD — в + последнюю очередь. +3. **Производительность:** вся пиксельная работа и тяжёлые алгоритмы — в Web + Worker; большие изображения декодировать сразу в downscale для превью; + прогресс-бары для MEDIUM/HARD. +4. **SEO/структура:** SvelteKit + static adapter — каждая утилита отдельный + маршрут (`/crop-png`, `/rotate-png`), что даёт удобные адреса и повторяет + распространённую структуру наборов PNG-инструментов. +5. **Семейства logo/icon/stamp/signature** — переиспользуют общие операции; в + коде это конфиг-опции той же страницы (например `/resize-png` и + `/resize-icon` = один компонент с разными подписями/дефолтами). Не плодить + дублирующий код. diff --git a/docs/archive/demo-fidelity.md b/docs/archive/demo-fidelity.md index d4f5dec..a2e2b67 100644 --- a/docs/archive/demo-fidelity.md +++ b/docs/archive/demo-fidelity.md @@ -1,103 +1,118 @@ # Demo fidelity: /preview/demo vs refs-html/demo.html > ⚠️ **УТРАТИЛ АКТУАЛЬНОСТЬ — архивная копия.** Актуальный план фикса верности и -> текущий статус пунктов B/F — в `docs/plan-design-fix.md` (Этап 1). Аудит верности -> теперь автоматизирован (`pnpm audit` → `web/audit/audit-report.md`; пиксель-дифф не -> используется); темы в `refs-html` переключаются (скрипт инжектится в -> `scripts/extract-static.mjs`). Ниже — исходный текст на момент написания. +> текущий статус пунктов B/F — в `docs/plan-design-fix.md` (Этап 1). Аудит +> верности теперь автоматизирован (`pnpm audit` → `web/audit/audit-report.md`; +> пиксель-дифф не используется); темы в `refs-html` переключаются (скрипт +> инжектится в `scripts/extract-static.mjs`). Ниже — исходный текст на момент +> написания. -Метод: Playwright, viewport 1440×900, скриншоты обеих страниц + замер вычисленных -стилей ключевых элементов + пиксель-дифф (без чтения картинки моделью). -Пиксель-дифф: **24.9%** (наш скролл 1731px, реф 1579px). Зоны различий размазаны -по всей высоте → править надо глобально, не точечно. +Метод: Playwright, viewport 1440×900, скриншоты обеих страниц + замер +вычисленных стилей ключевых элементов + пиксель-дифф (без чтения картинки +моделью). Пиксель-дифф: **24.9%** (наш скролл 1731px, реф 1579px). Зоны различий +размазаны по всей высоте → править надо глобально, не точечно. Теперь процесс автоматизирован: `pnpm audit` (Playwright) пишет -`web/audit/audit-report.md` — light/dark токены + поэлементный дифф стилей по тексту. -Пиксель-дифф убран (неудобен для работы). Фиксы можно делать в любой момент и -перепроверять аудитом. +`web/audit/audit-report.md` — light/dark токены + поэлементный дифф стилей по +тексту. Пиксель-дифф убран (неудобен для работы). Фиксы можно делать в любой +момент и перепроверять аудитом. ## Баги (объективно, правим в любом случае) - **B1. Кнопки/инпуты рендерятся шрифтом Arial, а не IBM Plex Sans.** - Доказательство: пробы `Reset pipeline` → `fontFamily: Arial`. Причина: у `button/input` - не задан `font-family: inherit`. Фикс: добавить в reset `design2.css` - `button, input, select, textarea { font-family: inherit; }`. Влияет на все страницы. (P1) -- **B2. Двойной топбар.** Страница демо рендерит свой `.topbar` (brand / AUTO PIPELINE / - help / theme / lang), который перекрывается глобальным `fixed` Header из - `preview/+layout.svelte` (в нём только theme-toggle). В рефе — один топбар. - Контент демо-топбара (brand, статус, язык) визуально спрятан за фикс-хедером. - Фикс: убрать кастомный `.topbar` из демо и сделать хедер лейаута реф-стайл топбаром - (brand + version + status + help + theme + lang) — улучшит и C9/C10. (P1) + Доказательство: пробы `Reset pipeline` → `fontFamily: Arial`. Причина: у + `button/input` не задан `font-family: inherit`. Фикс: добавить в reset + `design2.css` `button, input, select, textarea { font-family: inherit; }`. + Влияет на все страницы. (P1) +- **B2. Двойной топбар.** Страница демо рендерит свой `.topbar` (brand / AUTO + PIPELINE / help / theme / lang), который перекрывается глобальным `fixed` + Header из `preview/+layout.svelte` (в нём только theme-toggle). В рефе — один + топбар. Контент демо-топбара (brand, статус, язык) визуально спрятан за + фикс-хедером. Фикс: убрать кастомный `.topbar` из демо и сделать хедер лейаута + реф-стайл топбаром (brand + version + status + help + theme + lang) — улучшит + и C9/C10. (P1) ## Расхождения дизайна (нужно решение) -- **F1. Фон холста.** Реф: плоский серый `#EEF1F4`. Наш: белый + blueprint-сетка. - Решение: совпасть с рефом (убрать сетку на демо) или оставить сетку как сигнатуру нового дизайна. -- **F2. Бейдж типа шага** (BACKGROUND/TRANSFORM/STYLE). Реф: приглушённый серый `#6D787F`. - Решено C16: `StepCard` рендерит `type` как приглушённый моно-текст (не синий бейдж); - индекс шага (`step-index`) — синий `#1769D2`, как в рефе. -- **F3. Подписи превью-тайлов** (SOURCE / STEP 01 / FINAL OUTPUT). Реф: синий `#1769D2`, - weight 400. Решено C16: `PreviewTile` label — синий `#1769D2`. -- **F4. Раскладка preview-stack.** Реф: одна колонка (5 строк, выше). Наш: 2 колонки (grid). - Решение: одна колонка (как реф) или оставить 2 колонки как выбор нового дизайна. +- **F1. Фон холста.** Реф: плоский серый `#EEF1F4`. Наш: белый + + blueprint-сетка. Решение: совпасть с рефом (убрать сетку на демо) или оставить + сетку как сигнатуру нового дизайна. +- **F2. Бейдж типа шага** (BACKGROUND/TRANSFORM/STYLE). Реф: приглушённый серый + `#6D787F`. Решено C16: `StepCard` рендерит `type` как приглушённый моно-текст + (не синий бейдж); индекс шага (`step-index`) — синий `#1769D2`, как в рефе. +- **F3. Подписи превью-тайлов** (SOURCE / STEP 01 / FINAL OUTPUT). Реф: синий + `#1769D2`, weight 400. Решено C16: `PreviewTile` label — синий `#1769D2`. +- **F4. Раскладка preview-stack.** Реф: одна колонка (5 строк, выше). Наш: 2 + колонки (grid). Решение: одна колонка (как реф) или оставить 2 колонки как + выбор нового дизайна. - **F5. Вес лейблов контролов.** Реф: 400. Наш (`MonoLabel`): 500. Мелко. - **F6. Мета-подписи** (DIMENSIONS/FORMAT/SIZE): реф fs 9px, наш 10px. Мелко. -- **F7. Копирайт:** `Add tool` у нас `+ Add tool`; кнопка `Download result` — сверить стиль - (в рефе синяя с белым текстом 478×42). Пробы `Download result`/`Add tool`/`DIRECTION` - вернули NOT FOUND — скорее артефакт замера (вложенный текст/чип размера), проверить визуально. +- **F7. Копирайт:** `Add tool` у нас `+ Add tool`; кнопка `Download result` — + сверить стиль (в рефе синяя с белым текстом 478×42). Пробы + `Download result`/`Add tool`/`DIRECTION` вернули NOT FOUND — скорее артефакт + замера (вложенный текст/чип размера), проверить визуально. - **F8. h1 line-height** чуть выше (наш 150px vs реф 115px) — мелкий зазор. -- **F9. Вертикальный ритм:** контент на ~152px выше, зазоры между секциями отличаются - (pipeline-head y 434 vs 400 и т.д.) — подровнять паддинги под реф. -- **F10. Внутренние отступы preview-панели** (Hide intermediate x 907 vs 1218) — паддинг панели. +- **F9. Вертикальный ритм:** контент на ~152px выше, зазоры между секциями + отличаются (pipeline-head y 434 vs 400 и т.д.) — подровнять паддинги под реф. +- **F10. Внутренние отступы preview-панели** (Hide intermediate x 907 vs 1218) — + паддинг панели. ## Темы (отложено — F1-F4) -Серый фон `#EEF1F4` — это **светлая** тема. Уточнение по F1: наш токен `--background` -уже равен `#EEF1F4` (совпадает с рефом); видимое отличие — только blueprint-сетка -поверх фона, а не «белый vs серый». То есть F1 = «сетка против плоского», не цвет фона. +Серый фон `#EEF1F4` — это **светлая** тема. Уточнение по F1: наш токен +`--background` уже равен `#EEF1F4` (совпадает с рефом); видимое отличие — только +blueprint-сетка поверх фона, а не «белый vs серый». То есть F1 = «сетка против +плоского», не цвет фона. Перед тем как сверять цвета бейджей/тайлов/раскладок (F2-F4), нужно сначала зафиксировать палитры light/dark как единый источник токенов. Сейчас они есть в -`web/src/lib/styles/design2.css` (`:root` + `[data-theme="dark"]`), но не сверены с рефом. +`web/src/lib/styles/design2.css` (`:root` + `[data-theme="dark"]`), но не +сверены с рефом. **Как проверить темы в refs-html:** -- Открыть `refs-html/demo.html` (и соседние `gradient.html`, `background-remover.html`) - через Playwright/браузер. +- Открыть `refs-html/demo.html` (и соседние `gradient.html`, + `background-remover.html`) через Playwright/браузер. - Нажать theme-toggle в топбаре рефа (иконка луны/солнца) — переключает тему - (скорее всего меняет атрибут/класс на `` или `.page` и переопределяет CSS-переменные). -- Снять скриншоты светлой и тёмной темы, замерить computed-стиль `:root` переменных - рефа (через `getComputedStyle(document.documentElement).getPropertyValue('--...')`) - и сопоставить с нашими токенами в `design2.css`. + (скорее всего меняет атрибут/класс на `` или `.page` и переопределяет + CSS-переменные). +- Снять скриншоты светлой и тёмной темы, замерить computed-стиль `:root` + переменных рефа (через + `getComputedStyle(document.documentElement).getPropertyValue('--...')`) и + сопоставить с нашими токенами в `design2.css`. - Рефы, похоже, не доделаны по тёмной теме — зафиксировать, какие именно токены - меняются и где расхождения. На основе этого выровнять наши токены, затем вернуться - к F2 (бейдж типа), F3 (тайлы), F4 (раскладка preview-stack). + меняются и где расхождения. На основе этого выровнять наши токены, затем + вернуться к F2 (бейдж типа), F3 (тайлы), F4 (раскладка preview-stack). -Решение по F1-F4 принимается только после сверки токенов светлой/тёмной темы с рефом. +Решение по F1-F4 принимается только после сверки токенов светлой/тёмной темы с +рефом. **Состояние тем (уточнение):** наша dark-тема реализована в `design2.css` (`[data-theme="dark"]`), но пока подключена только на preview-маршрутах (старые -страницы грузят `app.css`). Этого достаточно для сверки: токены, отличные от light, -соберём по всем `refs-html/*.html` (не только `demo.html`), остальные токены -не меняются. Light-токены уже совпадают с рефом (`--background`, `--panel`, -`--foreground`, `--muted`, `--line`, `--blue` равны). +страницы грузят `app.css`). Этого достаточно для сверки: токены, отличные от +light, соберём по всем `refs-html/*.html` (не только `demo.html`), остальные +токены не меняются. Light-токены уже совпадают с рефом (`--background`, +`--panel`, `--foreground`, `--muted`, `--line`, `--blue` равны). ## Состояние (C16) -Цветовые расхождения против `refs-html/demo.html` устранены: стек шрифтов сверен с -рефом (`"IBM Plex Sans", sans-serif` / `"IBM Plex Mono", monospace`), бренд топбара — -`--foreground`, индекс шага — синий, тип шага и подпись плитки — как в рефе, -`Segmented` — 10px/32px. Аудит: токены light 10 / dark 16, элементы 47. Остаток — -sub-pixel (10px→9px лейблы, letter-spacing) и структурные (`display`/`rect` от наших -flex/grid раскладок против статичного мока); визуально незначимы, правим точечно при -желании. Старые тесты (583) — без регрессий. +Цветовые расхождения против `refs-html/demo.html` устранены: стек шрифтов сверен +с рефом (`"IBM Plex Sans", sans-serif` / `"IBM Plex Mono", monospace`), бренд +топбара — `--foreground`, индекс шага — синий, тип шага и подпись плитки — как в +рефе, `Segmented` — 10px/32px. Аудит: токены light 10 / dark 16, элементы 47. +Остаток — sub-pixel (10px→9px лейблы, letter-spacing) и структурные +(`display`/`rect` от наших flex/grid раскладок против статичного мока); +визуально незначимы, правим точечно при желании. Старые тесты (583) — без +регрессий. ## План действий (предлагаю) -> Актуальный пошаговый план фикса вынесен в `docs/plan-design-fix.md` -> (Этапы 0–1: рефакторинг + верность). Ниже — исходные наброски. +> Актуальный пошаговый план фикса вынесен в `docs/plan-design-fix.md` (Этапы +> 0–1: рефакторинг + верность). Ниже — исходные наброски. 1. Сразу поправить B1 + B2 (чёткие баги, улучшают все страницы). -2. По F1–F4 — решение за тобой (совпадать с рефом дословно ИЛИ оставить как сигнатуру нового дизайна). -3. F5–F10 — мелочи, подчистить в том же проходе, если решаем «совпадать с рефом». +2. По F1–F4 — решение за тобой (совпадать с рефом дословно ИЛИ оставить как + сигнатуру нового дизайна). +3. F5–F10 — мелочи, подчистить в том же проходе, если решаем «совпадать с + рефом». 4. Переснять скриншоты и перепроверить пиксель-дифф. diff --git a/docs/archive/design-system.md b/docs/archive/design-system.md index 9159f70..7c72971 100644 --- a/docs/archive/design-system.md +++ b/docs/archive/design-system.md @@ -4,20 +4,36 @@ ## 1. Цели и принципы -1. **Один источник правды** — все визуальные константы живут в токенах `web/src/app.css`; ни один компонент не пишет «магические» цвета/размеры напрямую. -2. **Никакого дублирования** — повторяющийся визуальный рецепт оформляется один раз: либо токен, либо глобальный утилитарный класс, либо компонент в `ui/`. -3. **Простые компоненты важнее умных** — если проще сделать отдельные `SliderField` и `TextField`, делаем два маленьких компонента, а не один конфигурируемый монстр. -4. **Scoped-стили — только для раскладки** конкретного компонента (сетка, отступы между блоками). Внешний вид контролов и текстов — из дизайн-системы. +1. **Один источник правды** — все визуальные константы живут в токенах + `web/src/app.css`; ни один компонент не пишет «магические» цвета/размеры + напрямую. +2. **Никакого дублирования** — повторяющийся визуальный рецепт оформляется один + раз: либо токен, либо глобальный утилитарный класс, либо компонент в `ui/`. +3. **Простые компоненты важнее умных** — если проще сделать отдельные + `SliderField` и `TextField`, делаем два маленьких компонента, а не один + конфигурируемый монстр. +4. **Scoped-стили — только для раскладки** конкретного компонента (сетка, + отступы между блоками). Внешний вид контролов и текстов — из дизайн-системы. ## 2. Аудит: что дублируется прямо сейчас -- **«Приглушённый текст»** (`--text-muted` + размер 0.85–0.95rem) — повторён в семи местах: `ToolPage` (.description, .hint), главная (.lead, .card-desc), лейаут (.nav-link, footer), `Preview` (.dims), `ParamForm` (label), `InfoPanel` (dt). -- **«Заголовок секции капсом»** (uppercase, letter-spacing, muted) — точная копипаста в `ToolPage` и на главной. -- **Базовые инпуты** (padding, border, radius) — живут только внутри `ParamForm`; любые новые типы контролов (слайдеры, пипетка цвета) пришлось бы копировать вручную. -- **Блок «label + отступы»** — дублировался бы в каждом `*Field`; решается обёрткой `Field.svelte`. -- **«Панель»** (surface + border + radius) — три ручные реализации одного паттерна: `DropZone`, карточки главной, строки `InfoPanel`. -- **Кнопки** — глобальные классы `.primary`/`.secondary` без компонента; состояния «занят/недоступен» каждый описывает заново. -- **Ошибка-баннер** — только в `ToolPage`, а понадобится минимум ещё раз в workspace. +- **«Приглушённый текст»** (`--text-muted` + размер 0.85–0.95rem) — повторён в + семи местах: `ToolPage` (.description, .hint), главная (.lead, .card-desc), + лейаут (.nav-link, footer), `Preview` (.dims), `ParamForm` (label), + `InfoPanel` (dt). +- **«Заголовок секции капсом»** (uppercase, letter-spacing, muted) — точная + копипаста в `ToolPage` и на главной. +- **Базовые инпуты** (padding, border, radius) — живут только внутри + `ParamForm`; любые новые типы контролов (слайдеры, пипетка цвета) пришлось бы + копировать вручную. +- **Блок «label + отступы»** — дублировался бы в каждом `*Field`; решается + обёрткой `Field.svelte`. +- **«Панель»** (surface + border + radius) — три ручные реализации одного + паттерна: `DropZone`, карточки главной, строки `InfoPanel`. +- **Кнопки** — глобальные классы `.primary`/`.secondary` без компонента; + состояния «занят/недоступен» каждый описывает заново. +- **Ошибка-баннер** — только в `ToolPage`, а понадобится минимум ещё раз в + workspace. ## 3. Токены (расширение `app.css`) @@ -55,11 +71,13 @@ - `.text-muted` — приглушённый текст (размер наследуется); - `.text-caption` — мелкий текст `--text-s`; - `.heading-section` — заголовок секции капсом (вместо двух копипаст); -- `.panel` — поверхность с бордером/радиусом (DropZone, карточки, InfoPanel-строки); +- `.panel` — поверхность с бордером/радиусом (DropZone, карточки, + InfoPanel-строки); - `.error-banner` — баннер ошибки; - `.visually-hidden` — скрытый, но читаемый скринридерами. -Элементные стили из текущего `app.css` (голые `input, select { ... }`) убрать — внешний вид контролов переезжает в компоненты `ui/`. +Элементные стили из текущего `app.css` (голые `input, select { ... }`) убрать — +внешний вид контролов переезжает в компоненты `ui/`. ## 5. Состав UI-компонентов (`web/src/lib/components/ui/`) @@ -76,15 +94,23 @@ | `ColorField.svelte` | `id`, `label`, `value = $bindable()` | color-ветка `ParamForm`; точка будущего расширения пипеткой | | `EmptyState.svelte` | `title`, `hint`, слот действия | «Обработка…», будущие пустые панели исходника/результата | -`Field` — чисто раскладочная обёртка (`
`), ничего не знает о типе контрола. На ней построены `TextField`, `SliderField`, `SelectField`, `ColorField`. Исключение — `CheckboxField`: у чекбокса label оборачивает контрол (кликабельная строка), поэтому у него своя раскладка. +`Field` — чисто раскладочная обёртка +(`
`), ничего не знает о +типе контрола. На ней построены `TextField`, `SliderField`, `SelectField`, +`ColorField`. Исключение — `CheckboxField`: у чекбокса label оборачивает контрол +(кликабельная строка), поэтому у него своя раскладка. ## 6. Миграция существующего -1. `ParamForm` — переписывается на `Field` + `*Field`-компоненты; собственная стилизация контролов и label удаляется. +1. `ParamForm` — переписывается на `Field` + `*Field`-компоненты; собственная + стилизация контролов и label удаляется. 2. `DownloadButton` и кнопки `ToolPage` — на `Button`. -3. `DropZone`, карточки главной, строки `InfoPanel` — на `.panel` + свои раскладочные стили. -4. `ToolPage`, главная, лейаут — приглушённые тексты на `.text-*`, заголовки секций на `.heading-section`, баннер на `.error-banner`. -5. Из `app.css` удалить глобальные классы кнопок и голые стили инпутов после перехода. +3. `DropZone`, карточки главной, строки `InfoPanel` — на `.panel` + свои + раскладочные стили. +4. `ToolPage`, главная, лейаут — приглушённые тексты на `.text-*`, заголовки + секций на `.heading-section`, баннер на `.error-banner`. +5. Из `app.css` удалить глобальные классы кнопок и голые стили инпутов после + перехода. ## 7. Порядок работ @@ -97,13 +123,18 @@ ## 8. Критерии готовности -- Ни один компонент не задаёт цвет/радиус/шрифт мимо токенов или классов системы. +- Ни один компонент не задаёт цвет/радиус/шрифт мимо токенов или классов + системы. - Все интерактивные контролы на страницах — экземпляры `ui/*`. -- `grep -E '#[0-9a-f]{3,6}' web/src/lib/components` не находит ничего, кроме токенов в `app.css`. -- Визуальной регрессии нет (ручная сверка всех страниц до/после), тесты и билд зелёные. +- `grep -E '#[0-9a-f]{3,6}' web/src/lib/components` не находит ничего, кроме + токенов в `app.css`. +- Визуальной регрессии нет (ручная сверка всех страниц до/после), тесты и билд + зелёные. ## 9. Что сознательно не делаем - Никаких CSS-фреймворков и препроцессоров — обычный CSS, как решено на старте. -- Никаких тем/тёмного режима — но токены сразу семантические, чтобы тема добавилась заменой значений `:root`. -- Никаких сложных составных компонентов (модалки, таблицы) — они появятся, когда потребуется реальная задача. +- Никаких тем/тёмного режима — но токены сразу семантические, чтобы тема + добавилась заменой значений `:root`. +- Никаких сложных составных компонентов (модалки, таблицы) — они появятся, когда + потребуется реальная задача. diff --git a/docs/archive/mvp-plan.md b/docs/archive/mvp-plan.md index c3b8b44..27e9d97 100644 --- a/docs/archive/mvp-plan.md +++ b/docs/archive/mvp-plan.md @@ -4,38 +4,59 @@ > > **Что сделано по плану:** > -> - Шаг 0 — каркас: SvelteKit + adapter-static, design-токены на CSS, лейаут с навигацией по категориям ✔ -> - Шаг 1 — ядро: `PixelImage`, декодирование файлов, кодирование PNG/JPEG/WebP, скачивание ✔ -> - Шаг 2 — операции ядра: color / geometry / alpha / analyze, чистые функции без DOM ✔ +> - Шаг 0 — каркас: SvelteKit + adapter-static, design-токены на CSS, лейаут с +> навигацией по категориям ✔ +> - Шаг 1 — ядро: `PixelImage`, декодирование файлов, кодирование PNG/JPEG/WebP, +> скачивание ✔ +> - Шаг 2 — операции ядра: color / geometry / alpha / analyze, чистые функции +> без DOM ✔ > - Шаг 3 — реестр из 11 инструментов + санитизация параметров ✔ -> - Шаг 4 — универсальные UI-компоненты (DropZone, Preview, ParamForm, DownloadButton, InfoPanel, ToolPage) ✔ -> - Шаг 5 — маршруты: `/tools/[id]` с prerender всех страниц, главная-каталог по категориям ✔ +> - Шаг 4 — универсальные UI-компоненты (DropZone, Preview, ParamForm, +> DownloadButton, InfoPanel, ToolPage) ✔ +> - Шаг 5 — маршруты: `/tools/[id]` с prerender всех страниц, главная-каталог по +> категориям ✔ > - Шаги 6–7 — обработка ошибок, состояния, полировка; верификация ✔ > -> **Checkpoint пройден:** все 11 инструментов вручную проверены в браузере (загрузка → применение → скачивание), включая найденный и исправленный баг resize с сохранением пропорций. Тесты 75/75, svelte-check 0 ошибок, статический экспорт собирается. +> **Checkpoint пройден:** все 11 инструментов вручную проверены в браузере +> (загрузка → применение → скачивание), включая найденный и исправленный баг +> resize с сохранением пропорций. Тесты 75/75, svelte-check 0 ошибок, +> статический экспорт собирается. > -> **Попутные улучшения сверх плана:** корневой скрипт `pnpm dev`; `sanitizeParams` против пустых/невалидных значений полей; семантика «вписать в размеры» для resize. +> **Попутные улучшения сверх плана:** корневой скрипт `pnpm dev`; +> `sanitizeParams` против пустых/невалидных значений полей; семантика «вписать в +> размеры» для resize. ## 0. Зафиксированные решения -- **Объём MVP:** только одиночные инструменты «загрузил → настроил → скачал». Пайплайн-workspace (`/workspace`) вне скоупа этого плана. -- **Набор инструментов первого среза:** ~11 базовых EASY (см. §3). Достаточно, чтобы проверить насквозь ядро → реестр → универсальную страницу. -- **Стили:** обычный CSS без фреймворка — design-токены на CSS custom properties, ноль зависимостей. -- **Стек:** SvelteKit + `@sveltejs/adapter-static` + TypeScript, пакетный менеджер pnpm (уже зафиксирован в корневом `package.json`). Весь код сайта — в `web/`. -- **Ядро без DOM:** все пиксельные операции — чистые функции над `PixelImage`; DOM/canvas допускается только в модуле IO и компонентах превью. +- **Объём MVP:** только одиночные инструменты «загрузил → настроил → скачал». + Пайплайн-workspace (`/workspace`) вне скоупа этого плана. +- **Набор инструментов первого среза:** ~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, без лишних аддонов). +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`: шапка с названием сайта и навигацией по категориям инструментов (категории — константа из реестра), футер. +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` отдаёт статический экспорт. +**Проверка:** `pnpm dev` открывает пустой каркас; `pnpm build && pnpm preview` +отдаёт статический экспорт. --- @@ -46,28 +67,42 @@ - `types.ts` ```ts - export type PixelImage = { width: number; height: number; data: Uint8ClampedArray }; // RGBA, как ImageData + 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`; + - `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, закодировать обратно, размеры совпадают. +**Проверка:** юнит-смоук — декодировать фикстурный PNG, закодировать обратно, +размеры совпадают. --- ## 3. Шаг 2 — Операции ядра (чистые функции) -Файлы в `web/src/lib/core/`, каждая функция: `(img, params) => new PixelImage`, без мутации входа: +Файлы в `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. +- `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 — ожидаемые значения руками. +**Проверка:** vitest на маленьких матрицах пикселей (2×2, 3×3): flip, rotate, +crop, grayscale — ожидаемые значения руками. --- @@ -77,8 +112,22 @@ ```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: "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 }; @@ -88,11 +137,15 @@ export type ToolEntry = { description: string; category: CategoryId; params: ParamDef[]; - run: (img: PixelImage, params: Record) => Promise | PixelImage; + run: ( + img: PixelImage, + params: Record, + ) => Promise | PixelImage; }; ``` -Категории (константа там же): `convert`, `alpha`, `color`, `geometry`, `analyze`. +Категории (константа там же): `convert`, `alpha`, `color`, `geometry`, +`analyze`. ### Состав первого среза — 11 инструментов @@ -110,9 +163,11 @@ export type ToolEntry = { | 10 | `remove-color-from-png` | alpha | hex color, tolerance % | | 11 | `png-info` | analyze | — особый случай: не картинка, а текстовая сводка | -Для `png-info` в записи реестра добавляется флаг `resultType: 'info'`; остальные — `'image'`. +Для `png-info` в записи реестра добавляется флаг `resultType: 'info'`; остальные +— `'image'`. -**Проверка:** импорт реестра в тесте — все id уникальны, у каждого `params` имеют дефолты, `run` определён. +**Проверка:** импорт реестра в тесте — все id уникальны, у каждого `params` +имеют дефолты, `run` определён. --- @@ -120,33 +175,45 @@ export type ToolEntry = { `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`, имя `.`. +- `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, справа превью + форма параметров + кнопки «Применить» / «Скачать». +- `ToolPage.svelte` — оркестратор страницы инструмента; состояния + `idle → loaded → processing → done | error`; раскладка: слева DropZone, справа + превью + форма параметров + кнопки «Применить» / «Скачать». -**Правило:** новый инструмент = новая запись в реестре (+ функция операции). Компоненты и маршрут не трогаем. +**Правило:** новый инструмент = новая запись в реестре (+ функция операции). +Компоненты и маршрут не трогаем. --- ## 6. Шаг 5 — Маршруты -- `/` (`src/routes/+page.svelte`) — сетка карточек инструментов, сгруппированных по категориям; данные из реестра. +- `/` (`src/routes/+page.svelte`) — сетка карточек инструментов, сгруппированных + по категориям; данные из реестра. - `/tools/[id]` — универсальная страница: - - `+page.ts`: `entries()` возвращает все `id` из реестра (обязательное условие prerender при adapter-static); + - `+page.ts`: `entries()` возвращает все `id` из реестра (обязательное условие + prerender при adapter-static); - `load()` возвращает запись реестра по `id`, неизвестный id → 404; - `+page.svelte` — тонкая обёртка над `ToolPage`. -**Проверка:** `pnpm build` — в `build/tools/` лежат все 11 страниц; главная содержит ссылки на каждую. +**Проверка:** `pnpm build` — в `build/tools/` лежат все 11 страниц; главная +содержит ссылки на каждую. --- ## 7. Шаг 6 — Ошибки и минимальная полировка -- Обработка ошибок: неподдерживаемый формат, ошибка декода, некорректные параметры crop (выход за границы — клампы или понятная ошибка). -- Состояния: «перетащите файл», спиннер во время обработки, disabled-кнопки до загрузки. +- Обработка ошибок: неподдерживаемый формат, ошибка декода, некорректные + параметры crop (выход за границы — клампы или понятная ошибка). +- Состояния: «перетащите файл», спиннер во время обработки, disabled-кнопки до + загрузки. - Тексты описаний для всех 11 инструментов (title/description уже в реестре). --- @@ -161,6 +228,10 @@ cd web && pnpm build # статический экспорт должен cd web && pnpm test # vitest: ядро (геометрия/цвет) + целостность реестра ``` -Ручной smoke-тест каждого из 11 инструментов: загрузка PNG → применить → скачать → открыть результат. +Ручной smoke-тест каждого из 11 инструментов: загрузка PNG → применить → скачать +→ открыть результат. -**Checkpoint MVP:** любой из 11 инструментов доступен по прямому URL с главной страницы; PNG загружается drag&drop; параметры применяются; результат скачивается; всё работает офлайн со статического хостинга; ни одной серверной зависимости. +**Checkpoint MVP:** любой из 11 инструментов доступен по прямому URL с главной +страницы; PNG загружается drag&drop; параметры применяются; результат +скачивается; всё работает офлайн со статического хостинга; ни одной серверной +зависимости. diff --git a/docs/archive/plan-audit.md b/docs/archive/plan-audit.md index 6efb4de..0655e48 100644 --- a/docs/archive/plan-audit.md +++ b/docs/archive/plan-audit.md @@ -2,15 +2,14 @@ > **СТАТУС: РЕАЛИЗОВАН (2026-09-07) — устарел.** Скрипты > `web/scripts/audit-dom.mjs`, `audit-css.mjs`, `audit-cdp.mjs`, -> `audit-cdp-responsive.mjs` и npm-скрипты `refs-dom-audit` / -> `refs-css-audit` / `refs-cdp-audit` на месте; старый текстовый `audit.mjs` -> удалён. Актуальная доводка верности и статус пунктов F — в -> `plan-design-fix.md`. +> `audit-cdp-responsive.mjs` и npm-скрипты `refs-dom-audit` / `refs-css-audit` / +> `refs-cdp-audit` на месте; старый текстовый `audit.mjs` удалён. Актуальная +> доводка верности и статус пунктов F — в `plan-design-fix.md`. -> Статус: к выполнению. Ветка `feat/redesign`. -> Источник правды по пикселям: `refs-html/*.html` (статический экспорт рефа). -> Актуальный рабочий аудит сейчас — `pnpm refs-audit` (`scripts/audit.mjs`), но он -> **сломан** (см. «Почему текущий аудит не работает») и подлежит замене. +> Статус: к выполнению. Ветка `feat/redesign`. Источник правды по пикселям: +> `refs-html/*.html` (статический экспорт рефа). Актуальный рабочий аудит сейчас +> — `pnpm refs-audit` (`scripts/audit.mjs`), но он **сломан** (см. «Почему +> текущий аудит не работает») и подлежит замене. ## Почему текущий аудит не работает @@ -37,13 +36,13 @@ Заводим их через `t()` — совпадёт с рефом (EN). 2. **Имена классов НЕ сводим к рефу.** Матчинг делаем по **структурному пути**, а не по классу. Компоненты `kit/` остаются идиоматичными, Stage 0 не - откатываем. Требуется только эквивалентность *структуры* (теги, вложенность, + откатываем. Требуется только эквивалентность _структуры_ (теги, вложенность, порядок сиблингов, наличие узлов, текст). 3. **Пиксельный дифф (Playwright + pixelmatch) пока НЕ делаем.** DOM+CSS-аудита достаточно; добавим как сеть позже, если стили что-то упустят. 4. **Порядок прогона маршрутов:** `demo` → `list-tools` → затем 2 инструмента - (`tools/linear-gradient-png` ↔ `gradient.html`, - `tools/remove-background-png` ↔ `background-remover.html`). + (`tools/linear-gradient-png` ↔ `gradient.html`, `tools/remove-background-png` + ↔ `background-remover.html`). ## Принципы @@ -106,15 +105,15 @@ Svelte-атрибутов и комментов гидрации), чтобы п - `moved` — тот же текст/тег, другой путь (перепутанный порядок/родитель); - `textMismatch` — нормализованный текст различается (в т.ч. RU vs EN). -Класс-мисматч — **только информационно**, не блокирует (классы свои по -решению №2). Счётчики: `added / removed / moved / textMismatch`. +Класс-мисматч — **только информационно**, не блокирует (классы свои по решению +№2). Счётчики: `added / removed / moved / textMismatch`. ### Рендеринг - **наше:** dev-сервер на `5179` (как сейчас в `audit.mjs`). - **реф:** `pathToFileURL` к `refs-html/.html` (`file://`). Реф — - самодостаточный (инлайн `