diff --git a/.impeccable/design.json b/.impeccable/design.json new file mode 100644 index 0000000..0bec707 --- /dev/null +++ b/.impeccable/design.json @@ -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": "", + "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": "", + "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": "
\"Anime\"
Title
", + "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": "Home", + "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": "
", + "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" + ] + } +} \ No newline at end of file diff --git a/DESIGN.md b/DESIGN.md new file mode 100644 index 0000000..37a0496 --- /dev/null +++ b/DESIGN.md @@ -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. \ No newline at end of file diff --git a/DESIGN_SYSTEM.md b/DESIGN_SYSTEM.md new file mode 100644 index 0000000..18e7fdc --- /dev/null +++ b/DESIGN_SYSTEM.md @@ -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 `` web component. + +**Usage:** +```gohtml + +
+ +
+ +
+``` + +**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 + +``` + +**Secondary:** +```html + +``` + +**Danger:** +```html + +``` + +### Empty State + +```html +
+ ... +

Title

+

Description

+ +
+``` + +### Card Grid + +```html +
+ +
+``` + +### Episode List Item + +```html + + EP{{.MalID}} + {{.Title}} + +``` + +## 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 \ No newline at end of file diff --git a/PRODUCT.md b/PRODUCT.md new file mode 100644 index 0000000..0cf496e --- /dev/null +++ b/PRODUCT.md @@ -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 \ No newline at end of file diff --git a/templates/components/README.md b/templates/components/README.md new file mode 100644 index 0000000..b2b6e3c --- /dev/null +++ b/templates/components/README.md @@ -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 ``) | +| 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. \ No newline at end of file