chore: add brainstorming docs and design reference

This commit is contained in:
achmad
2026-05-28 22:20:29 +07:00
commit d4d65b1dff
58 changed files with 12726 additions and 0 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,272 @@
# Kotobane — CMS-Driven Website Plan
## Context
Kotobane is a Japanese pop-culture news site (VTubers, Anime, Manga, Games, Music, Japan, Culture, Industry) with a fully-defined design system. The goal is to build a website where content can be added and updated from a Directus CMS without touching code or redeploying. The user runs everything on a VPS and already has a Directus instance (fresh install, no collections yet). There is a single author (no authors collection needed).
**Chosen architecture:** Next.js 14 App Router with ISR + on-demand revalidation. Pages are served as pre-built static HTML; when the editor publishes in Directus, a Directus Flow fires a webhook to `/api/revalidate` which surgically regenerates only the affected pages — no rebuild, no redeploy, content live in seconds.
**Design reference:** `docs/design-reference/kotobane-reference.html` — single file with four tabs: Homepage mockup, Article page mockup, Color system, Directus workflow. Open in a browser for full visual reference.
**Directus instance:** `https://cms.achmad.dev`
---
## Stack
- **Frontend:** Next.js 14 (App Router), TypeScript, Tailwind CSS
- **CMS:** Directus (existing VPS instance)
- **Fonts:** Inter (English) + Noto Sans JP (Japanese) via `next/font`
- **Icons:** Lucide React
- **Rich text rendering:** `@directus/sdk` + custom renderer for Directus rich text blocks
- **Image optimization:** Next.js `<Image>` pointing at Directus `/assets/` endpoint
---
## Final Color Palette
### Brand Accent
| Token | Hex | Usage |
|---|---|---|
| accent | `#00B4D8` | Primary buttons, links, active nav, "Read →", focus rings |
| accent-hover | `#00D4FF` | Hover state |
### Status / Badge Colors
| Token | Hex | Usage |
|---|---|---|
| violet | `#7C3AED` | Featured / hero badges, secondary actions |
| coral | `#D64545` | Trending, breaking news, error states |
| amber | `#FFB300` | New indicator, alerts, warnings |
| green | `#00D166` | Published status, success states |
### Backgrounds
| Token | Hex | Usage |
|---|---|---|
| bg | `#0D0D14` | Main page background |
| bg-elevated | `#171A21` | Navbar, sidebars |
| bg-card | `#1D212B` | Cards, inputs |
| border | `#2A3140` | All borders and dividers |
### Text
| Token | Hex | Usage |
|---|---|---|
| text-primary | `#F3F5F7` | Headlines, body |
| text-secondary | `#B8C0CC` | Excerpts, captions |
| text-muted | `#7D8795` | Timestamps, category labels, metadata |
| text-disabled | `#5C6470` | Disabled / placeholder |
> Category names in nav and cards use `text-muted` — no per-category color coding. Colors are reserved for status badges only.
---
## Directus Data Model
Create these collections in Directus admin:
### `articles`
| Field | Type | Notes |
|---|---|---|
| title | string | required |
| slug | string | unique, required — used in URL |
| status | string | `published` / `draft` — Directus built-in |
| content | rich text (WYSIWYG) | full article body |
| excerpt | text | short blurb shown on cards |
| featured_image | file (M2O → directus_files) | hero image |
| published_at | datetime | set on first publish |
| is_featured | boolean | controls hero slot on homepage |
| seo_title | string | optional override for `<title>` |
| seo_description | string | optional override for meta description |
| category | M2O → categories | required |
| tags | M2M → tags (junction: articles_tags) | optional |
### `categories`
| Field | Type | Notes |
|---|---|---|
| name | string | Anime, VTubers, Manga, Games, Music, Japan, Culture, Industry |
| slug | string | unique — used in URL |
| description | text | shown on category listing page |
### `tags`
| Field | Type | Notes |
|---|---|---|
| name | string | e.g. "hololive", "Spring 2025" |
| slug | string | unique — for URL filtering |
### `site_settings` (singleton)
| Field | Type | Notes |
|---|---|---|
| site_name | string | default: "Kotobane" |
| hero_article | M2O → articles | which article appears in the homepage hero |
| nav_categories | JSON / M2M → categories | ordered list for navbar |
---
## Directus Flow (webhook trigger)
Create one Flow in Directus:
- **Trigger:** "Event Hook" on `articles.items.update` and `articles.items.create` where `status = published`
- **Action:** Webhook POST to `https://yourdomain.com/api/revalidate`
- **Payload:** `{ "secret": "REVALIDATE_SECRET", "article_id": "{{$trigger.key}}" }`
The revalidate endpoint receives the article ID, then queries Directus itself to get the full `slug` and `category.slug` — this is more reliable than relying on the Flow payload containing all fields (Directus only sends changed fields, so `category` may be absent if only `status` changed).
Also add a second Flow trigger on `site_settings` update to revalidate the homepage.
---
## Next.js Project Structure
```
kotobane/
├── app/
│ ├── layout.tsx # Root layout: fonts, navbar, footer
│ ├── page.tsx # Homepage (ISR)
│ ├── [category]/
│ │ ├── page.tsx # Category listing (ISR)
│ │ └── [slug]/
│ │ └── page.tsx # Article detail (ISR)
│ └── api/
│ └── revalidate/
│ └── route.ts # POST endpoint for Directus webhook
├── components/
│ ├── layout/
│ │ ├── Navbar.tsx # Logo, nav categories, search icon
│ │ └── Footer.tsx
│ ├── home/
│ │ ├── HeroSection.tsx # Featured article hero
│ │ └── ArticleGrid.tsx # Latest articles grid
│ ├── article/
│ │ ├── ArticleCard.tsx # Card used in grids and listings
│ │ ├── ArticleBody.tsx # Rich text renderer
│ │ └── TagRow.tsx
│ └── search/
│ └── SearchOverlay.tsx # Cmd+K command palette search
├── lib/
│ ├── directus.ts # Directus SDK client + typed fetch helpers
│ └── types.ts # TypeScript types mirroring Directus collections
├── tailwind.config.ts # Design system tokens (colors, spacing, fonts)
└── .env.local # DIRECTUS_URL, DIRECTUS_TOKEN, REVALIDATE_SECRET
```
---
## Pages
### `/` — Homepage (`app/page.tsx`)
- Fetch: `site_settings` (hero article) + latest 12 published articles
- Sections: Hero → Latest grid → articles grouped by 23 featured categories
- ISR: `revalidate = false` (on-demand only, via webhook)
### `/[category]` — Category listing (`app/[category]/page.tsx`)
- Fetch: category by slug + articles filtered by that category, sorted by `published_at` desc
- `generateStaticParams`: pre-build all category slugs at deploy time
- Pagination: load more button (client-side, hits Directus with `offset`)
### `/[category]/[slug]` — Article (`app/[category]/[slug]/page.tsx`)
- Fetch: single article by slug with `fields=*,tags.*,category.*`
- Sections: Hero image → title → metadata → rich text body → tags → related articles (same category, limit 4)
- `generateMetadata`: uses `seo_title` / `seo_description` if set, falls back to article title/excerpt
- `generateStaticParams`: pre-build all published articles at deploy time; `dynamicParams = true` (Next.js default) ensures new articles published after deploy are generated on first request via ISR — no rebuild needed
### `/api/revalidate` — Webhook handler (`app/api/revalidate/route.ts`)
- Accepts POST with `{ secret, slug, category_slug }`
- Validates secret matches `REVALIDATE_SECRET` env var
- Calls `revalidatePath('/')`, `revalidatePath('/[category]', 'page')`, `revalidatePath('/[category]/[slug]', 'page')`
### Search — `SearchOverlay` (global, client component)
- Triggered by Cmd+K or clicking the search icon in Navbar
- Debounced input (300ms) → live GET to Directus `/items/articles?search=query&limit=8&fields=title,slug,category.slug`
- Results rendered as keyboard-navigable list (↑↓ Enter)
- No ISR needed — always queries Directus live
---
## Design System Integration (`tailwind.config.ts`)
Map the design system tokens directly into Tailwind:
```ts
colors: {
bg: { DEFAULT: '#0D0D14', elevated: '#171A21', card: '#1D212B' },
border: { DEFAULT: '#2A3140' },
accent: { DEFAULT: '#00B4D8', hover: '#00D4FF' },
violet: '#7C3AED',
coral: '#D64545',
amber: '#FFB300',
green: '#00D166',
text: { primary: '#F3F5F7', secondary: '#B8C0CC', muted: '#7D8795', disabled: '#5C6470' },
},
borderRadius: {
sm: '8px', md: '12px', lg: '16px', xl: '20px', '2xl': '28px',
},
```
Standard Tailwind spacing (multiples of 4/8) maps cleanly to the 8px spacing system.
---
## Directus SDK Client (`lib/directus.ts`)
Use `@directus/sdk` with a typed schema. Key helpers:
```ts
// Singleton client
const directus = createDirectus(DIRECTUS_URL).with(rest())
// Typed fetch helpers
getArticles(options) // list with filters
getArticleBySlug(slug) // single article
getCategoryBySlug(slug) // single category
getAllCategories() // for navbar + generateStaticParams
getSiteSettings() // homepage hero + nav order
searchArticles(query) // search overlay
```
All helpers use `readItems` / `readSingleton` from the SDK. Token auth via `staticToken(DIRECTUS_TOKEN)` — create a read-only token in Directus for the frontend.
---
## Environment Variables
```
DIRECTUS_URL=https://cms.achmad.dev
DIRECTUS_TOKEN=<read-only static token from Directus>
REVALIDATE_SECRET=<random string, shared with Directus Flow>
```
---
## Deployment (VPS)
- Run Next.js as a Node server: `next build && next start -p 3000`
- Use PM2 or systemd to keep it running
- Nginx reverse proxy in front (handles SSL, proxies to port 3000)
- Directus already running on same VPS (separate port/domain)
---
## Implementation Order
1. **Bootstrap**`create-next-app`, install deps (`@directus/sdk`, `lucide-react`), configure Tailwind with design tokens, set up fonts
2. **Directus collections** — create all 4 collections + junction table in Directus admin, add a few test articles
3. **SDK client**`lib/directus.ts` with typed helpers + `lib/types.ts`
4. **Layout**`Navbar` + `Footer`, root `layout.tsx`
5. **Homepage**`HeroSection` + `ArticleGrid` + `page.tsx`
6. **Article page**`ArticleCard`, `ArticleBody` (rich text renderer), full article page
7. **Category page** — listing with pagination
8. **Search overlay**`SearchOverlay` component wired to Cmd+K
9. **Revalidate endpoint**`/api/revalidate` + test with curl
10. **Directus Flow** — set up webhook trigger in Directus
11. **SEO**`generateMetadata` on article + category pages, `sitemap.ts`, `robots.ts`
12. **Deploy** — build, PM2/systemd, Nginx config
---
## Verification
- Add a test article in Directus → confirm it appears on homepage within seconds (no restart)
- Update an article → confirm revalidate endpoint fires and page updates
- Test search overlay with Cmd+K
- Check mobile layout (single-column per design system)
- Confirm Directus going offline does not break cached pages for visitors
- Lighthouse score: aim for 90+ performance (static HTML + lazy images)
@@ -0,0 +1,263 @@
# Kotobane — Brand & Design System
> "Words carried on wings." — 言羽
Kotobane is a Japanese pop-culture news site covering VTubers, Anime, Manga, Games, Music, Japan, Culture, and Industry.
---
## Brand Identity
**What it should feel like:**
- A modern Japanese digital newspaper
- A refined anime culture publication
- A calm but fast-moving stream of information
**What it must never feel like:**
- A loud clickbait anime blog
- A generic SaaS dashboard
- An overly playful otaku forum
**Emotional keywords:** intelligent · fast · atmospheric · minimal · editorial · futuristic · trustworthy
---
## Color Palette
### Brand Accent
| Token | Hex | Usage |
|---|---|---|
| accent | `#00B4D8` | Buttons, links, active nav, "Read →", focus rings |
| accent-hover | `#00D4FF` | Hover state |
### Status Colors
| Token | Hex | Usage |
|---|---|---|
| violet | `#7C3AED` | Featured / hero badges, secondary actions |
| coral | `#D64545` | Trending, breaking news, errors |
| amber | `#FFB300` | New indicator, alerts, warnings |
| green | `#00D166` | Published status, success |
### Backgrounds
| Token | Hex | Usage |
|---|---|---|
| bg | `#0D0D14` | Main page background |
| bg-elevated | `#171A21` | Navbar, elevated surfaces |
| bg-card | `#1D212B` | Cards, inputs |
| border | `#2A3140` | All borders and dividers |
### Text
| Token | Hex | Usage |
|---|---|---|
| text-primary | `#F3F5F7` | Headlines, body |
| text-secondary | `#B8C0CC` | Excerpts, captions |
| text-muted | `#7D8795` | Timestamps, category labels, metadata |
| text-disabled | `#5C6470` | Disabled / placeholder |
**Rules:**
- Category names are always `text-muted` — never color-coded
- No gradients on UI elements — flat colors only
- Secondary accent colors are used sparingly, only for semantic meaning (trending, featured, etc.)
---
## Typography
**Font stack:**
- English: **Inter**
- Japanese: **Noto Sans JP**
Load both via `next/font`. Use Noto Sans JP as a fallback for any Japanese text in article titles or tags.
### Scale
| Role | Size | Weight |
|---|---|---|
| Hero headline | 4856px | 700 |
| Article title | 3440px | 700 |
| Section title | 2224px | 700 |
| Card title | 1315px | 600 |
| Primary body | 16px | 400 |
| Compact body (excerpts) | 13px | 400 |
| Metadata / labels | 1012px | 500600 |
---
## Spacing
Base unit: **8px**. Only use these values — no arbitrary spacing.
`4 · 8 · 12 · 16 · 24 · 32 · 40 · 48 · 64 · 80 · 96`
---
## Layout
| Breakpoint | Grid | Max content width |
|---|---|---|
| Desktop | 12-column | 1200px |
| Tablet | 8-column | — |
| Mobile | Single column, content-first | — |
Article body width: **760780px** (centered).
---
## Border Radius
| Token | Value | Usage |
|---|---|---|
| radius-sm | 8px | Inputs, small chips |
| radius-md | 12px | Cards |
| radius-lg | 16px | Large cards, hero panels |
| radius-xl | 20px | Modals |
| radius-2xl | 28px | Full-page panels |
---
## Shadows
Shadows must be soft and diffused. No hard black shadows.
| Token | Usage |
|---|---|
| shadow-sm | Subtle card lift |
| shadow-md | Hover elevation |
| shadow-lg | Modals, overlays |
---
## Motion
| Property | Value |
|---|---|
| Duration | 150ms 250ms |
| Easing | `cubic-bezier(0.22, 1, 0.36, 1)` |
**Rules:**
- Motion communicates hierarchy and guides focus — never decorates
- No bouncing, no exaggerated scaling, no neon pulse effects
- Always respect `prefers-reduced-motion`
---
## Components
### Buttons
- **Primary:** solid `#00B4D8` background, black text
- **Secondary:** transparent background, `#00B4D8` border and text
- Never: pill buttons, excessive gradients, loud CTA colors
### Cards
- Background: `bg-card`
- Border: 1px solid `border`
- Radius: `radius-md` (12px)
- Hover: border lightens slightly (`#3a4560`), no scale transform
### Badges
- Use flat solid backgrounds, bold uppercase text, tight padding
- Text on dark badges: white. Text on light badges (amber, green): black
---
## Icons
Style: **outline, geometric, lightweight**
Recommended library: **Lucide React**
---
## Article Pages
Article pages are the most important pages. They must feel:
- Quiet and focused
- Distraction-free
- Premium and readable
Avoid: intrusive widgets, autoplay media, excessive sidebar clutter.
---
## Editorial Tone
Articles should be:
- Concise and informative
- Culturally aware and contextual
- Written in a calm, analytical voice — modern magazine journalism
**Good headlines:** direct, informative, contextual
**Bad headlines:** "YOU WON'T BELIEVE", "THIS CHANGES EVERYTHING", fake urgency
Never: clickbait, ragebait, excessive hype.
---
## Accessibility
Mandatory requirements:
- WCAG AA contrast minimum on all text
- Keyboard navigation throughout
- Visible focus states (use `ring-accent` / `#00B4D8`)
- Semantic HTML with proper heading hierarchy (one `<h1>` per page)
- `alt` text on all images
- `prefers-reduced-motion` support
---
## Performance
- SSR / ISR — no client-heavy rendering for core content
- Lazy-load all images below the fold
- `next/image` for all images (automatic WebP + sizing)
- Minimal layout shift (CLS < 0.1)
- Fonts loaded via `next/font` (no FOUT)
---
## SEO
Priority order: **readers → archives → search engines**
SEO must never dictate editorial tone. Every article and category page gets:
- `<title>``seo_title` if set, otherwise article title + " — Kotobane"
- `<meta description>``seo_description` if set, otherwise excerpt
- `<link rel="canonical">`
- Open Graph tags for social sharing
---
## AI Agent Rules
When modifying any UI in this project:
**Must:**
- Maintain spacing system (8px grid, no arbitrary values)
- Preserve typography hierarchy
- Use flat colors only — no gradients on UI elements
- Use accent colors sparingly and only for their defined semantic purpose
- Keep category labels in `text-muted` — never color-code them
**Must not:**
- Inject gradients onto UI elements
- Overcrowd layouts
- Add clickbait-style visual treatments
- Break the spacing system
- Add unnecessary UI chrome or decorations
- Add per-category color coding
---
## Logo
The 言羽 kanji mark serves as the primary logo in all contexts.
Characteristics:
- Rendered in `accent` color (`#00B4D8`) on dark backgrounds
- High letter-spacing in the wordmark (tracking: 3px+)
- Works at small sizes — suitable as favicon treatment
- No gradients on the logo
---
*Design reference with visual mockups: `docs/design-reference/kotobane-reference.html`*
*Implementation spec: `docs/superpowers/specs/2026-05-28-kotobane-cms-website-design.md`*