feat: fix demo page to correspond refs

This commit is contained in:
2026-08-29 07:23:31 +05:00
parent 9ff732f2cf
commit d29ec73322
23 changed files with 1296 additions and 606 deletions
+897 -273
View File
File diff suppressed because it is too large Load Diff
+156 -65
View File
@@ -1,95 +1,186 @@
# DOM audit (phase A)
_2026-08-29T01:35:12.853Z_
_2026-08-29T02:19:33.867Z_
## Сводка
| route | ref | added | removed | tag | text |
|---|---|---|---|---|---|
| `/preview/demo` | demo.html | 4 | 8 | 2 | 1 |
| `/preview/list-tools` | list-tools.html | 5 | 8 | 1 | 1 |
| `/preview/tools/linear-gradient-png` | gradient.html | 4 | 5 | 1 | 1 |
| `/preview/tools/remove-background-png` | background-remover.html | 4 | 5 | 1 | 1 |
| `/preview/demo` | demo.html | 0 | 0 | 0 | 0 |
| `/preview/list-tools` | list-tools.html | 78 | 22 | 5 | 1 |
| `/preview/tools/linear-gradient-png` | gradient.html | 6 | 7 | 2 | 2 |
| `/preview/tools/remove-background-png` | background-remover.html | 8 | 7 | 2 | 2 |
## /preview/demo (vs demo.html)
- added: **4** (≈227 узл.), removed: **8** (≈210 узл.), tag: **2**, text: **1**
- added: **0** (≈0 узл.), removed: **0** (≈0 узл.), tag: **0**, text: **0**
### Расхождения (топ 15)
### Расхождения (топ 0)
- `body > div:1` **tag**: ours `div` → ref `main`
- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.)
- `body > div:1 > div:1 > div:2` **tag**: ours `div` → ref `section`
- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2` **text**: ours `` → ref `PNG PROCESSING WORKSPACE`
- `body > div:1 > div:1 > div:2 > div:2 > div:1` **added** (`div` · 203 узл.)
- `body > div:1 > div:1 > div:2 > div:2 > span:1` **removed** (`span` "/" · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2` **removed** (`div` · 8 узл.)
- `body > div:1 > div:1 > div:2 > div:3` **removed** (`div` · 7 узл.)
- `body > div:1 > div:1 > div:2 > div:4` **removed** (`div` · 97 узл.)
- `body > div:1 > div:1 > div:2 > div:5` **removed** (`div` · 5 узл.)
- `body > div:1 > div:1 > footer:3` **added** (`footer` · 3 узл.)
- `body > div:1 > div:1 > section:2` **removed** (`section` · 65 узл.)
- `body > div:1 > header:1` **removed** (`header` · 21 узл.)
- `body > div:1 > footer:3` **removed** (`footer` · 6 узл.)
_расхождений нет_
## /preview/list-tools (vs list-tools.html)
- added: **5** (≈986 узл.), removed: **8** (≈297 узл.), tag: **1**, text: **1**
- added: **78** (≈963 узл.), removed: **22** (≈274 узл.), tag: **5**, text: **1**
### Расхождения (топ 15)
### Расхождения (топ 106)
- `body > div:1` **tag**: ours `div` → ref `main`
- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.)
- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2 > section:1` **added** (`section` · 963 узл.)
- `body > div:1 > div:1 > div:2 > div:2 > div:1` **removed** (`div` "EASY-PNG-TOOLS / CATALOG" · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2 > h1:2` **removed** (`h1` "Tool catalog" · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2 > p:3` **removed** (`p` "Focused utilities for working with PNG. Inspect, transform, and export — locally in your browser." · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2` **removed** (`div` · 4 узл.)
- `body > div:1 > div:1 > footer:3` **text**: ours `` → ref `ALL OPERATIONS RUN LOCALLY YOUR FILES NEVER LEAVE THIS DEVICE`
- `body > div:1 > div:1 > footer:3 > span:1` **added** (`span` "v0.1.0" · 1 узл.)
- `body > div:1 > div:1 > footer:3 > span:2` **added** (`span` "© 2026" · 1 узл.)
- `body > div:1 > div:1 > footer:3 > span:1` **removed** (`span` "•" · 1 узл.)
- `body > div:1 > div:1 > div:2` **removed** (`div` · 13 узл.)
- `body > div:1 > div:1 > div:3` **removed** (`div` · 255 узл.)
- `body > div:1 > header:1` **removed** (`header` · 21 узл.)
- `body > main:1 > header:1 > a:1 > span:3` **added** (`span` "/ LIST-TOOLS" · 1 узл.)
- `body > main:1 > header:1 > a:1 > span:3` **removed** (`span` "/ CATALOG" · 1 узл.)
- `body > main:1 > header:1 > div:3 > span:1` **text**: ours `AUTO PIPELINE` → ref `LOCAL MODE / READY`
- `body > main:1 > section:2` **tag**: ours `section` → ref `div`
- `body > main:1 > section:2 > header:1` **added** (`header` · 4 узл.)
- `body > main:1 > section:2 > section:2` **tag**: ours `section` → ref `div`
- `body > main:1 > section:2 > section:2 > header:1` **added** (`header` · 4 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:1` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:2` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:3` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:4` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:5` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:6` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:7` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:8` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:9` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:10` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:11` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:12` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:13` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:14` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:15` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:16` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:17` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:18` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:19` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:20` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > a:21` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > div:1` **removed** (`div` "EASY-PNG-TOOLS / CATALOG" · 1 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > h1:2` **removed** (`h1` "Tool catalog" · 1 узл.)
- `body > main:1 > section:2 > section:2 > div:2 > p:3` **removed** (`p` "Focused utilities for working with PNG. Inspect, transform, and export — locally in your browser." · 1 узл.)
- `body > main:1 > section:2 > section:2 > div:2` **removed** (`div` · 4 узл.)
- `body > main:1 > section:2 > section:3` **tag**: ours `section` → ref `div`
- `body > main:1 > section:2 > section:3 > header:1` **added** (`header` · 4 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:1` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:2` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:3` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:4` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:5` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:6` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:7` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:8` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:9` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:10` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:11` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:12` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:13` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:14` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:15` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:16` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:17` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:18` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:19` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:20` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > a:21` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > svg:1` **removed** (`svg` · 1 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > button:2` **removed** (`button` "ALL" · 1 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > button:3` **removed** (`button` "CONVERT" · 1 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > button:4` **removed** (`button` "TRANSPARENCY" · 1 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > button:5` **removed** (`button` "COLOR" · 1 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > button:6` **removed** (`button` "GEOMETRY" · 1 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > button:7` **removed** (`button` "FILTERS" · 1 узл.)
- `body > main:1 > section:2 > section:3 > div:2 > button:8` **removed** (`button` "ANALYZE" · 1 узл.)
- `body > main:1 > section:2 > section:3 > label:1` **removed** (`label` · 3 узл.)
- `body > main:1 > section:2 > section:4` **tag**: ours `section` → ref `div`
- `body > main:1 > section:2 > section:4 > header:1` **added** (`header` · 4 узл.)
- `body > main:1 > section:2 > section:4 > div:2` **tag**: ours `div` → ref `section`
- `body > main:1 > section:2 > section:4 > div:2 > a:1` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:2` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:3` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:4` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:5` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:6` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:7` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:8` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:9` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:10` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:11` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:12` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:13` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:14` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:15` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:16` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:17` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:18` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:19` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:20` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:21` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:22` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:23` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:24` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > a:25` **added** (`a` · 7 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > div:1` **removed** (`div` · 3 узл.)
- `body > main:1 > section:2 > section:4 > div:2 > div:2` **removed** (`div` · 41 узл.)
- `body > main:1 > section:2 > section:4 > section:2` **removed** (`section` · 45 узл.)
- `body > main:1 > section:2 > section:4 > section:3` **removed** (`section` · 45 узл.)
- `body > main:1 > section:2 > section:4 > section:4` **removed** (`section` · 45 узл.)
- `body > main:1 > section:2 > section:4 > section:5` **removed** (`section` · 37 узл.)
- `body > main:1 > section:2 > section:4 > section:6` **removed** (`section` · 37 узл.)
- `body > main:1 > section:2 > section:5` **added** (`section` · 132 узл.)
- `body > main:1 > section:2 > section:6` **added** (`section` · 62 узл.)
- `body > main:1 > section:2 > section:7` **added** (`section` · 27 узл.)
- `body > main:1 > section:2 > section:8` **added** (`section` · 97 узл.)
- `body > main:1 > section:2 > section:9` **added** (`section` · 153 узл.)
- `body > main:1 > section:2 > footer:4` **removed** (`footer` "ALL OPERATIONS RUN LOCALLY YOUR FILES NEVER LEAVE THIS DEVICE" · 2 узл.)
- `body > main:1 > footer:3` **added** (`footer` · 6 узл.)
## /preview/tools/linear-gradient-png (vs gradient.html)
- added: **4** (≈186 узл.), removed: **5** (≈99 узл.), tag: **1**, text: **1**
- added: **6** (≈155 узл.), removed: **7** (≈73 узл.), tag: **2**, text: **2**
### Расхождения (топ 11)
### Расхождения (топ 17)
- `body > div:1` **tag**: ours `div` → ref `main`
- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.)
- `body > div:1 > div:1 > div:2` **text**: ours `` → ref `PNG PROCESSING SINGLE TOOL`
- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2` **added** (`div` · 162 узл.)
- `body > div:1 > div:1 > div:2 > span:1` **removed** (`span` "/" · 1 узл.)
- `body > div:1 > div:1 > footer:3` **added** (`footer` · 3 узл.)
- `body > div:1 > div:1 > div:2` **removed** (`div` · 6 узл.)
- `body > div:1 > div:1 > div:3` **removed** (`div` · 66 узл.)
- `body > div:1 > header:1` **removed** (`header` · 21 узл.)
- `body > div:1 > footer:3` **removed** (`footer` · 5 узл.)
- `body > main:1 > header:1 > a:1 > span:3` **added** (`span` "/ TOOLS / LINEAR-GRADIENT-PNG" · 1 узл.)
- `body > main:1 > header:1 > a:1 > span:3` **removed** (`span` "/ GRADIENT" · 1 узл.)
- `body > main:1 > header:1 > div:3 > span:1` **text**: ours `AUTO PIPELINE` → ref `LIVE PREVIEW`
- `body > main:1 > div:2 > section:1` **tag**: ours `section` → ref `div`
- `body > main:1 > div:2 > section:1` **text**: ours `` → ref `PNG PROCESSING SINGLE TOOL`
- `body > main:1 > div:2 > section:1 > section:1` **added** (`section` · 145 узл.)
- `body > main:1 > div:2 > section:1 > span:1` **removed** (`span` "/" · 1 узл.)
- `body > main:1 > div:2 > section:2` **tag**: ours `section` → ref `div`
- `body > main:1 > div:2 > section:2 > header:1` **added** (`header` · 5 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > div:1` **added** (`div` · 1 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > div:2` **added** (`div` · 1 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > h1:1` **removed** (`h1` "Gradient background." · 1 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > p:2` **removed** (`p` "Create a clean, export-ready gradient with precise control over color, direction and transparency." · 1 узл.)
- `body > main:1 > div:2 > section:2 > span:2` **removed** (`span` "LIVE PREVIEW" · 2 узл.)
- `body > main:1 > div:2 > div:3` **removed** (`div` · 66 узл.)
- `body > main:1 > footer:3 > span:2` **added** (`span` "pipeline is local-only" · 2 узл.)
- `body > main:1 > footer:3 > span:2` **removed** (`span` "gradient tool · local-only" · 1 узл.)
## /preview/tools/remove-background-png (vs background-remover.html)
- added: **4** (≈186 узл.), removed: **5** (≈104 узл.), tag: **1**, text: **1**
- added: **8** (≈153 узл.), removed: **7** (≈78 узл.), tag: **2**, text: **2**
### Расхождения (топ 11)
### Расхождения (топ 19)
- `body > div:1` **tag**: ours `div` → ref `main`
- `body > div:1 > div:1 > header:1` **added** (`header` · 20 узл.)
- `body > div:1 > div:1 > div:2` **text**: ours `` → ref `PNG PROCESSING SINGLE TOOL`
- `body > div:1 > div:1 > div:2 > div:1` **added** (`div` · 1 узл.)
- `body > div:1 > div:1 > div:2 > div:2` **added** (`div` · 162 узл.)
- `body > div:1 > div:1 > div:2 > span:1` **removed** (`span` "/" · 1 узл.)
- `body > div:1 > div:1 > footer:3` **added** (`footer` · 3 узл.)
- `body > div:1 > div:1 > div:2` **removed** (`div` · 6 узл.)
- `body > div:1 > div:1 > div:3` **removed** (`div` · 71 узл.)
- `body > div:1 > header:1` **removed** (`header` · 21 узл.)
- `body > div:1 > footer:3` **removed** (`footer` · 5 узл.)
- `body > main:1 > header:1 > a:1 > span:3` **added** (`span` "/ TOOLS / REMOVE-BACKGROUND-PNG" · 1 узл.)
- `body > main:1 > header:1 > a:1 > span:3` **removed** (`span` "/ BACKGROUND REMOVER" · 1 узл.)
- `body > main:1 > header:1 > div:3 > span:1` **text**: ours `AUTO PIPELINE` → ref `AUTO PROCESSING`
- `body > main:1 > div:2 > section:1` **tag**: ours `section` → ref `div`
- `body > main:1 > div:2 > section:1` **text**: ours `` → ref `PNG PROCESSING SINGLE TOOL`
- `body > main:1 > div:2 > section:1 > section:1` **added** (`section` · 138 узл.)
- `body > main:1 > div:2 > section:1 > span:1` **removed** (`span` "/" · 1 узл.)
- `body > main:1 > div:2 > section:2` **tag**: ours `section` → ref `div`
- `body > main:1 > div:2 > section:2 > header:1` **added** (`header` · 5 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > div:1` **added** (`div` · 4 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > div:2` **added** (`div` · 1 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > div:3` **added** (`div` · 1 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > h1:1` **removed** (`h1` "Remove background." · 1 узл.)
- `body > main:1 > div:2 > section:2 > div:2 > p:2` **removed** (`p` "Select a background color and tune the edge detection. Changes are processed automatically in your browser." · 1 узл.)
- `body > main:1 > div:2 > section:2 > span:2` **removed** (`span` "LIVE PREVIEW" · 2 узл.)
- `body > main:1 > div:2 > div:3` **removed** (`div` · 71 узл.)
- `body > main:1 > footer:3 > span:2` **added** (`span` "pipeline is local-only" · 2 узл.)
- `body > main:1 > footer:3 > span:2` **removed** (`span` "background remover · local-only" · 1 узл.)
- `body > div:2` **added** (`div` · 1 узл.)
+1 -1
View File
@@ -23,6 +23,6 @@
%sveltekit.head%
</head>
<body data-sveltekit-preload-data="hover">
<div style="display: contents">%sveltekit.body%</div>
%sveltekit.body%
</body>
</html>
@@ -1,27 +0,0 @@
<script lang="ts">
import BlueprintGrid from "./BlueprintGrid.svelte";
let { children } = $props();
</script>
<div class="app-shell">
<BlueprintGrid />
<div class="app-content">
{@render children()}
</div>
</div>
<style>
.app-shell {
position: relative;
min-height: 100dvh;
background: var(--background);
color: var(--foreground);
font-family: var(--font-sans);
}
.app-content {
position: relative;
z-index: 1;
}
</style>
+4 -1
View File
@@ -1,16 +1,19 @@
<script lang="ts">
import type { Snippet } from "svelte";
import { Check } from "@lucide/svelte";
type Tone = "default" | "accent" | "success" | "amber";
interface Props {
tone?: Tone;
check?: boolean;
children: Snippet;
}
let { tone = "default", children }: Props = $props();
let { tone = "default", check = false, children }: Props = $props();
</script>
<span class="badge badge--{tone}">
{#if check}<Check size={12} />{/if}
{#if children}{@render children()}{/if}
</span>
@@ -1,22 +0,0 @@
<div class="blueprint-grid" aria-hidden="true"></div>
<style>
.blueprint-grid {
position: absolute;
inset: 0;
pointer-events: none;
background-image:
linear-gradient(
to right,
color-mix(in srgb, var(--line) 45%, transparent) 1px,
transparent 1px
),
linear-gradient(
to bottom,
color-mix(in srgb, var(--line) 45%, transparent) 1px,
transparent 1px
);
background-size: 32px 32px;
opacity: 0.55;
}
</style>
+31 -19
View File
@@ -1,12 +1,14 @@
<script lang="ts">
import Field from "./Field.svelte";
import { ChevronDown } from "@lucide/svelte";
interface Props {
label: string;
value: string;
dark?: boolean;
chevron?: boolean;
oninput?: (value: string) => void;
}
let { label, value = $bindable(), oninput }: Props = $props();
let { label, value = $bindable(), dark = false, chevron = false, oninput }: Props = $props();
function handle(e: Event) {
value = (e.target as HTMLInputElement).value;
@@ -14,43 +16,53 @@
}
</script>
<Field {label}>
<div class="control-block">
<label>{label}</label>
<div class="color-field">
<input type="color" class="color-swatch" {value} oninput={handle} />
<input
type="text"
class="color-hex"
{value}
oninput={handle}
spellcheck="false"
/>
<span class="swatch" class:dark style="background:{value}"></span>
<input type="text" aria-label={label} {value} oninput={handle} spellcheck="false" />
{#if chevron}<ChevronDown size={14} />{/if}
</div>
</Field>
</div>
<style>
.control-block {
display: flex;
flex-direction: column;
gap: 8px;
}
label {
font: 10px var(--font-mono);
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
}
.color-field {
display: flex;
align-items: center;
gap: 0.4rem;
}
.color-swatch {
width: 2rem;
.swatch {
width: 1.6rem;
height: 1.6rem;
padding: 0;
border: 1px solid var(--line);
border-radius: var(--radius);
background: transparent;
cursor: pointer;
flex: none;
}
.color-hex {
.color-field input {
flex: 1;
min-width: 0;
font-family: var(--font-mono);
font-size: 11px;
color: var(--foreground);
background: var(--background);
border: 1px solid var(--line);
border-radius: var(--radius);
padding: 0.3rem 0.5rem;
padding: 0.35rem 0.5rem;
text-transform: uppercase;
}
.color-field :global(svg) {
color: var(--muted);
flex: none;
}
</style>
@@ -1,23 +1,34 @@
<script lang="ts">
import { Download } from "@lucide/svelte";
import Button from "./Button.svelte";
import { Download, ArrowDownToLine } from "@lucide/svelte";
interface Props {
label: string;
size?: string;
onclick?: () => void;
}
let { label, size, onclick }: Props = $props();
let { label, onclick }: Props = $props();
</script>
<Button icon={Download} variant="primary" onclick={() => onclick?.()}>
{label}{#if size}<span class="dl-size">{size}</span>{/if}
</Button>
<button class="download-btn" type="button" onclick={() => onclick?.()}>
<Download size={16} />
{label}
<ArrowDownToLine size={14} />
</button>
<style>
.dl-size {
font-family: var(--font-mono);
font-size: 10px;
opacity: 0.8;
.download-btn {
display: inline-flex;
align-items: center;
gap: 8px;
border: 1px solid var(--blue);
background: var(--blue);
color: #fff;
font: 12px var(--font-mono);
letter-spacing: 0.04em;
padding: 9px 14px;
border-radius: var(--radius);
cursor: pointer;
}
.download-btn:hover {
background: color-mix(in srgb, var(--blue) 88%, #000);
}
</style>
+9 -8
View File
@@ -9,22 +9,23 @@
let { compact = false, children }: Props = $props();
</script>
<div class="field-grid" class:compact>
<div class="controls" class:compact>
{@render children()}
</div>
<style>
.field-grid {
display: grid;
grid-template-columns: 1.1fr 1fr 1.2fr;
gap: 16px;
.controls {
display: flex;
flex-direction: column;
gap: 14px;
}
.field-grid.compact {
.controls.compact {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 14px;
}
@media (max-width: 800px) {
.field-grid,
.field-grid.compact {
.controls.compact {
grid-template-columns: 1fr;
}
}
+1 -1
View File
@@ -8,7 +8,7 @@
<div class="meta-row">
<span class="meta-caption">{caption}</span>
<span class="meta-value">{value}</span>
<b class="meta-value">{value}</b>
</div>
<style>
+23 -8
View File
@@ -3,14 +3,19 @@
interface Props {
label: string;
caption: string;
children: Snippet;
}
let { label, children }: Props = $props();
let { label, caption, children }: Props = $props();
</script>
<div class="preview-tile">
<span class="tile-label">{label}</span>
<div class="tile-canvas">{@render children()}</div>
<div class="tile-label">
<span>{label}</span>
<b>{caption}</b>
</div>
</div>
<style>
@@ -23,15 +28,25 @@
padding: 0.4rem;
background: var(--panel);
}
.tile-label {
font: 10px var(--font-mono);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--blue);
}
.tile-canvas {
border-radius: var(--radius);
overflow: hidden;
min-height: 120px;
}
.tile-label {
display: flex;
align-items: baseline;
justify-content: space-between;
gap: 0.5rem;
}
.tile-label span {
font: 10px var(--font-mono);
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--blue);
}
.tile-label b {
font: 400 10px var(--font-mono);
color: var(--muted);
}
</style>
@@ -7,9 +7,10 @@
title: string;
meta?: string;
actions?: Snippet;
unwrapActions?: boolean;
}
let { label, title, meta, actions }: Props = $props();
let { label, title, meta, actions, unwrapActions = false }: Props = $props();
</script>
<div class="section-label">
@@ -18,9 +19,13 @@
<strong>{title}{#if meta}<em>{meta}</em>{/if}</strong>
</div>
{#if actions}
<div class="section-actions">
{#if unwrapActions}
{@render actions()}
</div>
{:else}
<div class="section-actions">
{@render actions()}
</div>
{/if}
{/if}
</div>
+1 -1
View File
@@ -22,7 +22,7 @@
<button
type="button"
class="segment"
class:active={value === opt.value}
class:selected={value === opt.value}
aria-pressed={value === opt.value}
onclick={() => select(opt.value)}
>
+21 -24
View File
@@ -1,6 +1,4 @@
<script lang="ts">
import Field from "./Field.svelte";
interface Props {
label: string;
value: number;
@@ -26,34 +24,33 @@
}
</script>
<Field {label}>
<div class="slider-field">
<input type="range" {min} {max} {step} {value} oninput={handle} />
<span class="slider-value">
{value}
{#if suffix}
{suffix}
{/if}
</span>
</div>
</Field>
<div class="control-block">
<label>{label} <output>{value}{suffix}</output></label>
<input type="range" {min} {max} {step} {value} oninput={handle} />
</div>
<style>
.slider-field {
.control-block {
display: flex;
flex-direction: column;
gap: 8px;
}
label {
display: flex;
align-items: center;
gap: 0.6rem;
width: 100%;
justify-content: space-between;
gap: 0.5rem;
font: 10px var(--font-mono);
letter-spacing: 0.1em;
text-transform: uppercase;
color: var(--muted);
}
output {
font: 11px var(--font-mono);
color: var(--foreground);
}
input[type="range"] {
flex: 1;
width: 100%;
accent-color: var(--blue);
}
.slider-value {
font-family: var(--font-mono);
font-size: 11px;
color: var(--foreground);
min-width: 4ch;
text-align: right;
}
</style>
+1 -1
View File
@@ -1,4 +1,4 @@
<span class="status-dot" aria-hidden="true"></span>
<i class="status-dot" aria-hidden="true"></i>
<style>
.status-dot {
+19 -21
View File
@@ -1,6 +1,6 @@
<script lang="ts">
import type { Snippet } from "svelte";
import { GripVertical, X } from "@lucide/svelte";
import { GripVertical, X, Ellipsis } from "@lucide/svelte";
interface Props {
index: number;
@@ -15,10 +15,8 @@
</script>
<article class="step-card">
<div class="step-rail">
<span class="step-index">{index.toString().padStart(2, "0")}</span>
<span class="drag" aria-hidden="true"><GripVertical size={16} /></span>
</div>
<div class="step-index">{index.toString().padStart(2, "0")}</div>
<GripVertical size={16} class="drag" aria-hidden="true" />
<div class="step-body">
<div class="step-heading">
<div class="step-heading-text">
@@ -35,43 +33,46 @@
>
<X size={16} />
</button>
<Ellipsis size={17} />
</div>
</div>
{#if children}
<div class="step-content">{@render children()}</div>
{@render children()}
{/if}
</div>
</article>
<style>
.step-card {
display: flex;
display: grid;
grid-template-columns: 46px 1fr;
grid-template-rows: auto 1fr;
border: 1px solid var(--line);
border-radius: var(--radius);
background: var(--panel);
overflow: hidden;
}
.step-rail {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 10px;
min-width: 46px;
padding: 17px 0 17px 15px;
border-right: 1px solid var(--line);
}
.step-index {
grid-column: 1;
grid-row: 1;
font: 600 22px var(--font-mono);
line-height: 1;
color: var(--blue);
padding: 17px 0 6px 15px;
border-right: 1px solid var(--line);
}
.drag {
grid-column: 1;
grid-row: 2;
color: var(--muted);
cursor: grab;
display: inline-flex;
padding: 0 0 17px 15px;
border-right: 1px solid var(--line);
}
.step-body {
flex: 1;
grid-column: 2;
grid-row: 1 / span 2;
min-width: 0;
display: flex;
flex-direction: column;
@@ -115,10 +116,7 @@
.step-remove:hover {
color: var(--danger);
}
.step-content {
.step-body > :not(.step-heading) {
padding: 0.75rem;
display: flex;
flex-direction: column;
gap: 0.5rem;
}
</style>
+1 -1
View File
@@ -21,7 +21,7 @@
aria-label={label}
onclick={handle}
>
<span class="toggle-knob"></span>
<i class="toggle-knob"></i>
</button>
<style>
+2 -2
View File
@@ -10,10 +10,10 @@
let { label, checked = $bindable(), onchange }: Props = $props();
</script>
<label class="toggle-row">
<div class="toggle-row">
<span>{label}</span>
<Toggle bind:checked {onchange} {label} />
</label>
</div>
<style>
.toggle-row {
+8 -3
View File
@@ -16,11 +16,11 @@
</script>
<header class="topbar">
<div class="brand">
<a class="brand" href={resolve("/")}>
<span class="brand-mark">EP</span>
<span>easy-png-tools</span>
{#if crumb}<span class="version">{crumb}</span>{/if}
</div>
</a>
<nav class="nav">
<a href={resolve("/preview/demo")}>Workspace</a>
<a href={resolve("/preview/list-tools")}>Catalog</a>
@@ -47,7 +47,7 @@
class="lang-btn"
class:active={lang === "RU"}
onclick={() => (lang = "RU")}>RU</button
>
><span class="lang-div">/</span>
<button
type="button"
class="lang-btn"
@@ -138,6 +138,11 @@
.lang-btn + .lang-btn {
border-left: 1px solid var(--line);
}
.lang-div {
color: var(--muted);
font: 10px var(--font-mono);
align-self: center;
}
.lang-btn.active {
background: var(--foreground);
color: var(--background);
@@ -30,8 +30,8 @@
class="ws"
style="--ws-cols:{columns};--ws-gap:{gap};--ws-pad:{padding};--ws-max:{maxWidth};--ws-sticky:{stickyTop};--ws-pad-mobile:{padMobile};--ws-gap-mobile:{gapMobile};"
>
<div class="ws-settings">{@render settings()}</div>
<div class="ws-preview">{@render preview()}</div>
<section class="ws-settings">{@render settings()}</section>
{@render preview()}
</div>
<style>
@@ -47,19 +47,11 @@
.ws-settings {
min-width: 0;
}
.ws-preview {
position: sticky;
top: var(--ws-sticky);
min-width: 0;
}
@media (max-width: 800px) {
.ws {
grid-template-columns: 1fr;
gap: var(--ws-gap-mobile);
padding: var(--ws-pad-mobile);
}
.ws-preview {
position: static;
}
}
</style>
+11 -7
View File
@@ -1,7 +1,7 @@
<script lang="ts">
import { page } from "$app/stores";
import AppShell from "$lib/components/kit/AppShell.svelte";
import TopBar from "$lib/components/kit/TopBar.svelte";
import { Link2 } from "@lucide/svelte";
import "$lib/styles/design2.css";
import type { Snippet } from "svelte";
@@ -39,19 +39,23 @@
.replace(/^\//, "")
.split("/")
.filter(Boolean);
const label = seg.length ? seg.join(" / ").toUpperCase() : "PREVIEW";
const map: Record<string, string> = { demo: "WORKSPACE" };
const label = seg.length
? seg.map((s) => map[s] ?? s.toUpperCase()).join(" / ")
: "PREVIEW";
return "/ " + label;
}
</script>
<div class="preview-root" data-theme={theme}>
<main class="preview-root" data-theme={theme}>
<TopBar {theme} {crumb} ontoggle={toggle} />
<AppShell>{@render children()}</AppShell>
{@render children()}
<footer class="preview-footer">
<span class="version">v0.1.0</span>
<span class="copy">© 2026</span>
<span>easy-png-tools <b>v2.4.0</b></span>
<span><Link2 size={13} /> pipeline is local-only</span>
<span>© 2024</span>
</footer>
</div>
</main>
<style>
.preview-footer {
+68 -85
View File
@@ -1,19 +1,17 @@
<script lang="ts">
import { Settings2, ChevronDown } from "@lucide/svelte";
import { Settings2, ChevronDown, Plus, SlidersHorizontal } from "@lucide/svelte";
import WorkspaceLayout from "$lib/components/kit/WorkspaceLayout.svelte";
import WorkspaceHeader from "./WorkspaceHeader.svelte";
import SectionLabel from "$lib/components/kit/SectionLabel.svelte";
import StepCard from "$lib/components/kit/StepCard.svelte";
import Badge from "$lib/components/kit/Badge.svelte";
import FieldGrid from "$lib/components/kit/FieldGrid.svelte";
import MonoLabel from "$lib/components/kit/MonoLabel.svelte";
import ColorField from "$lib/components/kit/ColorField.svelte";
import SliderField from "$lib/components/kit/SliderField.svelte";
import Segmented from "$lib/components/kit/Segmented.svelte";
import ToggleRow from "$lib/components/kit/ToggleRow.svelte";
import PreviewStack from "$lib/components/kit/PreviewStack.svelte";
import PreviewTile from "$lib/components/kit/PreviewTile.svelte";
import CheckerCanvas from "$lib/components/kit/CheckerCanvas.svelte";
import DownloadButton from "$lib/components/kit/DownloadButton.svelte";
import MetaList from "$lib/components/kit/MetaList.svelte";
import IconButton from "$lib/components/kit/IconButton.svelte";
@@ -78,9 +76,9 @@
file={{ name: "source.png", size: "1.8 MB" }}
/>
<SectionLabel label="PROCESSING PIPELINE" title="4 active steps" meta="• LIVE">
<SectionLabel label="PROCESSING PIPELINE" title="4 active steps" meta="• LIVE" unwrapActions>
{#snippet actions()}
<button class="add-btn" onclick={() => {}}>+ Add tool</button>
<button class="add-btn" onclick={() => {}}><Plus size={15} /> Add tool</button>
{/snippet}
</SectionLabel>
@@ -92,29 +90,29 @@
onremove={() => {}}
>
{#snippet tools()}
<Badge tone="success">AUTO</Badge>
<Badge tone="success" check>AUTO</Badge>
{/snippet}
<FieldGrid>
<ColorField label="Gradient color" bind:value={gradColor} />
<SliderField
label="DIRECTION"
bind:value={direction}
min={0}
max={360}
suffix="°"
/>
<div class="control-block">
<MonoLabel>OPACITY</MonoLabel>
<Segmented
bind:value={opacity}
options={[
{ value: "100", label: "100%" },
{ value: "75", label: "75%" },
{ value: "50", label: "50%" },
{ value: "25", label: "25%" },
]}
/>
</div>
<FieldGrid>
<ColorField label="COLOR" chevron bind:value={gradColor} />
<SliderField
label="DIRECTION"
bind:value={direction}
min={0}
max={360}
suffix=" °"
/>
<div class="control-block">
<label>OPACITY <output>{opacity}%</output></label>
<Segmented
bind:value={opacity}
options={[
{ value: "100", label: "100" },
{ value: "75", label: "75" },
{ value: "50", label: "50" },
{ value: "25", label: "25" },
]}
/>
</div>
</FieldGrid>
</StepCard>
@@ -125,24 +123,26 @@
onremove={() => {}}
>
{#snippet tools()}
<Badge tone="success">AUTO</Badge>
<Badge tone="success" check>AUTO</Badge>
{/snippet}
<div class="transform-note">Automatic subject detection enabled</div>
<div class="transform-note">
<SlidersHorizontal size={15} /> Automatic subject detection enabled
</div>
</StepCard>
<StepCard index={3} type="STYLE" title="Add outline" onremove={() => {}}>
{#snippet tools()}
<Badge tone="success">AUTO</Badge>
<Badge tone="success" check>AUTO</Badge>
{/snippet}
<FieldGrid compact>
<SliderField
label="Outline width"
bind:value={outlineWidth}
min={0}
max={8}
suffix="px"
/>
<ColorField label="Outline color" bind:value={outlineColor} />
<FieldGrid compact>
<SliderField
label="WIDTH"
bind:value={outlineWidth}
min={0}
max={8}
suffix=" px"
/>
<ColorField label="COLOR" dark bind:value={outlineColor} />
</FieldGrid>
</StepCard>
@@ -153,17 +153,17 @@
onremove={() => {}}
>
{#snippet tools()}
<Badge tone="success">AUTO</Badge>
<Badge tone="success" check>AUTO</Badge>
{/snippet}
<FieldGrid compact>
<SliderField
label="Corner radius"
bind:value={radius}
min={0}
max={48}
suffix="px"
/>
<ToggleRow label="Preserve aspect ratio" bind:checked={preserveAspect} />
<FieldGrid compact>
<SliderField
label="RADIUS"
bind:value={radius}
min={0}
max={48}
suffix=" px"
/>
<ToggleRow label="Preserve aspect ratio" bind:checked={preserveAspect} />
</FieldGrid>
</StepCard>
</div>
@@ -172,16 +172,15 @@
{/snippet}
{#snippet preview()}
<div class="preview-panel">
<section class="preview-panel">
<SectionLabel label="PIPELINE OUTPUTS" title="Visual history">
{#snippet actions()}
<button class="history-toggle" onclick={() => {}}>
Hide intermediate <ChevronDown size={15} />
</button>
<DownloadButton
label="Download result"
size="1.2 MB"
onclick={() => {}}
<DownloadButton
label="Download result"
onclick={() => {}}
/>
<MetaList
items={[
@@ -200,29 +199,24 @@
<PreviewStack pad="16px 0">
{#each previewTiles as tile (tile.label)}
<PreviewTile label={tile.label}>
<CheckerCanvas size="large">
<div
class="ph"
class:active={tile.active}
style="background:{tile.bg}; border-radius:{tile.radius ??
'0'}; box-shadow:{tile.ring ?? 'none'};"
>
<span class="sample-icon">PNG</span>
<span>easy-png-tools</span>
</div>
</CheckerCanvas>
<div class="tile-label-wrap">
<span class="tile-caption">{tile.caption}</span>
</div>
</PreviewTile>
<PreviewTile label={tile.label} caption={tile.caption}>
<div
class="ph"
class:active={tile.active}
style="background:{tile.bg}; border-radius:{tile.radius ??
'0'}; box-shadow:{tile.ring ?? 'none'};"
>
<span class="sample-icon">PNG</span>
<span>easy-png-tools</span>
</div>
</PreviewTile>
{/each}
</PreviewStack>
<p class="preview-note">
Output is generated in-browser. Your files never leave this device.
</p>
</div>
</section>
{/snippet}
</WorkspaceLayout>
@@ -259,6 +253,8 @@
background: var(--panel);
padding: 18px;
min-width: 0;
position: sticky;
top: 24px;
}
.history-toggle {
display: flex;
@@ -298,20 +294,6 @@
padding: 3px 5px;
font-size: 9px;
}
.tile-label-wrap {
display: flex;
justify-content: space-between;
gap: 8px;
padding: 8px;
border-top: 1px solid var(--line);
font: 9px var(--font-mono);
color: var(--muted);
}
.tile-caption {
color: var(--muted);
font-weight: 400;
text-align: right;
}
.preview-note {
margin: 12px 0 0;
color: var(--muted);
@@ -319,3 +301,4 @@
line-height: 1.5;
}
</style>
@@ -17,19 +17,17 @@
let { eyebrowA, eyebrowB, title, lede, file }: Props = $props();
</script>
<div class="ws-header">
<div class="title-row">
<div>
<div class="eyebrow">
{eyebrowA}{#if eyebrowB}<span>/</span>{eyebrowB}{/if}
</div>
<h1>{title}</h1>
{#if lede}<p class="lede">{lede}</p>{/if}
</div>
{#if file}
<FileChip name={file.name} size={file.size} />
{/if}
<div class="eyebrow">
{eyebrowA} <span>/</span> {eyebrowB}
</div>
<div class="title-row">
<div>
<h1>{title}</h1>
{#if lede}<p class="lede">{lede}</p>{/if}
</div>
{#if file}
<FileChip name={file.name} size={file.size} />
{/if}
</div>
<style>