mirror of
https://github.com/Ku6epXBOCTuK/easy-png-tools.git
synced 2026-09-14 21:46:35 +00:00
feat: add new design primitives
This commit is contained in:
@@ -0,0 +1,51 @@
|
|||||||
|
# AGENTS.md
|
||||||
|
|
||||||
|
Инструкции для агентов (и IDE-ассистентов) по работе с этим репозиторием.
|
||||||
|
|
||||||
|
## Стек
|
||||||
|
|
||||||
|
- **SvelteKit** (`web/`) — основное приложение, Svelte 5 (runes).
|
||||||
|
- Менеджер пакетов: **pnpm**. Запускать команды из `web/`, либо через
|
||||||
|
обёртки в корне: `pnpm build`, `pnpm dev` (это `pnpm --dir web ...`).
|
||||||
|
Не использовать npm.
|
||||||
|
- Сборка: `pnpm --dir web build`, проверка типов: `pnpm --dir web exec svelte-check --tsconfig ./tsconfig.json`,
|
||||||
|
тесты: `pnpm --dir web test`.
|
||||||
|
|
||||||
|
## Правила кода
|
||||||
|
|
||||||
|
### Svelte 5: типизация props через `interface Props`
|
||||||
|
|
||||||
|
Все типизированные пропсы компонентов описываются через локальный
|
||||||
|
`interface Props`, а деструктуризация идёт через аннотацию типа при `$props()`:
|
||||||
|
|
||||||
|
```svelte
|
||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
accent?: boolean;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
let { label, accent = false, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
```
|
||||||
|
|
||||||
|
Не использовать инлайн-дженерик `$props<{ ... }>()` — он тяжело читается и
|
||||||
|
разносит тип и деструктуризацию по разным местам.
|
||||||
|
|
||||||
|
> Позже планируется линтер/правило (eslint-plugin-svelte / custom rule),
|
||||||
|
> запрещающее `$props<...>()` и требующее `interface Props`.
|
||||||
|
|
||||||
|
### Дизайн: новый визуальный язык
|
||||||
|
|
||||||
|
Переезд на новый дизайн описан в `docs/plan-redesign.md`. Ключевые
|
||||||
|
ограничения при работе с веткой `feat/redesign`:
|
||||||
|
|
||||||
|
- Глобальные стили старого и нового дизайна разнесены по layout без
|
||||||
|
наследования: старое в `web/src/app.css` (группа `(old)/`), новое в
|
||||||
|
`web/src/lib/styles/design2.css` (ветка `preview/*`). Префикс `.ds2` не
|
||||||
|
используется.
|
||||||
|
- Все повторяющиеся визуальные элементы — отдельные компоненты в
|
||||||
|
`web/src/lib/components/kit/`, даже «просто div с двумя стилями».
|
||||||
|
- Токены нового дизайна живут в `design2.css`; `refs/` и `refs-html/` —
|
||||||
|
одноразовый источник, не поддерживаемый (правки дизайна — сразу в код).
|
||||||
|
- Коммиты мелкие (< ~500 строк), атомарные
|
||||||
@@ -304,7 +304,7 @@ src/routes/preview/tools/[id]/+page.svelte
|
|||||||
подключить шрифты в `design2.css` (self-host, offline-first).
|
подключить шрифты в `design2.css` (self-host, offline-first).
|
||||||
- [x] **[C4]** _(слит в C3: шрифты нельзя подключить до создания `design2.css`)_
|
- [x] **[C4]** _(слит в C3: шрифты нельзя подключить до создания `design2.css`)_
|
||||||
`[data-theme='dark']` (таблица §2). Без утилитарных классов.
|
`[data-theme='dark']` (таблица §2). Без утилитарных классов.
|
||||||
- [ ] **[C5]** Примитивы batch 1: `AppShell`, `BlueprintGrid`, `CheckerCanvas`,
|
- [x] **[C5]** Примитивы batch 1: `AppShell`, `BlueprintGrid`, `CheckerCanvas`,
|
||||||
`MonoLabel`, `StatusDot`/`StatusLine`.
|
`MonoLabel`, `StatusDot`/`StatusLine`.
|
||||||
- [ ] **[C6]** Примитивы batch 2: `Panel`, `PanelHeading`, `SettingGroup`,
|
- [ ] **[C6]** Примитивы batch 2: `Panel`, `PanelHeading`, `SettingGroup`,
|
||||||
`SettingsFooter`, `MetaRow`/`MetaList`, `Segmented`, `Badge`, `StepCard`,
|
`SettingsFooter`, `MetaRow`/`MetaList`, `Segmented`, `Badge`, `StepCard`,
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import BlueprintGrid from './BlueprintGrid.svelte';
|
||||||
|
|
||||||
|
let { children } = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="app-shell">
|
||||||
|
<BlueprintGrid />
|
||||||
|
<div class="app-content">
|
||||||
|
{@render children()}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.app-shell {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100dvh;
|
||||||
|
background: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
font-family: var(--font-sans);
|
||||||
|
}
|
||||||
|
|
||||||
|
.app-content {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
<div class="blueprint-grid" aria-hidden="true"></div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.blueprint-grid {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
pointer-events: none;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(to right, color-mix(in srgb, var(--line) 45%, transparent) 1px, transparent 1px),
|
||||||
|
linear-gradient(to bottom, color-mix(in srgb, var(--line) 45%, transparent) 1px, transparent 1px);
|
||||||
|
background-size: 32px 32px;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
size?: 'sm' | 'large';
|
||||||
|
}
|
||||||
|
let { children, size = 'large' }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<div class="checker-canvas checker-canvas--{size}">
|
||||||
|
{#if children}
|
||||||
|
{@render children()}
|
||||||
|
{/if}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.checker-canvas {
|
||||||
|
position: relative;
|
||||||
|
display: grid;
|
||||||
|
place-items: center;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #0b0f14;
|
||||||
|
background-image: linear-gradient(45deg, #1a2129 25%, transparent 25%),
|
||||||
|
linear-gradient(-45deg, #1a2129 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #1a2129 75%),
|
||||||
|
linear-gradient(-45deg, transparent 75%, #1a2129 75%);
|
||||||
|
background-size: 20px 20px;
|
||||||
|
background-position:
|
||||||
|
0 0,
|
||||||
|
0 10px,
|
||||||
|
10px -10px,
|
||||||
|
-10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checker-canvas--sm {
|
||||||
|
min-height: 180px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checker-canvas--large {
|
||||||
|
min-height: 360px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
interface Props {
|
||||||
|
accent?: boolean;
|
||||||
|
children?: import('svelte').Snippet;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { accent = false, children }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="mono-label" class:accent={accent}>
|
||||||
|
{#if children}
|
||||||
|
{@render children()}
|
||||||
|
{/if}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.mono-label {
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
font-size: 10px;
|
||||||
|
line-height: 1.2;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accent {
|
||||||
|
color: var(--blue);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
<span class="status-dot" aria-hidden="true"></span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.status-dot {
|
||||||
|
display: inline-block;
|
||||||
|
width: 8px;
|
||||||
|
height: 8px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--success);
|
||||||
|
box-shadow: 0 0 0 2px color-mix(in srgb, var(--success) 25%, transparent);
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
import StatusDot from './StatusDot.svelte';
|
||||||
|
import MonoLabel from './MonoLabel.svelte';
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
label: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
let { label }: Props = $props();
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<span class="status-line">
|
||||||
|
<StatusDot />
|
||||||
|
<MonoLabel>{label}</MonoLabel>
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.status-line {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user