# Git Pills — Design Spec
**Data:** 2026-03-31  
**Status:** Aprovado pelo usuário  
**Abordagem escolhida:** B — SPA leve + Remotion separado

---

## 1. Visão Geral

**Git Pills** é um site interativo estilo "dev newsletter dashboard" que cataloga e explica 28 repositórios/links curados da área de Claude Code, AI Tools, Awesome Lists e mídia. O objetivo é permitir que desenvolvedores descubram, entendam e usem rapidamente essas ferramentas.

**URL de produção:** `https://gitpills.ddw1sl.easypanel.host`  
**Docs:** `https://docs-gitpills.ddw1sl.easypanel.host`

---

## 2. Arquitetura

```
githunter/
├── src/                          ← App principal React 18 + Vite 6 + TypeScript
│   ├── features/
│   │   ├── pills/                ← cards, modais, views (Grid/Kanban/Grafo/Feed)
│   │   │   ├── components/
│   │   │   │   ├── PillCard.tsx
│   │   │   │   ├── PillCardCompact.tsx
│   │   │   │   ├── PillDrawer.tsx
│   │   │   │   ├── PillDrawerTabs.tsx
│   │   │   │   └── PillNode.tsx
│   │   │   ├── views/
│   │   │   │   ├── GridView.tsx
│   │   │   │   ├── KanbanView.tsx
│   │   │   │   ├── GraphView.tsx
│   │   │   │   └── FeedView.tsx
│   │   │   └── PillsPage.tsx
│   │   ├── filters/
│   │   │   ├── SearchBar.tsx
│   │   │   ├── CategoryFilter.tsx
│   │   │   └── ViewToggle.tsx
│   │   └── intro/
│   │       └── SplashScreen.tsx
│   ├── shared/
│   │   ├── components/ui/        ← shadcn/ui
│   │   ├── components/common/
│   │   │   ├── CategoryBadge.tsx
│   │   │   ├── DifficultyBadge.tsx
│   │   │   └── TypeIcon.tsx
│   │   ├── data/
│   │   │   └── pills.data.ts
│   │   ├── store/
│   │   │   └── pills.store.ts    ← Zustand store (estado global)
│   │   └── lib/
│   │       ├── utils.ts
│   │       └── theme.ts
│   ├── App.tsx
│   ├── main.tsx
│   └── index.css
├── remotion/                     ← Subpasta independente (não importa src/)
│   ├── PillOfTheDay.tsx
│   ├── Root.tsx
│   ├── render.ts
│   └── package.json             ← dependências Remotion isoladas
├── docs/
│   ├── tasks/
│   ├── guides/
│   └── index.html
├── public/
│   └── videos/                  ← MP4s gerados pelo Remotion (servidos estaticamente)
├── project-infra.json
├── package.json
└── vite.config.ts
```

---

## 3. Stack Tecnológica

| Camada | Tecnologia | Versão |
|--------|-----------|--------|
| Framework | React | 18 |
| Build | Vite | 6 |
| Linguagem | TypeScript | 5.7 |
| Estilização | Tailwind CSS | 3.4 |
| Animações Tailwind | tailwindcss-animate | latest |
| Componentes UI | shadcn/ui + Radix UI | latest |
| Animações app | Framer Motion | latest |
| Grafo | react-force-graph-2d | latest |
| Ícones | Lucide React | latest |
| Estado global | Zustand | 5 |
| Geração de vídeo | Remotion | 4 |
| Package Manager | npm | — |

---

## 4. Data Model

```ts
// Tipos explícitos — sem ambiguidade

type PillType = 'github' | 'youtube' | 'twitter' | 'website'

type PillCategory =
  | 'claude-code-tools'
  | 'awesome-lists'
  | 'official'
  | 'media'

type Difficulty = 'iniciante' | 'intermediário' | 'avançado'

interface Pill {
  id: string                    // slug kebab-case único: "hermes-agent"
  name: string                  // "Hermes Agent"
  category: PillCategory        // enum explícito acima
  url: string                   // URL completa do recurso original
  type: PillType                // enum explícito acima
  stars?: number                // estrelas GitHub (undefined para não-GitHub)
  tags: string[]                // ex: ["AI", "agents", "claude"] — lowercase
  headline: string              // máx 80 chars, 1 linha
  problem: string               // problema que resolve — máx 200 chars
  solution: string              // como resolve — máx 300 chars
  howToUse: string[]            // passos numerados — máx 6 itens
  useCases: string[]            // casos de uso — máx 4 itens
  difficulty: Difficulty        // enum explícito acima
  highlight?: boolean           // true = "Pill do Dia" (só 1 por vez)
  color: string                 // hex (#RRGGBB) — cor do node no grafo e accent do card
  relatedIds?: string[]         // IDs de pills relacionadas → arestas no grafo
}
```

