docs: add product and design documentation

This commit is contained in:
2026-05-05 18:04:16 +02:00
parent c3de4ae9a7
commit 6e7d77e49d
5 changed files with 582 additions and 0 deletions

105
.impeccable/design.json Normal file
View File

@@ -0,0 +1,105 @@
{
"schemaVersion": 2,
"generatedAt": "2026-05-05T00:00:00Z",
"title": "Design System: MAL",
"extensions": {
"colorMeta": {
"background": { "role": "neutral", "displayName": "Void Black", "canonical": "#080808" },
"background-sidebar": { "role": "neutral", "displayName": "Deep Charcoal", "canonical": "#0f0f0f" },
"background-header": { "role": "neutral", "displayName": "Elevated Black", "canonical": "#141414" },
"background-surface": { "role": "neutral", "displayName": "Surface Gray", "canonical": "#202020" },
"background-button": { "role": "neutral", "displayName": "Button Dark", "canonical": "#1a1a1a" },
"background-button-hover": { "role": "neutral", "displayName": "Button Hover", "canonical": "#252525" },
"foreground-muted": { "role": "neutral", "displayName": "Muted Gray", "canonical": "#6a6b70" },
"foreground": { "role": "neutral", "displayName": "Off White", "canonical": "#f8f9fa" },
"accent": { "role": "primary", "displayName": "Soft Violet", "canonical": "#9f7aea" },
"danger": { "role": "accent", "displayName": "Red Alert", "canonical": "#dc2626" }
},
"typographyMeta": {
"body": { "displayName": "Body", "purpose": "Standard text, descriptions" },
"title": { "displayName": "Title", "purpose": "Page headings" },
"label": { "displayName": "Label", "purpose": "Navigation, buttons, form labels" }
},
"shadows": [],
"motion": [
{ "name": "ease-default", "value": "cubic-bezier(0.4, 0, 0.2, 1)", "purpose": "Default transition easing" }
],
"breakpoints": []
},
"components": [
{
"name": "Primary Button",
"kind": "button",
"refersTo": "button-primary",
"description": "Main action button with purple background",
"html": "<button class=\"ds-btn-primary\">Action</button>",
"css": ".ds-btn-primary { background: #9f7aea; color: #080808; padding: 8px 16px; border: none; font-weight: 500; transition: background 0.2s; } .ds-btn-primary:hover { background: #b79ef5; }"
},
{
"name": "Secondary Button",
"kind": "button",
"refersTo": "button-secondary",
"description": "Secondary action with dark background",
"html": "<button class=\"ds-btn-secondary\">Action</button>",
"css": ".ds-btn-secondary { background: rgba(255,255,255,0.05); color: #f8f9fa; padding: 8px 16px; border: none; transition: background 0.2s; } .ds-btn-secondary:hover { background: rgba(255,255,255,0.1); }"
},
{
"name": "Anime Card",
"kind": "card",
"refersTo": "card-anime",
"description": "Anime poster card with hover reveal",
"html": "<div class=\"ds-card\"><img src=\"\" alt=\"Anime\"><div class=\"ds-card-overlay\"><span>Title</span></div></div>",
"css": ".ds-card { position: relative; aspect-ratio: 2/3; background: rgba(255,255,255,0.05); overflow: hidden; } .ds-card img { width: 100%; height: 100%; object-fit: cover; } .ds-card-overlay { position: absolute; inset: 0; background: rgba(0,0,0,0.8); opacity: 0; padding: 12px; transition: opacity 0.3s; display: flex; flex-direction: column; } .ds-card:hover .ds-card-overlay { opacity: 1; }"
},
{
"name": "Navigation Item",
"kind": "nav",
"refersTo": "nav-item",
"description": "Sidebar navigation link with active indicator",
"html": "<a class=\"ds-nav-item\" href=\"/\">Home</a>",
"css": ".ds-nav-item { display: flex; align-items: center; padding: 12px 28px; color: #6a6b70; text-decoration: none; transition: color 0.2s, background 0.2s; position: relative; } .ds-nav-item:hover { background: rgba(255,255,255,0.05); } .ds-nav-item.active { color: #9f7aea; } .ds-nav-item.active::before { content: ''; position: absolute; left: 0; top: 50%; transform: translateY(-50%); height: 32px; width: 2px; background: #9f7aea; }"
},
{
"name": "Dropdown Menu",
"kind": "custom",
"refersTo": "dropdown",
"description": "Custom dropdown with dark background",
"html": "<div class=\"ds-dropdown\"><button>Menu</button><div class=\"ds-dropdown-content\"><button>Option</button></div></div>",
"css": ".ds-dropdown { position: relative; } .ds-dropdown-content { position: absolute; top: 100%; right: 0; background: #1a1a1a; box-shadow: 0 8px 24px rgba(0,0,0,0.4); padding: 4px 0; display: none; min-width: 160px; } .ds-dropdown-content button { display: block; width: 100%; padding: 10px 20px; text-align: left; background: none; border: none; color: #f8f9fa; } .ds-dropdown-content button:hover { background: rgba(255,255,255,0.1); }"
}
],
"narrative": {
"northStar": "The Personal Theater",
"overview": "A dark, intimate interface for watching anime. Built for long viewing sessions in low-light environments. The aesthetic rejects the loud, gamified look of mainstream anime trackers. No flashy animations, no glassmorphism, no gradient text.",
"keyCharacteristics": [
"Dark by default; light mode secondary",
"Minimal chrome; content first",
"Purple accent ≤10% of any screen",
"No rounded corners",
"DM Sans system fonts"
],
"rules": [
{ "name": "The One Accent Rule", "body": "Purple appears on ≤10% of any given screen. Its rarity is the point.", "section": "colors" },
{ "name": "The Weight Contrast Rule", "body": "Headings use font-semibold (600), body uses font-normal (400). Avoid flat weight hierarchies.", "section": "typography" },
{ "name": "The Flat-By-Default Rule", "body": "Surfaces are flat at rest. No shadows. Depth conveyed through tonal layering.", "section": "elevation" }
],
"dos": [
"Do use accent purple for primary actions and active states only",
"Do keep interfaces dark by default",
"Do use weight contrast for hierarchy",
"Do reveal detail on hover",
"Do include back buttons on detail pages"
],
"donts": [
"Don't use rounded corners anywhere",
"Don't use color to differentiate toast types",
"Don't use gradient text",
"Don't use glassmorphism",
"Don't use hero metric layouts",
"Don't use identical card grids with no variation",
"Don't use modals as first thought",
"Don't use side-stripe borders",
"Don't use green accent—violet is the one and only"
]
}
}

