mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 13:36:36 +00:00
chore: update css lint rules, remove unused files
This commit is contained in:
@@ -114,8 +114,12 @@ scoped-путям (`kit/**`, `preview/**`).
|
|||||||
`hct()` (эммит в sRGB-hex делает postcss-плагин `web/scripts/postcss-hct.mjs`)
|
`hct()` (эммит в sRGB-hex делает postcss-плагин `web/scripts/postcss-hct.mjs`)
|
||||||
и только в preview.css.
|
и только в preview.css.
|
||||||
- Размеры: `--space-*`, `--text-*` (font-size), `--radius-*`, `--size-*`.
|
- Размеры: `--space-*`, `--text-*` (font-size), `--radius-*`, `--size-*`.
|
||||||
- Брейкпоинты: `--bp-mobile` (640px), `--bp-tablet` (800px), `--bp-desktop`
|
- Брейкпоинты: `@custom-media --bp-mobile (max-width: 640px)` /
|
||||||
(1100px) — mobile-first.
|
`--bp-tablet (800px)` / `--bp-desktop (1100px)` — объявляются в preview.css,
|
||||||
|
используются как `@media (--bp-*)`. CSS-переменные в `@media` не работают,
|
||||||
|
поэтому отдельных `--bp-*` токенов нет; раскрытие делает postcss-плагин
|
||||||
|
`postcss-custom-media` (конфиг `web/postcss.config.js`, определения
|
||||||
|
подтягиваются через `@csstools/postcss-global-data`).
|
||||||
- z-index: `--z-*`; длительности/анимации: `--duration-*`, `--ease-*`.
|
- z-index: `--z-*`; длительности/анимации: `--duration-*`, `--ease-*`.
|
||||||
- Правило `custom-property-pattern` в stylelint тестирует паттерн **без** `--`
|
- Правило `custom-property-pattern` в stylelint тестирует паттерн **без** `--`
|
||||||
(`--x` → `x`), а `declaration-property-value-disallowed-list` — целиком с `--`.
|
(`--x` → `x`), а `declaration-property-value-disallowed-list` — целиком с `--`.
|
||||||
|
|||||||
@@ -36,7 +36,9 @@ export default {
|
|||||||
hardcodedZIndex:
|
hardcodedZIndex:
|
||||||
"Hardcoded z-index '{{value}}'. Use a var(--z-...) token.",
|
"Hardcoded z-index '{{value}}'. Use a var(--z-...) token.",
|
||||||
hardcodedBreakpoint:
|
hardcodedBreakpoint:
|
||||||
"Hardcoded breakpoint '{{value}}' in @media. Use a var(--bp-...) token.",
|
"Hardcoded breakpoint '{{value}}' in @media. Use the @custom-media name instead: declare `@custom-media --bp-* (...)` in preview.css and write `@media (--bp-*)`.",
|
||||||
|
varInMedia:
|
||||||
|
"var() inside @media '{{value}}'. Custom properties do not resolve in media queries — declare `@custom-media --bp-* (...)` in preview.css and use `@media (--bp-*)`.",
|
||||||
colorMix:
|
colorMix:
|
||||||
"color-mix() in component ({{value}}). Tokenize the result in the design CSS file.",
|
"color-mix() in component ({{value}}). Tokenize the result in the design CSS file.",
|
||||||
},
|
},
|
||||||
@@ -69,6 +71,10 @@ export default {
|
|||||||
const BREAKPOINT_RE =
|
const BREAKPOINT_RE =
|
||||||
/(?:(?:max|min)-width)\s*:\s*(?!var\()(\d+(?:\.\d+)?(?:px|rem|em))/gi;
|
/(?:(?:max|min)-width)\s*:\s*(?!var\()(\d+(?:\.\d+)?(?:px|rem|em))/gi;
|
||||||
|
|
||||||
|
// Custom properties do not resolve inside @media conditions, so any
|
||||||
|
// var(--bp-*) inside @media is a latent bug — the block never applies.
|
||||||
|
const VAR_IN_MEDIA_RE = /var\(\s*(--[a-zA-Z0-9-]+)/g;
|
||||||
|
|
||||||
// Sub-rule 6: color-mix() is banned anywhere in a component style.
|
// Sub-rule 6: color-mix() is banned anywhere in a component style.
|
||||||
const COLOR_MIX_RE = /color-mix\s*\(/gi;
|
const COLOR_MIX_RE = /color-mix\s*\(/gi;
|
||||||
|
|
||||||
@@ -80,6 +86,10 @@ export default {
|
|||||||
while ((m = BREAKPOINT_RE.exec(params)) !== null) {
|
while ((m = BREAKPOINT_RE.exec(params)) !== null) {
|
||||||
report(atRule, "hardcodedBreakpoint", { value: m[1] });
|
report(atRule, "hardcodedBreakpoint", { value: m[1] });
|
||||||
}
|
}
|
||||||
|
VAR_IN_MEDIA_RE.lastIndex = 0;
|
||||||
|
while ((m = VAR_IN_MEDIA_RE.exec(params)) !== null) {
|
||||||
|
report(atRule, "varInMedia", { value: m[1] });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// --- declarations --- //
|
// --- declarations --- //
|
||||||
|
|||||||
@@ -23,6 +23,7 @@
|
|||||||
"refs-dom-audit": "node scripts/audit-dom.mjs"
|
"refs-dom-audit": "node scripts/audit-dom.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@csstools/postcss-global-data": "^4.0.0",
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@panmdaa/colors": "^0.4.2",
|
"@panmdaa/colors": "^0.4.2",
|
||||||
"@sveltejs/adapter-static": "^3.0.10",
|
"@sveltejs/adapter-static": "^3.0.10",
|
||||||
@@ -35,6 +36,7 @@
|
|||||||
"globals": "^17.11.0",
|
"globals": "^17.11.0",
|
||||||
"playwright": "^1.62.1",
|
"playwright": "^1.62.1",
|
||||||
"postcss": "^8.5.26",
|
"postcss": "^8.5.26",
|
||||||
|
"postcss-custom-media": "^12.0.1",
|
||||||
"prettier": "^3.9.6",
|
"prettier": "^3.9.6",
|
||||||
"prettier-plugin-svelte": "^4.1.1",
|
"prettier-plugin-svelte": "^4.1.1",
|
||||||
"stylelint": "^17.14.1",
|
"stylelint": "^17.14.1",
|
||||||
|
|||||||
Generated
+42
@@ -18,6 +18,9 @@ importers:
|
|||||||
specifier: ^1.33.0
|
specifier: ^1.33.0
|
||||||
version: 1.33.0(svelte@5.56.9(@typescript-eslint/types@8.67.0))
|
version: 1.33.0(svelte@5.56.9(@typescript-eslint/types@8.67.0))
|
||||||
devDependencies:
|
devDependencies:
|
||||||
|
'@csstools/postcss-global-data':
|
||||||
|
specifier: ^4.0.0
|
||||||
|
version: 4.0.0(postcss@8.5.26)
|
||||||
'@eslint/js':
|
'@eslint/js':
|
||||||
specifier: ^10.0.1
|
specifier: ^10.0.1
|
||||||
version: 10.0.1(eslint@10.9.0(supports-color@10.2.2))
|
version: 10.0.1(eslint@10.9.0(supports-color@10.2.2))
|
||||||
@@ -54,6 +57,9 @@ importers:
|
|||||||
postcss:
|
postcss:
|
||||||
specifier: ^8.5.26
|
specifier: ^8.5.26
|
||||||
version: 8.5.26
|
version: 8.5.26
|
||||||
|
postcss-custom-media:
|
||||||
|
specifier: ^12.0.1
|
||||||
|
version: 12.0.1(postcss@8.5.26)
|
||||||
prettier:
|
prettier:
|
||||||
specifier: ^3.9.6
|
specifier: ^3.9.6
|
||||||
version: 3.9.6
|
version: 3.9.6
|
||||||
@@ -104,6 +110,13 @@ packages:
|
|||||||
'@cacheable/utils@2.5.0':
|
'@cacheable/utils@2.5.0':
|
||||||
resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
|
resolution: {integrity: sha512-buipgOVDkkPXNR5+xBpDw7Zk2n1EvU7qBJCNUcL7rhQ//kfpOXPAvQ511Os0vpLYJ1pZnvudNytkQt2hst3wqA==}
|
||||||
|
|
||||||
|
'@csstools/cascade-layer-name-parser@3.0.0':
|
||||||
|
resolution: {integrity: sha512-/3iksyevwRfSJx5yH0RkcrcYXwuhMQx3Juqf40t97PeEy2/Mz2TItZ/z/216qpe4GgOyFBP8MKIwVvytzHmfIQ==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
peerDependencies:
|
||||||
|
'@csstools/css-parser-algorithms': ^4.0.0
|
||||||
|
'@csstools/css-tokenizer': ^4.0.0
|
||||||
|
|
||||||
'@csstools/css-calc@3.3.0':
|
'@csstools/css-calc@3.3.0':
|
||||||
resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==}
|
resolution: {integrity: sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==}
|
||||||
engines: {node: '>=20.19.0'}
|
engines: {node: '>=20.19.0'}
|
||||||
@@ -136,6 +149,12 @@ packages:
|
|||||||
'@csstools/css-parser-algorithms': ^4.0.0
|
'@csstools/css-parser-algorithms': ^4.0.0
|
||||||
'@csstools/css-tokenizer': ^4.0.0
|
'@csstools/css-tokenizer': ^4.0.0
|
||||||
|
|
||||||
|
'@csstools/postcss-global-data@4.0.0':
|
||||||
|
resolution: {integrity: sha512-mPKrL3Tzt8k1V+hTkU8XTH6JKRekB97oKHv/MekC9nfmRa+gMf1swlHRt8YK722sYN4xOipl17FZst99jsScLA==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
peerDependencies:
|
||||||
|
postcss: ^8.4
|
||||||
|
|
||||||
'@csstools/selector-resolve-nested@4.0.1':
|
'@csstools/selector-resolve-nested@4.0.1':
|
||||||
resolution: {integrity: sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==}
|
resolution: {integrity: sha512-j3vdQu0XwLME5qOTWxm8cnmvsf423R2YL6DbKklCHZwkDm7UdKNu6RPlw4REIJhSlKBICY3B70/7QZdicLqZgg==}
|
||||||
engines: {node: '>=20.19.0'}
|
engines: {node: '>=20.19.0'}
|
||||||
@@ -1176,6 +1195,12 @@ packages:
|
|||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
postcss-custom-media@12.0.1:
|
||||||
|
resolution: {integrity: sha512-66syE14+VeqkUf0rRX0bvbTCbNRJF132jD+ceo8th1dap2YJEAqpdh5uG98CE3IbgHT7m9XM0GIlOazNWqQdeA==}
|
||||||
|
engines: {node: '>=20.19.0'}
|
||||||
|
peerDependencies:
|
||||||
|
postcss: ^8.4
|
||||||
|
|
||||||
postcss-load-config@3.1.4:
|
postcss-load-config@3.1.4:
|
||||||
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==}
|
||||||
engines: {node: '>= 10'}
|
engines: {node: '>= 10'}
|
||||||
@@ -1582,6 +1607,11 @@ snapshots:
|
|||||||
hashery: 1.5.1
|
hashery: 1.5.1
|
||||||
keyv: 5.6.0
|
keyv: 5.6.0
|
||||||
|
|
||||||
|
'@csstools/cascade-layer-name-parser@3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||||
|
dependencies:
|
||||||
|
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-tokenizer': 4.0.0
|
||||||
|
|
||||||
'@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
'@csstools/css-calc@3.3.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)':
|
||||||
dependencies:
|
dependencies:
|
||||||
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||||
@@ -1602,6 +1632,10 @@ snapshots:
|
|||||||
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||||
'@csstools/css-tokenizer': 4.0.0
|
'@csstools/css-tokenizer': 4.0.0
|
||||||
|
|
||||||
|
'@csstools/postcss-global-data@4.0.0(postcss@8.5.26)':
|
||||||
|
dependencies:
|
||||||
|
postcss: 8.5.26
|
||||||
|
|
||||||
'@csstools/selector-resolve-nested@4.0.1(postcss-selector-parser@7.1.5)':
|
'@csstools/selector-resolve-nested@4.0.1(postcss-selector-parser@7.1.5)':
|
||||||
dependencies:
|
dependencies:
|
||||||
postcss-selector-parser: 7.1.5
|
postcss-selector-parser: 7.1.5
|
||||||
@@ -2520,6 +2554,14 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents: 2.3.2
|
fsevents: 2.3.2
|
||||||
|
|
||||||
|
postcss-custom-media@12.0.1(postcss@8.5.26):
|
||||||
|
dependencies:
|
||||||
|
'@csstools/cascade-layer-name-parser': 3.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-parser-algorithms': 4.0.0(@csstools/css-tokenizer@4.0.0)
|
||||||
|
'@csstools/css-tokenizer': 4.0.0
|
||||||
|
'@csstools/media-query-list-parser': 5.0.0(@csstools/css-parser-algorithms@4.0.0(@csstools/css-tokenizer@4.0.0))(@csstools/css-tokenizer@4.0.0)
|
||||||
|
postcss: 8.5.26
|
||||||
|
|
||||||
postcss-load-config@3.1.4(postcss@8.5.26):
|
postcss-load-config@3.1.4(postcss@8.5.26):
|
||||||
dependencies:
|
dependencies:
|
||||||
lilconfig: 2.1.0
|
lilconfig: 2.1.0
|
||||||
|
|||||||
+13
-1
@@ -1,5 +1,17 @@
|
|||||||
import postcssHct from "./scripts/postcss-hct.mjs";
|
import postcssHct from "./scripts/postcss-hct.mjs";
|
||||||
|
import postcssGlobalData from "@csstools/postcss-global-data";
|
||||||
|
import postcssCustomMedia from "postcss-custom-media";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
plugins: [postcssHct],
|
plugins: [
|
||||||
|
// Breakpoints live only in preview.css (next to the --bp-* tokens);
|
||||||
|
// global-data injects them so @media (--bp-*) expands in every file.
|
||||||
|
postcssGlobalData({
|
||||||
|
files: ["src/preview.css"],
|
||||||
|
}),
|
||||||
|
postcssCustomMedia({
|
||||||
|
preserve: false,
|
||||||
|
}),
|
||||||
|
postcssHct,
|
||||||
|
],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Component } from "svelte";
|
|
||||||
import { Upload } from "@lucide/svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
name: string;
|
|
||||||
size?: string;
|
|
||||||
icon?: Component<{ size?: number }>;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { name, size, icon = Upload }: Props = $props();
|
|
||||||
const Cmp = $derived(icon);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="file-chip">
|
|
||||||
<Cmp size={15} />
|
|
||||||
<span>{name}</span>
|
|
||||||
{#if size}<b>{size}</b>{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.file-chip {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
white-space: nowrap;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
background: var(--panel);
|
|
||||||
font: 11px var(--font-mono);
|
|
||||||
color: var(--muted);
|
|
||||||
padding: 10px 12px;
|
|
||||||
}
|
|
||||||
.file-chip b {
|
|
||||||
color: var(--blue);
|
|
||||||
font-weight: 500;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import { RotateCcw } from "@lucide/svelte";
|
|
||||||
import StatusDot from "./StatusDot.svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
onreset?: () => void;
|
|
||||||
note?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { onreset, note = "changes are applied automatically" }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="pipeline-footer">
|
|
||||||
<button class="reset-btn" onclick={() => onreset?.()}>
|
|
||||||
<RotateCcw size={14} /> Reset pipeline
|
|
||||||
</button>
|
|
||||||
<span class="auto-note"><StatusDot /> {note}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.pipeline-footer {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding-top: 18px;
|
|
||||||
}
|
|
||||||
.reset-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 7px;
|
|
||||||
color: var(--blue);
|
|
||||||
background: none;
|
|
||||||
border: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 12px;
|
|
||||||
}
|
|
||||||
.auto-note {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
color: var(--muted);
|
|
||||||
font: 11px var(--font-mono);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,107 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import CheckerCanvas from "$lib/components/kit/CheckerCanvas.svelte";
|
|
||||||
import Dropzone from "$lib/components/kit/Dropzone.svelte";
|
|
||||||
import EmptyState from "$lib/components/kit/EmptyState.svelte";
|
|
||||||
import Panel from "$lib/components/kit/layout/Panel.svelte";
|
|
||||||
import MetaList from "$lib/components/kit/MetaList.svelte";
|
|
||||||
import PreviewStack from "$lib/components/kit/PreviewStack.svelte";
|
|
||||||
import PreviewTile from "$lib/components/kit/PreviewTile.svelte";
|
|
||||||
import StatusLine from "$lib/components/kit/StatusLine.svelte";
|
|
||||||
import Button from "$lib/components/kit/ui/Button.svelte";
|
|
||||||
import DownloadButton from "$lib/components/kit/ui/DownloadButton.svelte";
|
|
||||||
import type { PixelImage } from "$lib/core/types";
|
|
||||||
|
|
||||||
interface PreviewItem {
|
|
||||||
toolId: string;
|
|
||||||
title: string;
|
|
||||||
url: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface MetaItem {
|
|
||||||
caption: string;
|
|
||||||
value: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
needsSource: boolean;
|
|
||||||
sourceImg: PixelImage | null;
|
|
||||||
sourceUrl: string;
|
|
||||||
stepOutputs: PreviewItem[];
|
|
||||||
resultUrl: string;
|
|
||||||
resultMeta: MetaItem[];
|
|
||||||
processing: boolean;
|
|
||||||
chainError: string;
|
|
||||||
onFile: (file: File) => void;
|
|
||||||
onClearSource: () => void;
|
|
||||||
onDownload: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let {
|
|
||||||
needsSource,
|
|
||||||
sourceImg,
|
|
||||||
sourceUrl,
|
|
||||||
stepOutputs,
|
|
||||||
resultUrl,
|
|
||||||
resultMeta,
|
|
||||||
processing,
|
|
||||||
chainError,
|
|
||||||
onFile,
|
|
||||||
onClearSource,
|
|
||||||
onDownload,
|
|
||||||
}: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Panel title="Pipeline output" eyebrow="RESULT">
|
|
||||||
{#if resultUrl}
|
|
||||||
<DownloadButton label="Download result" onclick={onDownload} />
|
|
||||||
{/if}
|
|
||||||
|
|
||||||
<div class="preview-body">
|
|
||||||
{#if chainError}
|
|
||||||
<EmptyState title="Pipeline failed" description={chainError} />
|
|
||||||
{/if}
|
|
||||||
{#if needsSource && !sourceImg}
|
|
||||||
<Dropzone onfile={onFile} />
|
|
||||||
{:else if needsSource && sourceImg}
|
|
||||||
<Button variant="outline" onclick={onClearSource} label="Change image" />
|
|
||||||
{/if}
|
|
||||||
<PreviewStack>
|
|
||||||
{#if needsSource && sourceImg}
|
|
||||||
<PreviewTile label="SOURCE" caption="">
|
|
||||||
<CheckerCanvas size="sm">
|
|
||||||
<img class="tile-img" src={sourceUrl} alt="source" />
|
|
||||||
</CheckerCanvas>
|
|
||||||
</PreviewTile>
|
|
||||||
{/if}
|
|
||||||
{#each stepOutputs as out, i (out.toolId + i)}
|
|
||||||
<PreviewTile
|
|
||||||
label={`STEP ${String(i + 1).padStart(2, "0")}`}
|
|
||||||
caption=""
|
|
||||||
>
|
|
||||||
<CheckerCanvas size="sm">
|
|
||||||
<img class="tile-img" src={out.url} alt={out.title} />
|
|
||||||
</CheckerCanvas>
|
|
||||||
</PreviewTile>
|
|
||||||
{/each}
|
|
||||||
</PreviewStack>
|
|
||||||
{#if processing}
|
|
||||||
<StatusLine label="processing pipeline" />
|
|
||||||
{/if}
|
|
||||||
<MetaList items={resultMeta} />
|
|
||||||
</div>
|
|
||||||
</Panel>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.preview-body {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 1rem;
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
.tile-img {
|
|
||||||
max-width: 100%;
|
|
||||||
max-height: 220px;
|
|
||||||
display: block;
|
|
||||||
border-radius: var(--radius);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Snippet } from "svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
pad?: string;
|
|
||||||
children: Snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { pad = "0", children }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="preview-stack" style="padding:{pad};">
|
|
||||||
{@render children()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.preview-stack {
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 12px;
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
@media (max-width: 1100px) {
|
|
||||||
.preview-stack {
|
|
||||||
grid-template-columns: repeat(3, minmax(220px, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Snippet } from "svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
label: string;
|
|
||||||
caption: string;
|
|
||||||
active?: boolean;
|
|
||||||
children: Snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { label, caption, active = false, children }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="preview-tile" class:active>
|
|
||||||
<div class="tile-canvas">{@render children()}</div>
|
|
||||||
<div class="tile-label">
|
|
||||||
<span>{label}</span>
|
|
||||||
<b>{caption}</b>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.preview-tile {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
background: var(--background);
|
|
||||||
&.active {
|
|
||||||
border-color: var(--blue);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.tile-canvas {
|
|
||||||
width: 100%;
|
|
||||||
min-width: 0;
|
|
||||||
min-height: 260px;
|
|
||||||
min-height: clamp(300px, 22vw, 430px);
|
|
||||||
overflow: hidden;
|
|
||||||
aspect-ratio: 1.35;
|
|
||||||
background-color: var(--checker-background);
|
|
||||||
background-image:
|
|
||||||
linear-gradient(45deg, var(--checker-background-cross) 25%, #0000 25%),
|
|
||||||
linear-gradient(-45deg, var(--checker-background-cross) 25%, #0000 25%),
|
|
||||||
linear-gradient(45deg, #0000 75%, var(--checker-background-cross) 75%),
|
|
||||||
linear-gradient(-45deg, #0000 75%, var(--checker-background-cross) 75%);
|
|
||||||
background-position:
|
|
||||||
0 0,
|
|
||||||
0 7px,
|
|
||||||
7px -7px,
|
|
||||||
-7px 0;
|
|
||||||
background-size: 14px 14px;
|
|
||||||
place-items: center;
|
|
||||||
padding: 16px;
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
.tile-label {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 8px;
|
|
||||||
padding: 8px;
|
|
||||||
border-top: 1px solid var(--line);
|
|
||||||
font: 9px var(--font-mono);
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
|
||||||
.tile-label span {
|
|
||||||
font: 9px var(--font-mono);
|
|
||||||
color: var(--blue);
|
|
||||||
}
|
|
||||||
.tile-label b {
|
|
||||||
font: 400 9px var(--font-mono);
|
|
||||||
color: var(--muted);
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Snippet } from "svelte";
|
|
||||||
import MonoLabel from "./MonoLabel.svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
label: string;
|
|
||||||
title: string;
|
|
||||||
meta?: string;
|
|
||||||
actions?: Snippet;
|
|
||||||
unwrapActions?: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { label, title, meta, actions, unwrapActions = false }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="section-label">
|
|
||||||
<div class="section-text">
|
|
||||||
<MonoLabel>{label}</MonoLabel>
|
|
||||||
<strong
|
|
||||||
>{title}{#if meta}<em>{meta}</em>{/if}</strong
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
{#if actions}
|
|
||||||
{#if unwrapActions}
|
|
||||||
{@render actions()}
|
|
||||||
{:else}
|
|
||||||
<div class="section-actions">
|
|
||||||
{@render actions()}
|
|
||||||
</div>
|
|
||||||
{/if}
|
|
||||||
{/if}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.section-label {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
border-bottom: 1px solid var(--line);
|
|
||||||
padding-bottom: 12px;
|
|
||||||
}
|
|
||||||
.section-text strong {
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
font: 600 14px var(--font-mono);
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
.section-text em {
|
|
||||||
color: #25a96a;
|
|
||||||
font-style: normal;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
.section-actions {
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: flex-end;
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import FileChip from "$lib/components/kit/FileChip.svelte";
|
|
||||||
|
|
||||||
interface FileInfo {
|
|
||||||
name: string;
|
|
||||||
size?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
eyebrowA: string;
|
|
||||||
eyebrowB?: string;
|
|
||||||
title: string;
|
|
||||||
lede?: string;
|
|
||||||
file?: FileInfo;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { eyebrowA, eyebrowB, title, lede, file }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<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>
|
|
||||||
.eyebrow {
|
|
||||||
font: 10px var(--font-mono);
|
|
||||||
letter-spacing: 0.12em;
|
|
||||||
color: var(--blue);
|
|
||||||
margin-bottom: 18px;
|
|
||||||
}
|
|
||||||
.eyebrow span {
|
|
||||||
margin: 0 7px;
|
|
||||||
color: var(--muted);
|
|
||||||
}
|
|
||||||
.title-row {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-end;
|
|
||||||
gap: 30px;
|
|
||||||
margin-bottom: 56px;
|
|
||||||
}
|
|
||||||
h1 {
|
|
||||||
margin: 0 0 16px;
|
|
||||||
font-size: clamp(36px, 4vw, 64px);
|
|
||||||
font-weight: 650;
|
|
||||||
line-height: 1;
|
|
||||||
letter-spacing: -0.06em;
|
|
||||||
color: var(--foreground);
|
|
||||||
max-width: 700px;
|
|
||||||
}
|
|
||||||
.lede {
|
|
||||||
margin: 0;
|
|
||||||
max-width: 550px;
|
|
||||||
color: var(--muted);
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
.title-row {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Snippet } from "svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
compact?: boolean;
|
|
||||||
children: Snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { compact = false, children }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="controls" class:compact>
|
|
||||||
{@render children()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.controls {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1.1fr 1fr 1.2fr;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
.controls.compact {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
.controls,
|
|
||||||
.controls.compact {
|
|
||||||
grid-auto-flow: row;
|
|
||||||
grid-auto-columns: auto;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Field from "./Field.svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
label: string;
|
|
||||||
value: number;
|
|
||||||
min?: number;
|
|
||||||
max?: number;
|
|
||||||
step?: number;
|
|
||||||
oninput?: (value: number) => void;
|
|
||||||
}
|
|
||||||
let { label, value = $bindable(), min, max, step, oninput }: Props = $props();
|
|
||||||
|
|
||||||
function handle(e: Event) {
|
|
||||||
value = Number((e.target as HTMLInputElement).value);
|
|
||||||
oninput?.(value);
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<Field {label}>
|
|
||||||
<input
|
|
||||||
class="number-field"
|
|
||||||
type="number"
|
|
||||||
{min}
|
|
||||||
{max}
|
|
||||||
{step}
|
|
||||||
{value}
|
|
||||||
oninput={handle}
|
|
||||||
/>
|
|
||||||
</Field>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.number-field {
|
|
||||||
width: 100%;
|
|
||||||
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.35rem 0.5rem;
|
|
||||||
}
|
|
||||||
.number-field:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--blue);
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import Toggle from "../ui/Toggle.svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
label: string;
|
|
||||||
checked: boolean;
|
|
||||||
onchange?: (checked: boolean) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { label, checked = $bindable(), onchange }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="toggle-row">
|
|
||||||
<span>{label}</span>
|
|
||||||
<Toggle bind:checked {onchange} {label} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.toggle-row {
|
|
||||||
color: var(--foreground);
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import type { Snippet } from "svelte";
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
settings: Snippet;
|
|
||||||
preview: Snippet;
|
|
||||||
}
|
|
||||||
|
|
||||||
let { settings, preview }: Props = $props();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<div class="page-grid">
|
|
||||||
<section class="ws-settings">{@render settings()}</section>
|
|
||||||
{@render preview()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.page-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(calc(50% - 28px), 1fr));
|
|
||||||
align-items: start;
|
|
||||||
gap: 56px;
|
|
||||||
width: 100%;
|
|
||||||
max-width: none;
|
|
||||||
margin: auto;
|
|
||||||
padding: 60px clamp(24px, 4vw, 72px) 72px;
|
|
||||||
}
|
|
||||||
.ws-settings {
|
|
||||||
min-width: 0;
|
|
||||||
}
|
|
||||||
@media (max-width: 800px) {
|
|
||||||
.page-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
gap: var(--ws-gap-mobile);
|
|
||||||
padding: 36px 16px 48px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -3,7 +3,6 @@
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<nav class="nav">
|
<nav class="nav">
|
||||||
<a href={resolve("/preview/demo")}>Workspace</a>
|
|
||||||
<a href={resolve("/preview/list-tools")}>Catalog</a>
|
<a href={resolve("/preview/list-tools")}>Catalog</a>
|
||||||
<a href={resolve("/preview/tools/linear-gradient-png")}>Gradient</a>
|
<a href={resolve("/preview/tools/linear-gradient-png")}>Gradient</a>
|
||||||
<a href={resolve("/preview/tools/remove-background-png")}>
|
<a href={resolve("/preview/tools/remove-background-png")}>
|
||||||
|
|||||||
+11
-3
@@ -5,6 +5,10 @@
|
|||||||
@import url("@fontsource/ibm-plex-mono/400.css");
|
@import url("@fontsource/ibm-plex-mono/400.css");
|
||||||
@import url("@fontsource/ibm-plex-mono/500.css");
|
@import url("@fontsource/ibm-plex-mono/500.css");
|
||||||
|
|
||||||
|
@custom-media --bp-mobile (max-width: 640px);
|
||||||
|
@custom-media --bp-tablet (max-width: 800px);
|
||||||
|
@custom-media --bp-desktop (max-width: 1100px);
|
||||||
|
|
||||||
@layer app, design;
|
@layer app, design;
|
||||||
|
|
||||||
@layer design {
|
@layer design {
|
||||||
@@ -118,6 +122,7 @@
|
|||||||
--color-info: hct(340 80 48);
|
--color-info: hct(340 80 48);
|
||||||
|
|
||||||
--color-main-tint: hct(from var(--brand-main) h 65 55);
|
--color-main-tint: hct(from var(--brand-main) h 65 55);
|
||||||
|
--color-main-soft: color-mix(in srgb, var(--color-main) 12%, transparent);
|
||||||
--color-accent-tint: hct(from var(--brand-alt) h 65 55);
|
--color-accent-tint: hct(from var(--brand-alt) h 65 55);
|
||||||
--color-success-tint: hct(160 65 55);
|
--color-success-tint: hct(160 65 55);
|
||||||
--color-warning-tint: hct(80 65 55);
|
--color-warning-tint: hct(80 65 55);
|
||||||
@@ -141,6 +146,10 @@
|
|||||||
--size-border: 1px;
|
--size-border: 1px;
|
||||||
--size-border-thick: 2px;
|
--size-border-thick: 2px;
|
||||||
|
|
||||||
|
--size-preview-canvas-min-height-s: 180px;
|
||||||
|
--size-preview-canvas-min-height-l: 360px;
|
||||||
|
--size-panel-min-height: 220px;
|
||||||
|
|
||||||
--font-size-s: 12px;
|
--font-size-s: 12px;
|
||||||
--font-size-m: 14px;
|
--font-size-m: 14px;
|
||||||
--font-size-l: 16px;
|
--font-size-l: 16px;
|
||||||
@@ -150,9 +159,8 @@
|
|||||||
--radius-s: 0px;
|
--radius-s: 0px;
|
||||||
--radius-m: 4px;
|
--radius-m: 4px;
|
||||||
|
|
||||||
--bp-mobile: 640px;
|
--duration-s: 0.12s;
|
||||||
--bp-tablet: 800px;
|
--duration-m: 0.15s;
|
||||||
--bp-desktop: 1100px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[data-theme="dark"] {
|
[data-theme="dark"] {
|
||||||
|
|||||||
@@ -35,14 +35,12 @@
|
|||||||
let status = $derived(toStatus(page.url.pathname));
|
let status = $derived(toStatus(page.url.pathname));
|
||||||
|
|
||||||
const CRUMB: Record<string, string> = {
|
const CRUMB: Record<string, string> = {
|
||||||
"/preview/demo": "WORKSPACE",
|
|
||||||
"/preview/list-tools": "CATALOG",
|
"/preview/list-tools": "CATALOG",
|
||||||
"/preview/tools/linear-gradient-png": "GRADIENT",
|
"/preview/tools/linear-gradient-png": "GRADIENT",
|
||||||
"/preview/tools/remove-background-png": "BACKGROUND REMOVER",
|
"/preview/tools/remove-background-png": "BACKGROUND REMOVER",
|
||||||
};
|
};
|
||||||
|
|
||||||
const STATUS: Record<string, string> = {
|
const STATUS: Record<string, string> = {
|
||||||
"/preview/demo": "AUTO PIPELINE",
|
|
||||||
"/preview/list-tools": "LOCAL MODE / READY",
|
"/preview/list-tools": "LOCAL MODE / READY",
|
||||||
"/preview/tools/linear-gradient-png": "LIVE PREVIEW",
|
"/preview/tools/linear-gradient-png": "LIVE PREVIEW",
|
||||||
"/preview/tools/remove-background-png": "AUTO PROCESSING",
|
"/preview/tools/remove-background-png": "AUTO PROCESSING",
|
||||||
@@ -70,7 +68,7 @@
|
|||||||
|
|
||||||
<style>
|
<style>
|
||||||
main {
|
main {
|
||||||
background-color: var(--background);
|
background-color: var(--color-background);
|
||||||
background-image:
|
background-image:
|
||||||
linear-gradient(var(--background-muted) 1px, #0000 1px),
|
linear-gradient(var(--background-muted) 1px, #0000 1px),
|
||||||
linear-gradient(90deg, var(--background-muted) 1px, #0000 1px);
|
linear-gradient(90deg, var(--background-muted) 1px, #0000 1px);
|
||||||
|
|||||||
@@ -1,321 +0,0 @@
|
|||||||
<script lang="ts">
|
|
||||||
import ColorField from "$lib/components/kit/fields/ColorField.svelte";
|
|
||||||
import FieldGrid from "$lib/components/kit/fields/FieldGrid.svelte";
|
|
||||||
import SliderField from "$lib/components/kit/fields/SliderField.svelte";
|
|
||||||
import ToggleRow from "$lib/components/kit/fields/ToggleRow.svelte";
|
|
||||||
import PageGrid from "$lib/components/kit/layout/PageGrid.svelte";
|
|
||||||
import MetaList from "$lib/components/kit/MetaList.svelte";
|
|
||||||
import PipelineFooter from "$lib/components/kit/PipelineFooter.svelte";
|
|
||||||
import PreviewStack from "$lib/components/kit/PreviewStack.svelte";
|
|
||||||
import PreviewTile from "$lib/components/kit/PreviewTile.svelte";
|
|
||||||
import SectionLabel from "$lib/components/kit/SectionLabel.svelte";
|
|
||||||
import StepCard from "$lib/components/kit/StepCard.svelte";
|
|
||||||
import Badge from "$lib/components/kit/ui/Badge.svelte";
|
|
||||||
import DownloadButton from "$lib/components/kit/ui/DownloadButton.svelte";
|
|
||||||
import IconButton from "$lib/components/kit/ui/IconButton.svelte";
|
|
||||||
import Segmented from "$lib/components/kit/ui/Segmented.svelte";
|
|
||||||
import WorkspaceHeader from "$lib/components/kit/WorkspaceHeader.svelte";
|
|
||||||
import {
|
|
||||||
ChevronDown,
|
|
||||||
Plus,
|
|
||||||
Settings2,
|
|
||||||
SlidersHorizontal,
|
|
||||||
} from "@lucide/svelte";
|
|
||||||
|
|
||||||
let gradColor = $state("#DCEBFF");
|
|
||||||
let direction = $state(135);
|
|
||||||
let opacity = $state("100");
|
|
||||||
let outlineWidth = $state(2);
|
|
||||||
let outlineColor = $state("#16202B");
|
|
||||||
let radius = $state(18);
|
|
||||||
let preserveAspect = $state(false);
|
|
||||||
|
|
||||||
const previewTiles = [
|
|
||||||
{ label: "SOURCE", caption: "original.png · 1200 × 800", bg: "#8d9aa5" },
|
|
||||||
{
|
|
||||||
label: "STEP 01",
|
|
||||||
caption: "gradient applied",
|
|
||||||
bg: "linear-gradient(135deg, #DCEBFF, #8BC8F5)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "STEP 02",
|
|
||||||
caption: "background removed",
|
|
||||||
bg: "linear-gradient(135deg, #DCEBFF, #8BC8F5)",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "STEP 03",
|
|
||||||
caption: "outline added",
|
|
||||||
bg: "linear-gradient(135deg, #DCEBFF, #8BC8F5)",
|
|
||||||
ring: "0 0 0 2px #16202B",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "FINAL OUTPUT",
|
|
||||||
caption: "ready · PNG-24",
|
|
||||||
bg: "linear-gradient(135deg, #DCEBFF, #8BC8F5)",
|
|
||||||
radius: "18px",
|
|
||||||
ring: "0 0 0 2px #16202B",
|
|
||||||
active: true,
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<svelte:head>
|
|
||||||
<title>easy-png-tools / Demo</title>
|
|
||||||
<meta name="robots" content="noindex, nofollow" />
|
|
||||||
</svelte:head>
|
|
||||||
|
|
||||||
<PageGrid>
|
|
||||||
{#snippet settings()}
|
|
||||||
<WorkspaceHeader
|
|
||||||
eyebrowA="PNG PROCESSING"
|
|
||||||
eyebrowB="WORKSPACE"
|
|
||||||
title="Build your image pipeline."
|
|
||||||
lede="Chain simple tools together. Every change is processed automatically and previewed at each stage."
|
|
||||||
file={{ name: "source.png", size: "1.8 MB" }}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<SectionLabel
|
|
||||||
label="PROCESSING PIPELINE"
|
|
||||||
title="4 active steps"
|
|
||||||
meta="• LIVE"
|
|
||||||
unwrapActions
|
|
||||||
>
|
|
||||||
{#snippet actions()}
|
|
||||||
<button class="add-btn" onclick={() => {}}
|
|
||||||
><Plus size={15} /> Add tool</button
|
|
||||||
>
|
|
||||||
{/snippet}
|
|
||||||
</SectionLabel>
|
|
||||||
|
|
||||||
<div class="steps-list">
|
|
||||||
<StepCard
|
|
||||||
index={1}
|
|
||||||
type="BACKGROUND"
|
|
||||||
title="Gradient background"
|
|
||||||
onremove={() => {}}
|
|
||||||
>
|
|
||||||
{#snippet tools()}
|
|
||||||
<Badge tone="success" check label="AUTO" />
|
|
||||||
{/snippet}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<StepCard
|
|
||||||
index={2}
|
|
||||||
type="TRANSFORM"
|
|
||||||
title="Remove background"
|
|
||||||
onremove={() => {}}
|
|
||||||
>
|
|
||||||
{#snippet tools()}
|
|
||||||
<Badge tone="success" check label="AUTO" />
|
|
||||||
{/snippet}
|
|
||||||
<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" check label="AUTO" />
|
|
||||||
{/snippet}
|
|
||||||
<FieldGrid compact>
|
|
||||||
<SliderField
|
|
||||||
label="WIDTH"
|
|
||||||
bind:value={outlineWidth}
|
|
||||||
min={0}
|
|
||||||
max={8}
|
|
||||||
suffix=" px"
|
|
||||||
/>
|
|
||||||
<ColorField label="COLOR" dark bind:value={outlineColor} />
|
|
||||||
</FieldGrid>
|
|
||||||
</StepCard>
|
|
||||||
|
|
||||||
<StepCard
|
|
||||||
index={4}
|
|
||||||
type="STYLE"
|
|
||||||
title="Round corners"
|
|
||||||
onremove={() => {}}
|
|
||||||
>
|
|
||||||
{#snippet tools()}
|
|
||||||
<Badge tone="success" check label="AUTO" />
|
|
||||||
{/snippet}
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<PipelineFooter onreset={() => {}} />
|
|
||||||
{/snippet}
|
|
||||||
|
|
||||||
{#snippet preview()}
|
|
||||||
<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" onclick={() => {}} />
|
|
||||||
<MetaList
|
|
||||||
items={[
|
|
||||||
{ caption: "DIMENSIONS", value: "1200 × 800 px" },
|
|
||||||
{ caption: "FORMAT", value: "PNG-24" },
|
|
||||||
{ caption: "SIZE", value: "1.2 MB" },
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
<IconButton
|
|
||||||
icon={Settings2}
|
|
||||||
label="Preview settings"
|
|
||||||
onclick={() => {}}
|
|
||||||
/>
|
|
||||||
{/snippet}
|
|
||||||
</SectionLabel>
|
|
||||||
|
|
||||||
<PreviewStack pad="16px 0">
|
|
||||||
{#each previewTiles as tile (tile.label)}
|
|
||||||
<PreviewTile
|
|
||||||
label={tile.label}
|
|
||||||
caption={tile.caption}
|
|
||||||
active={tile.active || false}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
class="image-preview"
|
|
||||||
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>
|
|
||||||
</section>
|
|
||||||
{/snippet}
|
|
||||||
</PageGrid>
|
|
||||||
|
|
||||||
<style>
|
|
||||||
.add-btn {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 7px;
|
|
||||||
color: var(--blue);
|
|
||||||
background: none;
|
|
||||||
border: 0;
|
|
||||||
font-size: 12px;
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
.steps-list {
|
|
||||||
display: grid;
|
|
||||||
gap: 10px;
|
|
||||||
padding-top: 14px;
|
|
||||||
}
|
|
||||||
.transform-note {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
color: var(--muted);
|
|
||||||
font: 11px var(--font-mono);
|
|
||||||
}
|
|
||||||
.preview-panel {
|
|
||||||
min-width: 0;
|
|
||||||
margin-top: 0;
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
background: var(--panel);
|
|
||||||
align-self: start;
|
|
||||||
padding: 18px;
|
|
||||||
position: sticky;
|
|
||||||
top: 24px;
|
|
||||||
}
|
|
||||||
.history-toggle {
|
|
||||||
border: 1px solid var(--line);
|
|
||||||
color: var(--muted);
|
|
||||||
font: 10px var(--font-mono);
|
|
||||||
letter-spacing: 0.04em;
|
|
||||||
background: 0 0;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
display: flex;
|
|
||||||
width: auto;
|
|
||||||
margin: 0;
|
|
||||||
padding: 8px 10px;
|
|
||||||
& :global(svg) {
|
|
||||||
transform: rotate(180deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.history-toggle:hover {
|
|
||||||
color: var(--foreground);
|
|
||||||
background: var(--background);
|
|
||||||
}
|
|
||||||
.image-preview {
|
|
||||||
min-width: 0;
|
|
||||||
max-width: 100%;
|
|
||||||
overflow: hidden;
|
|
||||||
aspect-ratio: 1.5;
|
|
||||||
color: #16202b;
|
|
||||||
width: 80%;
|
|
||||||
font: 600 10px var(--font-mono);
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
gap: 5px;
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
.sample-icon {
|
|
||||||
border: 1px solid;
|
|
||||||
padding: 3px 5px;
|
|
||||||
font-size: 9px;
|
|
||||||
}
|
|
||||||
.preview-note {
|
|
||||||
margin: 12px 0 0;
|
|
||||||
color: var(--muted);
|
|
||||||
font-size: 11px;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
label {
|
|
||||||
color: var(--muted);
|
|
||||||
font: 10px var(--font-mono);
|
|
||||||
letter-spacing: 0.08em;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
display: flex;
|
|
||||||
& output {
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
import ImageCard from "$lib/components/kit/ImageCard.svelte";
|
import ImageCard from "$lib/components/kit/ImageCard.svelte";
|
||||||
import Panel from "$lib/components/kit/layout/Panel.svelte";
|
import Panel from "$lib/components/kit/layout/Panel.svelte";
|
||||||
import MetaList from "$lib/components/kit/MetaList.svelte";
|
import MetaList from "$lib/components/kit/MetaList.svelte";
|
||||||
import PreviewTile from "$lib/components/kit/PreviewTile.svelte";
|
|
||||||
import SettingGroup from "$lib/components/kit/SettingGroup.svelte";
|
import SettingGroup from "$lib/components/kit/SettingGroup.svelte";
|
||||||
import SettingsFooter from "$lib/components/kit/SettingsFooter.svelte";
|
import SettingsFooter from "$lib/components/kit/SettingsFooter.svelte";
|
||||||
import StatusLine from "$lib/components/kit/StatusLine.svelte";
|
import StatusLine from "$lib/components/kit/StatusLine.svelte";
|
||||||
@@ -24,8 +23,8 @@
|
|||||||
import Toggle from "$lib/components/kit/ui/Toggle.svelte";
|
import Toggle from "$lib/components/kit/ui/Toggle.svelte";
|
||||||
import { Download, ImageOff, Plus, Trash2 } from "@lucide/svelte";
|
import { Download, ImageOff, Plus, Trash2 } from "@lucide/svelte";
|
||||||
|
|
||||||
import { TOOLS } from "$lib/registry-new";
|
|
||||||
import { TOOL_ICONS } from "$lib/preview/tool-icons";
|
import { TOOL_ICONS } from "$lib/preview/tool-icons";
|
||||||
|
import { TOOLS } from "$lib/registry-new";
|
||||||
|
|
||||||
let mode = $state("preview");
|
let mode = $state("preview");
|
||||||
let radius = $state(8);
|
let radius = $state(8);
|
||||||
@@ -158,12 +157,6 @@
|
|||||||
<ImageCard label="result.png · 512×512">
|
<ImageCard label="result.png · 512×512">
|
||||||
<CheckerCanvas size="sm" />
|
<CheckerCanvas size="sm" />
|
||||||
</ImageCard>
|
</ImageCard>
|
||||||
<PreviewTile label="tile-01" caption="caption-01">
|
|
||||||
<CheckerCanvas size="sm" />
|
|
||||||
</PreviewTile>
|
|
||||||
<PreviewTile label="tile-02" caption="caption-01">
|
|
||||||
<CheckerCanvas size="sm" />
|
|
||||||
</PreviewTile>
|
|
||||||
</div>
|
</div>
|
||||||
<SettingsFooter>
|
<SettingsFooter>
|
||||||
<MetaList
|
<MetaList
|
||||||
|
|||||||
@@ -23,10 +23,10 @@ export default {
|
|||||||
// ^(brand-main|brand-alt)$ — brand exceptions
|
// ^(brand-main|brand-alt)$ — brand exceptions
|
||||||
// |^(color|space|size|text|radius|bp|font)- — prefixed
|
// |^(color|space|size|text|radius|bp|font)- — prefixed
|
||||||
"custom-property-pattern": [
|
"custom-property-pattern": [
|
||||||
"^(brand-main|brand-alt)$|^(color|space|size|text|radius|bp|font)-",
|
"^(brand-main|brand-alt)$|^(color|space|size|text|radius|bp|font|duration|ease|z)-",
|
||||||
{
|
{
|
||||||
message:
|
message:
|
||||||
'"%s" must be prefixed: --color-*, --space-*, --size-*, --text-*, --radius-*, --bp-*, --font-*; brand: only --brand-main/--brand-alt',
|
'"%s" must be prefixed: --color-*, --space-*, --size-*, --text-*, --radius-*, --bp-*, --font-*, --duration-*, --ease-*, --z-*; brand: only --brand-main/--brand-alt',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
// Rule: !important is banned in CSS files. Overriding a look must happen
|
// Rule: !important is banned in CSS files. Overriding a look must happen
|
||||||
|
|||||||
Reference in New Issue
Block a user