diff --git a/docs/plan-list-tools.md b/docs/plan-list-tools.md new file mode 100644 index 0000000..bf3cb20 --- /dev/null +++ b/docs/plan-list-tools.md @@ -0,0 +1,97 @@ +# Plan: `/preview/list-tools` → design2 (catalog) + +Ветка `feat/redesign`. Цель — привести каталог инструментов к рефу +`refs-html/list-tools.html`. Сейчас страница вообще не на design2: по +мульти-аудиту `onlyOurs=257, onlyRef=54` (демо-страница — `18/11`). + +## Текущее состояние + +- `src/routes/preview/list-tools/+page.svelte` использует собственную вёрстку + (`.catalog` / `.cat-head` / `.cat-grid`) + `Panel` / `PanelHeading` / + `ToolCard` (kit). Нет поиска, фильтров, счётчика, навигация по категориям + сделана через `Panel`-блоки, а не через `.catalog-groups`. +- Реф (`list-tools.html`) строит: + - `.catalog-page` → `.catalog-head` (`.eyebrow`, `h1` «Tool catalog», + `p` lede, `.catalog-total` «NN TOOLS AVAILABLE» с большим числом), + - `.catalog-toolbar` (`.catalog-search` + `.catalog-filters` с кнопками), + - `.catalog-groups` (grid `repeat(2,minmax(0,1fr))`, gap `56px 28px`) → + `.catalog-group` (`.group-title` «CAT · NN TOOLS») → `.tool-cards` → + `.tool-card`. +- `.tool-card` (реф) — `display:grid`, колонки `42px minmax(0,1fr) 24px 18px`, + `min-height:106px`, `gap:14px`, `padding:16px`: `.tool-icon` (42×42 + синий размытый квадрат), `.tool-copy` (`strong` 13px mono + `span` 12px mono + muted), `.tool-index` (10px mono, сверху), `.tool-arrow` (появляется при hover). +- Токены `design2.css` уже совпадают с рефом (`--background #eef1f4`, + `--panel #f8fafb`, `--foreground #17212b`, `--muted #6d7883`, `--line #cbd3da`, + `--blue #1769d2`, `--radius 4px`) — править токены не нужно. + +## Шаги + +1. **Restyle `kit/ToolCard.svelte`** под реф (меняется «на месте» — используется + ещё в `preview/+page` и `preview/kit`, стиль унифицируется, ок): + - grid-колонки `42px minmax(0,1fr) 24px 18px`, `align-items:center`, + `gap:14px`, `min-height:106px`, `padding:16px`, `border:1px solid line`, + `background:panel`, `border-radius:radius`. + - `.tool-icon`: 42×42 `place-items:center`, `background:color-mix(in srgb, + var(--blue) 12%, transparent)`, `color:var(--blue)` (реф использует + `--panel-strong`; вместо нового токена берём этот `color-mix`). + - `.tool-copy strong`: `600 13px var(--font-mono)`; `span`: `12px/1.5 mono` + `muted`. + - `.tool-index`: `10px mono muted`, `align-self:start`. + - `.tool-arrow`: `color:var(--blue)`, `opacity:0` → `1` + `translate(2px,-2px)` + на `.tool-card:hover`. + - DOM-порядок как в рефе: `icon, copy, index, arrow`. + - `href` через `resolve` из `$app/paths` (убрать `svelte-ignore`). + +2. **`kit/CatalogHeader.svelte`**: `.eyebrow` «EASY-PNG-TOOLS / CATALOG», + `h1` «Tool catalog», lede; `.catalog-total` с динамическим `TOOLS.length` + (реф показывает `32`). + +3. **`kit/CatalogToolbar.svelte`**: `.catalog-search` (иконка + ``) и + `.catalog-filters` (кнопки по категориям). Состояние `query` / `category` + поднимается наружу через `bind:` (или callback-пропы), чтобы страница + фильтровала список. Фильтры функциональные (клиентский фильтр по названию + + категории), не декоративные. + +4. **`kit/CatalogGroup.svelte`**: `.group-title` (uppercase label категории + + «NN TOOLS») + слот со списком карточек; сетка `.tool-cards` — `gap:10px` + (реф), на мобиле (`@media max-width:800px`) всё схлопывается в 1 колонку. + +5. **Переписать `preview/list-tools/+page.svelte`** как тонкую композицию: + - обёртка `.catalog-page` (`padding:60px clamp(24px,4vw,72px) 72px`); + - ``; + - ``; + - `.catalog-groups` из отфильтрованных групп (по `CATEGORIES`, пересчитав + `tools` с учётом `query`/`category`); + - внутри `` — `` на каждый tool (`href= + "/preview/tools/{id}"`, `icon` из `tool-icons`, `index` по порядку, + `title`/`description` из registry); + - убрать `Panel`/`PanelHeading` из этого маршрута. + +6. **Категории/локализация**: labels привести к рефу (реф: ALL / CONVERT / + TRANSPARENCY / COLOR / GEOMETRY / FILTERS / ANALYZE). Наши `CATEGORIES` + (`convert/alpha/color/geometry/filters/text/analyze/generate`) — сопоставить + (`alpha`→TRANSPARENCY и т.п.); точное сопоставление уточнить по рефу и + `i18n/ru.ts`. Фильтры = ALL + те же label'ы. Тексты заголовков/lede — по + рефу (англ. «Tool catalog» / «Focused utilities for working with PNG…»), + либо оставить RU-вариант, если это сознательное отклонение (зафиксировать). + +7. **Адаптив**: перенести правила рефа (`@media max-width:800px`) — + `.catalog-groups` → 1fr, `.tool-card` → `38px minmax(0,1fr) 18px`, скрыть + `.tool-index`/показать `.tool-arrow`, `.catalog-head` в колонку и т.д. + +## Верификация + +- `pnpm lint`, `pnpm exec svelte-check --tsconfig ./tsconfig.json`, + `pnpm build` — без ошибок. +- `pnpm refs-audit` (теперь мульти-маршрутный) → глянуть секцию + `/preview/list-tools`: `onlyOurs`/`onlyRef` должны упасть с `257/54` + примерно до уровня demo (`18/11`). Остаточный diff (если будет) зафиксировать + в этом файле. + +## Примечания + +- `ToolCard` правится на месте — влияет на `preview/+page` и `preview/kit` + (там тоже карточки каталога, унификация уместна). +- Старый дизайн (`routes/(old)/**`, `components/search/**`) не трогать. +- Коммиты делает автор (агент не коммитит). diff --git a/refs-html/README.md b/refs-html/README.md index 7c67ffd..42a0262 100644 --- a/refs-html/README.md +++ b/refs-html/README.md @@ -6,12 +6,17 @@ ## Файлы -| Файл | Маршрут в refs | Что показывает | -| ------------------------- | ------------------------------------ | --------------------------------------------------- | -| `index.html` | `/` | Главная: пайплайн-воркспейс + панель превью | -| `demo.html` | `/easy-png-tools/demo` | То же, что главная (зеркало) | -| `gradient.html` | `/easy-png-tools/gradient` | Инструмент «Градиент»: настройки + превью + CSS | -| `background-remover.html` | `/easy-png-tools/background-remover` | Инструмент «Удаление фона»: сравнение source/result | +| Файл | Маршрут в нашем приложении (audit) | Что показывает | +| ------------------------- | -------------------------------------- | --------------------------------------------------- | +| `demo.html` | `/preview/demo` | Пайплайн-воркспейс + панель превью | +| `list-tools.html` | `/preview/list-tools` | Каталог инструментов (группы, поиск, фильтры) | +| `gradient.html` | `/preview/tools/linear-gradient-png` | Инструмент «Градиент»: настройки + превью + CSS | +| `background-remover.html` | `/preview/tools/remove-background-png` | Инструмент «Удаление фона»: сравнение source/result | + +> `index.html` не используется: в Next-рефе это зеркало `demo` (главная == `/easy-png-tools/demo`), +> а в нашем приложении `/preview` — своя страница (воркспейс). Файл не генерируется +> экстрактором (`scripts/extract-static.mjs` пропускает `index.html`) и исключён +> из аудита (`EXCLUDE` в `web/scripts/audit.mjs`). ## Особенности diff --git a/refs-html/index.html b/refs-html/index.html deleted file mode 100644 index 37d55ae..0000000 --- a/refs-html/index.html +++ /dev/null @@ -1,2608 +0,0 @@ - - - - - - - - - easy-png-tools / Demo - - - - - - -
-
- - EP - - easy-png-tools - - / - WORKSPACE - - - -
- - - - AUTO PIPELINE - - - -
- - / - - -
-
-
-
-
-
PNG PROCESSING - / - WORKSPACE -
-
-
-

Build your image pipeline. -

-

Chain simple tools together. Every change is processed automatically and previewed at each stage. -

-
-
- - source.png - - 1.8 MB - -
-
-
-
- PROCESSING PIPELINE - - 4 - active steps - • LIVE - - -
- -
-
-
-
01 -
- -
-
-
- BACKGROUND - -

Gradient background -

-
-
- - AUTO - - - -
-
-
-
- -
- - - - -
-
-
- - -
-
- -
- - - - -
-
-
-
-
-
-
02 -
- -
-
-
- TRANSFORM - -

Remove background -

-
-
- - AUTO - - - -
-
-
- Automatic subject detection enabled -
-
-
-
-
03 -
- -
-
-
- STYLE - -

Add outline -

-
-
- - AUTO - - - -
-
-
-
- - -
-
- -
- - - -
-
-
-
-
-
-
04 -
- -
-
-
- STYLE - -

Round corners -

-
-
- - AUTO - - - -
-
-
-
- - -
-
- Preserve aspect ratio - - -
-
-
-
-
- -
-
-
-
- PIPELINE OUTPUTS - - Visual history - -
-
- - -
-
- DIMENSIONS - - 1200 × 800 px - -
-
- FORMAT - - PNG-24 - -
-
- SIZE - - 1.2 MB - -
-
- -
-
-
-
-
-
- PNG - - easy-png-tools - -
-
-
- SOURCE - - original.png · 1200 × 800 - -
-
-
-
-
- PNG - - easy-png-tools - -
-
-
- STEP 01 - - gradient applied - -
-
-
-
-
- PNG - - easy-png-tools - -
-
-
- STEP 02 - - background removed - -
-
-
-
-
- PNG - - easy-png-tools - -
-
-
- STEP 03 - - outline added - -
-
-
-
-
- PNG - - easy-png-tools - -
-
-
- FINAL OUTPUT - - ready · PNG-24 - -
-
-
-

Output is generated in-browser. Your files never leave this device. -