---

## 5. Catálogo das 28 Pills

### claude-code-tools (15 itens)
| ID | Nome | URL |
|----|------|-----|
| ruflo | ruflo | https://github.com/ruvnet/ruflo |
| vibe-voice | VibeVoice | https://github.com/microsoft/VibeVoice |
| claude-code-best-practice | Claude Code Best Practice | https://github.com/shanraisshan/claude-code-best-practice |
| claude-howto | Claude HowTo | https://github.com/luongnv89/claude-howto |
| hermes-agent | Hermes Agent | https://github.com/NousResearch/hermes-agent |
| project-nomad | Project Nomad | https://github.com/Crosstalk-Solutions/project-nomad |
| everything-claude-code | Everything Claude Code | https://github.com/affaan-m/everything-claude-code |
| awesome-claude-code | Awesome Claude Code | https://github.com/hesreallyhim/awesome-claude-code |
| oh-my-claudecode | Oh My Claude Code | https://github.com/yeachan-heo/oh-my-claudecode |
| cli-anything | CLI-Anything | https://github.com/HKUDS/CLI-Anything |
| claude-mem | Claude Mem | https://github.com/thedotmack/claude-mem |
| superpowers | Superpowers | https://github.com/obra/superpowers |
| awesome-claude-skills | Awesome Claude Skills | https://github.com/ComposioHQ/awesome-claude-skills |
| claude-code-templates | Claude Code Templates | https://github.com/davila7/claude-code-templates |
| aitmpl | AI Templates | https://www.aitmpl.com/ |

### official (3 itens)
| ID | Nome | URL |
|----|------|-----|
| anthropic-home | Anthropic | https://www.anthropic.com/ |
| claude-docs | Claude Platform Docs | https://platform.claude.com/docs/en/home |
| claude-release-notes | Claude Release Notes | https://platform.claude.com/docs/en/release-notes/overview |

### awesome-lists (5 itens)
| ID | Nome | URL |
|----|------|-----|
| awesome-selfhosted | Awesome Selfhosted | https://github.com/awesome-selfhosted/awesome-selfhosted |
| awesome-python | Awesome Python | https://github.com/vinta/awesome-python |
| awesome | Awesome | https://github.com/sindresorhus/awesome |
| book-of-secret-knowledge | Book of Secret Knowledge | https://github.com/trimstray/the-book-of-secret-knowledge |
| firecrawl-browser-comparison | Browser Automation Comparison | https://www.firecrawl.dev/blog/browser-automation-tools-comparison |

### media (5 itens)
| ID | Nome | URL |
|----|------|-----|
| github-trending | GitHub Trending | https://github.com/trending |
| bcherny-tweet | bcherny Tweet | https://x.com/bcherny/status/2017742752566632544 |
| yt-short-1 | YouTube Short #1 | https://www.youtube.com/shorts/riPfkB5j7ok |
| yt-short-2 | YouTube Short #2 | https://www.youtube.com/shorts/AWBsGEuVhuE |

---

## 6. Estado Global — Zustand Store

```ts
// src/shared/store/pills.store.ts
interface PillsStore {
  // Estado das views
  activeView: 'grid' | 'kanban' | 'graph' | 'feed'
  setActiveView: (view: ActiveView) => void

  // Filtros
  searchQuery: string            // string de busca — filtro em memória, debounce 200ms
  setSearchQuery: (q: string) => void
  selectedCategories: PillCategory[]   // multi-seleção (array vazio = todas)
  toggleCategory: (cat: PillCategory) => void

  // Drawer/Modal
  selectedPillId: string | null
  openDrawer: (id: string) => void
  closeDrawer: () => void

  // Derivados (computed via selectors)
  // filteredPills: Pill[] — calculado por selector, não no store
}
```

**Busca:** filtra pelos campos `name`, `headline`, `tags`, `problem` — busca local em memória com debounce de 200ms. Case-insensitive.

**Categorias:** multi-seleção. Array vazio = mostrar todas. Click numa categoria a adiciona/remove do array. Toggle visual com checkmark.