155
DESIGN.md Normal file
View File

@@ -0,0 +1,155 @@
---
name: MAL
description: Personal anime streaming & watchlist service
colors:
background: "#080808"
background-sidebar: "#0f0f0f"
background-header: "#141414"
background-surface: "#202020"
background-button: "#1a1a1a"
background-button-hover: "#252525"
foreground-muted: "#6a6b70"
foreground: "#f8f9fa"
accent: "#9f7aea"
danger: "#dc2626"
typography:
body:
fontFamily: "'DM Sans', 'Segoe UI', system-ui, sans-serif"
fontSize: "14px"
fontWeight: 400
lineHeight: 1.5
rounded:
default: "6px"
spacing:
sm: "0.25rem"
md: "0.5rem"
lg: "1rem"
xl: "1.5rem"
2xl: "2rem"
components:
button-primary:
backgroundColor: "{colors.accent}"
textColor: "#080808"
padding: "8px 16px"
button-secondary:
backgroundColor: "{colors.background-button}"
textColor: "{colors.foreground}"
padding: "8px 16px"
button-secondary-hover:
backgroundColor: "{colors.background-button-hover}"
---
# Design System: MAL
## 1. Overview
**Creative North Star: "The Personal Theater"**
A dark, intimate interface for watching anime. Built for long viewing sessions in low-light environments—think late-night streaming on a personal server. The aesthetic rejects the loud, gamified look of mainstream anime trackers. No flashy animations, no glassmorphism, no gradient text. Just content and controls.
**Key Characteristics:**
- Dark by default; light mode exists but is secondary
- Minimal chrome; the anime artwork carries the visual weight
- Purple accent used sparingly (<10% of any screen)
- No rounded corners; sharp edges throughout
- System fonts (DM Sans) for reliability across devices
## 2. Colors
The palette is intentionally restrained. Dark surfaces dominate to reduce eye strain during long sessions.
### Primary
- **Accent Purple** (`#9f7aea`): Navigation highlights, active states, primary actions. Used on active sidebar items, hover states on important buttons, current episode indicators.
### Neutral
- **Background** (`#080808`): Main page background. Deep black with a hint of warmth.
- **Background Sidebar** (`#0f0f0f`): Slightly elevated surface for navigation panel.
- **Background Header** (`#141414`): Sticky header surface, slightly brighter than page.
- **Background Surface** (`#202020`): Cards, dropdowns, search inputs.
- **Background Button** (`#1a1a1a`): Default button state, form fields.
- **Background Button Hover** (`#252525`): Button hover state.
- **Foreground Muted** (`#6a6b70`): Secondary text, icons, placeholders.
- **Foreground** (`#f8f9fa`): Primary text, headings.
### Named Rules
**The One Accent Rule.** Purple appears on ≤10% of any given screen. Its rarity is the point. Navigation highlights use a subtle left border; primary buttons use the accent background.
## 3. Typography
**Body Font:** DM Sans (with Segoe UI, system-ui fallbacks)
**Character:** Clean, legible, unremarkable. The typeface should be invisible—content first.
### Hierarchy
- **Title** (`600`, `20px`): Page headings.
- **Body** (`400`, `14px`): Standard text, descriptions.
- **Label** (`500`, `14px`, `0.025em`): Navigation items, buttons, form labels.
- **Small** (`400`, `12px`): Metadata, episode numbers, timestamps.
### Named Rules
**The Weight Contrast Rule.** Headings use `font-semibold` (600), body uses `font-normal` (400). Avoid flat weight hierarchies.
## 4. Elevation
**Flat by default.** No shadows at rest. Depth is conveyed through tonal layering—different background colors rather than shadow layers.
- **Hover state:** Background shifts from `#1a1a1a` to `#252525`.
- **Active states:** Subtle accent tint (`bg-accent/20`) or accent left border.
- **No shadows.** The interface is intentionally flat.
### Shadow Vocabulary
None used. If shadows are needed for overlays (modals, dropdowns), use `box-shadow: 0 8px 24px rgba(0,0,0,0.4)`.
## 5. Components
### Buttons
- **Shape:** No rounded corners (sharp edges).
- **Primary:** Purple background (`bg-accent`), dark text. Used for the most important action on each screen.
- **Secondary:** Dark background (`bg-white/5`), light text. Used for prev/next episode, filters.
- **Hover:** Background shifts lighter (`bg-white/10`). Transition duration 200ms.
### Cards / Anime Grid
- **Corner Style:** No radius. Square corners.
- **Background:** None at rest. Dark image placeholder (`bg-white/5`) behind anime poster.
- **Hover:** Overlay appears (`bg-black/80`) revealing title, synopsis, and watchlist button.
- **Internal Padding:** 12px (main content), 8px (card hover overlay).
### Navigation (Sidebar)
- **Style:** Fixed width (256px), collapsible to icons only.
- **Default:** Text in `foreground-muted` (`#6a6b70`), no background.
- **Hover:** `bg-white/5`.
- **Active:** Left accent border (`w-0.5 bg-accent`), accent text color. Subtle glow (`shadow-[0_0_8px_rgba(159,122,234,0.6)]`).
### Dropdowns
- **Background:** `#1a1a1a` with shadow.
- **No radius.** Sharp corners.
- **Items:** 12px vertical padding, hover `bg-white/10`.
### Input Fields
- **Style:** `bg-white/5`, `border-transparent`, 8px vertical padding.
- **Focus:** Border shifts to accent color (`focus:border-accent`).
### Toast Notifications
- **Style:** Monochromatic, no color coding by type.
- **Background:** `bg-white/10` with `border-white/20`.
- **No rounded corners.**
## 6. Do's and Don'ts
### Do:
- **Do** use accent purple for primary actions and active states only.
- **Do** keep interfaces dark by default—lighter backgrounds are for special emphasis only.
- **Do** use weight contrast (semibold headings, normal body) for hierarchy.
- **Do** reveal detail on hover (anime cards, navigation).
- **Do** include back buttons on detail pages.
### Don't:
- **Don't** use rounded corners anywhere.
- **Don't** use color to differentiate toast types—all toasts look the same.
- **Don't** use gradient text.
- **Don't** use glassmorphism (blur backgrounds, transparency effects).
- **Don't** use hero metric layouts (big number + small label).
- **Don't** use identical card grids with no visual variation.
- **Don't** use modals as a first thought—inline and progressive disclosure preferred.
- **Don't** use side-stripe borders (colored left/right borders on cards).
- **Don't** use the green accent—violet is the one and only accent.