-
-
-
- easy-png-tools - v2.4.0 - - - - pipeline is local-only - - © 2024 - -
-
- - - - - - - - - - \ No newline at end of file diff --git a/scripts/extract-static.mjs b/scripts/extract-static.mjs index aab5faa..6aad3a1 100644 --- a/scripts/extract-static.mjs +++ b/scripts/extract-static.mjs @@ -1,5 +1,5 @@ -import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from 'node:fs'; -import { join, dirname, basename } from 'node:path'; +import { readFileSync, writeFileSync, mkdirSync, readdirSync, statSync } from "node:fs"; +import { join, dirname, basename } from "node:path"; // Usage: node extract-static.mjs // e.g. node extract-static.mjs .next\server\app ..\refs-html @@ -25,40 +25,50 @@ const THEME_TOGGLE = ` `; function injectThemeToggle(html) { - if (html.includes('__themeToggleInjected')) return html; - const tag = ''; - if (html.includes(tag)) return html.replace(tag, THEME_TOGGLE + '\n' + tag); - if (html.includes('')) return html.replace('', THEME_TOGGLE + '\n'); - return html + '\n' + THEME_TOGGLE; + if (html.includes("__themeToggleInjected")) return html; + const tag = ""; + if (html.includes(tag)) return html.replace(tag, THEME_TOGGLE + "\n" + tag); + if (html.includes("")) return html.replace("", THEME_TOGGLE + "\n"); + return html + "\n" + THEME_TOGGLE; } // --- CSS: find the compiled chunk and pretty-print it --- -const cssChunks = join(root, '..', '..', 'static', 'chunks'); +const cssChunks = join(root, "..", "..", "static", "chunks"); const cssFile = readdirSync(cssChunks) - .filter(f => f.endsWith('.css')) + .filter((f) => f.endsWith(".css")) .sort((a, b) => statSync(join(cssChunks, b)).size - statSync(join(cssChunks, a)).size)[0]; -const css = readFileSync(join(cssChunks, cssFile), 'utf8'); +const css = readFileSync(join(cssChunks, cssFile), "utf8"); const prettyCss = css - .replace(/\r/g, '') - .replace(/\{/g, ' {\n ') - .replace(/;/g, ';\n ') - .replace(/\}/g, '\n}\n') - .replace(/\n{3,}/g, '\n\n') + .replace(/\r/g, "") + .replace(/\{/g, " {\n ") + .replace(/;/g, ";\n ") + .replace(/\}/g, "\n}\n") + .replace(/\n{3,}/g, "\n\n") .trim(); // --- conservative html formatter --- const VOID = new Set([ - 'area','base','br','col','embed','hr','img','input', - 'link','meta','param','source','track','wbr', + "area", + "base", + "br", + "col", + "embed", + "hr", + "img", + "input", + "link", + "meta", + "param", + "source", + "track", + "wbr", ]); -const RAW = new Set(['pre','textarea','script','style']); +const RAW = new Set(["pre", "textarea", "script", "style"]); function fmtHtml(html) { - const tokens = html.match( - /|]*>|<\/?[a-zA-Z][^>]*>|[^<]+||]*>|<\/?[a-zA-Z][^>]*>|[^<]+|`, 'i').test(tk)) { + if (new RegExp(``, "i").test(tk)) { depth--; rawTag = null; } @@ -75,39 +85,45 @@ function fmtHtml(html) { const isOpen = /^<[a-zA-Z]/.test(tk); const isClose = /^<\//.test(tk); - let name = ''; + let name = ""; if (isOpen || isClose) { - name = (tk.match(/^<\/?([a-zA-Z0-9-]+)/) || [])[1]?.toLowerCase() || ''; + name = (tk.match(/^<\/?([a-zA-Z0-9-]+)/) || [])[1]?.toLowerCase() || ""; } if (isOpen && RAW.has(name)) { - out.push('\n' + ' '.repeat(depth) + tk); - if (!/\/>$/.test(tk)) { depth++; rawTag = name; } + out.push("\n" + " ".repeat(depth) + tk); + if (!/\/>$/.test(tk)) { + depth++; + rawTag = name; + } } else if (isOpen && !RAW.has(name)) { - out.push('\n' + ' '.repeat(depth) + tk); + out.push("\n" + " ".repeat(depth) + tk); if (!/\/>$/.test(tk) && !VOID.has(name)) depth++; } else if (isClose) { depth = Math.max(0, depth - 1); - out.push('\n' + ' '.repeat(depth) + tk); + out.push("\n" + " ".repeat(depth) + tk); } else if (/^ !basename(rel).startsWith('_') && !basename(rel).startsWith('404')) + .filter((rel) => !basename(rel).startsWith("_") && !basename(rel).startsWith("404")) + // `index.html` нам не нужен как реф, поэтому не генерируем его. + .filter((rel) => basename(rel).toLowerCase() !== "index.html") .map((rel) => [rel, basename(rel)]); -console.log(`discovered ${pages.length} page(s):`, pages.map((p) => p[1]).join(', ')); +console.log(`discovered ${pages.length} page(s):`, pages.map((p) => p[1]).join(", ")); for (const [src, out] of pages) { - let html = readFileSync(join(root, src), 'utf8'); + let html = readFileSync(join(root, src), "utf8"); // strip scripts — pure static snapshot - html = html.replace(//g, ''); - html = html.replace(/]+rel="preload"[^>]+as="script"[^>]*>/g, ''); + html = html.replace(//g, ""); + html = html.replace(/]+rel="preload"[^>]+as="script"[^>]*>/g, ""); // inline compiled css instead of linking /_next/... - html = html.replace( - /]+rel="stylesheet"[^>]*>/g, - () => '', - ); + html = html.replace(/]+rel="stylesheet"[^>]*>/g, () => ""); html = fmtHtml(html); html = injectThemeToggle(html); @@ -140,5 +155,5 @@ for (const [src, out] of pages) { const file = join(dest, out); mkdirSync(dirname(file), { recursive: true }); writeFileSync(file, html); - console.log('written:', file); + console.log("written:", file); } diff --git a/web/audit/audit-report.json b/web/audit/audit-report.json index 02544dd..36a9e02 100644 --- a/web/audit/audit-report.json +++ b/web/audit/audit-report.json @@ -1,1436 +1,3418 @@ { - "generatedAt": "2026-08-28T14:54:53.564Z", - "ours": "http://127.0.0.1:5179/preview/demo", - "ref": "C:\\+XBOCTuK\\+life_projects\\easy-png-tools\\refs-html\\demo.html", - "tokenDiffs": { - "light": [ - { - "key": "--danger", - "ours": "#e5484d", - "ref": "—" + "generatedAt": "2026-08-28T15:27:27.891Z", + "reports": [ + { + "route": "/preview/demo", + "ref": "demo.html", + "tokenDiffs": { + "light": [ + { + "key": "--danger", + "ours": "#e5484d", + "ref": "—" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ], + "dark": [ + { + "key": "--panel", + "ours": "#182129", + "ref": "#f8fafb" + }, + { + "key": "--blue", + "ours": "#54a2ff", + "ref": "#1769d2" + }, + { + "key": "--muted", + "ours": "#91a0ac", + "ref": "#6d7883" + }, + { + "key": "--background", + "ours": "#11171d", + "ref": "#eef1f4" + }, + { + "key": "--danger", + "ours": "#ff7479", + "ref": "—" + }, + { + "key": "--foreground", + "ours": "#e8eef2", + "ref": "#17212b" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--line", + "ours": "#33414c", + "ref": "#cbd3da" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ] }, - { - "key": "--success", - "ours": "#25a96a", - "ref": "—" + "elementDiffs": [ + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)", + "tag": "span", + "text": "easy-png-tools", + "deltas": { + "rect": { + "ours": { + "x": 98, + "y": 23, + "w": 118, + "h": 18 + }, + "ref": { + "x": 98, + "y": 23, + "w": 108, + "h": 17 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)", + "tag": "a", + "text": "Workspace", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 513, + "y": 24, + "w": 69, + "h": 15 + }, + "ref": { + "x": 792, + "y": 26, + "w": 49, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)", + "tag": "a", + "text": "Catalog", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 600, + "y": 24, + "w": 54, + "h": 15 + }, + "ref": { + "x": 864, + "y": 26, + "w": 39, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)", + "tag": "a", + "text": "Gradient", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 672, + "y": 24, + "w": 61, + "h": 15 + }, + "ref": { + "x": 924, + "y": 26, + "w": 44, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)", + "tag": "a", + "text": "Background remover", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 751, + "y": 24, + "w": 138, + "h": 15 + }, + "ref": { + "x": 990, + "y": 26, + "w": 99, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)", + "tag": "button", + "text": "RU", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)", + "tag": "button", + "text": "EN", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 1349, + "y": 19, + "w": 32, + "h": 25 + }, + "ref": { + "x": 1352, + "y": 20, + "w": 29, + "h": 24 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "/", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Mono\", monospace", + "ref": "\"IBM Plex Sans\", sans-serif" + }, + "fontSize": { + "ours": "10px", + "ref": "16px" + }, + "color": { + "ours": "rgb(23, 105, 210)", + "ref": "rgb(23, 33, 43)" + }, + "margin": { + "ours": "0px 7px", + "ref": "0px" + }, + "letterSpacing": { + "ours": "1.2px", + "ref": "normal" + }, + "lineHeight": { + "ours": "normal", + "ref": "24px" + }, + "display": { + "ours": "inline", + "ref": "block" + }, + "rect": { + "ours": { + "x": 165, + "y": 124, + "w": 7, + "h": 13 + }, + "ref": { + "x": 1348, + "y": 20, + "w": 4, + "h": 24 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>h1:nth-child(2)", + "tag": "h1", + "text": "Build your image pipeline.", + "deltas": { + "lineHeight": { + "ours": "51.84px", + "ref": "57.6px" + }, + "rect": { + "ours": { + "x": 58, + "y": 155, + "w": 474, + "h": 104 + }, + "ref": { + "x": 58, + "y": 154, + "w": 482, + "h": 115 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>p:nth-child(3)", + "tag": "p", + "text": "Chain simple tools together. Every chang", + "deltas": { + "rect": { + "ours": { + "x": 58, + "y": 275, + "w": 474, + "h": 51 + }, + "ref": { + "x": 58, + "y": 285, + "w": 482, + "h": 51 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>span:nth-child(2)", + "tag": "span", + "text": "source.png", + "deltas": { + "rect": { + "ours": { + "x": 597, + "y": 300, + "w": 66, + "h": 14 + }, + "ref": { + "x": 606, + "y": 311, + "w": 60, + "h": 13 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>b:nth-child(3)", + "tag": "b", + "text": "1.8 MB", + "deltas": { + "rect": { + "ours": { + "x": 671, + "y": 300, + "w": 40, + "h": 14 + }, + "ref": { + "x": 674, + "y": 311, + "w": 36, + "h": 13 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "PROCESSING PIPELINE", + "deltas": { + "letterSpacing": { + "ours": "1px", + "ref": "1.2px" + }, + "lineHeight": { + "ours": "12px", + "ref": "normal" + }, + "display": { + "ours": "block", + "ref": "inline" + }, + "rect": { + "ours": { + "x": 58, + "y": 382, + "w": 154, + "h": 12 + }, + "ref": { + "x": 58, + "y": 400, + "w": 127, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>strong:nth-child(2)>em:nth-child(1)", + "tag": "em", + "text": "• LIVE", + "deltas": { + "rect": { + "ours": { + "x": 175, + "y": 405, + "w": 36, + "h": 13 + }, + "ref": { + "x": 173, + "y": 425, + "w": 33, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "01", + "deltas": { + "fontSize": { + "ours": "22px", + "ref": "11px" + }, + "fontWeight": { + "ours": "600", + "ref": "400" + }, + "padding": { + "ours": "0px", + "ref": "17px 0px 0px 15px" + }, + "lineHeight": { + "ours": "22px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 74, + "y": 464, + "w": 26, + "h": 22 + }, + "ref": { + "x": 59, + "y": 466, + "w": 46, + "h": 150 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "BACKGROUND", + "deltas": { + "display": { + "ours": "block", + "ref": "inline" + }, + "rect": { + "ours": { + "x": 117, + "y": 455, + "w": 171, + "h": 13 + }, + "ref": { + "x": 143, + "y": 489, + "w": 67, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", + "tag": "h2", + "text": "Gradient background", + "deltas": { + "rect": { + "ours": { + "x": 117, + "y": 473, + "w": 171, + "h": 19 + }, + "ref": { + "x": 143, + "y": 510, + "w": 157, + "h": 18 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(3)>div:nth-child(2)>button:nth-child(1)", + "tag": "button", + "text": "100%", + "deltas": { + "color": { + "ours": "rgb(255, 255, 255)", + "ref": "rgb(23, 33, 43)" + }, + "backgroundColor": { + "ours": "rgb(23, 105, 210)", + "ref": "rgba(0, 0, 0, 0)" + }, + "padding": { + "ours": "0px 11.2px", + "ref": "0px" + }, + "letterSpacing": { + "ours": "0.6px", + "ref": "0.8px" + }, + "display": { + "ours": "flex", + "ref": "block" + }, + "rect": { + "ours": { + "x": 518, + "y": 552, + "w": 49, + "h": 30 + }, + "ref": { + "x": 680, + "y": 546, + "w": 25, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(2)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "02", + "deltas": { + "fontSize": { + "ours": "22px", + "ref": "11px" + }, + "fontWeight": { + "ours": "600", + "ref": "400" + }, + "padding": { + "ours": "0px", + "ref": "17px 0px 0px 15px" + }, + "lineHeight": { + "ours": "22px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 74, + "y": 624, + "w": 26, + "h": 22 + }, + "ref": { + "x": 59, + "y": 628, + "w": 46, + "h": 136 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "TRANSFORM", + "deltas": { + "display": { + "ours": "block", + "ref": "inline" + }, + "rect": { + "ours": { + "x": 117, + "y": 615, + "w": 153, + "h": 13 + }, + "ref": { + "x": 143, + "y": 651, + "w": 60, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", + "tag": "h2", + "text": "Remove background", + "deltas": { + "rect": { + "ours": { + "x": 117, + "y": 633, + "w": 153, + "h": 19 + }, + "ref": { + "x": 143, + "y": 672, + "w": 140, + "h": 18 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(3)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "03", + "deltas": { + "fontSize": { + "ours": "22px", + "ref": "11px" + }, + "fontWeight": { + "ours": "600", + "ref": "400" + }, + "padding": { + "ours": "0px", + "ref": "17px 0px 0px 15px" + }, + "lineHeight": { + "ours": "22px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 74, + "y": 746, + "w": 26, + "h": 22 + }, + "ref": { + "x": 59, + "y": 776, + "w": 46, + "h": 150 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(3)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "STYLE", + "deltas": { + "display": { + "ours": "block", + "ref": "inline" + }, + "rect": { + "ours": { + "x": 117, + "y": 737, + "w": 99, + "h": 13 + }, + "ref": { + "x": 143, + "y": 799, + "w": 34, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(3)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", + "tag": "h2", + "text": "Add outline", + "deltas": { + "rect": { + "ours": { + "x": 117, + "y": 755, + "w": 99, + "h": 19 + }, + "ref": { + "x": 143, + "y": 820, + "w": 91, + "h": 18 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(4)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "04", + "deltas": { + "fontSize": { + "ours": "22px", + "ref": "11px" + }, + "fontWeight": { + "ours": "600", + "ref": "400" + }, + "padding": { + "ours": "0px", + "ref": "17px 0px 0px 15px" + }, + "lineHeight": { + "ours": "22px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 74, + "y": 896, + "w": 26, + "h": 22 + }, + "ref": { + "x": 59, + "y": 938, + "w": 46, + "h": 142 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(4)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", + "tag": "h2", + "text": "Round corners", + "deltas": { + "rect": { + "ours": { + "x": 117, + "y": 905, + "w": 117, + "h": 19 + }, + "ref": { + "x": 143, + "y": 982, + "w": 107, + "h": 18 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(4)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>label:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Preserve aspect ratio", + "deltas": { + "fontSize": { + "ours": "13.6px", + "ref": "16px" + }, + "lineHeight": { + "ours": "normal", + "ref": "24px" + }, + "display": { + "ours": "block", + "ref": "inline" + }, + "rect": { + "ours": { + "x": 422, + "y": 973, + "w": 129, + "h": 18 + }, + "ref": { + "x": 432, + "y": 1021, + "w": 156, + "h": 17 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "PIPELINE OUTPUTS", + "deltas": { + "letterSpacing": { + "ours": "1px", + "ref": "1.2px" + }, + "lineHeight": { + "ours": "12px", + "ref": "normal" + }, + "display": { + "ours": "block", + "ref": "inline" + }, + "rect": { + "ours": { + "x": 799, + "y": 178, + "w": 94, + "h": 24 + }, + "ref": { + "x": 799, + "y": 188, + "w": 54, + "h": 36 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>strong:nth-child(2)", + "tag": "strong", + "text": "Visual history", + "deltas": { + "rect": { + "ours": { + "x": 799, + "y": 209, + "w": 94, + "h": 36 + }, + "ref": { + "x": 799, + "y": 233, + "w": 87, + "h": 34 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>button:nth-child(2)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "1.2 MB", + "deltas": { + "fontSize": { + "ours": "10px", + "ref": "11px" + }, + "color": { + "ours": "rgb(255, 255, 255)", + "ref": "rgb(23, 33, 43)" + }, + "margin": { + "ours": "0px", + "ref": "5px 0px 0px" + }, + "display": { + "ours": "inline", + "ref": "block" + }, + "rect": { + "ours": { + "x": 1097, + "y": 240, + "w": 36, + "h": 13 + }, + "ref": { + "x": 1246, + "y": 291, + "w": 79, + "h": 13 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "DIMENSIONS", + "deltas": { + "letterSpacing": { + "ours": "0.9px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 1157, + "y": 216, + "w": 63, + "h": 11 + }, + "ref": { + "x": 1073, + "y": 271, + "w": 79, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(1)>span:nth-child(2)", + "tag": "span", + "text": "1200 × 800 px", + "deltas": { + "margin": { + "ours": "0px", + "ref": "5px 0px 0px" + }, + "rect": { + "ours": { + "x": 1236, + "y": 214, + "w": 86, + "h": 14 + }, + "ref": { + "x": 1073, + "y": 291, + "w": 79, + "h": 13 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "FORMAT", + "deltas": { + "letterSpacing": { + "ours": "0.9px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 1157, + "y": 240, + "w": 38, + "h": 11 + }, + "ref": { + "x": 1159, + "y": 271, + "w": 79, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(2)>span:nth-child(2)", + "tag": "span", + "text": "PNG-24", + "deltas": { + "margin": { + "ours": "0px", + "ref": "5px 0px 0px" + }, + "rect": { + "ours": { + "x": 1282, + "y": 238, + "w": 40, + "h": 14 + }, + "ref": { + "x": 1159, + "y": 291, + "w": 79, + "h": 13 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(3)>span:nth-child(1)", + "tag": "span", + "text": "SIZE", + "deltas": { + "letterSpacing": { + "ours": "0.9px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 1157, + "y": 265, + "w": 25, + "h": 11 + }, + "ref": { + "x": 1246, + "y": 271, + "w": 79, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "SOURCE", + "deltas": { + "fontSize": { + "ours": "10px", + "ref": "9px" + }, + "letterSpacing": { + "ours": "0.8px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 806, + "y": 317, + "w": 550, + "h": 13 + }, + "ref": { + "x": 808, + "y": 663, + "w": 30, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "PNG", + "deltas": { + "rect": { + "ours": { + "x": 1067, + "y": 496, + "w": 28, + "h": 19 + }, + "ref": { + "x": 923, + "y": 478, + "w": 27, + "h": 18 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "original.png · 1200 × 800", + "deltas": { + "rect": { + "ours": { + "x": 814, + "y": 704, + "w": 135, + "h": 11 + }, + "ref": { + "x": 942, + "y": 663, + "w": 124, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "STEP 01", + "deltas": { + "fontSize": { + "ours": "10px", + "ref": "9px" + }, + "letterSpacing": { + "ours": "0.8px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 806, + "y": 750, + "w": 550, + "h": 13 + }, + "ref": { + "x": 1096, + "y": 663, + "w": 35, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "gradient applied", + "deltas": { + "rect": { + "ours": { + "x": 814, + "y": 1137, + "w": 86, + "h": 11 + }, + "ref": { + "x": 1275, + "y": 663, + "w": 79, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>span:nth-child(1)", + "tag": "span", + "text": "STEP 02", + "deltas": { + "fontSize": { + "ours": "10px", + "ref": "9px" + }, + "letterSpacing": { + "ours": "0.8px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 806, + "y": 1182, + "w": 550, + "h": 13 + }, + "ref": { + "x": 808, + "y": 1021, + "w": 35, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "background removed", + "deltas": { + "rect": { + "ours": { + "x": 814, + "y": 1569, + "w": 97, + "h": 11 + }, + "ref": { + "x": 977, + "y": 1021, + "w": 89, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(4)>span:nth-child(1)", + "tag": "span", + "text": "STEP 03", + "deltas": { + "fontSize": { + "ours": "10px", + "ref": "9px" + }, + "letterSpacing": { + "ours": "0.8px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 806, + "y": 1615, + "w": 550, + "h": 13 + }, + "ref": { + "x": 1096, + "y": 1021, + "w": 35, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(4)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "outline added", + "deltas": { + "rect": { + "ours": { + "x": 814, + "y": 2002, + "w": 70, + "h": 11 + }, + "ref": { + "x": 1290, + "y": 1021, + "w": 64, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(5)>span:nth-child(1)", + "tag": "span", + "text": "FINAL OUTPUT", + "deltas": { + "fontSize": { + "ours": "10px", + "ref": "9px" + }, + "letterSpacing": { + "ours": "0.8px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 806, + "y": 2047, + "w": 550, + "h": 13 + }, + "ref": { + "x": 808, + "y": 1378, + "w": 59, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(5)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "ready · PNG-24", + "deltas": { + "rect": { + "ours": { + "x": 814, + "y": 2434, + "w": 76, + "h": 11 + }, + "ref": { + "x": 997, + "y": 1378, + "w": 69, + "h": 10 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>p:nth-child(3)", + "tag": "p", + "text": "Output is generated in-browser. Your fil", + "deltas": { + "rect": { + "ours": { + "x": 799, + "y": 2489, + "w": 565, + "h": 17 + }, + "ref": { + "x": 799, + "y": 1425, + "w": 565, + "h": 17 + } + } + } + } + ], + "onlyOurs": [ + "/ DEMO", + "+ Add tool", + "AUTO", + "Gradient color", + "DIRECTION", + "135 °", + "OPACITY", + "75%", + "50%", + "25%", + "Automatic subject detection enabled", + "Outline width", + "2 px", + "Outline color", + "Corner radius", + "18 px", + "v0.1.0", + "© 2026" + ], + "onlyRef": [ + "/ \n WORKSPACE", + "COLOR", + "135\n °", + "100", + "75", + "50", + "25", + "2\n px", + "18\n px", + "v2.4.0", + "© 2024" + ], + "counts": { + "tokenLight": 10, + "tokenDark": 16, + "elements": 47, + "onlyOurs": 18, + "onlyRef": 11 + } + }, + { + "route": "/preview/list-tools", + "ref": "list-tools.html", + "tokenDiffs": { + "light": [ + { + "key": "--danger", + "ours": "#e5484d", + "ref": "—" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ], + "dark": [ + { + "key": "--panel", + "ours": "#182129", + "ref": "#f8fafb" + }, + { + "key": "--blue", + "ours": "#54a2ff", + "ref": "#1769d2" + }, + { + "key": "--muted", + "ours": "#91a0ac", + "ref": "#6d7883" + }, + { + "key": "--background", + "ours": "#11171d", + "ref": "#eef1f4" + }, + { + "key": "--danger", + "ours": "#ff7479", + "ref": "—" + }, + { + "key": "--foreground", + "ours": "#e8eef2", + "ref": "#17212b" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--line", + "ours": "#33414c", + "ref": "#cbd3da" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ] }, - { - "key": "--text-sm--line-height", - "ours": "—", - "ref": "calc(1.25 / .875)" + "elementDiffs": [ + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)", + "tag": "span", + "text": "easy-png-tools", + "deltas": { + "rect": { + "ours": { + "x": 98, + "y": 23, + "w": 118, + "h": 18 + }, + "ref": { + "x": 98, + "y": 23, + "w": 108, + "h": 17 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)", + "tag": "a", + "text": "Workspace", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 534, + "y": 24, + "w": 69, + "h": 15 + }, + "ref": { + "x": 761, + "y": 26, + "w": 49, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)", + "tag": "a", + "text": "Catalog", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "color": { + "ours": "rgb(109, 120, 131)", + "ref": "rgb(23, 105, 210)" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 621, + "y": 24, + "w": 54, + "h": 15 + }, + "ref": { + "x": 832, + "y": 26, + "w": 39, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)", + "tag": "a", + "text": "Gradient", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 693, + "y": 24, + "w": 61, + "h": 15 + }, + "ref": { + "x": 893, + "y": 26, + "w": 44, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)", + "tag": "a", + "text": "Background remover", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 773, + "y": 24, + "w": 138, + "h": 15 + }, + "ref": { + "x": 959, + "y": 26, + "w": 99, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)", + "tag": "button", + "text": "RU", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)", + "tag": "button", + "text": "EN", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 1349, + "y": 19, + "w": 32, + "h": 25 + }, + "ref": { + "x": 1352, + "y": 20, + "w": 29, + "h": 24 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(1)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Convert JPG to PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 104, + "y": 302, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 494, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(2)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Convert WebP to PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 364, + "y": 311, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 610, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(7)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "PNG to Base64", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 364, + "y": 429, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 726, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(9)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "PNG to Data URI", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 885, + "y": 429, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 842, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(17)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Convert PNG to JPG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 364, + "y": 717, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 958, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(3)>div:nth-child(2)>a:nth-child(8)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Round corners PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 625, + "y": 1239, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 726, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(3)>div:nth-child(2)>a:nth-child(11)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Outline PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 104, + "y": 1384, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 842, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(7)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Grayscale PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 364, + "y": 2096, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 1276, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(8)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Invert colors PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 625, + "y": 2104, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 1392, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(10)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Change PNG opacity", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 1146, + "y": 2096, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 958, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(20)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Temperature PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 1146, + "y": 2361, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 1624, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(1)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Resize PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 104, + "y": 2731, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 1160, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(2)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Crop PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 364, + "y": 2740, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 1276, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(3)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Rotate PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 625, + "y": 2748, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 1392, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(4)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Flip PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 885, + "y": 2757, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 1508, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(5)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Add padding to PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 1146, + "y": 2748, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 1624, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(1)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Blur PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 104, + "y": 3396, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 1826, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(2)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Sharpen PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 364, + "y": 3387, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 1942, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(3)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Vignette PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 625, + "y": 3404, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 2058, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(8)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "JPEG artifacts", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 625, + "y": 3540, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 2174, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(8)>div:nth-child(2)>a:nth-child(9)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "PNG info", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 885, + "y": 4142, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 1826, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(8)>div:nth-child(2)>a:nth-child(13)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "PNG orientation", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 625, + "y": 4287, + "w": 175, + "h": 19 + }, + "ref": { + "x": 807, + "y": 2174, + "w": 488, + "h": 15 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(9)>div:nth-child(2)>a:nth-child(7)>span:nth-child(2)>span:nth-child(1)", + "tag": "span", + "text": "Create gradient PNG", + "deltas": { + "fontFamily": { + "ours": "\"IBM Plex Sans\", sans-serif", + "ref": "\"IBM Plex Mono\", monospace" + }, + "fontSize": { + "ours": "14.4px", + "ref": "13px" + }, + "rect": { + "ours": { + "x": 364, + "y": 4632, + "w": 175, + "h": 19 + }, + "ref": { + "x": 131, + "y": 1160, + "w": 488, + "h": 15 + } + } + } + } + ], + "onlyOurs": [ + "/ LIST-TOOLS", + "CATALOG", + "All tools", + "Каталог всех PNG-инструментов, сгруппиро", + "convert", + "Convert", + "Opens a JPEG and saves it as lossless PN", + "Re-encodes a WebP image into universal P", + "Convert GIF to PNG", + "Extracts the first frame of a GIF animat", + "Convert BMP to PNG", + "Re-encodes BMP into compact lossless PNG", + "Convert ICO to PNG", + "Turns an .ico icon into a regular PNG of", + "Convert PNG to BMP", + "Saves the image as 24-bit BMP without an", + "Encodes the image into a base64 string f", + "Base64 to PNG", + "Decodes a base64 string or data-uri back", + "Builds a full data-uri (data:image/png;b", + "Data URI to PNG", + "Decodes data:image/…;base64,… back into ", + "PNG to HEX pixels", + "Shows all pixels as rrggbbaa hex values ", + "HEX pixels to PNG", + "Assembles an image from rrggbbaa hex val", + "PNG to Bytes", + "Lists every pixel as four decimal bytes ", + "Bytes to PNG", + "Assembles an image from decimal RGBA byt", + "PNG to RGB Values", + "Lists every pixel as rgba(r, g, b, a), o", + "RGB Values to PNG", + "Assembles an image from rgba(r, g, b, a)", + "Transparency is composited over the chos", + "Convert PNG to WebP", + "Re-encodes the image into WebP with adju", + "Compress PNG", + "Shrinks the PNG by reducing its palette ", + "Reduce PNG to Size", + "Binary-searches the palette size until t", + "SVG to PNG", + "Decodes SVG markup into a raster image. ", + "alpha", + "Alpha & transparency", + "Circle Mask PNG", + "Cuts the image into a circle. Diameter i", + "Square Mask PNG", + "Cuts the image into a rectangle with sid", + "Star Mask PNG", + "Cuts the image into an n-pointed star wi", + "Wavy Mask PNG", + "Cuts the image into a wavy-edged circle:", + "Remove alpha channel PNG", + "Composites the image over a white backgr", + "Set alpha channel PNG", + "Assigns the same opacity to all pixels; ", + "Extract alpha mask PNG", + "Turns transparency into a black-and-whit", + "Clips corners by a radius set as a perce" + ], + "onlyRef": [ + "/ \n CATALOG", + "/", + "EASY-PNG-TOOLS / CATALOG", + "Tool catalog", + "Focused utilities for working with PNG. ", + "32", + "ALL", + "CONVERT", + "TRANSPARENCY", + "COLOR", + "GEOMETRY", + "FILTERS", + "ANALYZE", + "05\n TOOLS", + "Re-encode JPEG files as lossless PNG whi", + "01", + "Turn WebP images into a universal PNG fo", + "02", + "Encode an image as a base64 string for e", + "03", + "Build a complete data URI ready for HTML", + "04", + "Composite transparency over a selected b", + "05", + "Remove background PNG", + "Remove a solid background by color, tole", + "Extract alpha mask", + "Turn the alpha channel into a clean blac", + "Clip the image corners by a precise radi", + "Add a colored ring around opaque content", + "Multiply the alpha channel while keeping", + "Generate a smooth transition between two", + "Convert the image to luminance-based sha", + "Invert every color channel while leaving", + "Brightness & contrast", + "Adjust brightness and contrast across a ", + "Make an image warmer or cooler with a si", + "Scale an image with bilinear interpolati", + "Cut a rectangular area with exact coordi", + "Rotate by 90, 180, or 270 degrees withou", + "Mirror the image horizontally or vertica", + "Expand the canvas on all sides by a chos", + "04\n TOOLS", + "Apply a fast Gaussian-style blur with tr", + "Emphasize edges with an adjustable sharp", + "Smoothly darken the image edges while pr", + "Simulate low-quality JPEG recompression ", + "Inspect dimensions, alpha presence, and ", + "Check grayscale", + "Report whether the image contains only s", + "Check transparency", + "Detect transparent and semi-transparent ", + "Classify the image as portrait, landscap", + "•" + ], + "counts": { + "tokenLight": 10, + "tokenDark": 16, + "elements": 30, + "onlyOurs": 257, + "onlyRef": 54 + } + }, + { + "route": "/preview/tools/linear-gradient-png", + "ref": "gradient.html", + "tokenDiffs": { + "light": [ + { + "key": "--danger", + "ours": "#e5484d", + "ref": "—" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ], + "dark": [ + { + "key": "--panel", + "ours": "#182129", + "ref": "#f8fafb" + }, + { + "key": "--blue", + "ours": "#54a2ff", + "ref": "#1769d2" + }, + { + "key": "--muted", + "ours": "#91a0ac", + "ref": "#6d7883" + }, + { + "key": "--background", + "ours": "#11171d", + "ref": "#eef1f4" + }, + { + "key": "--danger", + "ours": "#ff7479", + "ref": "—" + }, + { + "key": "--foreground", + "ours": "#e8eef2", + "ref": "#17212b" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--line", + "ours": "#33414c", + "ref": "#cbd3da" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ] }, - { - "key": "--text-xs--line-height", - "ours": "—", - "ref": "calc(1 / .75)" + "elementDiffs": [ + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)", + "tag": "span", + "text": "easy-png-tools", + "deltas": { + "rect": { + "ours": { + "x": 98, + "y": 23, + "w": 118, + "h": 18 + }, + "ref": { + "x": 98, + "y": 23, + "w": 108, + "h": 17 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)", + "tag": "a", + "text": "Workspace", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 596, + "y": 24, + "w": 69, + "h": 15 + }, + "ref": { + "x": 798, + "y": 26, + "w": 49, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)", + "tag": "a", + "text": "Catalog", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 683, + "y": 24, + "w": 54, + "h": 15 + }, + "ref": { + "x": 870, + "y": 26, + "w": 39, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)", + "tag": "a", + "text": "Gradient", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "color": { + "ours": "rgb(109, 120, 131)", + "ref": "rgb(23, 105, 210)" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 754, + "y": 24, + "w": 61, + "h": 15 + }, + "ref": { + "x": 930, + "y": 26, + "w": 44, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)", + "tag": "a", + "text": "Background remover", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 834, + "y": 24, + "w": 138, + "h": 15 + }, + "ref": { + "x": 996, + "y": 26, + "w": 99, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)", + "tag": "button", + "text": "RU", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)", + "tag": "button", + "text": "EN", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 1349, + "y": 19, + "w": 32, + "h": 25 + }, + "ref": { + "x": 1352, + "y": 20, + "w": 29, + "h": 24 + } + } + } + } + ], + "onlyOurs": [ + "/ TOOLS / LINEAR-GRADIENT-PNG", + "STEPS", + "Pipeline", + "AUTO", + "01", + "generate", + "Create gradient PNG", + "Width", + "Height", + "Start color", + "End color", + "Direction", + "Horizontal", + "Vertical", + "Add step", + "Split PNG into HSL", + "Split PNG into HSV", + "Split PNG into HSI", + "Convert PNG to CMYK Colors", + "Convert PNG to YCbCr Colors", + "Convert PNG to LAB Colors", + "Show Transparent Areas PNG", + "Show Grayscale Pixels PNG", + "Show Color Pixels PNG", + "Light Pixel Mask PNG", + "Dark Pixel Mask PNG", + "Unique Color Mask PNG", + "Convert JPG to PNG", + "Convert WebP to PNG", + "Convert GIF to PNG", + "Convert BMP to PNG", + "Convert ICO to PNG", + "Convert PNG to BMP", + "Resize PNG", + "Crop PNG", + "Rotate PNG", + "Flip PNG", + "Add padding to PNG", + "Add border to PNG", + "Fit PNG onto background", + "Tile PNG", + "Circle Mask PNG", + "Square Mask PNG", + "Star Mask PNG", + "Wavy Mask PNG", + "Center PNG by content", + "Blur PNG", + "Sharpen PNG", + "Grayscale PNG", + "Invert colors PNG", + "Brightness & contrast PNG", + "Change PNG opacity", + "Sepia effect", + "Change hue PNG", + "Extract channel PNG", + "Swap channels PNG", + "Black & white threshold PNG", + "Posterize PNG", + "Two colors PNG", + "Convert PNG to JPG" + ], + "onlyRef": [ + "/ \n GRADIENT", + "/", + "Gradient background.", + "Create a clean, export-ready gradient wi", + "GRADIENT SETTINGS", + "Configure output", + "TOOL 01", + "GRADIENT TYPE", + "Linear", + "Radial", + "COLOR STOPS", + "→", + "135\n °", + "0° →", + "45° ↗", + "90° ↑", + "135° ↖", + "180° ←", + "225° ↙", + "270° ↓", + "315° ↘", + "100\n %", + "OUTPUT PREVIEW", + "gradient.png", + "PNG", + "GENERATED CSS", + "background: linear-gradient(135deg, #176", + "v2.4.0", + "gradient tool · local-only", + "© 2024" + ], + "counts": { + "tokenLight": 10, + "tokenDark": 16, + "elements": 7, + "onlyOurs": 111, + "onlyRef": 30 + } + }, + { + "route": "/preview/tools/remove-background-png", + "ref": "background-remover.html", + "tokenDiffs": { + "light": [ + { + "key": "--danger", + "ours": "#e5484d", + "ref": "—" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ], + "dark": [ + { + "key": "--panel", + "ours": "#182129", + "ref": "#f8fafb" + }, + { + "key": "--blue", + "ours": "#54a2ff", + "ref": "#1769d2" + }, + { + "key": "--muted", + "ours": "#91a0ac", + "ref": "#6d7883" + }, + { + "key": "--background", + "ours": "#11171d", + "ref": "#eef1f4" + }, + { + "key": "--danger", + "ours": "#ff7479", + "ref": "—" + }, + { + "key": "--foreground", + "ours": "#e8eef2", + "ref": "#17212b" + }, + { + "key": "--success", + "ours": "#25a96a", + "ref": "—" + }, + { + "key": "--line", + "ours": "#33414c", + "ref": "#cbd3da" + }, + { + "key": "--text-sm--line-height", + "ours": "—", + "ref": "calc(1.25 / .875)" + }, + { + "key": "--text-xs--line-height", + "ours": "—", + "ref": "calc(1 / .75)" + }, + { + "key": "--font-weight-medium", + "ours": "—", + "ref": "500" + }, + { + "key": "--spacing", + "ours": "—", + "ref": ".25rem" + }, + { + "key": "--text-xs", + "ours": "—", + "ref": ".75rem" + }, + { + "key": "--radius-md", + "ours": "—", + "ref": ".375rem" + }, + { + "key": "--radius-lg", + "ours": "—", + "ref": ".5rem" + }, + { + "key": "--text-sm", + "ours": "—", + "ref": ".875rem" + } + ] }, - { - "key": "--font-weight-medium", - "ours": "—", - "ref": "500" - }, - { - "key": "--spacing", - "ours": "—", - "ref": ".25rem" - }, - { - "key": "--text-xs", - "ours": "—", - "ref": ".75rem" - }, - { - "key": "--radius-md", - "ours": "—", - "ref": ".375rem" - }, - { - "key": "--radius-lg", - "ours": "—", - "ref": ".5rem" - }, - { - "key": "--text-sm", - "ours": "—", - "ref": ".875rem" - } - ], - "dark": [ - { - "key": "--panel", - "ours": "#182129", - "ref": "#f8fafb" - }, - { - "key": "--blue", - "ours": "#54a2ff", - "ref": "#1769d2" - }, - { - "key": "--muted", - "ours": "#91a0ac", - "ref": "#6d7883" - }, - { - "key": "--background", - "ours": "#11171d", - "ref": "#eef1f4" - }, - { - "key": "--danger", - "ours": "#ff7479", - "ref": "—" - }, - { - "key": "--foreground", - "ours": "#e8eef2", - "ref": "#17212b" - }, - { - "key": "--success", - "ours": "#25a96a", - "ref": "—" - }, - { - "key": "--line", - "ours": "#33414c", - "ref": "#cbd3da" - }, - { - "key": "--text-sm--line-height", - "ours": "—", - "ref": "calc(1.25 / .875)" - }, - { - "key": "--text-xs--line-height", - "ours": "—", - "ref": "calc(1 / .75)" - }, - { - "key": "--font-weight-medium", - "ours": "—", - "ref": "500" - }, - { - "key": "--spacing", - "ours": "—", - "ref": ".25rem" - }, - { - "key": "--text-xs", - "ours": "—", - "ref": ".75rem" - }, - { - "key": "--radius-md", - "ours": "—", - "ref": ".375rem" - }, - { - "key": "--radius-lg", - "ours": "—", - "ref": ".5rem" - }, - { - "key": "--text-sm", - "ours": "—", - "ref": ".875rem" - } - ] - }, - "elementDiffs": [ - { - "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)", - "tag": "span", - "text": "easy-png-tools", - "deltas": { - "rect": { - "ours": { - "x": 98, - "y": 23, - "w": 118, - "h": 18 - }, - "ref": { - "x": 98, - "y": 23, - "w": 108, - "h": 17 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)", - "tag": "a", - "text": "Workspace", - "deltas": { - "fontSize": { - "ours": "12px", - "ref": "10px" - }, - "letterSpacing": { - "ours": "0.48px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 513, - "y": 24, - "w": 69, - "h": 15 - }, - "ref": { - "x": 792, - "y": 26, - "w": 49, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)", - "tag": "a", - "text": "Catalog", - "deltas": { - "fontSize": { - "ours": "12px", - "ref": "10px" - }, - "letterSpacing": { - "ours": "0.48px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 600, - "y": 24, - "w": 54, - "h": 15 - }, - "ref": { - "x": 864, - "y": 26, - "w": 39, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)", - "tag": "a", - "text": "Gradient", - "deltas": { - "fontSize": { - "ours": "12px", - "ref": "10px" - }, - "letterSpacing": { - "ours": "0.48px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 672, - "y": 24, - "w": 61, - "h": 15 - }, - "ref": { - "x": 924, - "y": 26, - "w": 44, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)", - "tag": "a", - "text": "Background remover", - "deltas": { - "fontSize": { - "ours": "12px", - "ref": "10px" - }, - "letterSpacing": { - "ours": "0.48px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 751, - "y": 24, - "w": 138, - "h": 15 - }, - "ref": { - "x": 990, - "y": 26, - "w": 99, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)", - "tag": "button", - "text": "RU", - "deltas": { - "letterSpacing": { - "ours": "0.6px", - "ref": "normal" - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)", - "tag": "button", - "text": "EN", - "deltas": { - "letterSpacing": { - "ours": "0.6px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 1349, - "y": 19, - "w": 32, - "h": 25 - }, - "ref": { - "x": 1352, - "y": 20, - "w": 29, - "h": 24 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "/", - "deltas": { - "fontFamily": { - "ours": "\"IBM Plex Mono\", monospace", - "ref": "\"IBM Plex Sans\", sans-serif" - }, - "fontSize": { - "ours": "10px", - "ref": "16px" - }, - "color": { - "ours": "rgb(23, 105, 210)", - "ref": "rgb(23, 33, 43)" - }, - "margin": { - "ours": "0px 7px", - "ref": "0px" - }, - "letterSpacing": { - "ours": "1.2px", - "ref": "normal" - }, - "lineHeight": { - "ours": "normal", - "ref": "24px" - }, - "display": { - "ours": "inline", - "ref": "block" - }, - "rect": { - "ours": { - "x": 165, - "y": 124, - "w": 7, - "h": 13 - }, - "ref": { - "x": 1348, - "y": 20, - "w": 4, - "h": 24 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>h1:nth-child(2)", - "tag": "h1", - "text": "Build your image pipeline.", - "deltas": { - "lineHeight": { - "ours": "51.84px", - "ref": "57.6px" - }, - "rect": { - "ours": { - "x": 58, - "y": 155, - "w": 474, - "h": 104 - }, - "ref": { - "x": 58, - "y": 154, - "w": 482, - "h": 115 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>p:nth-child(3)", - "tag": "p", - "text": "Chain simple tools together. Every chang", - "deltas": { - "rect": { - "ours": { - "x": 58, - "y": 275, - "w": 474, - "h": 51 - }, - "ref": { - "x": 58, - "y": 285, - "w": 482, - "h": 51 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>span:nth-child(2)", - "tag": "span", - "text": "source.png", - "deltas": { - "rect": { - "ours": { - "x": 597, - "y": 300, - "w": 66, - "h": 14 - }, - "ref": { - "x": 606, - "y": 311, - "w": 60, - "h": 13 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>b:nth-child(3)", - "tag": "b", - "text": "1.8 MB", - "deltas": { - "rect": { - "ours": { - "x": 671, - "y": 300, - "w": 40, - "h": 14 - }, - "ref": { - "x": 674, - "y": 311, - "w": 36, - "h": 13 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "PROCESSING PIPELINE", - "deltas": { - "letterSpacing": { - "ours": "1px", - "ref": "1.2px" - }, - "lineHeight": { - "ours": "12px", - "ref": "normal" - }, - "display": { - "ours": "block", - "ref": "inline" - }, - "rect": { - "ours": { - "x": 58, - "y": 382, - "w": 154, - "h": 12 - }, - "ref": { - "x": 58, - "y": 400, - "w": 127, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>strong:nth-child(2)>em:nth-child(1)", - "tag": "em", - "text": "• LIVE", - "deltas": { - "rect": { - "ours": { - "x": 175, - "y": 405, - "w": 36, - "h": 13 - }, - "ref": { - "x": 173, - "y": 425, - "w": 33, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "01", - "deltas": { - "fontSize": { - "ours": "22px", - "ref": "11px" - }, - "fontWeight": { - "ours": "600", - "ref": "400" - }, - "padding": { - "ours": "0px", - "ref": "17px 0px 0px 15px" - }, - "lineHeight": { - "ours": "22px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 74, - "y": 464, - "w": 26, - "h": 22 - }, - "ref": { - "x": 59, - "y": 466, - "w": 46, - "h": 150 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "BACKGROUND", - "deltas": { - "display": { - "ours": "block", - "ref": "inline" - }, - "rect": { - "ours": { - "x": 117, - "y": 455, - "w": 171, - "h": 13 - }, - "ref": { - "x": 143, - "y": 489, - "w": 67, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", - "tag": "h2", - "text": "Gradient background", - "deltas": { - "rect": { - "ours": { - "x": 117, - "y": 473, - "w": 171, - "h": 19 - }, - "ref": { - "x": 143, - "y": 510, - "w": 157, - "h": 18 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(3)>div:nth-child(2)>button:nth-child(1)", - "tag": "button", - "text": "100%", - "deltas": { - "color": { - "ours": "rgb(255, 255, 255)", - "ref": "rgb(23, 33, 43)" - }, - "backgroundColor": { - "ours": "rgb(23, 105, 210)", - "ref": "rgba(0, 0, 0, 0)" - }, - "padding": { - "ours": "0px 11.2px", - "ref": "0px" - }, - "letterSpacing": { - "ours": "0.6px", - "ref": "0.8px" - }, - "display": { - "ours": "flex", - "ref": "block" - }, - "rect": { - "ours": { - "x": 518, - "y": 552, - "w": 49, - "h": 30 - }, - "ref": { - "x": 680, - "y": 546, - "w": 25, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(2)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "02", - "deltas": { - "fontSize": { - "ours": "22px", - "ref": "11px" - }, - "fontWeight": { - "ours": "600", - "ref": "400" - }, - "padding": { - "ours": "0px", - "ref": "17px 0px 0px 15px" - }, - "lineHeight": { - "ours": "22px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 74, - "y": 624, - "w": 26, - "h": 22 - }, - "ref": { - "x": 59, - "y": 628, - "w": 46, - "h": 136 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "TRANSFORM", - "deltas": { - "display": { - "ours": "block", - "ref": "inline" - }, - "rect": { - "ours": { - "x": 117, - "y": 615, - "w": 153, - "h": 13 - }, - "ref": { - "x": 143, - "y": 651, - "w": 60, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", - "tag": "h2", - "text": "Remove background", - "deltas": { - "rect": { - "ours": { - "x": 117, - "y": 633, - "w": 153, - "h": 19 - }, - "ref": { - "x": 143, - "y": 672, - "w": 140, - "h": 18 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(3)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "03", - "deltas": { - "fontSize": { - "ours": "22px", - "ref": "11px" - }, - "fontWeight": { - "ours": "600", - "ref": "400" - }, - "padding": { - "ours": "0px", - "ref": "17px 0px 0px 15px" - }, - "lineHeight": { - "ours": "22px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 74, - "y": 746, - "w": 26, - "h": 22 - }, - "ref": { - "x": 59, - "y": 776, - "w": 46, - "h": 150 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(3)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "STYLE", - "deltas": { - "display": { - "ours": "block", - "ref": "inline" - }, - "rect": { - "ours": { - "x": 117, - "y": 737, - "w": 99, - "h": 13 - }, - "ref": { - "x": 143, - "y": 799, - "w": 34, - "h": 12 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(3)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", - "tag": "h2", - "text": "Add outline", - "deltas": { - "rect": { - "ours": { - "x": 117, - "y": 755, - "w": 99, - "h": 19 - }, - "ref": { - "x": 143, - "y": 820, - "w": 91, - "h": 18 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(4)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "04", - "deltas": { - "fontSize": { - "ours": "22px", - "ref": "11px" - }, - "fontWeight": { - "ours": "600", - "ref": "400" - }, - "padding": { - "ours": "0px", - "ref": "17px 0px 0px 15px" - }, - "lineHeight": { - "ours": "22px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 74, - "y": 896, - "w": 26, - "h": 22 - }, - "ref": { - "x": 59, - "y": 938, - "w": 46, - "h": 142 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(4)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>h2:nth-child(2)", - "tag": "h2", - "text": "Round corners", - "deltas": { - "rect": { - "ours": { - "x": 117, - "y": 905, - "w": 117, - "h": 19 - }, - "ref": { - "x": 143, - "y": 982, - "w": 107, - "h": 18 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(3)>article:nth-child(4)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>label:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "Preserve aspect ratio", - "deltas": { - "fontSize": { - "ours": "13.6px", - "ref": "16px" - }, - "lineHeight": { - "ours": "normal", - "ref": "24px" - }, - "display": { - "ours": "block", - "ref": "inline" - }, - "rect": { - "ours": { - "x": 422, - "y": 973, - "w": 129, - "h": 18 - }, - "ref": { - "x": 432, - "y": 1021, - "w": 156, - "h": 17 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "PIPELINE OUTPUTS", - "deltas": { - "letterSpacing": { - "ours": "1px", - "ref": "1.2px" - }, - "lineHeight": { - "ours": "12px", - "ref": "normal" - }, - "display": { - "ours": "block", - "ref": "inline" - }, - "rect": { - "ours": { - "x": 799, - "y": 178, - "w": 94, - "h": 24 - }, - "ref": { - "x": 799, - "y": 188, - "w": 54, - "h": 36 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>strong:nth-child(2)", - "tag": "strong", - "text": "Visual history", - "deltas": { - "rect": { - "ours": { - "x": 799, - "y": 209, - "w": 94, - "h": 36 - }, - "ref": { - "x": 799, - "y": 233, - "w": 87, - "h": 34 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>button:nth-child(2)>span:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "1.2 MB", - "deltas": { - "fontSize": { - "ours": "10px", - "ref": "11px" - }, - "color": { - "ours": "rgb(255, 255, 255)", - "ref": "rgb(23, 33, 43)" - }, - "margin": { - "ours": "0px", - "ref": "5px 0px 0px" - }, - "display": { - "ours": "inline", - "ref": "block" - }, - "rect": { - "ours": { - "x": 1097, - "y": 240, - "w": 36, - "h": 13 - }, - "ref": { - "x": 1246, - "y": 291, - "w": 79, - "h": 13 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "DIMENSIONS", - "deltas": { - "letterSpacing": { - "ours": "0.9px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 1157, - "y": 216, - "w": 63, - "h": 11 - }, - "ref": { - "x": 1073, - "y": 271, - "w": 79, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(1)>span:nth-child(2)", - "tag": "span", - "text": "1200 × 800 px", - "deltas": { - "margin": { - "ours": "0px", - "ref": "5px 0px 0px" - }, - "rect": { - "ours": { - "x": 1236, - "y": 214, - "w": 86, - "h": 14 - }, - "ref": { - "x": 1073, - "y": 291, - "w": 79, - "h": 13 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "FORMAT", - "deltas": { - "letterSpacing": { - "ours": "0.9px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 1157, - "y": 240, - "w": 38, - "h": 11 - }, - "ref": { - "x": 1159, - "y": 271, - "w": 79, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(2)>span:nth-child(2)", - "tag": "span", - "text": "PNG-24", - "deltas": { - "margin": { - "ours": "0px", - "ref": "5px 0px 0px" - }, - "rect": { - "ours": { - "x": 1282, - "y": 238, - "w": 40, - "h": 14 - }, - "ref": { - "x": 1159, - "y": 291, - "w": 79, - "h": 13 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(3)>span:nth-child(1)", - "tag": "span", - "text": "SIZE", - "deltas": { - "letterSpacing": { - "ours": "0.9px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 1157, - "y": 265, - "w": 25, - "h": 11 - }, - "ref": { - "x": 1246, - "y": 271, - "w": 79, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "SOURCE", - "deltas": { - "fontSize": { - "ours": "10px", - "ref": "9px" - }, - "letterSpacing": { - "ours": "0.8px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 806, - "y": 317, - "w": 550, - "h": 13 - }, - "ref": { - "x": 808, - "y": 663, - "w": 30, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)", - "tag": "span", - "text": "PNG", - "deltas": { - "rect": { - "ours": { - "x": 1067, - "y": 496, - "w": 28, - "h": 19 - }, - "ref": { - "x": 923, - "y": 478, - "w": 27, - "h": 18 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "original.png · 1200 × 800", - "deltas": { - "rect": { - "ours": { - "x": 814, - "y": 704, - "w": 135, - "h": 11 - }, - "ref": { - "x": 942, - "y": 663, - "w": 124, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "STEP 01", - "deltas": { - "fontSize": { - "ours": "10px", - "ref": "9px" - }, - "letterSpacing": { - "ours": "0.8px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 806, - "y": 750, - "w": 550, - "h": 13 - }, - "ref": { - "x": 1096, - "y": 663, - "w": 35, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "gradient applied", - "deltas": { - "rect": { - "ours": { - "x": 814, - "y": 1137, - "w": 86, - "h": 11 - }, - "ref": { - "x": 1275, - "y": 663, - "w": 79, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>span:nth-child(1)", - "tag": "span", - "text": "STEP 02", - "deltas": { - "fontSize": { - "ours": "10px", - "ref": "9px" - }, - "letterSpacing": { - "ours": "0.8px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 806, - "y": 1182, - "w": 550, - "h": 13 - }, - "ref": { - "x": 808, - "y": 1021, - "w": 35, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(3)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "background removed", - "deltas": { - "rect": { - "ours": { - "x": 814, - "y": 1569, - "w": 97, - "h": 11 - }, - "ref": { - "x": 977, - "y": 1021, - "w": 89, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(4)>span:nth-child(1)", - "tag": "span", - "text": "STEP 03", - "deltas": { - "fontSize": { - "ours": "10px", - "ref": "9px" - }, - "letterSpacing": { - "ours": "0.8px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 806, - "y": 1615, - "w": 550, - "h": 13 - }, - "ref": { - "x": 1096, - "y": 1021, - "w": 35, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(4)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "outline added", - "deltas": { - "rect": { - "ours": { - "x": 814, - "y": 2002, - "w": 70, - "h": 11 - }, - "ref": { - "x": 1290, - "y": 1021, - "w": 64, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(5)>span:nth-child(1)", - "tag": "span", - "text": "FINAL OUTPUT", - "deltas": { - "fontSize": { - "ours": "10px", - "ref": "9px" - }, - "letterSpacing": { - "ours": "0.8px", - "ref": "normal" - }, - "rect": { - "ours": { - "x": 806, - "y": 2047, - "w": 550, - "h": 13 - }, - "ref": { - "x": 808, - "y": 1378, - "w": 59, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(5)>div:nth-child(2)>div:nth-child(2)>span:nth-child(1)", - "tag": "span", - "text": "ready · PNG-24", - "deltas": { - "rect": { - "ours": { - "x": 814, - "y": 2434, - "w": 76, - "h": 11 - }, - "ref": { - "x": 997, - "y": 1378, - "w": 69, - "h": 10 - } - } - } - }, - { - "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>p:nth-child(3)", - "tag": "p", - "text": "Output is generated in-browser. Your fil", - "deltas": { - "rect": { - "ours": { - "x": 799, - "y": 2489, - "w": 565, - "h": 17 - }, - "ref": { - "x": 799, - "y": 1425, - "w": 565, - "h": 17 + "elementDiffs": [ + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)", + "tag": "span", + "text": "easy-png-tools", + "deltas": { + "rect": { + "ours": { + "x": 98, + "y": 23, + "w": 118, + "h": 18 + }, + "ref": { + "x": 98, + "y": 23, + "w": 108, + "h": 17 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)", + "tag": "a", + "text": "Workspace", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 603, + "y": 24, + "w": 69, + "h": 15 + }, + "ref": { + "x": 780, + "y": 26, + "w": 49, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)", + "tag": "a", + "text": "Catalog", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 690, + "y": 24, + "w": 54, + "h": 15 + }, + "ref": { + "x": 851, + "y": 26, + "w": 39, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)", + "tag": "a", + "text": "Gradient", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 762, + "y": 24, + "w": 61, + "h": 15 + }, + "ref": { + "x": 912, + "y": 26, + "w": 44, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)", + "tag": "a", + "text": "Background remover", + "deltas": { + "fontSize": { + "ours": "12px", + "ref": "10px" + }, + "color": { + "ours": "rgb(109, 120, 131)", + "ref": "rgb(23, 105, 210)" + }, + "letterSpacing": { + "ours": "0.48px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 841, + "y": 24, + "w": 138, + "h": 15 + }, + "ref": { + "x": 978, + "y": 26, + "w": 99, + "h": 12 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)", + "tag": "button", + "text": "RU", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)", + "tag": "button", + "text": "EN", + "deltas": { + "letterSpacing": { + "ours": "0.6px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 1349, + "y": 19, + "w": 32, + "h": 25 + }, + "ref": { + "x": 1352, + "y": 20, + "w": 29, + "h": 24 + } + } + } + }, + { + "path": "body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>section:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(1)", + "tag": "span", + "text": "RESULT", + "deltas": { + "letterSpacing": { + "ours": "1px", + "ref": "normal" + }, + "lineHeight": { + "ours": "12px", + "ref": "normal" + }, + "rect": { + "ours": { + "x": 726, + "y": 125, + "w": 108, + "h": 12 + }, + "ref": { + "x": 954, + "y": 467, + "w": 33, + "h": 12 + } + } } } + ], + "onlyOurs": [ + "/ TOOLS / REMOVE-BACKGROUND-PNG", + "STEPS", + "Pipeline", + "AUTO", + "01", + "alpha", + "Remove background PNG (smart)", + "Background color", + "Similarity tolerance, %", + "10", + "Outer regions only", + "Edge smoothing, passes", + "1", + "Add step", + "Split PNG into HSL", + "Split PNG into HSV", + "Split PNG into HSI", + "Convert PNG to CMYK Colors", + "Convert PNG to YCbCr Colors", + "Convert PNG to LAB Colors", + "Show Transparent Areas PNG", + "Show Grayscale Pixels PNG", + "Show Color Pixels PNG", + "Light Pixel Mask PNG", + "Dark Pixel Mask PNG", + "Unique Color Mask PNG", + "Convert JPG to PNG", + "Convert WebP to PNG", + "Convert GIF to PNG", + "Convert BMP to PNG", + "Convert ICO to PNG", + "Convert PNG to BMP", + "Resize PNG", + "Crop PNG", + "Rotate PNG", + "Flip PNG", + "Add padding to PNG", + "Add border to PNG", + "Fit PNG onto background", + "Tile PNG", + "Circle Mask PNG", + "Square Mask PNG", + "Star Mask PNG", + "Wavy Mask PNG", + "Center PNG by content", + "Blur PNG", + "Sharpen PNG", + "Grayscale PNG", + "Invert colors PNG", + "Brightness & contrast PNG", + "Change PNG opacity", + "Sepia effect", + "Change hue PNG", + "Extract channel PNG", + "Swap channels PNG", + "Black & white threshold PNG", + "Posterize PNG", + "Two colors PNG", + "Convert PNG to JPG", + "Convert PNG to WebP" + ], + "onlyRef": [ + "/ \n BACKGROUND REMOVER", + "/", + "Remove background.", + "Select a background color and tune the e", + "REMOVER SETTINGS", + "Configure detection", + "TOOL 02", + "BACKGROUND COLOR", + "72\n %", + "strict edges", + "more removal", + "OUTER COLOR ONLY", + "Only remove connected background pixels ", + "SHOW MASK", + "Preview the detected transparency mask.", + "SOURCE / RESULT", + "comparison.png", + "SOURCE", + "original.png", + "OBJECT", + "1200 × 800", + "removed-bg.png", + "PNG", + "PNG-24", + "ENABLED", + "v2.4.0", + "background remover · local-only", + "© 2024" + ], + "counts": { + "tokenLight": 10, + "tokenDark": 16, + "elements": 8, + "onlyOurs": 110, + "onlyRef": 28 } } - ], - "onlyOurs": [ - "/ DEMO", - "+ Add tool", - "AUTO", - "Gradient color", - "DIRECTION", - "135 °", - "OPACITY", - "75%", - "50%", - "25%", - "Automatic subject detection enabled", - "Outline width", - "2 px", - "Outline color", - "Corner radius", - "18 px", - "v0.1.0", - "© 2026" - ], - "onlyRef": [ - "/ \n WORKSPACE", - "COLOR", - "135\n °", - "100", - "75", - "50", - "25", - "2\n px", - "18\n px", - "v2.4.0", - "© 2024" - ], - "counts": { - "tokenLight": 10, - "tokenDark": 16, - "elements": 47, - "onlyOurs": 18, - "onlyRef": 11 - } + ] } \ No newline at end of file diff --git a/web/audit/audit-report.md b/web/audit/audit-report.md index d677d06..c1c09a6 100644 --- a/web/audit/audit-report.md +++ b/web/audit/audit-report.md @@ -1,17 +1,22 @@ -# Style audit: http://127.0.0.1:5179/preview/demo +# Style audit (multi-route) -vs C:\+XBOCTuK\+life_projects\easy-png-tools\refs-html\demo.html - -_2026-08-28T14:54:53.564Z_ +_2026-08-28T15:27:27.893Z_ ## Сводка -- Токены light: **10** расх. -- Токены dark: **16** расх. -- Элементы (стиль/геометрия): **47** расх. -- Только у нас: 18, только в рефе: 11 +| route | ref | tokL | tokD | els | onlyOurs | onlyRef | +|---|---|---|---|---|---|---| +| `/preview/demo` | demo.html | 10 | 16 | 47 | 18 | 11 | +| `/preview/list-tools` | list-tools.html | 10 | 16 | 30 | 257 | 54 | +| `/preview/tools/linear-gradient-png` | gradient.html | 10 | 16 | 7 | 111 | 30 | +| `/preview/tools/remove-background-png` | background-remover.html | 10 | 16 | 8 | 110 | 28 | -## Токены — Light +## /preview/demo (vs demo.html) + +- Токены light: **10**, dark: **16** +- Элементы: **47**, только у нас: 18, только в рефе: 11 + +### Токены — Light | token | ours | ref | |---|---|---| @@ -26,7 +31,7 @@ _2026-08-28T14:54:53.564Z_ | `--radius-lg` | — | .5rem | | `--text-sm` | — | .875rem | -## Токены — Dark +### Токены — Dark | token | ours | ref | |---|---|---| @@ -47,7 +52,7 @@ _2026-08-28T14:54:53.564Z_ | `--radius-lg` | — | .5rem | | `--text-sm` | — | .875rem | -## Расхождения элементов (топ 47) +### Расхождения элементов (топ 47) ### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>div:nth-child(1)>span:nth-child(1)` — "/" - **fontFamily**: `"IBM Plex Mono", monospace` → `"IBM Plex Sans", sans-serif` @@ -259,7 +264,7 @@ _2026-08-28T14:54:53.564Z_ ### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>div:nth-child(1)>p:nth-child(3)` — "Output is generated in-browser. Your fil" - **rect**: `799,2489 565x17` → `799,1425 565x17` -## Только у нас (структурно) +### Только у нас - `/ DEMO` - `+ Add tool` @@ -280,7 +285,7 @@ _2026-08-28T14:54:53.564Z_ - `v0.1.0` - `© 2026` -## Только в рефе (структурно) +### Только в рефе - `/ WORKSPACE` @@ -297,3 +302,666 @@ _2026-08-28T14:54:53.564Z_ px` - `v2.4.0` - `© 2024` + + +## /preview/list-tools (vs list-tools.html) + +- Токены light: **10**, dark: **16** +- Элементы: **30**, только у нас: 257, только в рефе: 54 + +### Токены — Light + +| token | ours | ref | +|---|---|---| +| `--danger` | #e5484d | — | +| `--success` | #25a96a | — | +| `--text-sm--line-height` | — | calc(1.25 / .875) | +| `--text-xs--line-height` | — | calc(1 / .75) | +| `--font-weight-medium` | — | 500 | +| `--spacing` | — | .25rem | +| `--text-xs` | — | .75rem | +| `--radius-md` | — | .375rem | +| `--radius-lg` | — | .5rem | +| `--text-sm` | — | .875rem | + +### Токены — Dark + +| token | ours | ref | +|---|---|---| +| `--panel` | #182129 | #f8fafb | +| `--blue` | #54a2ff | #1769d2 | +| `--muted` | #91a0ac | #6d7883 | +| `--background` | #11171d | #eef1f4 | +| `--danger` | #ff7479 | — | +| `--foreground` | #e8eef2 | #17212b | +| `--success` | #25a96a | — | +| `--line` | #33414c | #cbd3da | +| `--text-sm--line-height` | — | calc(1.25 / .875) | +| `--text-xs--line-height` | — | calc(1 / .75) | +| `--font-weight-medium` | — | 500 | +| `--spacing` | — | .25rem | +| `--text-xs` | — | .75rem | +| `--radius-md` | — | .375rem | +| `--radius-lg` | — | .5rem | +| `--text-sm` | — | .875rem | + +### Расхождения элементов (топ 30) + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)` — "Catalog" + - **fontSize**: `12px` → `10px` + - **color**: `rgb(109, 120, 131)` → `rgb(23, 105, 210)` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `621,24 54x15` → `832,26 39x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)` — "Workspace" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `534,24 69x15` → `761,26 49x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)` — "Gradient" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `693,24 61x15` → `893,26 44x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)` — "Background remover" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `773,24 138x15` → `959,26 99x12` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(1)>span:nth-child(2)>span:nth-child(1)` — "Convert JPG to PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `104,302 175x19` → `131,494 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(2)>span:nth-child(2)>span:nth-child(1)` — "Convert WebP to PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `364,311 175x19` → `131,610 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(7)>span:nth-child(2)>span:nth-child(1)` — "PNG to Base64" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `364,429 175x19` → `131,726 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(9)>span:nth-child(2)>span:nth-child(1)` — "PNG to Data URI" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `885,429 175x19` → `131,842 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(2)>div:nth-child(2)>a:nth-child(17)>span:nth-child(2)>span:nth-child(1)` — "Convert PNG to JPG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `364,717 175x19` → `131,958 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(3)>div:nth-child(2)>a:nth-child(8)>span:nth-child(2)>span:nth-child(1)` — "Round corners PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `625,1239 175x19` → `807,726 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(3)>div:nth-child(2)>a:nth-child(11)>span:nth-child(2)>span:nth-child(1)` — "Outline PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `104,1384 175x19` → `807,842 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(7)>span:nth-child(2)>span:nth-child(1)` — "Grayscale PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `364,2096 175x19` → `131,1276 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(8)>span:nth-child(2)>span:nth-child(1)` — "Invert colors PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `625,2104 175x19` → `131,1392 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(10)>span:nth-child(2)>span:nth-child(1)` — "Change PNG opacity" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `1146,2096 175x19` → `807,958 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(4)>div:nth-child(2)>a:nth-child(20)>span:nth-child(2)>span:nth-child(1)` — "Temperature PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `1146,2361 175x19` → `131,1624 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(1)>span:nth-child(2)>span:nth-child(1)` — "Resize PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `104,2731 175x19` → `807,1160 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(2)>span:nth-child(2)>span:nth-child(1)` — "Crop PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `364,2740 175x19` → `807,1276 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(3)>span:nth-child(2)>span:nth-child(1)` — "Rotate PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `625,2748 175x19` → `807,1392 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(4)>span:nth-child(2)>span:nth-child(1)` — "Flip PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `885,2757 175x19` → `807,1508 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(5)>div:nth-child(2)>a:nth-child(5)>span:nth-child(2)>span:nth-child(1)` — "Add padding to PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `1146,2748 175x19` → `807,1624 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(1)>span:nth-child(2)>span:nth-child(1)` — "Blur PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `104,3396 175x19` → `131,1826 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(2)>span:nth-child(2)>span:nth-child(1)` — "Sharpen PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `364,3387 175x19` → `131,1942 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(3)>span:nth-child(2)>span:nth-child(1)` — "Vignette PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `625,3404 175x19` → `131,2058 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(6)>div:nth-child(2)>a:nth-child(8)>span:nth-child(2)>span:nth-child(1)` — "JPEG artifacts" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `625,3540 175x19` → `131,2174 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(8)>div:nth-child(2)>a:nth-child(9)>span:nth-child(2)>span:nth-child(1)` — "PNG info" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `885,4142 175x19` → `807,1826 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(8)>div:nth-child(2)>a:nth-child(13)>span:nth-child(2)>span:nth-child(1)` — "PNG orientation" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `625,4287 175x19` → `807,2174 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>section:nth-child(1)>section:nth-child(9)>div:nth-child(2)>a:nth-child(7)>span:nth-child(2)>span:nth-child(1)` — "Create gradient PNG" + - **fontFamily**: `"IBM Plex Sans", sans-serif` → `"IBM Plex Mono", monospace` + - **fontSize**: `14.4px` → `13px` + - **rect**: `364,4632 175x19` → `131,1160 488x15` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)` — "EN" + - **letterSpacing**: `0.6px` → `normal` + - **rect**: `1349,19 32x25` → `1352,20 29x24` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)` — "easy-png-tools" + - **rect**: `98,23 118x18` → `98,23 108x17` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)` — "RU" + - **letterSpacing**: `0.6px` → `normal` + +### Только у нас + +- `/ LIST-TOOLS` +- `CATALOG` +- `All tools` +- `Каталог всех PNG-инструментов, сгруппиро` +- `convert` +- `Convert` +- `Opens a JPEG and saves it as lossless PN` +- `Re-encodes a WebP image into universal P` +- `Convert GIF to PNG` +- `Extracts the first frame of a GIF animat` +- `Convert BMP to PNG` +- `Re-encodes BMP into compact lossless PNG` +- `Convert ICO to PNG` +- `Turns an .ico icon into a regular PNG of` +- `Convert PNG to BMP` +- `Saves the image as 24-bit BMP without an` +- `Encodes the image into a base64 string f` +- `Base64 to PNG` +- `Decodes a base64 string or data-uri back` +- `Builds a full data-uri (data:image/png;b` +- `Data URI to PNG` +- `Decodes data:image/…;base64,… back into ` +- `PNG to HEX pixels` +- `Shows all pixels as rrggbbaa hex values ` +- `HEX pixels to PNG` +- `Assembles an image from rrggbbaa hex val` +- `PNG to Bytes` +- `Lists every pixel as four decimal bytes ` +- `Bytes to PNG` +- `Assembles an image from decimal RGBA byt` +- `PNG to RGB Values` +- `Lists every pixel as rgba(r, g, b, a), o` +- `RGB Values to PNG` +- `Assembles an image from rgba(r, g, b, a)` +- `Transparency is composited over the chos` +- `Convert PNG to WebP` +- `Re-encodes the image into WebP with adju` +- `Compress PNG` +- `Shrinks the PNG by reducing its palette ` +- `Reduce PNG to Size` +- `Binary-searches the palette size until t` +- `SVG to PNG` +- `Decodes SVG markup into a raster image. ` +- `alpha` +- `Alpha & transparency` +- `Circle Mask PNG` +- `Cuts the image into a circle. Diameter i` +- `Square Mask PNG` +- `Cuts the image into a rectangle with sid` +- `Star Mask PNG` +- `Cuts the image into an n-pointed star wi` +- `Wavy Mask PNG` +- `Cuts the image into a wavy-edged circle:` +- `Remove alpha channel PNG` +- `Composites the image over a white backgr` +- `Set alpha channel PNG` +- `Assigns the same opacity to all pixels; ` +- `Extract alpha mask PNG` +- `Turns transparency into a black-and-whit` +- `Clips corners by a radius set as a perce` + +### Только в рефе + +- `/ + CATALOG` +- `/` +- `EASY-PNG-TOOLS / CATALOG` +- `Tool catalog` +- `Focused utilities for working with PNG. ` +- `32` +- `ALL` +- `CONVERT` +- `TRANSPARENCY` +- `COLOR` +- `GEOMETRY` +- `FILTERS` +- `ANALYZE` +- `05 + TOOLS` +- `Re-encode JPEG files as lossless PNG whi` +- `01` +- `Turn WebP images into a universal PNG fo` +- `02` +- `Encode an image as a base64 string for e` +- `03` +- `Build a complete data URI ready for HTML` +- `04` +- `Composite transparency over a selected b` +- `05` +- `Remove background PNG` +- `Remove a solid background by color, tole` +- `Extract alpha mask` +- `Turn the alpha channel into a clean blac` +- `Clip the image corners by a precise radi` +- `Add a colored ring around opaque content` +- `Multiply the alpha channel while keeping` +- `Generate a smooth transition between two` +- `Convert the image to luminance-based sha` +- `Invert every color channel while leaving` +- `Brightness & contrast` +- `Adjust brightness and contrast across a ` +- `Make an image warmer or cooler with a si` +- `Scale an image with bilinear interpolati` +- `Cut a rectangular area with exact coordi` +- `Rotate by 90, 180, or 270 degrees withou` +- `Mirror the image horizontally or vertica` +- `Expand the canvas on all sides by a chos` +- `04 + TOOLS` +- `Apply a fast Gaussian-style blur with tr` +- `Emphasize edges with an adjustable sharp` +- `Smoothly darken the image edges while pr` +- `Simulate low-quality JPEG recompression ` +- `Inspect dimensions, alpha presence, and ` +- `Check grayscale` +- `Report whether the image contains only s` +- `Check transparency` +- `Detect transparent and semi-transparent ` +- `Classify the image as portrait, landscap` +- `•` + + +## /preview/tools/linear-gradient-png (vs gradient.html) + +- Токены light: **10**, dark: **16** +- Элементы: **7**, только у нас: 111, только в рефе: 30 + +### Токены — Light + +| token | ours | ref | +|---|---|---| +| `--danger` | #e5484d | — | +| `--success` | #25a96a | — | +| `--text-sm--line-height` | — | calc(1.25 / .875) | +| `--text-xs--line-height` | — | calc(1 / .75) | +| `--font-weight-medium` | — | 500 | +| `--spacing` | — | .25rem | +| `--text-xs` | — | .75rem | +| `--radius-md` | — | .375rem | +| `--radius-lg` | — | .5rem | +| `--text-sm` | — | .875rem | + +### Токены — Dark + +| token | ours | ref | +|---|---|---| +| `--panel` | #182129 | #f8fafb | +| `--blue` | #54a2ff | #1769d2 | +| `--muted` | #91a0ac | #6d7883 | +| `--background` | #11171d | #eef1f4 | +| `--danger` | #ff7479 | — | +| `--foreground` | #e8eef2 | #17212b | +| `--success` | #25a96a | — | +| `--line` | #33414c | #cbd3da | +| `--text-sm--line-height` | — | calc(1.25 / .875) | +| `--text-xs--line-height` | — | calc(1 / .75) | +| `--font-weight-medium` | — | 500 | +| `--spacing` | — | .25rem | +| `--text-xs` | — | .75rem | +| `--radius-md` | — | .375rem | +| `--radius-lg` | — | .5rem | +| `--text-sm` | — | .875rem | + +### Расхождения элементов (топ 7) + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)` — "Gradient" + - **fontSize**: `12px` → `10px` + - **color**: `rgb(109, 120, 131)` → `rgb(23, 105, 210)` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `754,24 61x15` → `930,26 44x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)` — "Workspace" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `596,24 69x15` → `798,26 49x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)` — "Catalog" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `683,24 54x15` → `870,26 39x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)` — "Background remover" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `834,24 138x15` → `996,26 99x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)` — "EN" + - **letterSpacing**: `0.6px` → `normal` + - **rect**: `1349,19 32x25` → `1352,20 29x24` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)` — "easy-png-tools" + - **rect**: `98,23 118x18` → `98,23 108x17` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)` — "RU" + - **letterSpacing**: `0.6px` → `normal` + +### Только у нас + +- `/ TOOLS / LINEAR-GRADIENT-PNG` +- `STEPS` +- `Pipeline` +- `AUTO` +- `01` +- `generate` +- `Create gradient PNG` +- `Width` +- `Height` +- `Start color` +- `End color` +- `Direction` +- `Horizontal` +- `Vertical` +- `Add step` +- `Split PNG into HSL` +- `Split PNG into HSV` +- `Split PNG into HSI` +- `Convert PNG to CMYK Colors` +- `Convert PNG to YCbCr Colors` +- `Convert PNG to LAB Colors` +- `Show Transparent Areas PNG` +- `Show Grayscale Pixels PNG` +- `Show Color Pixels PNG` +- `Light Pixel Mask PNG` +- `Dark Pixel Mask PNG` +- `Unique Color Mask PNG` +- `Convert JPG to PNG` +- `Convert WebP to PNG` +- `Convert GIF to PNG` +- `Convert BMP to PNG` +- `Convert ICO to PNG` +- `Convert PNG to BMP` +- `Resize PNG` +- `Crop PNG` +- `Rotate PNG` +- `Flip PNG` +- `Add padding to PNG` +- `Add border to PNG` +- `Fit PNG onto background` +- `Tile PNG` +- `Circle Mask PNG` +- `Square Mask PNG` +- `Star Mask PNG` +- `Wavy Mask PNG` +- `Center PNG by content` +- `Blur PNG` +- `Sharpen PNG` +- `Grayscale PNG` +- `Invert colors PNG` +- `Brightness & contrast PNG` +- `Change PNG opacity` +- `Sepia effect` +- `Change hue PNG` +- `Extract channel PNG` +- `Swap channels PNG` +- `Black & white threshold PNG` +- `Posterize PNG` +- `Two colors PNG` +- `Convert PNG to JPG` + +### Только в рефе + +- `/ + GRADIENT` +- `/` +- `Gradient background.` +- `Create a clean, export-ready gradient wi` +- `GRADIENT SETTINGS` +- `Configure output` +- `TOOL 01` +- `GRADIENT TYPE` +- `Linear` +- `Radial` +- `COLOR STOPS` +- `→` +- `135 + °` +- `0° →` +- `45° ↗` +- `90° ↑` +- `135° ↖` +- `180° ←` +- `225° ↙` +- `270° ↓` +- `315° ↘` +- `100 + %` +- `OUTPUT PREVIEW` +- `gradient.png` +- `PNG` +- `GENERATED CSS` +- `background: linear-gradient(135deg, #176` +- `v2.4.0` +- `gradient tool · local-only` +- `© 2024` + + +## /preview/tools/remove-background-png (vs background-remover.html) + +- Токены light: **10**, dark: **16** +- Элементы: **8**, только у нас: 110, только в рефе: 28 + +### Токены — Light + +| token | ours | ref | +|---|---|---| +| `--danger` | #e5484d | — | +| `--success` | #25a96a | — | +| `--text-sm--line-height` | — | calc(1.25 / .875) | +| `--text-xs--line-height` | — | calc(1 / .75) | +| `--font-weight-medium` | — | 500 | +| `--spacing` | — | .25rem | +| `--text-xs` | — | .75rem | +| `--radius-md` | — | .375rem | +| `--radius-lg` | — | .5rem | +| `--text-sm` | — | .875rem | + +### Токены — Dark + +| token | ours | ref | +|---|---|---| +| `--panel` | #182129 | #f8fafb | +| `--blue` | #54a2ff | #1769d2 | +| `--muted` | #91a0ac | #6d7883 | +| `--background` | #11171d | #eef1f4 | +| `--danger` | #ff7479 | — | +| `--foreground` | #e8eef2 | #17212b | +| `--success` | #25a96a | — | +| `--line` | #33414c | #cbd3da | +| `--text-sm--line-height` | — | calc(1.25 / .875) | +| `--text-xs--line-height` | — | calc(1 / .75) | +| `--font-weight-medium` | — | 500 | +| `--spacing` | — | .25rem | +| `--text-xs` | — | .75rem | +| `--radius-md` | — | .375rem | +| `--radius-lg` | — | .5rem | +| `--text-sm` | — | .875rem | + +### Расхождения элементов (топ 8) + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(4)` — "Background remover" + - **fontSize**: `12px` → `10px` + - **color**: `rgb(109, 120, 131)` → `rgb(23, 105, 210)` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `841,24 138x15` → `978,26 99x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(1)` — "Workspace" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `603,24 69x15` → `780,26 49x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(2)` — "Catalog" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `690,24 54x15` → `851,26 39x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>nav:nth-child(2)>a:nth-child(3)` — "Gradient" + - **fontSize**: `12px` → `10px` + - **letterSpacing**: `0.48px` → `normal` + - **rect**: `762,24 61x15` → `912,26 44x12` + +### `body>div:nth-child(1)>div:nth-child(1)>div:nth-child(2)>div:nth-child(2)>div:nth-child(1)>div:nth-child(2)>section:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(1)` — "RESULT" + - **letterSpacing**: `1px` → `normal` + - **lineHeight**: `12px` → `normal` + - **rect**: `726,125 108x12` → `954,467 33x12` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(2)` — "EN" + - **letterSpacing**: `0.6px` → `normal` + - **rect**: `1349,19 32x25` → `1352,20 29x24` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(1)>span:nth-child(2)` — "easy-png-tools" + - **rect**: `98,23 118x18` → `98,23 108x17` + +### `body>div:nth-child(1)>div:nth-child(1)>header:nth-child(1)>div:nth-child(3)>div:nth-child(4)>button:nth-child(1)` — "RU" + - **letterSpacing**: `0.6px` → `normal` + +### Только у нас + +- `/ TOOLS / REMOVE-BACKGROUND-PNG` +- `STEPS` +- `Pipeline` +- `AUTO` +- `01` +- `alpha` +- `Remove background PNG (smart)` +- `Background color` +- `Similarity tolerance, %` +- `10` +- `Outer regions only` +- `Edge smoothing, passes` +- `1` +- `Add step` +- `Split PNG into HSL` +- `Split PNG into HSV` +- `Split PNG into HSI` +- `Convert PNG to CMYK Colors` +- `Convert PNG to YCbCr Colors` +- `Convert PNG to LAB Colors` +- `Show Transparent Areas PNG` +- `Show Grayscale Pixels PNG` +- `Show Color Pixels PNG` +- `Light Pixel Mask PNG` +- `Dark Pixel Mask PNG` +- `Unique Color Mask PNG` +- `Convert JPG to PNG` +- `Convert WebP to PNG` +- `Convert GIF to PNG` +- `Convert BMP to PNG` +- `Convert ICO to PNG` +- `Convert PNG to BMP` +- `Resize PNG` +- `Crop PNG` +- `Rotate PNG` +- `Flip PNG` +- `Add padding to PNG` +- `Add border to PNG` +- `Fit PNG onto background` +- `Tile PNG` +- `Circle Mask PNG` +- `Square Mask PNG` +- `Star Mask PNG` +- `Wavy Mask PNG` +- `Center PNG by content` +- `Blur PNG` +- `Sharpen PNG` +- `Grayscale PNG` +- `Invert colors PNG` +- `Brightness & contrast PNG` +- `Change PNG opacity` +- `Sepia effect` +- `Change hue PNG` +- `Extract channel PNG` +- `Swap channels PNG` +- `Black & white threshold PNG` +- `Posterize PNG` +- `Two colors PNG` +- `Convert PNG to JPG` +- `Convert PNG to WebP` + +### Только в рефе + +- `/ + BACKGROUND REMOVER` +- `/` +- `Remove background.` +- `Select a background color and tune the e` +- `REMOVER SETTINGS` +- `Configure detection` +- `TOOL 02` +- `BACKGROUND COLOR` +- `72 + %` +- `strict edges` +- `more removal` +- `OUTER COLOR ONLY` +- `Only remove connected background pixels ` +- `SHOW MASK` +- `Preview the detected transparency mask.` +- `SOURCE / RESULT` +- `comparison.png` +- `SOURCE` +- `original.png` +- `OBJECT` +- `1200 × 800` +- `removed-bg.png` +- `PNG` +- `PNG-24` +- `ENABLED` +- `v2.4.0` +- `background remover · local-only` +- `© 2024` diff --git a/web/scripts/audit.mjs b/web/scripts/audit.mjs index 2ef028a..15b6b04 100644 --- a/web/scripts/audit.mjs +++ b/web/scripts/audit.mjs @@ -1,11 +1,12 @@ -// Стиль-аудит: снимает design-токены (light/dark) и дерево вычисленных стилей -// нашей preview-страницы и refs-html страницы, сравнивает и пишет отчёт. -// Запуск: pnpm audit (опции: --ours --ref --no-serve --route ) +// Стиль-аудит: для каждого preview-маршрута снимает design-токены (light/dark) +// и дерево вычисленных стилей нашей страницы и соответствующей refs-html +// страницы, сравнивает и пишет сводный отчёт. +// Запуск: pnpm audit (опции: --route --ref --no-serve --ours ) import { chromium } from "playwright"; import { spawn } from "node:child_process"; -import { readFileSync, writeFileSync, mkdirSync } from "node:fs"; +import { readFileSync, writeFileSync, mkdirSync, readdirSync } from "node:fs"; import { pathToFileURL } from "node:url"; -import { resolve } from "node:path"; +import { resolve, join } from "node:path"; const args = (() => { const a = {}; @@ -21,9 +22,7 @@ const args = (() => { const WEB = process.cwd(); const PORT = 5179; -const ROUTE = args.route || "/preview/demo"; -const OURS = args.ours || `http://127.0.0.1:${PORT}${ROUTE}`; -const REF = args.ref || resolve(WEB, "../refs-html/demo.html"); +const REFS_DIR = resolve(WEB, "../refs-html"); const FIELDS = [ "fontFamily", @@ -41,6 +40,104 @@ const FIELDS = [ "gap", ]; +// Динамические сегменты: явный список значений + сопоставление id → файл рефа +// (id инструмента в registry.ts не совпадает с именем refs-html-файла). +const DYNAMIC = { + "tools/[id]": { + ids: ["linear-gradient-png", "remove-background-png"], + refFor: (id) => + id === "linear-gradient-png" + ? "gradient.html" + : id === "remove-background-png" + ? "background-remover.html" + : null, + }, +}; + +// Маршруты, которые не с чем сравнивать (нет релевантного рефа). Например, +// `/preview` (индекс/воркспейс) в нашем приложении — своя страница, а +// `refs-html/index.html` — это зеркало demo из Next-рефа, не его реф. +const EXCLUDE = new Set(["/preview"]); + +const refNameFor = (route) => { + if (route === "/preview") return "index.html"; + const last = route.split("/").filter(Boolean).pop(); + return `${last}.html`; +}; + +function discoverRoutes() { + const base = resolve(WEB, "src/routes/preview"); + const out = []; + const walk = (dir) => { + let ents; + try { + ents = readdirSync(dir, { withFileTypes: true }); + } catch { + return; + } + for (const e of ents) { + const p = join(dir, e.name); + if (e.isDirectory()) walk(p); + else if (e.name === "+page.svelte") { + const rel = p.slice(base.length).replace(/\\/g, "/"); + const parts = rel.split("/").filter(Boolean); + parts.pop(); // +page.svelte + out.push(parts.length ? `/preview/${parts.join("/")}` : "/preview"); + } + } + }; + walk(base); + return out; +} + +function buildTargets() { + if (args.route) { + const ref = args.ref || refNameFor(args.route); + return [{ route: args.route, ref }]; + } + const targets = []; + const seen = new Set(); + const add = (route, ref) => { + if (!ref || seen.has(route)) return; + if (!refExists(ref)) { + console.warn(`[audit] ref not found, skipped: ${route} -> ${ref}`); + return; + } + seen.add(route); + targets.push({ route, ref }); + }; + for (const route of discoverRoutes()) { + if (route.includes("[")) continue; // динамика — ниже + if (EXCLUDE.has(route)) { + console.log(`[audit] excluded (no relevant ref): ${route}`); + continue; + } + add(route, refNameFor(route)); + } + for (const [seg, cfg] of Object.entries(DYNAMIC)) { + for (const id of cfg.ids) { + add(`/preview/${seg.replace("[id]", id)}`, cfg.refFor(id)); + } + } + // Рефы без маршрута — просто сообщаем, не падаем. + const routed = new Set(targets.map((t) => t.ref)); + for (const f of refsList()) { + if (!routed.has(f)) console.warn(`[audit] ref без маршрута: ${f}`); + } + return targets; +} + +function refsList() { + try { + return readdirSync(REFS_DIR).filter((f) => f.endsWith(".html")); + } catch { + return []; + } +} +function refExists(name) { + return refsList().includes(name); +} + const waitFor = async (url, ms = 60000) => { const t = Date.now(); while (Date.now() - t < ms) { @@ -133,7 +230,156 @@ const snapshot = (page, url) => return { tokensLight, tokensDark, tree: walk(document.body, "body") }; }, url); +const TOK_NOISE = /^(--tw-|--lightningcss-|--default-)/; + +function diffPair(ours, ref) { + const tokDiff = (a, b) => { + const keys = new Set([...Object.keys(a), ...Object.keys(b)]); + const rows = []; + for (const k of keys) { + if (TOK_NOISE.test(k)) continue; + const av = a[k] ?? "—"; + const bv = b[k] ?? "—"; + if (av !== bv) rows.push({ key: k, ours: av, ref: bv }); + } + return rows; + }; + const tokenDiffs = { + light: tokDiff(ours.tokensLight, ref.tokensLight), + dark: tokDiff(ours.tokensDark, ref.tokensDark), + }; + + const byText = (tree) => { + const m = new Map(); + for (const e of tree) if (e.text) if (!m.has(e.text)) m.set(e.text, e); + return m; + }; + const txtO = byText(ours.tree); + const txtR = byText(ref.tree); + const onlyOurs = []; + const onlyRef = []; + const elementDiffs = []; + for (const [k, o] of txtO) { + const r = txtR.get(k); + if (!r) { + onlyOurs.push(k); + continue; + } + const deltas = {}; + for (const f of FIELDS) { + const ov = (o.style[f] ?? "").toString(); + const rv = (r.style[f] ?? "").toString(); + if (ov !== rv) deltas[f] = { ours: ov, ref: rv }; + } + const dr = o.rect; + const rr = r.rect; + if ( + Math.abs(dr.x - rr.x) > 2 || + Math.abs(dr.y - rr.y) > 2 || + Math.abs(dr.w - rr.w) > 2 || + Math.abs(dr.h - rr.h) > 2 + ) + deltas.rect = { ours: dr, ref: rr }; + if (Object.keys(deltas).length) + elementDiffs.push({ path: o.path, tag: o.tag, text: k, deltas }); + } + for (const [k] of txtR) if (!txtO.has(k)) onlyRef.push(k); + + return { + tokenDiffs, + elementDiffs, + onlyOurs: onlyOurs.slice(0, 60), + onlyRef: onlyRef.slice(0, 60), + counts: { + tokenLight: tokenDiffs.light.length, + tokenDark: tokenDiffs.dark.length, + elements: elementDiffs.length, + onlyOurs: onlyOurs.length, + onlyRef: onlyRef.length, + }, + }; +} + +async function auditOne(page, target) { + const oursUrl = args.ours || `http://127.0.0.1:${PORT}${target.route}`; + const refUrl = pathToFileURL(resolve(REFS_DIR, target.ref)).href; + + await page.goto(oursUrl, { waitUntil: "load" }); + await page.evaluate(() => document.fonts.ready); + await page.waitForTimeout(300); + const ours = await snapshot(page, oursUrl); + + await page.goto(refUrl, { waitUntil: "load" }); + await page.evaluate(() => document.fonts.ready); + await page.waitForTimeout(300); + const ref = await snapshot(page, refUrl); + + const diff = diffPair(ours, ref); + return { route: target.route, ref: target.ref, ...diff }; +} + +function toMarkdown(reports) { + const tokTable = (rows) => + rows.length + ? `| token | ours | ref |\n|---|---|---|\n` + + rows.map((r) => `| \`${r.key}\` | ${r.ours} | ${r.ref} |`).join("\n") + : "_все совпадают_"; + const elBlock = (el) => { + const d = Object.entries(el.deltas) + .map(([k, v]) => { + if (k === "rect") { + const f = (r) => `${r.x},${r.y} ${r.w}x${r.h}`; + return ` - **rect**: \`${f(v.ours)}\` → \`${f(v.ref)}\``; + } + return ` - **${k}**: \`${v.ours}\` → \`${v.ref}\``; + }) + .join("\n"); + return `### \`${el.path}\`${el.text ? ` — "${el.text}"` : ""}\n${d}`; + }; + const summary = + `# Style audit (multi-route)\n\n_${new Date().toISOString()}_\n\n` + + `## Сводка\n\n| route | ref | tokL | tokD | els | onlyOurs | onlyRef |\n|---|---|---|---|---|---|---|\n` + + reports + .map( + (r) => + `| \`${r.route}\` | ${r.ref} | ${r.counts.tokenLight} | ${r.counts.tokenDark} | ${r.counts.elements} | ${r.counts.onlyOurs} | ${r.counts.onlyRef} |`, + ) + .join("\n"); + const sections = reports + .map((r) => { + const sorted = [...r.elementDiffs].sort( + (x, y) => Object.keys(y.deltas).length - Object.keys(x.deltas).length, + ); + const els = sorted.length + ? sorted.slice(0, 100).map(elBlock).join("\n\n") + : "_расхождений нет_"; + return ( + `\n\n## ${r.route} (vs ${r.ref})\n\n` + + `- Токены light: **${r.counts.tokenLight}**, dark: **${r.counts.tokenDark}**\n` + + `- Элементы: **${r.counts.elements}**, только у нас: ${r.counts.onlyOurs}, только в рефе: ${r.counts.onlyRef}\n\n` + + `### Токены — Light\n\n${tokTable(r.tokenDiffs.light)}\n\n` + + `### Токены — Dark\n\n${tokTable(r.tokenDiffs.dark)}\n\n` + + `### Расхождения элементов (топ ${Math.min(100, sorted.length)})\n\n${els}\n\n` + + `### Только у нас\n\n` + + (r.onlyOurs.length ? r.onlyOurs.map((p) => `- \`${p}\``).join("\n") : "_—_") + + `\n\n### Только в рефе\n\n` + + (r.onlyRef.length ? r.onlyRef.map((p) => `- \`${p}\``).join("\n") : "_—_") + ); + }) + .join("\n"); + return summary + sections + "\n"; +} + async function main() { + const targets = buildTargets(); + if (!targets.length) { + console.error("no audit targets (refs-html/*.html missing?)"); + process.exit(1); + } + console.log( + `[audit] targets: ${targets.map((t) => t.route).join(", ")}`, + ); + let server; if (!args.noServe) { const bin = process.platform === "win32" ? "pnpm.cmd" : "pnpm"; @@ -143,7 +389,7 @@ async function main() { shell: true, detached: process.platform !== "win32", }); - await waitFor(OURS); + await waitFor(args.ours || `http://127.0.0.1:${PORT}${targets[0].route}`); } const browser = await chromium.launch(); @@ -151,104 +397,21 @@ async function main() { viewport: { width: 1440, height: 900 }, deviceScaleFactor: 1, }); + const reports = []; try { - await page.goto(OURS, { waitUntil: "load" }); - await page.evaluate(() => document.fonts.ready); - await page.waitForTimeout(400); - const ours = await snapshot(page, OURS); - - await page.goto(pathToFileURL(REF).href, { waitUntil: "load" }); - await page.evaluate(() => document.fonts.ready); - await page.waitForTimeout(400); - const ref = await snapshot(page, pathToFileURL(REF).href); - - // token diff (игнорируем служебный мусор Tailwind/lightningcss — он - // ref-only и не относится к нашим смысловым токенам) - const TOK_NOISE = /^(--tw-|--lightningcss-|--default-)/; - const tokDiff = (a, b) => { - const keys = new Set([...Object.keys(a), ...Object.keys(b)]); - const rows = []; - for (const k of keys) { - if (TOK_NOISE.test(k)) continue; - const av = a[k] ?? "—"; - const bv = b[k] ?? "—"; - if (av !== bv) rows.push({ key: k, ours: av, ref: bv }); - } - return rows; - }; - const tokenDiffs = { - light: tokDiff(ours.tokensLight, ref.tokensLight), - dark: tokDiff(ours.tokensDark, ref.tokensDark), - }; - - // element diff: совпадающие по тексту элементы (структуры разные, - // поэтому nth-child-пути не совпадают — матчим по видимому тексту) - const byText = (tree) => { - const m = new Map(); - for (const e of tree) if (e.text) if (!m.has(e.text)) m.set(e.text, e); - return m; - }; - const txtO = byText(ours.tree); - const txtR = byText(ref.tree); - const onlyOurs = []; - const onlyRef = []; - const elementDiffs = []; - for (const [k, o] of txtO) { - const r = txtR.get(k); - if (!r) { - onlyOurs.push(k); - continue; - } - const deltas = {}; - for (const f of FIELDS) { - const ov = (o.style[f] ?? "").toString(); - const rv = (r.style[f] ?? "").toString(); - if (ov !== rv) deltas[f] = { ours: ov, ref: rv }; - } - const dr = o.rect; - const rr = r.rect; - if ( - Math.abs(dr.x - rr.x) > 2 || - Math.abs(dr.y - rr.y) > 2 || - Math.abs(dr.w - rr.w) > 2 || - Math.abs(dr.h - rr.h) > 2 - ) - deltas.rect = { ours: dr, ref: rr }; - if (Object.keys(deltas).length) - elementDiffs.push({ path: o.path, tag: o.tag, text: k, deltas }); + for (const target of targets) { + const rep = await auditOne(page, target); + reports.push(rep); + console.log( + `[audit] ${rep.route}: tokL=${rep.counts.tokenLight} tokD=${rep.counts.tokenDark} els=${rep.counts.elements} onlyOurs=${rep.counts.onlyOurs} onlyRef=${rep.counts.onlyRef}`, + ); } - for (const [k] of txtR) if (!txtO.has(k)) onlyRef.push(k); - - const report = { - generatedAt: new Date().toISOString(), - ours: OURS, - ref: REF, - tokenDiffs, - elementDiffs, - onlyOurs: onlyOurs.slice(0, 60), - onlyRef: onlyRef.slice(0, 60), - counts: { - tokenLight: tokenDiffs.light.length, - tokenDark: tokenDiffs.dark.length, - elements: elementDiffs.length, - onlyOurs: onlyOurs.length, - onlyRef: onlyRef.length, - }, - }; - mkdirSync(resolve(WEB, "audit"), { recursive: true }); writeFileSync( resolve(WEB, "audit/audit-report.json"), - JSON.stringify(report, null, 2), - ); - writeFileSync( - resolve(WEB, "audit/audit-report.md"), - toMarkdown(report, ours, ref), - ); - console.log( - `audit done: tokens light/dark=${tokenDiffs.light.length}/${tokenDiffs.dark.length}, ` + - `elements=${elementDiffs.length}, onlyOurs=${onlyOurs.length}, onlyRef=${onlyRef.length}`, + JSON.stringify({ generatedAt: new Date().toISOString(), reports }, null, 2), ); + writeFileSync(resolve(WEB, "audit/audit-report.md"), toMarkdown(reports)); console.log(`report: web/audit/audit-report.md`); } finally { await browser.close(); @@ -264,51 +427,6 @@ async function main() { } } -function toMarkdown(report, ours, ref) { - const tokTable = (rows, a, b) => - rows.length - ? `| token | ours | ref |\n|---|---|---|\n` + - rows.map((r) => `| \`${r.key}\` | ${r.ours} | ${r.ref} |`).join("\n") - : "_все совпадают_"; - const sorted = [...report.elementDiffs].sort( - (x, y) => Object.keys(y.deltas).length - Object.keys(x.deltas).length, - ); - const elRows = sorted - .slice(0, 100) - .map((e) => { - const d = Object.entries(e.deltas) - .map(([k, v]) => { - if (k === "rect") { - const f = (r) => `${r.x},${r.y} ${r.w}x${r.h}`; - return ` - **rect**: \`${f(v.ours)}\` → \`${f(v.ref)}\``; - } - return ` - **${k}**: \`${v.ours}\` → \`${v.ref}\``; - }) - .join("\n"); - return `### \`${e.path}\`${e.text ? ` — "${e.text}"` : ""}\n${d}`; - }) - .join("\n\n"); - return ( - `# Style audit: ${report.ours}\n\nvs ${report.ref}\n\n_${report.generatedAt}_\n\n` + - `## Сводка\n\n- Токены light: **${report.counts.tokenLight}** расх.\n` + - `- Токены dark: **${report.counts.tokenDark}** расх.\n` + - `- Элементы (стиль/геометрия): **${report.counts.elements}** расх.\n` + - `- Только у нас: ${report.counts.onlyOurs}, только в рефе: ${report.counts.onlyRef}\n\n` + - `## Токены — Light\n\n${tokTable(report.tokenDiffs.light)}\n\n` + - `## Токены — Dark\n\n${tokTable(report.tokenDiffs.dark)}\n\n` + - `## Расхождения элементов (топ ${Math.min(100, sorted.length)})\n\n${elRows}\n\n` + - `## Только у нас (структурно)\n\n` + - (report.onlyOurs.length - ? report.onlyOurs.map((p) => `- \`${p}\``).join("\n") - : "_—_") + - `\n\n## Только в рефе (структурно)\n\n` + - (report.onlyRef.length - ? report.onlyRef.map((p) => `- \`${p}\``).join("\n") - : "_—_") + - `\n` - ); -} - main().catch((e) => { console.error(e); process.exit(1);