**Deep link:** o `selectedPillId` é sincronizado com query param `?pill=<id>` via `useSearchParams` do React Router, permitindo compartilhar link direto para uma pill específica.

---

## 7. Views Detalhadas

### View Grid (padrão)
- 4 colunas desktop (xl) / 3 colunas (lg) / 2 tablet (md) / 1 mobile (sm)
- Card: gradiente sutil por cor da categoria, `TypeIcon`, nome, headline, `CategoryBadge`, `DifficultyBadge`
- Hover: `scale(1.02)` + `box-shadow` glow via Framer Motion `whileHover`
- Click no card: `openDrawer(pill.id)`

### View Kanban
- 4 colunas fixas (não drag-and-drop — estado não persiste, é só visualização)
- Cada coluna = uma `PillCategory` com header colorido e contador de pills
- Cards compactos `PillCardCompact`: só nome + headline, 1 linha
- Scroll vertical independente dentro de cada coluna (overflow-y: auto)

### View Grafo
- `react-force-graph-2d` com nodes e arestas
- **Nodes:** uma pill = um node. Cor = `pill.color`. Tamanho = `Math.log(stars + 1) * 3` (min 8px, max 24px). Label = `pill.name`
- **Arestas:** definidas via `pill.relatedIds`. Bidirecional. Cor cinza semi-transparente.
- **Interação:** click no node → `openDrawer(pill.id)`. Zoom com scroll. Drag dos nodes.
- **Fallback:** se `relatedIds` vazio, nodes da mesma categoria têm arestas automáticas (força fraca)

### View Feed
- Lista vertical, gap-2, sem paginação (28 itens cabe em scroll)
- Cada item: `CategoryBadge` (pill colorida) + nome bold + headline + tags (badges inline)
- Busca destaca texto encontrado com `<mark>` estilizado
- Hover: background sutil, cursor pointer → `openDrawer(pill.id)`

### Drawer de Detalhe (lateral)
- Componente `Sheet` do shadcn/ui, `side="right"`, largura 480px (full em mobile)
- **Header:** `TypeIcon` + nome (h2) + `CategoryBadge` + stars (se GitHub) + botão "Abrir ↗"
- **Tabs** (shadcn/ui `Tabs`):
  - Tab 1 — **"O que é"**: `headline` (lead) + `problem` (parágrafo com ícone ⚠️) + `solution` (parágrafo com ícone ✅)
  - Tab 2 — **"Como usar"**: lista numerada de `howToUse[]` (máx 6 passos)
  - Tab 3 — **"Casos de uso"**: bullets de `useCases[]` (máx 4 itens) + `DifficultyBadge`
- **Footer:** botão "Copiar link" (clipboard API) + botão "Abrir no [GitHub/YouTube/Twitter/Site] ↗"

---

## 8. Splash Screen

- Aparece uma vez por sessão — flag `sessionStorage.getItem('pills-splash-seen')`
- Framer Motion: logo "Git Pills" com `initial={{ opacity:0, scale:0.8 }}` → `animate={{ opacity:1, scale:1 }}`, spring, 600ms
- Partículas: 20 pequenos badges de código (pill names) flutuando com motion aleatório
- Botão "ESC para pular" + click na tela pula → `sessionStorage.setItem('pills-splash-seen', '1')`
- Duração automática: 2.5s, então `exit` animation + unmount

---

## 9. Remotion — "Pill do Dia"

**Localização:** subpasta `/remotion/` com `package.json` próprio (Remotion e dependências separadas do app principal). Importa os dados das pills via JSON (não importa `src/` diretamente).

**Contrato de dados:**
```bash
# remotion/render.ts lê de remotion/pills.json (gerado por script)
npm run export-pills   # gera remotion/pills.json a partir de src/shared/data/pills.data.ts
npm run render -- --pill hermes-agent
# → public/videos/pill-of-the-day-hermes-agent.mp4
```

**Formato:** 1080x1920px, 30fps, 15 segundos (450 frames)

**Timeline:**
```
Frames 0–60    (0s–2s)   → Logo "Git Pills" fade in + scale spring
Frames 60–150  (2s–5s)   → "Pill do Dia:" + nome (typewriter: 1 char a cada 3 frames)
Frames 150–300 (5s–10s)  → Card animado: headline (slide up, frame 150) + problem (frame 180) + solution (frame 220)
Frames 300–390 (10s–13s) → Tags aparecem: stagger de 15 frames cada
Frames 390–450 (13s–15s) → CTA: URL do site + QR code (fade in)
```