257
DESIGN_SYSTEM.md Normal file
View File

@@ -0,0 +1,257 @@
# MAL Design System
## Overview
This design system is for the MAL anime streaming service. It provides reusable components, design tokens, and patterns for building consistent UI.
## Design Tokens
### Colors
| Token | Value | Usage |
|-------|-------|-------|
| `--color-background` | `#080808` | Main page background |
| `--color-background-sidebar` | `#0f0f0f` | Sidebar background |
| `--color-background-header` | `#141414` | Sticky header |
| `--color-background-surface` | `#202020` | Cards, dropdowns |
| `--color-background-button` | `#1a1a1a` | Button default state |
| `--color-background-button-hover` | `#252525` | Button hover state |
| `--color-foreground-muted` | `#6a6b70` | Secondary text, icons |
| `--color-foreground` | `#f8f9fa` | Primary text |
| `--color-accent` | `#9f7aea` | Primary accent (violet) |
| `--color-danger` | `#dc2626` | Error states, delete actions |
### Typography
| Role | Font | Size | Weight |
|------|------|------|--------|
| Title | DM Sans | 20px | 600 |
| Body | DM Sans | 14px | 400 |
| Label | DM Sans | 14px | 500 |
| Small | DM Sans | 12px | 400 |
### Spacing
| Token | Value |
|-------|-------|
| `--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 |
## Components
### 1. Anime Card (`anime_card.gohtml`)
A poster card with hover reveal.
**Usage:**
```gohtml
{{template "anime_card" dict "Anime" . "WithActions" true "IsWatchlist" false}}
```
**Props:**
- `Anime` - Anime data object
- `WithActions` - Show hover overlay with actions (bool)
- `IsWatchlist` - Show in-watchlist state (bool)
- `Compact` - Hide metadata below (bool)
- `HideTitle` - Hide title below image (bool)
- `HasTopBadge` - Adjust overlay padding for badge (bool)
**States:**
- Default: Image with subtle dark placeholder
- Hover: Black overlay (80%) reveals title, synopsis, watchlist button
### 2. Navigation (`navigation.gohtml`)
Sidebar navigation with active indicator.
**Usage:**
```gohtml
{{template "navigation" dict "CurrentPath" .CurrentPath}}
```
**Props:**
- `CurrentPath` - Current page path for active state
**Items:**
- Home (`/`)
- Browse (`/browse`)
- Discover (`/discover`)
- Watchlist (`/watchlist`)
**States:**
- Default: Muted gray text
- Hover: `bg-white/5`
- Active: Accent text + left border (`w-0.5 bg-accent`) + subtle glow
### 3. Dropdown (`dropdown.gohtml`)
Custom dropdown using `<ui-dropdown>` web component.
**Usage:**
```gohtml
<ui-dropdown class="relative block" data-align="left" data-width="w-48">
<div data-trigger>
<button>Trigger</button>
</div>
<div data-content class="hidden absolute ...">
<button>Option 1</button>
<button>Option 2</button>
</div>
</ui-dropdown>
```
**Attributes:**
- `data-align` - `left` or `right` alignment
- `data-width` - Width (Tailwind class)
**Styling:**
- Background: `#1a1a1a`
- No border radius
- Items: 10px vertical padding, hover `bg-white/10`
### 4. Filter Bar (`filter_bar.gohtml`)
Search and filter controls for browse page.
**Usage:**
```gohtml
{{template "filter_bar" .}}
```
**Controls:**
- Search input (text)
- Genre dropdown (multi-select)
- Status dropdown (airing, complete, upcoming)
- Type dropdown (tv, movie, ova, special, ona)
- Sort dropdown + order toggle
### 5. Header (`header.gohtml`)
Sticky header with logo, search, and user menu.
**Usage:**
```gohtml
{{template "header" .}}
```
**Sections:**
- Left: Mobile menu toggle, sidebar toggle, logo
- Center: Search input (desktop only)
- Right: User avatar dropdown
### 6. Watchlist Actions (`watchlist_actions.gohtml`)
Watchlist toggle button.
**Usage:**
```gohtml
{{template "watchlist_actions" dict "MalID" 123 "IsWatchlist" true}}
```
**Props:**
- `MalID` - Anime MAL ID
- `IsWatchlist` - Current state
**States:**
- Not in watchlist: Outline icon, accent on hover
- In watchlist: Filled icon (`fill: currentColor`)
### 7. Video Player (`video_player.gohtml`)
Video player container for episodes.
**Usage:**
```gohtml
{{template "video_player" dict "WatchData" .WatchData "TotalEpisodes" .Anime.Episodes}}
```
**Props:**
- `WatchData` - Video source data
- `TotalEpisodes` - Total episode count
### 8. Continue Watching (`continue_watching.gohtml`)
Horizontal row for continue watching section.
**Usage:**
```gohtml
{{template "continue_watching" .ContinueWatching}}
```
**Props:**
- Array of anime with watch progress
## Patterns
### Button Variants
**Primary:**
```html
<button class="bg-accent text-black px-4 py-2">Action</button>
```
**Secondary:**
```html
<button class="bg-white/5 text-neutral-300 px-4 py-2 hover:bg-white/10">Action</button>
```
**Danger:**
```html
<button class="bg-red-500/10 text-red-400 px-4 py-2 hover:bg-red-500/20">Delete</button>
```
### Empty State
```html
<div class="flex flex-col items-center justify-center gap-4 py-24">
<svg class="h-16 w-16 opacity-30">...</svg>
<p class="text-neutral-300 font-medium">Title</p>
<p class="text-neutral-500 text-sm">Description</p>
<div class="flex gap-3 mt-2">
<a class="bg-accent/20 text-accent px-5 py-2.5">Action</a>
<a class="bg-white/10 text-neutral-200 px-5 py-2.5">Secondary</a>
</div>
</div>
```
### Card Grid
```html
<div class="grid grid-cols-2 gap-4 md:grid-cols-3 lg:grid-cols-4 2xl:grid-cols-6">
<!-- Cards -->
</div>
```
### Episode List Item
```html
<a class="flex items-center gap-3 px-3 py-2 hover:bg-white/5 border-l-2 {{if .Filler}}border-l-yellow-500{{end}} {{if .Recap}}border-l-blue-500{{end}} {{if $isCurrent}}bg-accent/20{{end}}">
<span class="w-10 shrink-0 text-xs font-medium text-neutral-500">EP{{.MalID}}</span>
<span class="truncate text-sm text-neutral-300">{{.Title}}</span>
</a>
```
## Migration Guide
### Adding a New Component
1. Create template in `templates/components/`
2. Document props and usage in this file
3. Add to component index above
### Updating Existing Components
1. Make changes in `templates/components/`
2. Update this documentation
3. Test across all usage sites
### Design Token Changes
1. Update in `static/style.css`
2. Update YAML frontmatter in `DESIGN.md`
3. Update this token reference

