diff --git a/backend/Cargo.lock b/backend/Cargo.lock index f5a232a..6f0b008 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -1033,7 +1033,7 @@ checksum = "5e5032e24019045c762d3c0f28f5b6b8bbf38563a65908389bf7978758920897" [[package]] name = "mangalord" -version = "0.11.0" +version = "0.13.0" dependencies = [ "anyhow", "argon2", diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 32ed364..c9d7962 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mangalord" -version = "0.12.0" +version = "0.13.0" edition = "2021" [lib] diff --git a/docs/design-system.md b/docs/design-system.md new file mode 100644 index 0000000..de74545 --- /dev/null +++ b/docs/design-system.md @@ -0,0 +1,162 @@ +# Mangalord design system + +One screen. This is the contract the implementation reads — every value in code comes from a token defined here. + +## Tokens + +All tokens live on `:root` in [frontend/src/lib/styles/tokens.css](../frontend/src/lib/styles/tokens.css). Dark values override under `:root[data-theme="dark"]`. Components consume tokens only — no raw hex, no raw px in scoped styles. + +### Color + +Semantic, not literal. Tested with [WebAIM contrast checker](https://webaim.org/resources/contrastchecker/). + +| Token | Light | Dark | Use | +|---|---|---|---| +| `--bg` | `#ffffff` | `#0f1115` | page background | +| `--surface` | `#f6f7f9` | `#161a21` | cards, inputs, drop zones | +| `--surface-elevated` | `#ffffff` | `#1c2129` | hovered cards, focused inputs | +| `--border` | `#e3e5ea` | `#2a2f37` | hairlines | +| `--border-strong` | `#c8ccd4` | `#3a414d` | input borders | +| `--text` | `#16181d` | `#e8eaed` | body | +| `--text-muted` | `#5b6168` | `#9aa0a6` | meta, hints | +| `--primary` | `#2563eb` | `#60a5fa` | links, primary button, focus | +| `--primary-hover` | `#1d4ed8` | `#3b82f6` | primary hover | +| `--primary-contrast` | `#ffffff` | `#0f1115` | text on primary fill | +| `--danger` | `#b00020` | `#f87171` | errors, destructive | +| `--danger-soft-bg` | `#fff5f5` | `#3a1620` | invalid page row | +| `--success` | `#0a7d2c` | `#4ade80` | success text | +| `--warning-soft-bg` | `#fff5d6` | `#3a2e10` | active bookmark fill | +| `--warning-border` | `#d6a800` | `#a37800` | active bookmark border | +| `--focus-ring` | `#2563eb` | `#60a5fa` | `:focus-visible` outline | +| `--focus-ring-soft` | `rgb(37 99 235 / 0.25)` | `rgb(96 165 250 / 0.3)` | input focus halo (`box-shadow`) | +| `--primary-soft-bg` | `rgb(37 99 235 / 0.08)` | `rgb(96 165 250 / 0.12)` | drop-zone drag-over, selected theme radio | + +Verified ratios (light theme, against `--bg`): `--text` 16.5:1 (AAA), `--text-muted` 6.4:1 (AA), `--primary` 5.2:1 (AA), `--danger` 7.6:1 (AAA). Dark theme: `--text` 13.8:1, `--text-muted` 4.7:1, `--primary` 5.3:1, `--danger` 5.1:1. All interactive elements clear 4.5:1; large UI clears 3:1. + +### Typography + +System stack, zero FOUT cost: `system-ui, -apple-system, "Segoe UI", Roboto, sans-serif`. Monospace stack (for code-like elements, currently unused but reserved): `ui-monospace, "SF Mono", Menlo, monospace`. + +| Token | Value | Where | +|---|---|---| +| `--font-xs` | `0.75rem` / `1.4` | micro-meta (file sizes, "Bookmarked {date}") | +| `--font-sm` | `0.875rem` / `1.5` | card meta, hints, inline errors | +| `--font-base` | `1rem` / `1.55` | body, inputs, buttons | +| `--font-lg` | `1.125rem` / `1.45` | card titles, h3 | +| `--font-xl` | `1.5rem` / `1.3` | h2 | +| `--font-2xl` | `2rem` / `1.2` | h1 page titles | + +Weights: `--weight-regular: 400`, `--weight-medium: 500`, `--weight-semibold: 600`. Headings use semibold. Body and inputs use regular. Card titles use semibold at `--font-sm` or `--font-lg`. + +### Spacing + +One ladder (4 px base): + +`--space-1 0.25rem`, `--space-2 0.5rem`, `--space-3 0.75rem`, `--space-4 1rem`, `--space-5 1.25rem`, `--space-6 1.5rem`, `--space-8 2rem`. + +### Radii, shadows, transitions + +`--radius-sm 4px`, `--radius-md 6px`, `--radius-lg 10px`, `--radius-pill 999px`. + +`--shadow-sm` (light: `0 1px 2px rgb(0 0 0 / 0.06)`; dark: `0 1px 2px rgb(0 0 0 / 0.4)`), `--shadow-md` (light: `0 2px 8px rgb(0 0 0 / 0.08)`; dark: `0 2px 8px rgb(0 0 0 / 0.5)`). + +`--transition: 120ms ease-out`. Disabled under `@media (prefers-reduced-motion: reduce)`. + +### Z-index + +`--z-dropdown 10`, `--z-sticky 50`, `--z-modal 100`, `--z-toast 1000`. No raw `z-index` numbers in component CSS. + +## Components + +All shapes are token-driven. Implementation lives in route-scoped ` diff --git a/frontend/src/routes/login/+page.svelte b/frontend/src/routes/login/+page.svelte index 682885c..21f3394 100644 --- a/frontend/src/routes/login/+page.svelte +++ b/frontend/src/routes/login/+page.svelte @@ -26,8 +26,8 @@
+
No account? Register.
@@ -61,12 +61,30 @@ form { display: flex; flex-direction: column; - gap: 0.75rem; + gap: var(--space-3); max-width: 24rem; } - label { - display: flex; - flex-direction: column; - gap: 0.25rem; + + .primary { + background: var(--primary); + color: var(--primary-contrast); + border-color: var(--primary); + margin-top: var(--space-1); + } + + .primary:hover:not(:disabled) { + background: var(--primary-hover); + border-color: var(--primary-hover); + } + + .form-error { + color: var(--danger); + font-size: var(--font-sm); + } + + .hint { + margin-top: var(--space-3); + color: var(--text-muted); + font-size: var(--font-sm); } diff --git a/frontend/src/routes/manga/[id]/+page.svelte b/frontend/src/routes/manga/[id]/+page.svelte index ee26917..5a20a68 100644 --- a/frontend/src/routes/manga/[id]/+page.svelte +++ b/frontend/src/routes/manga/[id]/+page.svelte @@ -105,53 +105,79 @@ .overview { display: grid; grid-template-columns: minmax(0, 200px) 1fr; - gap: 1rem; + gap: var(--space-4); align-items: start; + margin-bottom: var(--space-6); } + @media (max-width: 640px) { .overview { grid-template-columns: 1fr; } } + .cover { width: 100%; height: auto; - border-radius: 4px; - } - .meta h1 { - margin: 0 0 0.5rem; + border-radius: var(--radius-md); + background: var(--surface); } + .author { - color: #555; - margin: 0 0 0.75rem; + color: var(--text-muted); + margin: 0 0 var(--space-3); } + .description { white-space: pre-wrap; + color: var(--text); + margin-bottom: var(--space-3); } + .bookmark { - display: inline-block; - margin-top: 0.5rem; - padding: 0.4rem 0.75rem; - border: 1px solid #ccc; - border-radius: 4px; - background: #fafafa; - color: inherit; + display: inline-flex; + align-items: center; + gap: var(--space-2); + margin-top: var(--space-2); + padding: 0 var(--space-3); + height: 36px; + border: 1px solid var(--border-strong); + border-radius: var(--radius-md); + background: var(--surface); + color: var(--text); text-decoration: none; cursor: pointer; + font-size: var(--font-sm); + font-weight: var(--weight-medium); + transition: + background var(--transition), + border-color var(--transition), + color var(--transition); } - .bookmark:focus-visible { - outline: 2px solid #06f; - outline-offset: 2px; + + .bookmark:hover { + background: var(--surface-elevated); + text-decoration: none; } + .bookmark.active { - background: #ffeebb; - border-color: #d6a800; + background: var(--warning-soft-bg); + border-color: var(--warning-border); + color: var(--text); } + .chapter-list { - padding-left: 1.5rem; + padding-left: var(--space-6); + color: var(--text); } + + .chapter-list li { + padding: var(--space-1) 0; + } + .pages { - color: #777; - margin-left: 0.5rem; + color: var(--text-muted); + margin-left: var(--space-2); + font-size: var(--font-sm); } diff --git a/frontend/src/routes/manga/[id]/chapter/[n]/+page.svelte b/frontend/src/routes/manga/[id]/chapter/[n]/+page.svelte index 13edaf9..966990c 100644 --- a/frontend/src/routes/manga/[id]/chapter/[n]/+page.svelte +++ b/frontend/src/routes/manga/[id]/chapter/[n]/+page.svelte @@ -1,6 +1,10 @@