docs: update refs, update script

This commit is contained in:
2026-08-28 14:27:44 +05:00
parent 5093b50f5c
commit 10278981ba
17 changed files with 1352 additions and 1187 deletions
+9
View File
@@ -1 +1,10 @@
node_modules node_modules
# Reference source (legacy Next.js design in refs/): branding icons are not needed
refs/public/icon.svg
refs/public/icon-dark-32x32.png
refs/public/icon-light-32x32.png
refs/public/apple-icon.png
refs/public/placeholder-logo.svg
refs/public/placeholder-logo.png
refs/public/placeholder.svg
+2 -1
View File
@@ -7,7 +7,8 @@
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"dev": "pnpm --dir web dev", "dev": "pnpm --dir web dev",
"build": "pnpm --dir web build", "build": "pnpm --dir web build",
"format": "pnpm --dir web format" "format": "pnpm --dir web format",
"html": "pnpm --dir refs build && node scripts/extract-static.mjs refs/.next/server/app refs-html"
}, },
"keywords": [], "keywords": [],
"author": "Ku6epXBOCTuK", "author": "Ku6epXBOCTuK",
+79 -61
View File
@@ -7,11 +7,6 @@
@layer properties { @layer properties {
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) { @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) {
*,:before,:after,::backdrop { *,:before,:after,::backdrop {
--tw-rotate-x:initial;
--tw-rotate-y:initial;
--tw-rotate-z:initial;
--tw-skew-x:initial;
--tw-skew-y:initial;
--tw-border-style:solid; --tw-border-style:solid;
--tw-font-weight:initial; --tw-font-weight:initial;
--tw-shadow:0 0 #0000; --tw-shadow:0 0 #0000;
@@ -290,12 +285,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
} }
@layer components; @layer components;
@layer utilities { @layer utilities {
.static {
position:static
}
.inline {
display:inline
}
.inline-flex { .inline-flex {
display:inline-flex display:inline-flex
} }
@@ -330,9 +319,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
.shrink-0 { .shrink-0 {
flex-shrink:0 flex-shrink:0
} }
.transform {
transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)
}
.items-center { .items-center {
align-items:center align-items:center
} }
@@ -630,7 +616,7 @@ button {
--line:#33414c; --line:#33414c;
--muted:#91a0ac; --muted:#91a0ac;
--blue:#54a2ff; --blue:#54a2ff;
background-image:linear-gradient(#1b252d 1px,#0000 1px),linear-gradient(90deg,#1b252d 1px,#0000 1px) background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer { .topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer {
align-items:center; align-items:center;
@@ -643,6 +629,26 @@ button {
height:64px; height:64px;
padding:0 clamp(20px,4vw,64px) padding:0 clamp(20px,4vw,64px)
} }
.topbar>.brand {
color:var(--foreground);
text-decoration:none
}
.top-nav {
align-items:center;
gap:22px;
margin-left:auto;
margin-right:28px;
display:flex
}
.top-nav a {
color:var(--muted);
font:10px var(--font-mono);
white-space:nowrap;
text-decoration:none
}
.top-nav a:hover,.top-nav a.active {
color:var(--blue)
}
.brand { .brand {
font:600 14px var(--font-mono); font:600 14px var(--font-mono);
gap:10px gap:10px
@@ -1103,6 +1109,9 @@ h1 {
gap:32px gap:32px
} }
}
.dark-mode .app-shell {
background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.dark-mode .tile-canvas { .dark-mode .tile-canvas {
background-color:#202a31; background-color:#202a31;
@@ -1113,7 +1122,22 @@ h1 {
} }
@media (max-width:800px) { @media (max-width:800px) {
.topbar { .topbar {
padding:0 16px flex-wrap:wrap;
gap:12px;
height:auto;
min-height:64px;
padding:12px 16px
}
.top-nav {
order:3;
gap:12px;
width:100%;
margin:0;
padding-bottom:2px;
overflow:auto
}
.top-nav a {
font-size:9px
} }
.status { .status {
display:none display:none
@@ -1532,9 +1556,9 @@ h1 {
} }
.catalog-page { .catalog-page {
width:100%; width:100%;
max-width:1680px; max-width:none;
margin:0 auto; margin:0;
padding:64px clamp(24px,4vw,72px) 72px padding:60px clamp(24px,4vw,72px) 72px
} }
.catalog-head { .catalog-head {
border-bottom:1px solid var(--line); border-bottom:1px solid var(--line);
@@ -1803,26 +1827,6 @@ h1 {
gap:12px 18px gap:12px 18px
} }
}
@property --tw-rotate-x {
syntax:"*";
inherits:false
}
@property --tw-rotate-y {
syntax:"*";
inherits:false
}
@property --tw-rotate-z {
syntax:"*";
inherits:false
}
@property --tw-skew-x {
syntax:"*";
inherits:false
}
@property --tw-skew-y {
syntax:"*";
inherits:false
} }
@property --tw-border-style { @property --tw-border-style {
syntax:"*"; syntax:"*";
@@ -1984,43 +1988,57 @@ h1 {
<!--$--> <!--$-->
<!--/$--> <!--/$-->
</div> </div>
<main class="app-shell dark-mode"> <main class="app-shell">
<header class="topbar"> <header class="topbar">
<div class="brand"> <a class="brand" href="/">
<span class="brand-mark">EP <span class="brand-mark">EP
</span> </span>
<span>easy-png-tools <span>easy-png-tools
</span> </span>
<span class="version">/ BACKGROUND REMOVER <span class="version">/
<!-- -->BACKGROUND REMOVER
</span> </span>
</div> </a>
<nav class="top-nav" aria-label="Primary navigation">
<a class="" href="/">Workspace
</a>
<a class="" href="/easy-png-tools/list-tools">Catalog
</a>
<a class="" href="/easy-png-tools/gradient">Gradient
</a>
<a class="active" href="/easy-png-tools/background-remover">Background remover
</a>
</nav>
<div class="top-actions"> <div class="top-actions">
<span class="status"> <span class="status">
<i> <i>
</i> AUTO PROCESSING </i>
<!-- -->AUTO PROCESSING
</span> </span>
<button class="icon-btn" aria-label="Toggle theme"> <button class="icon-btn" aria-label="Help">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-question-mark" aria-hidden="true">
<circle cx="12" cy="12" r="4"> <circle cx="12" cy="12" r="10">
</circle> </circle>
<path d="M12 2v2"> <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3">
</path> </path>
<path d="M12 20v2"> <path d="M12 17h.01">
</path>
<path d="m4.93 4.93 1.41 1.41">
</path>
<path d="m17.66 17.66 1.41 1.41">
</path>
<path d="M2 12h2">
</path>
<path d="M20 12h2">
</path>
<path d="m6.34 17.66-1.41 1.41">
</path>
<path d="m19.07 4.93-1.41 1.41">
</path> </path>
</svg> </svg>
</button> </button>
<button class="icon-btn" aria-label="Toggle theme">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-moon" aria-hidden="true">
<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401">
</path>
</svg>
</button>
<div class="language">
<button class="active">RU
</button>
<span>/
</span>
<button>EN
</button>
</div>
</div> </div>
</header> </header>
<div class="tool-page"> <div class="tool-page">
+62 -44
View File
@@ -7,11 +7,6 @@
@layer properties { @layer properties {
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) { @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) {
*,:before,:after,::backdrop { *,:before,:after,::backdrop {
--tw-rotate-x:initial;
--tw-rotate-y:initial;
--tw-rotate-z:initial;
--tw-skew-x:initial;
--tw-skew-y:initial;
--tw-border-style:solid; --tw-border-style:solid;
--tw-font-weight:initial; --tw-font-weight:initial;
--tw-shadow:0 0 #0000; --tw-shadow:0 0 #0000;
@@ -290,12 +285,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
} }
@layer components; @layer components;
@layer utilities { @layer utilities {
.static {
position:static
}
.inline {
display:inline
}
.inline-flex { .inline-flex {
display:inline-flex display:inline-flex
} }
@@ -330,9 +319,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
.shrink-0 { .shrink-0 {
flex-shrink:0 flex-shrink:0
} }
.transform {
transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)
}
.items-center { .items-center {
align-items:center align-items:center
} }
@@ -630,7 +616,7 @@ button {
--line:#33414c; --line:#33414c;
--muted:#91a0ac; --muted:#91a0ac;
--blue:#54a2ff; --blue:#54a2ff;
background-image:linear-gradient(#1b252d 1px,#0000 1px),linear-gradient(90deg,#1b252d 1px,#0000 1px) background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer { .topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer {
align-items:center; align-items:center;
@@ -643,6 +629,26 @@ button {
height:64px; height:64px;
padding:0 clamp(20px,4vw,64px) padding:0 clamp(20px,4vw,64px)
} }
.topbar>.brand {
color:var(--foreground);
text-decoration:none
}
.top-nav {
align-items:center;
gap:22px;
margin-left:auto;
margin-right:28px;
display:flex
}
.top-nav a {
color:var(--muted);
font:10px var(--font-mono);
white-space:nowrap;
text-decoration:none
}
.top-nav a:hover,.top-nav a.active {
color:var(--blue)
}
.brand { .brand {
font:600 14px var(--font-mono); font:600 14px var(--font-mono);
gap:10px gap:10px
@@ -1103,6 +1109,9 @@ h1 {
gap:32px gap:32px
} }
}
.dark-mode .app-shell {
background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.dark-mode .tile-canvas { .dark-mode .tile-canvas {
background-color:#202a31; background-color:#202a31;
@@ -1113,7 +1122,22 @@ h1 {
} }
@media (max-width:800px) { @media (max-width:800px) {
.topbar { .topbar {
padding:0 16px flex-wrap:wrap;
gap:12px;
height:auto;
min-height:64px;
padding:12px 16px
}
.top-nav {
order:3;
gap:12px;
width:100%;
margin:0;
padding-bottom:2px;
overflow:auto
}
.top-nav a {
font-size:9px
} }
.status { .status {
display:none display:none
@@ -1532,9 +1556,9 @@ h1 {
} }
.catalog-page { .catalog-page {
width:100%; width:100%;
max-width:1680px; max-width:none;
margin:0 auto; margin:0;
padding:64px clamp(24px,4vw,72px) 72px padding:60px clamp(24px,4vw,72px) 72px
} }
.catalog-head { .catalog-head {
border-bottom:1px solid var(--line); border-bottom:1px solid var(--line);
@@ -1803,26 +1827,6 @@ h1 {
gap:12px 18px gap:12px 18px
} }
}
@property --tw-rotate-x {
syntax:"*";
inherits:false
}
@property --tw-rotate-y {
syntax:"*";
inherits:false
}
@property --tw-rotate-z {
syntax:"*";
inherits:false
}
@property --tw-skew-x {
syntax:"*";
inherits:false
}
@property --tw-skew-y {
syntax:"*";
inherits:false
} }
@property --tw-border-style { @property --tw-border-style {
syntax:"*"; syntax:"*";
@@ -1986,18 +1990,30 @@ h1 {
</div> </div>
<main class="app-shell"> <main class="app-shell">
<header class="topbar"> <header class="topbar">
<div class="brand"> <a class="brand" href="/">
<span class="brand-mark">EP <span class="brand-mark">EP
</span> </span>
<span>easy-png-tools <span>easy-png-tools
</span> </span>
<span class="version">/ DEMO <span class="version">/
<!-- -->WORKSPACE
</span> </span>
</div> </a>
<nav class="top-nav" aria-label="Primary navigation">
<a class="" href="/">Workspace
</a>
<a class="" href="/easy-png-tools/list-tools">Catalog
</a>
<a class="" href="/easy-png-tools/gradient">Gradient
</a>
<a class="" href="/easy-png-tools/background-remover">Background remover
</a>
</nav>
<div class="top-actions"> <div class="top-actions">
<span class="status"> <span class="status">
<i> <i>
</i> AUTO PIPELINE </i>
<!-- -->AUTO PIPELINE
</span> </span>
<button class="icon-btn" aria-label="Help"> <button class="icon-btn" aria-label="Help">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-question-mark" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-question-mark" aria-hidden="true">
@@ -2018,7 +2034,9 @@ h1 {
<div class="language"> <div class="language">
<button class="active">RU <button class="active">RU
</button> </button>
<button class="">EN <span>/
</span>
<button>EN
</button> </button>
</div> </div>
</div> </div>
+82 -60
View File
@@ -7,11 +7,6 @@
@layer properties { @layer properties {
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) { @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) {
*,:before,:after,::backdrop { *,:before,:after,::backdrop {
--tw-rotate-x:initial;
--tw-rotate-y:initial;
--tw-rotate-z:initial;
--tw-skew-x:initial;
--tw-skew-y:initial;
--tw-border-style:solid; --tw-border-style:solid;
--tw-font-weight:initial; --tw-font-weight:initial;
--tw-shadow:0 0 #0000; --tw-shadow:0 0 #0000;
@@ -290,12 +285,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
} }
@layer components; @layer components;
@layer utilities { @layer utilities {
.static {
position:static
}
.inline {
display:inline
}
.inline-flex { .inline-flex {
display:inline-flex display:inline-flex
} }
@@ -330,9 +319,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
.shrink-0 { .shrink-0 {
flex-shrink:0 flex-shrink:0
} }
.transform {
transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)
}
.items-center { .items-center {
align-items:center align-items:center
} }
@@ -630,7 +616,7 @@ button {
--line:#33414c; --line:#33414c;
--muted:#91a0ac; --muted:#91a0ac;
--blue:#54a2ff; --blue:#54a2ff;
background-image:linear-gradient(#1b252d 1px,#0000 1px),linear-gradient(90deg,#1b252d 1px,#0000 1px) background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer { .topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer {
align-items:center; align-items:center;
@@ -643,6 +629,26 @@ button {
height:64px; height:64px;
padding:0 clamp(20px,4vw,64px) padding:0 clamp(20px,4vw,64px)
} }
.topbar>.brand {
color:var(--foreground);
text-decoration:none
}
.top-nav {
align-items:center;
gap:22px;
margin-left:auto;
margin-right:28px;
display:flex
}
.top-nav a {
color:var(--muted);
font:10px var(--font-mono);
white-space:nowrap;
text-decoration:none
}
.top-nav a:hover,.top-nav a.active {
color:var(--blue)
}
.brand { .brand {
font:600 14px var(--font-mono); font:600 14px var(--font-mono);
gap:10px gap:10px
@@ -1103,6 +1109,9 @@ h1 {
gap:32px gap:32px
} }
}
.dark-mode .app-shell {
background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.dark-mode .tile-canvas { .dark-mode .tile-canvas {
background-color:#202a31; background-color:#202a31;
@@ -1113,7 +1122,22 @@ h1 {
} }
@media (max-width:800px) { @media (max-width:800px) {
.topbar { .topbar {
padding:0 16px flex-wrap:wrap;
gap:12px;
height:auto;
min-height:64px;
padding:12px 16px
}
.top-nav {
order:3;
gap:12px;
width:100%;
margin:0;
padding-bottom:2px;
overflow:auto
}
.top-nav a {
font-size:9px
} }
.status { .status {
display:none display:none
@@ -1532,9 +1556,9 @@ h1 {
} }
.catalog-page { .catalog-page {
width:100%; width:100%;
max-width:1680px; max-width:none;
margin:0 auto; margin:0;
padding:64px clamp(24px,4vw,72px) 72px padding:60px clamp(24px,4vw,72px) 72px
} }
.catalog-head { .catalog-head {
border-bottom:1px solid var(--line); border-bottom:1px solid var(--line);
@@ -1803,26 +1827,6 @@ h1 {
gap:12px 18px gap:12px 18px
} }
}
@property --tw-rotate-x {
syntax:"*";
inherits:false
}
@property --tw-rotate-y {
syntax:"*";
inherits:false
}
@property --tw-rotate-z {
syntax:"*";
inherits:false
}
@property --tw-skew-x {
syntax:"*";
inherits:false
}
@property --tw-skew-y {
syntax:"*";
inherits:false
} }
@property --tw-border-style { @property --tw-border-style {
syntax:"*"; syntax:"*";
@@ -1984,39 +1988,57 @@ h1 {
<!--$--> <!--$-->
<!--/$--> <!--/$-->
</div> </div>
<main class="app-shell dark-mode"> <main class="app-shell">
<header class="topbar"> <header class="topbar">
<div class="brand"> <a class="brand" href="/">
<span class="brand-mark">EP <span class="brand-mark">EP
</span> </span>
<span>easy-png-tools <span>easy-png-tools
</span> </span>
<span class="version">/ GRADIENT <span class="version">/
<!-- -->GRADIENT
</span> </span>
</div> </a>
<nav class="top-nav" aria-label="Primary navigation">
<a class="" href="/">Workspace
</a>
<a class="" href="/easy-png-tools/list-tools">Catalog
</a>
<a class="active" href="/easy-png-tools/gradient">Gradient
</a>
<a class="" href="/easy-png-tools/background-remover">Background remover
</a>
</nav>
<div class="top-actions"> <div class="top-actions">
<button class="icon-btn" aria-label="Toggle theme"> <span class="status">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-sun" aria-hidden="true"> <i>
<circle cx="12" cy="12" r="4"> </i>
<!-- -->LIVE PREVIEW
</span>
<button class="icon-btn" aria-label="Help">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-question-mark" aria-hidden="true">
<circle cx="12" cy="12" r="10">
</circle> </circle>
<path d="M12 2v2"> <path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3">
</path> </path>
<path d="M12 20v2"> <path d="M12 17h.01">
</path>
<path d="m4.93 4.93 1.41 1.41">
</path>
<path d="m17.66 17.66 1.41 1.41">
</path>
<path d="M2 12h2">
</path>
<path d="M20 12h2">
</path>
<path d="m6.34 17.66-1.41 1.41">
</path>
<path d="m19.07 4.93-1.41 1.41">
</path> </path>
</svg> </svg>
</button> </button>
<button class="icon-btn" aria-label="Toggle theme">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-moon" aria-hidden="true">
<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401">
</path>
</svg>
</button>
<div class="language">
<button class="active">RU
</button>
<span>/
</span>
<button>EN
</button>
</div>
</div> </div>
</header> </header>
<div class="tool-page"> <div class="tool-page">
+62 -44
View File
@@ -7,11 +7,6 @@
@layer properties { @layer properties {
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) { @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) {
*,:before,:after,::backdrop { *,:before,:after,::backdrop {
--tw-rotate-x:initial;
--tw-rotate-y:initial;
--tw-rotate-z:initial;
--tw-skew-x:initial;
--tw-skew-y:initial;
--tw-border-style:solid; --tw-border-style:solid;
--tw-font-weight:initial; --tw-font-weight:initial;
--tw-shadow:0 0 #0000; --tw-shadow:0 0 #0000;
@@ -290,12 +285,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
} }
@layer components; @layer components;
@layer utilities { @layer utilities {
.static {
position:static
}
.inline {
display:inline
}
.inline-flex { .inline-flex {
display:inline-flex display:inline-flex
} }
@@ -330,9 +319,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
.shrink-0 { .shrink-0 {
flex-shrink:0 flex-shrink:0
} }
.transform {
transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)
}
.items-center { .items-center {
align-items:center align-items:center
} }
@@ -630,7 +616,7 @@ button {
--line:#33414c; --line:#33414c;
--muted:#91a0ac; --muted:#91a0ac;
--blue:#54a2ff; --blue:#54a2ff;
background-image:linear-gradient(#1b252d 1px,#0000 1px),linear-gradient(90deg,#1b252d 1px,#0000 1px) background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer { .topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer {
align-items:center; align-items:center;
@@ -643,6 +629,26 @@ button {
height:64px; height:64px;
padding:0 clamp(20px,4vw,64px) padding:0 clamp(20px,4vw,64px)
} }
.topbar>.brand {
color:var(--foreground);
text-decoration:none
}
.top-nav {
align-items:center;
gap:22px;
margin-left:auto;
margin-right:28px;
display:flex
}
.top-nav a {
color:var(--muted);
font:10px var(--font-mono);
white-space:nowrap;
text-decoration:none
}
.top-nav a:hover,.top-nav a.active {
color:var(--blue)
}
.brand { .brand {
font:600 14px var(--font-mono); font:600 14px var(--font-mono);
gap:10px gap:10px
@@ -1103,6 +1109,9 @@ h1 {
gap:32px gap:32px
} }
}
.dark-mode .app-shell {
background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.dark-mode .tile-canvas { .dark-mode .tile-canvas {
background-color:#202a31; background-color:#202a31;
@@ -1113,7 +1122,22 @@ h1 {
} }
@media (max-width:800px) { @media (max-width:800px) {
.topbar { .topbar {
padding:0 16px flex-wrap:wrap;
gap:12px;
height:auto;
min-height:64px;
padding:12px 16px
}
.top-nav {
order:3;
gap:12px;
width:100%;
margin:0;
padding-bottom:2px;
overflow:auto
}
.top-nav a {
font-size:9px
} }
.status { .status {
display:none display:none
@@ -1532,9 +1556,9 @@ h1 {
} }
.catalog-page { .catalog-page {
width:100%; width:100%;
max-width:1680px; max-width:none;
margin:0 auto; margin:0;
padding:64px clamp(24px,4vw,72px) 72px padding:60px clamp(24px,4vw,72px) 72px
} }
.catalog-head { .catalog-head {
border-bottom:1px solid var(--line); border-bottom:1px solid var(--line);
@@ -1803,26 +1827,6 @@ h1 {
gap:12px 18px gap:12px 18px
} }
}
@property --tw-rotate-x {
syntax:"*";
inherits:false
}
@property --tw-rotate-y {
syntax:"*";
inherits:false
}
@property --tw-rotate-z {
syntax:"*";
inherits:false
}
@property --tw-skew-x {
syntax:"*";
inherits:false
}
@property --tw-skew-y {
syntax:"*";
inherits:false
} }
@property --tw-border-style { @property --tw-border-style {
syntax:"*"; syntax:"*";
@@ -1986,18 +1990,30 @@ h1 {
</div> </div>
<main class="app-shell"> <main class="app-shell">
<header class="topbar"> <header class="topbar">
<div class="brand"> <a class="brand" href="/">
<span class="brand-mark">EP <span class="brand-mark">EP
</span> </span>
<span>easy-png-tools <span>easy-png-tools
</span> </span>
<span class="version">/ DEMO <span class="version">/
<!-- -->WORKSPACE
</span> </span>
</div> </a>
<nav class="top-nav" aria-label="Primary navigation">
<a class="active" href="/">Workspace
</a>
<a class="" href="/easy-png-tools/list-tools">Catalog
</a>
<a class="" href="/easy-png-tools/gradient">Gradient
</a>
<a class="" href="/easy-png-tools/background-remover">Background remover
</a>
</nav>
<div class="top-actions"> <div class="top-actions">
<span class="status"> <span class="status">
<i> <i>
</i> AUTO PIPELINE </i>
<!-- -->AUTO PIPELINE
</span> </span>
<button class="icon-btn" aria-label="Help"> <button class="icon-btn" aria-label="Help">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-question-mark" aria-hidden="true"> <svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-question-mark" aria-hidden="true">
@@ -2018,7 +2034,9 @@ h1 {
<div class="language"> <div class="language">
<button class="active">RU <button class="active">RU
</button> </button>
<button class="">EN <span>/
</span>
<button>EN
</button> </button>
</div> </div>
</div> </div>
+92 -46
View File
@@ -7,11 +7,6 @@
@layer properties { @layer properties {
@supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) { @supports (((-webkit-hyphens:none)) and (not (margin-trim:inline))) or ((-moz-orient:inline) and (not (color:rgb(from red r g b)))) {
*,:before,:after,::backdrop { *,:before,:after,::backdrop {
--tw-rotate-x:initial;
--tw-rotate-y:initial;
--tw-rotate-z:initial;
--tw-skew-x:initial;
--tw-skew-y:initial;
--tw-border-style:solid; --tw-border-style:solid;
--tw-font-weight:initial; --tw-font-weight:initial;
--tw-shadow:0 0 #0000; --tw-shadow:0 0 #0000;
@@ -290,12 +285,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
} }
@layer components; @layer components;
@layer utilities { @layer utilities {
.static {
position:static
}
.inline {
display:inline
}
.inline-flex { .inline-flex {
display:inline-flex display:inline-flex
} }
@@ -330,9 +319,6 @@ button,input:where([type=button],[type=reset],[type=submit]) {
.shrink-0 { .shrink-0 {
flex-shrink:0 flex-shrink:0
} }
.transform {
transform:var(--tw-rotate-x,) var(--tw-rotate-y,) var(--tw-rotate-z,) var(--tw-skew-x,) var(--tw-skew-y,)
}
.items-center { .items-center {
align-items:center align-items:center
} }
@@ -630,7 +616,7 @@ button {
--line:#33414c; --line:#33414c;
--muted:#91a0ac; --muted:#91a0ac;
--blue:#54a2ff; --blue:#54a2ff;
background-image:linear-gradient(#1b252d 1px,#0000 1px),linear-gradient(90deg,#1b252d 1px,#0000 1px) background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer { .topbar,.brand,.top-actions,.language,.status,.title-row,.pipeline-head,.step-heading,.step-tools,.pipeline-footer,.preview-top,.footer {
align-items:center; align-items:center;
@@ -643,6 +629,26 @@ button {
height:64px; height:64px;
padding:0 clamp(20px,4vw,64px) padding:0 clamp(20px,4vw,64px)
} }
.topbar>.brand {
color:var(--foreground);
text-decoration:none
}
.top-nav {
align-items:center;
gap:22px;
margin-left:auto;
margin-right:28px;
display:flex
}
.top-nav a {
color:var(--muted);
font:10px var(--font-mono);
white-space:nowrap;
text-decoration:none
}
.top-nav a:hover,.top-nav a.active {
color:var(--blue)
}
.brand { .brand {
font:600 14px var(--font-mono); font:600 14px var(--font-mono);
gap:10px gap:10px
@@ -1103,6 +1109,9 @@ h1 {
gap:32px gap:32px
} }
}
.dark-mode .app-shell {
background-image:linear-gradient(#1e2a3447 1px,#0000 1px),linear-gradient(90deg,#1e2a3447 1px,#0000 1px)
} }
.dark-mode .tile-canvas { .dark-mode .tile-canvas {
background-color:#202a31; background-color:#202a31;
@@ -1113,7 +1122,22 @@ h1 {
} }
@media (max-width:800px) { @media (max-width:800px) {
.topbar { .topbar {
padding:0 16px flex-wrap:wrap;
gap:12px;
height:auto;
min-height:64px;
padding:12px 16px
}
.top-nav {
order:3;
gap:12px;
width:100%;
margin:0;
padding-bottom:2px;
overflow:auto
}
.top-nav a {
font-size:9px
} }
.status { .status {
display:none display:none
@@ -1532,9 +1556,9 @@ h1 {
} }
.catalog-page { .catalog-page {
width:100%; width:100%;
max-width:1680px; max-width:none;
margin:0 auto; margin:0;
padding:64px clamp(24px,4vw,72px) 72px padding:60px clamp(24px,4vw,72px) 72px
} }
.catalog-head { .catalog-head {
border-bottom:1px solid var(--line); border-bottom:1px solid var(--line);
@@ -1803,26 +1827,6 @@ h1 {
gap:12px 18px gap:12px 18px
} }
}
@property --tw-rotate-x {
syntax:"*";
inherits:false
}
@property --tw-rotate-y {
syntax:"*";
inherits:false
}
@property --tw-rotate-z {
syntax:"*";
inherits:false
}
@property --tw-skew-x {
syntax:"*";
inherits:false
}
@property --tw-skew-y {
syntax:"*";
inherits:false
} }
@property --tw-border-style { @property --tw-border-style {
syntax:"*"; syntax:"*";
@@ -1984,19 +1988,60 @@ h1 {
<!--$--> <!--$-->
<!--/$--> <!--/$-->
</div> </div>
<main class="catalog-page"> <main class="app-shell">
<header class="catalog-nav"> <header class="topbar">
<a class="catalog-brand" href="/">easy-png-tools <a class="brand" href="/">
<span class="brand-mark">EP
</span>
<span>easy-png-tools
</span>
<span class="version">/
<!-- -->CATALOG
</span>
</a> </a>
<nav> <nav class="top-nav" aria-label="Primary navigation">
<a href="/">Workspace <a class="" href="/">Workspace
</a> </a>
<a class="current" href="/easy-png-tools/list-tools">Catalog <a class="active" href="/easy-png-tools/list-tools">Catalog
</a>
<a class="" href="/easy-png-tools/gradient">Gradient
</a>
<a class="" href="/easy-png-tools/background-remover">Background remover
</a> </a>
</nav> </nav>
<span class="catalog-nav-status">LOCAL MODE / READY <div class="top-actions">
<span class="status">
<i>
</i>
<!-- -->LOCAL MODE / READY
</span> </span>
<button class="icon-btn" aria-label="Help">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-circle-question-mark" aria-hidden="true">
<circle cx="12" cy="12" r="10">
</circle>
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3">
</path>
<path d="M12 17h.01">
</path>
</svg>
</button>
<button class="icon-btn" aria-label="Toggle theme">
<svg xmlns="http://www.w3.org/2000/svg" width="17" height="17" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-moon" aria-hidden="true">
<path d="M20.985 12.486a9 9 0 1 1-9.473-9.472c.405-.022.617.46.402.803a6 6 0 0 0 8.268 8.268c.344-.215.825-.004.803.401">
</path>
</svg>
</button>
<div class="language">
<button class="active">RU
</button>
<span>/
</span>
<button>EN
</button>
</div>
</div>
</header> </header>
<div class="catalog-page">
<div class="catalog-head"> <div class="catalog-head">
<div> <div>
<div class="eyebrow">EASY-PNG-TOOLS / CATALOG <div class="eyebrow">EASY-PNG-TOOLS / CATALOG
@@ -2901,6 +2946,7 @@ h1 {
<span> <span>
</span> YOUR FILES NEVER LEAVE THIS DEVICE </span> YOUR FILES NEVER LEAVE THIS DEVICE
</footer> </footer>
</div>
</main> </main>
<!--$--> <!--$-->
<!--/$--> <!--/$-->
@@ -1,20 +1,20 @@
'use client' 'use client'
import TopBar from '@/components/top-bar'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { Download, Moon, RotateCcw, Sun, Upload, Check } from 'lucide-react' import { Download, RotateCcw, Upload, Check } from 'lucide-react'
export default function BackgroundRemoverPage() { export default function BackgroundRemoverPage() {
const [color, setColor] = useState('#E8EEF2') const [color, setColor] = useState('#E8EEF2')
const [similarity, setSimilarity] = useState(72) const [similarity, setSimilarity] = useState(72)
const [outerOnly, setOuterOnly] = useState(true) const [outerOnly, setOuterOnly] = useState(true)
const [showMask, setShowMask] = useState(false) const [showMask, setShowMask] = useState(false)
const [dark, setDark] = useState(true)
const [downloaded, setDownloaded] = useState(false) const [downloaded, setDownloaded] = useState(false)
const previewBackground = showMask ? 'repeating-conic-gradient(#7b8791 0 25%, #cbd3da 0 50%) 50% / 28px 28px' : 'repeating-conic-gradient(#d7dce0 0 25%, #f3f5f6 0 50%) 50% / 28px 28px' const previewBackground = showMask ? 'repeating-conic-gradient(#7b8791 0 25%, #cbd3da 0 50%) 50% / 28px 28px' : 'repeating-conic-gradient(#d7dce0 0 25%, #f3f5f6 0 50%) 50% / 28px 28px'
const subjectStyle = useMemo(() => ({ background: showMask ? '#596773' : 'linear-gradient(145deg,#1769d2 0 38%,#00a8c7 38% 66%,#bd7411 66%)', opacity: showMask ? .88 : 1, clipPath: `polygon(23% 11%, 73% 8%, 89% 30%, 79% 81%, 52% 93%, 17% 78%, 8% 39%)` }), [showMask]) const subjectStyle = useMemo(() => ({ background: showMask ? '#596773' : 'linear-gradient(145deg,#1769d2 0 38%,#00a8c7 38% 66%,#bd7411 66%)', opacity: showMask ? .88 : 1, clipPath: `polygon(23% 11%, 73% 8%, 89% 30%, 79% 81%, 52% 93%, 17% 78%, 8% 39%)` }), [showMask])
const reset = () => { setColor('#E8EEF2'); setSimilarity(72); setOuterOnly(true); setShowMask(false) } const reset = () => { setColor('#E8EEF2'); setSimilarity(72); setOuterOnly(true); setShowMask(false) }
return <main className={`app-shell ${dark ? 'dark-mode' : ''}`}> return <main className="app-shell">
<header className="topbar"><div className="brand"><span className="brand-mark">EP</span><span>easy-png-tools</span><span className="version">/ BACKGROUND REMOVER</span></div><div className="top-actions"><span className="status"><i /> AUTO PROCESSING</span><button className="icon-btn" aria-label="Toggle theme" onClick={() => setDark(!dark)}>{dark ? <Sun size={17} /> : <Moon size={17} />}</button></div></header> <TopBar section="BACKGROUND REMOVER" status="AUTO PROCESSING" />
<div className="tool-page"><div className="eyebrow">PNG PROCESSING <span>/</span> SINGLE TOOL</div><div className="tool-title"><div><h1>Remove background.</h1><p className="lede">Select a background color and tune the edge detection. Changes are processed automatically in your browser.</p></div><span className="tool-status"><i /> LIVE PREVIEW</span></div> <div className="tool-page"><div className="eyebrow">PNG PROCESSING <span>/</span> SINGLE TOOL</div><div className="tool-title"><div><h1>Remove background.</h1><p className="lede">Select a background color and tune the edge detection. Changes are processed automatically in your browser.</p></div><span className="tool-status"><i /> LIVE PREVIEW</span></div>
<div className="remover-layout"><section className="settings-panel"><div className="panel-heading"><div><span className="label">REMOVER SETTINGS</span><strong>Configure detection</strong></div><span className="step-type">TOOL 02</span></div> <div className="remover-layout"><section className="settings-panel"><div className="panel-heading"><div><span className="label">REMOVER SETTINGS</span><strong>Configure detection</strong></div><span className="step-type">TOOL 02</span></div>
<div className="setting-group"><label>BACKGROUND COLOR</label><div className="color-field"><span className="swatch" style={{ background: color }} /><input value={color} onChange={e => setColor(e.target.value)} /><input className="native-color" type="color" value={color} onChange={e => setColor(e.target.value)} aria-label="Choose background color" /></div><div className="color-reference"><span style={{ background: color }} /> sampled from image background</div></div> <div className="setting-group"><label>BACKGROUND COLOR</label><div className="color-field"><span className="swatch" style={{ background: color }} /><input value={color} onChange={e => setColor(e.target.value)} /><input className="native-color" type="color" value={color} onChange={e => setColor(e.target.value)} aria-label="Choose background color" /></div><div className="color-reference"><span style={{ background: color }} /> sampled from image background</div></div>
+4 -4
View File
@@ -1,7 +1,8 @@
'use client' 'use client'
import TopBar from '@/components/top-bar'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { Check, ChevronDown, Copy, Download, Moon, RotateCcw, Sun } from 'lucide-react' import { Check, ChevronDown, Copy, Download, RotateCcw } from 'lucide-react'
const directions = ['0° →', '45° ↗', '90° ↑', '135° ↖', '180° ←', '225° ↙', '270° ↓', '315° ↘'] const directions = ['0° →', '45° ↗', '90° ↑', '135° ↖', '180° ←', '225° ↙', '270° ↓', '315° ↘']
@@ -11,14 +12,13 @@ export default function GradientPage() {
const [direction, setDirection] = useState(135) const [direction, setDirection] = useState(135)
const [opacity, setOpacity] = useState(100) const [opacity, setOpacity] = useState(100)
const [type, setType] = useState<'linear' | 'radial'>('linear') const [type, setType] = useState<'linear' | 'radial'>('linear')
const [dark, setDark] = useState(true)
const [copied, setCopied] = useState(false) const [copied, setCopied] = useState(false)
const gradient = useMemo(() => type === 'linear' ? `linear-gradient(${direction}deg, ${start} 0%, ${end} 100%)` : `radial-gradient(circle, ${start} 0%, ${end} 100%)`, [direction, end, start, type]) const gradient = useMemo(() => type === 'linear' ? `linear-gradient(${direction}deg, ${start} 0%, ${end} 100%)` : `radial-gradient(circle, ${start} 0%, ${end} 100%)`, [direction, end, start, type])
const css = `background: ${gradient};\nopacity: ${opacity / 100};` const css = `background: ${gradient};\nopacity: ${opacity / 100};`
const reset = () => { setStart('#1769D2'); setEnd('#00A8C7'); setDirection(135); setOpacity(100); setType('linear') } const reset = () => { setStart('#1769D2'); setEnd('#00A8C7'); setDirection(135); setOpacity(100); setType('linear') }
const copyCss = async () => { await navigator.clipboard?.writeText(css); setCopied(true); setTimeout(() => setCopied(false), 1400) } const copyCss = async () => { await navigator.clipboard?.writeText(css); setCopied(true); setTimeout(() => setCopied(false), 1400) }
return <main className={`app-shell ${dark ? 'dark-mode' : ''}`}> return <main className="app-shell">
<header className="topbar"><div className="brand"><span className="brand-mark">EP</span><span>easy-png-tools</span><span className="version">/ GRADIENT</span></div><div className="top-actions"><button className="icon-btn" aria-label="Toggle theme" onClick={() => setDark(!dark)}>{dark ? <Sun size={17} /> : <Moon size={17} />}</button></div></header> <TopBar section="GRADIENT" status="LIVE PREVIEW" />
<div className="tool-page"><div className="eyebrow">PNG PROCESSING <span>/</span> SINGLE TOOL</div><div className="tool-title"><div><h1>Gradient background.</h1><p className="lede">Create a clean, export-ready gradient with precise control over color, direction and transparency.</p></div><span className="tool-status"><i /> LIVE PREVIEW</span></div> <div className="tool-page"><div className="eyebrow">PNG PROCESSING <span>/</span> SINGLE TOOL</div><div className="tool-title"><div><h1>Gradient background.</h1><p className="lede">Create a clean, export-ready gradient with precise control over color, direction and transparency.</p></div><span className="tool-status"><i /> LIVE PREVIEW</span></div>
<div className="gradient-layout"><section className="settings-panel"><div className="panel-heading"><div><span className="label">GRADIENT SETTINGS</span><strong>Configure output</strong></div><span className="step-type">TOOL 01</span></div> <div className="gradient-layout"><section className="settings-panel"><div className="panel-heading"><div><span className="label">GRADIENT SETTINGS</span><strong>Configure output</strong></div><span className="step-type">TOOL 01</span></div>
<div className="setting-group"><label>GRADIENT TYPE</label><div className="segmented wide"><button className={type === 'linear' ? 'selected' : ''} onClick={() => setType('linear')}>Linear</button><button className={type === 'radial' ? 'selected' : ''} onClick={() => setType('radial')}>Radial</button></div></div> <div className="setting-group"><label>GRADIENT TYPE</label><div className="segmented wide"><button className={type === 'linear' ? 'selected' : ''} onClick={() => setType('linear')}>Linear</button><button className={type === 'radial' ? 'selected' : ''} onClick={() => setType('radial')}>Radial</button></div></div>
+3 -2
View File
@@ -1,5 +1,6 @@
"use client" "use client"
import TopBar from '@/components/top-bar'
import Link from "next/link" import Link from "next/link"
import { useMemo, useState } from "react" import { useMemo, useState } from "react"
import { ArrowUpRight, FileImage, Filter, Search, Sparkles } from "lucide-react" import { ArrowUpRight, FileImage, Filter, Search, Sparkles } from "lucide-react"
@@ -17,11 +18,11 @@ export default function ListToolsPage() {
const [query, setQuery] = useState("") const [query, setQuery] = useState("")
const [active, setActive] = useState("ALL") const [active, setActive] = useState("ALL")
const filtered = useMemo(() => groups.map(group => ({ ...group, tools: group.tools.filter(([name, description]) => (active === "ALL" || active === group.name) && `${name} ${description}`.toLowerCase().includes(query.toLowerCase())) })).filter(group => group.tools.length), [query, active]) const filtered = useMemo(() => groups.map(group => ({ ...group, tools: group.tools.filter(([name, description]) => (active === "ALL" || active === group.name) && `${name} ${description}`.toLowerCase().includes(query.toLowerCase())) })).filter(group => group.tools.length), [query, active])
return <main className="catalog-page"><header className="catalog-nav"><Link href="/" className="catalog-brand">easy-png-tools</Link><nav><Link href="/">Workspace</Link><Link className="current" href="/easy-png-tools/list-tools">Catalog</Link></nav><span className="catalog-nav-status">LOCAL MODE / READY</span></header> return <main className="app-shell"><TopBar section="CATALOG" status="LOCAL MODE / READY" /><div className="catalog-page">
<div className="catalog-head"><div><div className="eyebrow">EASY-PNG-TOOLS / CATALOG</div><h1>Tool catalog</h1><p>Focused utilities for working with PNG. Inspect, transform, and export locally in your browser.</p></div><div className="catalog-total"><b>32</b><span>TOOLS<br />AVAILABLE</span></div></div> <div className="catalog-head"><div><div className="eyebrow">EASY-PNG-TOOLS / CATALOG</div><h1>Tool catalog</h1><p>Focused utilities for working with PNG. Inspect, transform, and export locally in your browser.</p></div><div className="catalog-total"><b>32</b><span>TOOLS<br />AVAILABLE</span></div></div>
<div className="catalog-toolbar"><label className="catalog-search"><Search size={16} /><input aria-label="Search tools" placeholder="Search tools..." value={query} onChange={e => setQuery(e.target.value)} /></label><div className="catalog-filters"><Filter size={15} />{["ALL", ...groups.map(g => g.name)].map(name => <button key={name} className={active === name ? "active" : ""} onClick={() => setActive(name)}>{name}</button>)}</div></div> <div className="catalog-toolbar"><label className="catalog-search"><Search size={16} /><input aria-label="Search tools" placeholder="Search tools..." value={query} onChange={e => setQuery(e.target.value)} /></label><div className="catalog-filters"><Filter size={15} />{["ALL", ...groups.map(g => g.name)].map(name => <button key={name} className={active === name ? "active" : ""} onClick={() => setActive(name)}>{name}</button>)}</div></div>
<div className="catalog-groups">{filtered.map(group => <section className="catalog-group" key={group.name}><div className="group-title"><span>{group.name}</span><i>{String(group.tools.length).padStart(2, "0")} TOOLS</i></div><div className="tool-cards">{group.tools.map(([name, description, href], index) => <Link className="tool-card" href={href || "#"} key={name} onClick={e => { if (!href) e.preventDefault() }}><span className="tool-icon"><FileImage size={19} /></span><span className="tool-copy"><strong>{name}</strong><span>{description}</span></span><span className="tool-index">{String(index + 1).padStart(2, "0")}</span><ArrowUpRight size={16} className="tool-arrow" /></Link>)}</div></section>)}</div> <div className="catalog-groups">{filtered.map(group => <section className="catalog-group" key={group.name}><div className="group-title"><span>{group.name}</span><i>{String(group.tools.length).padStart(2, "0")} TOOLS</i></div><div className="tool-cards">{group.tools.map(([name, description, href], index) => <Link className="tool-card" href={href || "#"} key={name} onClick={e => { if (!href) e.preventDefault() }}><span className="tool-icon"><FileImage size={19} /></span><span className="tool-copy"><strong>{name}</strong><span>{description}</span></span><span className="tool-index">{String(index + 1).padStart(2, "0")}</span><ArrowUpRight size={16} className="tool-arrow" /></Link>)}</div></section>)}</div>
{!filtered.length && <div className="catalog-empty"><Sparkles size={18} /> No tools match your search.</div>} {!filtered.length && <div className="catalog-empty"><Sparkles size={18} /> No tools match your search.</div>}
<footer className="catalog-footer">ALL OPERATIONS RUN LOCALLY <span></span> YOUR FILES NEVER LEAVE THIS DEVICE</footer> <footer className="catalog-footer">ALL OPERATIONS RUN LOCALLY <span></span> YOUR FILES NEVER LEAVE THIS DEVICE</footer>
</main> </div></main>
} }
+5 -5
View File
File diff suppressed because one or more lines are too long
+4 -4
View File
@@ -1,7 +1,8 @@
'use client' 'use client'
import TopBar from '@/components/top-bar'
import { useMemo, useState } from 'react' import { useMemo, useState } from 'react'
import { ArrowDownToLine, Check, ChevronDown, CircleHelp, Download, GripVertical, Link2, Moon, MoreHorizontal, Plus, RotateCcw, Settings2, SlidersHorizontal, Sun, Upload, X } from 'lucide-react' import { ArrowDownToLine, Check, ChevronDown, Download, GripVertical, Link2, MoreHorizontal, Plus, RotateCcw, Settings2, SlidersHorizontal, Upload, X } from 'lucide-react'
const initialSteps = [ const initialSteps = [
{ id: 1, title: 'Gradient background', type: 'BACKGROUND' }, { id: 1, title: 'Gradient background', type: 'BACKGROUND' },
@@ -20,14 +21,13 @@ export default function Page() {
const [radius, setRadius] = useState(18) const [radius, setRadius] = useState(18)
const [outline, setOutline] = useState(2) const [outline, setOutline] = useState(2)
const [language, setLanguage] = useState('RU') const [language, setLanguage] = useState('RU')
const [dark, setDark] = useState(false)
const [showIntermediate, setShowIntermediate] = useState(true) const [showIntermediate, setShowIntermediate] = useState(true)
const [gradient, setGradient] = useState('#DCEBFF') const [gradient, setGradient] = useState('#DCEBFF')
const gradientStyle = useMemo(() => ({ background: `linear-gradient(${angle}deg, ${gradient}, #8BC8F5)` }), [angle, gradient]) const gradientStyle = useMemo(() => ({ background: `linear-gradient(${angle}deg, ${gradient}, #8BC8F5)` }), [angle, gradient])
const finalStyle = { ...gradientStyle, borderRadius: radius, boxShadow: `0 0 0 ${outline}px #16202B` } const finalStyle = { ...gradientStyle, borderRadius: radius, boxShadow: `0 0 0 ${outline}px #16202B` }
return <main className={dark ? 'app-shell dark-mode' : 'app-shell'}> return <main className="app-shell">
<header className="topbar"><div className="brand"><span className="brand-mark">EP</span><span>easy-png-tools</span><span className="version">/ DEMO</span></div><div className="top-actions"><span className="status"><i /> AUTO PIPELINE</span><button className="icon-btn" aria-label="Help"><CircleHelp size={17} /></button><button className="icon-btn" aria-label="Toggle theme" onClick={() => setDark(!dark)}>{dark ? <Sun size={17} /> : <Moon size={17} />}</button><div className="language"><button className={language === 'RU' ? 'active' : ''} onClick={() => setLanguage('RU')}>RU</button><button className={language === 'EN' ? 'active' : ''} onClick={() => setLanguage('EN')}>EN</button></div></div></header> <TopBar section="WORKSPACE" status="AUTO PIPELINE" />
<div className="page-grid"> <div className="page-grid">
<section className="workspace"><div className="eyebrow">PNG PROCESSING <span>/</span> WORKSPACE</div><div className="title-row"><div><h1>Build your image pipeline.</h1><p className="lede">Chain simple tools together. Every change is processed automatically and previewed at each stage.</p></div><div className="file-chip"><Upload size={15} /><span>source.png</span><b>1.8 MB</b></div></div> <section className="workspace"><div className="eyebrow">PNG PROCESSING <span>/</span> WORKSPACE</div><div className="title-row"><div><h1>Build your image pipeline.</h1><p className="lede">Chain simple tools together. Every change is processed automatically and previewed at each stage.</p></div><div className="file-chip"><Upload size={15} /><span>source.png</span><b>1.8 MB</b></div></div>
<div className="pipeline-head"><div><span className="label">PROCESSING PIPELINE</span><strong>{steps.length} active steps <em> LIVE</em></strong></div><button className="add-btn" onClick={() => setSteps([...steps, { id: Date.now(), title: 'New adjustment', type: 'STYLE' }])}><Plus size={15} /> Add tool</button></div> <div className="pipeline-head"><div><span className="label">PROCESSING PIPELINE</span><strong>{steps.length} active steps <em> LIVE</em></strong></div><button className="add-btn" onClick={() => setSteps([...steps, { id: Date.now(), title: 'New adjustment', type: 'STYLE' }])}><Plus size={15} /> Add tool</button></div>
+36
View File
@@ -0,0 +1,36 @@
"use client"
import Link from "next/link"
import { CircleHelp, Moon, Sun } from "lucide-react"
import { usePathname } from "next/navigation"
import { useEffect, useState } from "react"
const links = [
["Workspace", "/"],
["Catalog", "/easy-png-tools/list-tools"],
["Gradient", "/easy-png-tools/gradient"],
["Background remover", "/easy-png-tools/background-remover"],
] as const
export function TopBar({ section = "WORKSPACE", status = "LOCAL MODE / READY" }: { section?: string; status?: string }) {
const pathname = usePathname()
const [dark, setDark] = useState(false)
useEffect(() => {
const stored = window.localStorage.getItem("ep-theme")
const isDark = stored === "dark"
setDark(isDark)
document.documentElement.classList.toggle("dark-mode", isDark)
}, [])
const toggleTheme = () => {
const next = !dark
setDark(next)
document.documentElement.classList.toggle("dark-mode", next)
window.localStorage.setItem("ep-theme", next ? "dark" : "light")
}
return <header className="topbar"><Link href="/" className="brand"><span className="brand-mark">EP</span><span>easy-png-tools</span><span className="version">/ {section}</span></Link><nav className="top-nav" aria-label="Primary navigation">{links.map(([label, href]) => <Link key={href} className={pathname === href ? "active" : ""} href={href}>{label}</Link>)}</nav><div className="top-actions"><span className="status"><i /> {status}</span><button className="icon-btn" aria-label="Help"><CircleHelp size={17} /></button><button className="icon-btn" aria-label="Toggle theme" onClick={toggleTheme}>{dark ? <Sun size={17} /> : <Moon size={17} />}</button><div className="language"><button className="active">RU</button><span>/</span><button>EN</button></div></div></header>
}
export default TopBar
-1
View File
@@ -5,7 +5,6 @@
"scripts": { "scripts": {
"dev": "next dev", "dev": "next dev",
"build": "next build", "build": "next build",
"html": "node extract-static.mjs .next\\server\\app ..\\refs-html",
"start": "next start" "start": "next start"
}, },
"dependencies": { "dependencies": {
+1 -4
View File
@@ -4,9 +4,6 @@ settings:
autoInstallPeers: true autoInstallPeers: true
excludeLinksFromLockfile: false excludeLinksFromLockfile: false
overrides:
hono: 4.12.25
importers: importers:
.: .:
@@ -268,7 +265,7 @@ packages:
resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
engines: {node: '>=18.14.1'} engines: {node: '>=18.14.1'}
peerDependencies: peerDependencies:
hono: 4.12.25 hono: ^4
'@img/colour@1.1.0': '@img/colour@1.1.0':
resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==} resolution: {integrity: sha512-Td76q7j57o/tLVdgS746cYARfSyxk8iEfRxewL9h4OMzYhbW4TAcppl0mT4eyqXddh6L/jwoM75mo7ixa/pCeQ==}
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB