mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
refactor: set root layout, rename css, fix links in lint rules \ etc
This commit is contained in:
@@ -37,9 +37,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 the @custom-media name instead: declare `@custom-media --bp-* (...)` in preview.css and write `@media (--bp-*)`.",
|
"Hardcoded breakpoint '{{value}}' in @media. Use the @custom-media name instead: declare `@custom-media --bp-* (...)` in app.css and write `@media (--bp-*)`.",
|
||||||
varInMedia:
|
varInMedia:
|
||||||
"var() inside @media '{{value}}'. Custom properties do not resolve in media queries — declare `@custom-media --bp-* (...)` in preview.css and use `@media (--bp-*)`.",
|
"var() inside @media '{{value}}'. Custom properties do not resolve in media queries — declare `@custom-media --bp-* (...)` in app.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.",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
// A custom property may be DEFINED inside a component's <style> block, but its
|
// A custom property may be DEFINED inside a component's <style> block, but its
|
||||||
// value must not introduce a design primitive: a raw color literal (hex/rgb/
|
// value must not introduce a design primitive: a raw color literal (hex/rgb/
|
||||||
// oklch/named) or an absolute size (px/rem/em). This keeps the design surface
|
// oklch/named) or an absolute size (px/rem/em). This keeps the design surface
|
||||||
// (colors/sizes) a single source of truth in preview.css, while still allowing
|
// (colors/sizes) a single source of truth in app.css, while still allowing
|
||||||
// local DERIVED variables built from tokens: var(--...), calc(), unitless
|
// local DERIVED variables built from tokens: var(--...), calc(), unitless
|
||||||
// ratios (--ratio: 1.5) — those are legitimate component-local state.
|
// ratios (--ratio: 1.5) — those are legitimate component-local state.
|
||||||
|
|
||||||
@@ -24,7 +24,7 @@ export default {
|
|||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
tokenPrimitive:
|
tokenPrimitive:
|
||||||
"Custom property '{{prop}}' defines a primitive '{{value}}' in a component. Move it to preview.css or derive it from tokens via var()/calc().",
|
"Custom property '{{prop}}' defines a primitive '{{value}}' in a component. Move it to app.css or derive it from tokens via var()/calc().",
|
||||||
},
|
},
|
||||||
schema: [],
|
schema: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
// Rule: NO UNDEFINED CSS-TOKEN USAGE IN COMPONENTS.
|
// Rule: NO UNDEFINED CSS-TOKEN USAGE IN COMPONENTS.
|
||||||
// A var(--...) referenced in a Svelte <style> block must exist in the design
|
// A var(--...) referenced in a Svelte <style> block must exist in the design
|
||||||
// token file (src/preview.css) or be a local override defined in the same
|
// token file (src/app.css) or be a local override defined in the same
|
||||||
// component. Catches typos and drop-in tokens that were never added to the
|
// component. Catches typos and drop-in tokens that were never added to the
|
||||||
// "single source of truth".
|
// "single source of truth".
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ const VAR_REF_RE = /var\(\s*(--[\w-]+)/g;
|
|||||||
const definedCache = new Map();
|
const definedCache = new Map();
|
||||||
|
|
||||||
function getDefinedTokens(cwd) {
|
function getDefinedTokens(cwd) {
|
||||||
const file = resolve(cwd, "src/preview.css");
|
const file = resolve(cwd, "src/app.css");
|
||||||
if (!existsSync(file)) return null;
|
if (!existsSync(file)) return null;
|
||||||
if (definedCache.has(file)) return definedCache.get(file);
|
if (definedCache.has(file)) return definedCache.get(file);
|
||||||
|
|
||||||
@@ -34,13 +34,13 @@ export default {
|
|||||||
type: "problem",
|
type: "problem",
|
||||||
docs: {
|
docs: {
|
||||||
description:
|
description:
|
||||||
"Disallow referencing a CSS variable that is not defined in preview.css (design token file) and not defined locally in the component.",
|
"Disallow referencing a CSS variable that is not defined in app.css (design token file) and not defined locally in the component.",
|
||||||
category: "Design tokens",
|
category: "Design tokens",
|
||||||
recommended: true,
|
recommended: true,
|
||||||
},
|
},
|
||||||
messages: {
|
messages: {
|
||||||
undefinedToken:
|
undefinedToken:
|
||||||
"CSS variable '{{token}}' is not defined in preview.css and not locally in this component.",
|
"CSS variable '{{token}}' is not defined in app.css and not locally in this component.",
|
||||||
},
|
},
|
||||||
schema: [],
|
schema: [],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Goal: a single source of truth for design. In Svelte components, hardcoded
|
* Goal: a single source of truth for design. In Svelte components, hardcoded
|
||||||
* colors and sizes are banned; everything must come from CSS variables (tokens
|
* colors and sizes are banned; everything must come from CSS variables (tokens
|
||||||
* in preview.css). Rules inspect the postcss AST of Svelte <style> blocks
|
* in app.css). Rules inspect the postcss AST of Svelte <style> blocks
|
||||||
* exposed by svelte-eslint-parser.
|
* exposed by svelte-eslint-parser.
|
||||||
*/
|
*/
|
||||||
import noCategoryMismatch from "./design-tokens/no-category-mismatch.js";
|
import noCategoryMismatch from "./design-tokens/no-category-mismatch.js";
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import postcssCustomMedia from "postcss-custom-media";
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
plugins: [
|
plugins: [
|
||||||
// Breakpoints live only in preview.css (next to the --bp-* tokens);
|
// Breakpoints live only in app.css (next to the --bp-* tokens);
|
||||||
// global-data injects them so @media (--bp-*) expands in every file.
|
// global-data injects them so @media (--bp-*) expands in every file.
|
||||||
postcssGlobalData({
|
postcssGlobalData({
|
||||||
files: ["src/preview.css"],
|
files: ["src/app.css"],
|
||||||
}),
|
}),
|
||||||
postcssCustomMedia({
|
postcssCustomMedia({
|
||||||
preserve: false,
|
preserve: false,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
// Design-token audit, one command: theme parity, hct-only color authorship and
|
// Design-token audit, one command: theme parity, hct-only color authorship and
|
||||||
// unused-token warnings over src/preview.css.
|
// unused-token warnings over src/app.css.
|
||||||
//
|
//
|
||||||
// Usage: pnpm --dir web exec node scripts/check-tokens.mjs
|
// Usage: pnpm --dir web exec node scripts/check-tokens.mjs
|
||||||
// Exit code 1 when a failing check (parity or color authorship) reports.
|
// Exit code 1 when a failing check (parity or color authorship) reports.
|
||||||
@@ -36,10 +36,10 @@ async function main() {
|
|||||||
|
|
||||||
const unused = checkUnused(root);
|
const unused = checkUnused(root);
|
||||||
if (unused.length > 0) {
|
if (unused.length > 0) {
|
||||||
console.log("\nUnused tokens (defined in preview.css, never used):");
|
console.log("\nUnused tokens (defined in app.css, never used):");
|
||||||
for (const token of unused) console.log(` ${token}`);
|
for (const token of unused) console.log(` ${token}`);
|
||||||
} else {
|
} else {
|
||||||
console.log("All preview.css tokens are used somewhere.");
|
console.log("All app.css tokens are used somewhere.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (failed) process.exitCode = 1;
|
if (failed) process.exitCode = 1;
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Color authorship: every color value in preview.css must be authored as hct()
|
// Color authorship: every color value in app.css must be authored as hct()
|
||||||
// — literals AND derived forms (hct(from var(...) h c t) with channel math) —
|
// — literals AND derived forms (hct(from var(...) h c t) with channel math) —
|
||||||
// so the whole palette is computed through HCT channels and output as sRGB by
|
// so the whole palette is computed through HCT channels and output as sRGB by
|
||||||
// the postcss-hct plugin. Exceptions: the seed tokens --brand-main/--brand-alt
|
// the postcss-hct plugin. Exceptions: the seed tokens --brand-main/--brand-alt
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Shared plumbing for the design-token audits: reading preview.css, scanning
|
// Shared plumbing for the design-token audits: reading app.css, scanning
|
||||||
// for var() usages across src, and the color/form predicates the checks use.
|
// for var() usages across src, and the color/form predicates the checks use.
|
||||||
|
|
||||||
import { readdirSync, readFileSync, statSync } from "node:fs";
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
||||||
@@ -7,7 +7,7 @@ import { join } from "node:path";
|
|||||||
import { fileURLToPath } from "node:url";
|
import { fileURLToPath } from "node:url";
|
||||||
import postcss from "postcss";
|
import postcss from "postcss";
|
||||||
|
|
||||||
export const FILE = new URL("../../src/preview.css", import.meta.url);
|
export const FILE = new URL("../../src/app.css", import.meta.url);
|
||||||
const SRC_DIR = fileURLToPath(new URL("../../src/", import.meta.url));
|
const SRC_DIR = fileURLToPath(new URL("../../src/", import.meta.url));
|
||||||
|
|
||||||
// A var(--x) REFERENCE anywhere in the app (primary argument only).
|
// A var(--x) REFERENCE anywhere in the app (primary argument only).
|
||||||
@@ -65,7 +65,7 @@ export function collectTokens(root, selector) {
|
|||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse preview.css once and hand out the postcss root plus both theme maps.
|
// Parse app.css once and hand out the postcss root plus both theme maps.
|
||||||
export async function parsePreview() {
|
export async function parsePreview() {
|
||||||
const css = await readFile(FILE, "utf8");
|
const css = await readFile(FILE, "utf8");
|
||||||
const root = postcss.parse(css);
|
const root = postcss.parse(css);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// Unused tokens: defined in preview.css but never referenced via var()
|
// Unused tokens: defined in app.css but never referenced via var()
|
||||||
// anywhere in src. A dead token is not the single source of truth — it's dust.
|
// anywhere in src. A dead token is not the single source of truth — it's dust.
|
||||||
// Reported as a warning; it does not fail the run.
|
// Reported as a warning; it does not fail the run.
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { Color } from "@panmdaa/colors";
|
|||||||
|
|
||||||
declare const process: { cwd(): string };
|
declare const process: { cwd(): string };
|
||||||
|
|
||||||
const css = readFileSync("src/preview.css", "utf8");
|
const css = readFileSync("src/app.css", "utf8");
|
||||||
|
|
||||||
type Hct = { h: number; c: number; t: number };
|
type Hct = { h: number; c: number; t: number };
|
||||||
|
|
||||||
|
|||||||
@@ -1,28 +1,70 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { page } from "$app/state";
|
||||||
import favicon from "$lib/assets/favicon.svg";
|
import favicon from "$lib/assets/favicon.svg";
|
||||||
import { onMount } from "svelte";
|
import Footer from "$lib/components/kit/layout/Footer.svelte";
|
||||||
|
import TopBar from "$lib/components/kit/layout/TopBar.svelte";
|
||||||
import { initLocale } from "$lib/i18n/locale.svelte";
|
import { initLocale } from "$lib/i18n/locale.svelte";
|
||||||
import { initTheme } from "$lib/theme.svelte";
|
import { getTheme, initTheme, setTheme } from "$lib/theme.svelte";
|
||||||
|
import type { Snippet } from "svelte";
|
||||||
|
import { onMount } from "svelte";
|
||||||
|
import "../app.css";
|
||||||
|
|
||||||
let { children } = $props();
|
interface Props {
|
||||||
|
children: Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { children }: Props = $props();
|
||||||
|
|
||||||
|
let theme = $derived(getTheme());
|
||||||
|
|
||||||
|
function toggle() {
|
||||||
|
setTheme(theme === "light" ? "dark" : "light");
|
||||||
|
}
|
||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
initLocale();
|
initLocale();
|
||||||
initTheme();
|
initTheme();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let crumb = $derived(toCrumb(page.url.pathname));
|
||||||
|
|
||||||
|
const CRUMB: Record<string, string> = {
|
||||||
|
"/list-tools": "CATALOG",
|
||||||
|
"/tools/linear-gradient-png": "GRADIENT",
|
||||||
|
"/tools/remove-background-png": "BACKGROUND REMOVER",
|
||||||
|
};
|
||||||
|
|
||||||
|
function toCrumb(path: string): string {
|
||||||
|
return (
|
||||||
|
"/ " +
|
||||||
|
(CRUMB[path] ??
|
||||||
|
path.split("/").filter(Boolean).pop()?.toUpperCase() ??
|
||||||
|
"HOME")
|
||||||
|
);
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
<link rel="icon" href={favicon} />
|
<link rel="icon" href={favicon} />
|
||||||
</svelte:head>
|
</svelte:head>
|
||||||
|
|
||||||
{@render children()}
|
<main class="preview-root" data-theme={theme}>
|
||||||
|
<TopBar {theme} {crumb} ontoggle={toggle} />
|
||||||
|
{@render children()}
|
||||||
|
<Footer />
|
||||||
|
</main>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
/* Порядок cascade-слоёв для всего документа (грузится на всех маршрутах):
|
.preview-root {
|
||||||
app = старый дизайн ((old)), design = новый (preview/*). design выигрывает
|
background-color: var(--color-background);
|
||||||
у app на совпадающих правилах независимо от порядка <link> / истории
|
background-image:
|
||||||
SPA-навигации. Содержимое слоёв — в app.css (@layer app) и design2.css
|
linear-gradient(var(--color-background-muted) 1px, transparent 1px),
|
||||||
(@layer design). */
|
linear-gradient(90deg, var(--color-background-muted) 1px, transparent 1px);
|
||||||
|
background-size: var(--space-xxxl) var(--space-xxxl);
|
||||||
|
min-height: 100vh;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
@layer app, design;
|
@layer app, design;
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
import Footer from "$lib/components/kit/layout/Footer.svelte";
|
import Footer from "$lib/components/kit/layout/Footer.svelte";
|
||||||
import TopBar from "$lib/components/kit/layout/TopBar.svelte";
|
import TopBar from "$lib/components/kit/layout/TopBar.svelte";
|
||||||
import type { Snippet } from "svelte";
|
import type { Snippet } from "svelte";
|
||||||
import "../../preview.css";
|
import "../../app.css";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
children: Snippet;
|
children: Snippet;
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
import { t } from "$lib/i18n/t";
|
import { t } from "$lib/i18n/t";
|
||||||
import { getTheme, initTheme, setTheme } from "$lib/theme.svelte";
|
import { getTheme, initTheme, setTheme } from "$lib/theme.svelte";
|
||||||
import { onMount } from "svelte";
|
import { onMount } from "svelte";
|
||||||
import "../../old.css";
|
import "../../app_v1.css";
|
||||||
|
|
||||||
let { children } = $props();
|
let { children } = $props();
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
// Stylelint config — design-token enforcement for easy-png-tools.
|
// Stylelint config — design-token enforcement for easy-png-tools.
|
||||||
// FWHM: keeps the design system a single source of truth. Colors and sizes must
|
// FWHM: keeps the design system a single source of truth. Colors and sizes must
|
||||||
// come from prefixed tokens; direct color values are allowed only for the two
|
// come from prefixed tokens; direct color values are allowed only for the two
|
||||||
// brand tokens. old.css is the legacy design and is ignored (removed later).
|
// brand tokens. app_v1.css is the legacy design and is ignored (removed later).
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
extends: ["stylelint-config-standard"],
|
extends: ["stylelint-config-standard"],
|
||||||
ignoreFiles: [
|
ignoreFiles: [
|
||||||
"src/old.css",
|
"src/app_v1.css",
|
||||||
"**/node_modules/**",
|
"**/node_modules/**",
|
||||||
"**/build/**",
|
"**/build/**",
|
||||||
"**/.svelte-kit/**",
|
"**/.svelte-kit/**",
|
||||||
@@ -52,7 +52,7 @@ export default {
|
|||||||
},
|
},
|
||||||
overrides: [
|
overrides: [
|
||||||
{
|
{
|
||||||
// Applies to every CSS file EXCEPT old.css (already in ignoreFiles) —
|
// Applies to every CSS file EXCEPT app_v1.css (already in ignoreFiles) —
|
||||||
// the legacy design is exempt and will be removed later.
|
// the legacy design is exempt and will be removed later.
|
||||||
files: ["src/**/*.css"],
|
files: ["src/**/*.css"],
|
||||||
rules: {
|
rules: {
|
||||||
|
|||||||
Reference in New Issue
Block a user