**Na home:** `<video>` tag HTML5 no header, autoplay muted loop, apontando para `/videos/pill-of-the-day-<id>.mp4`. Pill com `highlight: true` é a ativa.

**Invocação:** manual pelo desenvolvedor. Não roda em CI automaticamente.

---

## 10. Infra e Deploy

| Serviço | Porta | PM2 Name | Domínio |
|---------|-------|----------|---------|
| Frontend (Vite build + preview) | 4100 | gitpills-frontend | gitpills.ddw1sl.easypanel.host |
| Docs (serve estático) | 4101 | docs-gitpills | docs-gitpills.ddw1sl.easypanel.host |

**Portas:** 4100/4101 — a confirmar com `netstat -tlnp` antes de subir.

**Build:** `npm run build` → `dist/`. Servido via `npx vite preview --port 4100 --host 0.0.0.0`.

**MP4s:** gerados em `public/videos/` → copiados para `dist/videos/` pelo Vite build automaticamente (pasta `public/` é copiada para `dist/` pelo Vite).

**Docs:** `npx serve docs/ -l 4101 --no-clipboard`

---

## 11. Testes (critérios de aceite)

### Critérios de pass/fail — browser via remote-browser

| Teste | Critério Pass |
|-------|--------------|
| Splash screen | Aparece no primeiro acesso, desaparece após 2.5s ou ESC |
| Splash skip | ESC e click na tela encerram o splash imediatamente |
| Splash não repete | Recarregar a página não mostra splash novamente na mesma sessão |
| View Grid | 28 cards visíveis, sem erros no console |
| View Kanban | 4 colunas visíveis com totais corretos por categoria |
| View Grafo | Nodes aparecem, click em node abre drawer |
| View Feed | 28 itens na lista |
| Busca | Digitar "claude" filtra para pills com "claude" em name/headline/tags/problem |
| Filtro categoria | Selecionar "official" mostra apenas 3 pills |
| Multi-seleção | Selecionar "official" + "media" mostra 8 pills |
| Drawer abre | Click em qualquer card abre drawer lateral com nome correto |
| Tabs do drawer | Todas as 3 tabs funcionam sem erro |
| Copiar link | Botão "Copiar link" copia URL da pill para clipboard |
| Deep link | `?pill=hermes-agent` abre o drawer diretamente |
| Dark mode | Toggle dark/light muda o tema visualmente |
| Dark mode persiste | Recarregar mantém o tema escolhido |
| Vídeo Remotion | `npm run export-pills && npm run render -- --pill hermes-agent` gera MP4 válido |
| Console limpo | Zero erros no console do browser após navegar em todas as views |

---

## 12. Componentes — Responsabilidades Claras

| Componente | Responsabilidade | Deps |
|-----------|-----------------|------|
| `SplashScreen` | Animação de entrada, flag sessionStorage | Framer Motion |
| `PillCard` | Card completo para Grid view | pill data, openDrawer |
| `PillCardCompact` | Card 1 linha para Kanban/Feed | pill data, openDrawer |
| `PillDrawer` | Sheet lateral, header e footer | shadcn Sheet, pill data |
| `PillDrawerTabs` | 3 tabs de conteúdo dentro do drawer | shadcn Tabs, pill data |
| `PillNode` | Node do grafo com tooltip | react-force-graph |
| `GridView` | Grid responsivo de PillCards | filteredPills |
| `KanbanView` | 4 colunas fixas de PillCardCompact | filteredPills |
| `GraphView` | Grafo de força com nodes/arestas | pills.data, react-force-graph-2d |
| `FeedView` | Lista densa de PillCardCompact | filteredPills, searchQuery (para highlight) |
| `PillsPage` | Composição de Header + ActiveView + Drawer | Zustand store |
| `SearchBar` | Input com debounce 200ms → setSearchQuery | Zustand |
| `CategoryFilter` | Checkboxes multi-seleção | Zustand |
| `ViewToggle` | 4 ícones toggle → setActiveView | Zustand |
| `CategoryBadge` | Badge colorido por categoria | pill.category |
| `DifficultyBadge` | Badge verde/amarelo/vermelho | pill.difficulty |
| `TypeIcon` | Ícone GitHub/YouTube/Twitter/Globe | pill.type |