33
PRODUCT.md Normal file
View File

@@ -0,0 +1,33 @@
# MAL - Anime Streaming & Watchlist Service
## Product Purpose
Personal anime streaming service with watchlist management for me and my friends. Simple, functional, easy to navigate.
## Users
- **Primary**: Me (owner)
- **Secondary**: Close friends with access
- Technical comfort: Medium - comfortable with self-hosted services
## Brand Tone
- **Practical**: No fluff, just works
- **Clean**: Minimal aesthetic, functional over decorative
- **Personal**: Built for us, not for distribution
## Design Principles
1. **Easy to maneuver** - Clear navigation, predictable patterns
2. **Content-first** - Anime artwork and details take precedence
3. **Fast interactions** - HTMX for snappy partial updates
4. **Dark theme default** - Easy on eyes for long viewing sessions
## Anti-References
- No anime list sites (MyAnimeList, AniList clones)
- No generic SaaS dashboards
- No "AI slop" aesthetics - gradient text, glassmorphism, hero metrics
## Register
**Product** - Design serves the utility, not the reverse.
## Technical Stack
- Go backend (HTMX for interactivity)
- Tailwind CSS
- No JavaScript framework - vanilla JS only

View File

@@ -0,0 +1,32 @@
# Components Index
## Available Templates
| Component | File | Purpose |
|-----------|------|---------|
| Anime Card | `anime_card.gohtml` | Poster card with hover reveal |
| Continue Watching | `continue_watching.gohtml` | Continue watching row |
| Dropdown | `dropdown.gohtml` | Dropdown wrapper (also uses `<ui-dropdown>`) |
| Filter Bar | `filter_bar.gohtml` | Search + filters for browse |
| Header | `header.gohtml` | Sticky header with nav |
| Navigation | `navigation.gohtml` | Sidebar navigation |
| Video Player | `video_player.gohtml` | Episode video container |
| Watchlist Actions | `watchlist_actions.gohtml` | Add/remove watchlist button |
| Watch Order | `watch_order.gohtml` | Watch order queue |
## Usage
All components are exposed as Go templates. Import by name:
```gohtml
{{template "anime_card" dict "Anime" .Data "WithActions" true}}
{{template "navigation" dict "CurrentPath" .CurrentPath}}
{{template "header" .}}
```
## Props Convention
Components accept a `dict` with named keys:
- `dict "Key" .Value "Key2" .Value2`
This keeps prop names explicit and self-documenting.