docs: add motion, dark mode & custom titlebar design

Design document covering three visual improvements:
- Playful bouncy Framer Motion animations for every interaction
- Subtle dark mode lift for HDR monitors (18-22% → 25-30% lightness)
- Custom window titlebar merged into TopBar with accent-colored controls
This commit is contained in:
Your Name
2026-02-15 20:51:53 +02:00
parent 46c0df3ab8
commit 1e487e95a1

View File

@@ -0,0 +1,188 @@
# Motion, Dark Mode & Custom Titlebar Design
**Date:** 2026-02-15
**Goal:** Add playful bouncy animations everywhere, lighten dark mode for HDR monitors, and implement custom window decorations merged into the TopBar
**Approach:** Centralized motion system + CSS variable tuning + Tauri decoration override
---
## 1. Motion System Foundation
Create `src/lib/motion.ts` — shared animation config imported by all components.
### Spring Presets (Bouncy Profile)
- **bouncy** — stiffness: 400, damping: 15, mass: 0.8 (main preset, visible overshoot)
- **snappy** — stiffness: 500, damping: 20 (micro-interactions — buttons, toggles)
- **gentle** — stiffness: 200, damping: 20 (larger elements — modals, page transitions)
- **wobbly** — stiffness: 300, damping: 10 (playful emphasis — toasts, notifications)
### Reusable Variants
- `fadeSlideUp` — enters from below with opacity fade (cards, list items)
- `fadeSlideDown` — enters from above (dropdowns, menus)
- `scaleIn` — scales from 0.9 to 1 with bounce (modals, popovers)
- `staggerContainer` — parent variant that staggers children
### Stagger Helper
`staggerChildren(delay = 0.04)` — generates parent transition variants for cascading entrances.
---
## 2. Component-by-Component Motion Rollout
### Page Transitions (App.tsx)
- Wrap view switch in `AnimatePresence mode="wait"`
- Board-list exits with fade+slide-left, board enters with fade+slide-right
- Uses `gentle` spring
### Board List (BoardList.tsx)
- Board cards stagger in on mount using `staggerContainer`
- Each BoardCard uses `fadeSlideUp` entrance
- Empty state fades in
### Board View (BoardView.tsx)
- Columns stagger in from left to right on mount (0.06s delay each)
- Cards within each column stagger in (0.03s delay)
### Card Thumbnails (CardThumbnail.tsx)
- Migrate existing spring to shared `bouncy` preset
- `whileHover` scale 1.02 with shadow elevation
- `whileTap` scale 0.98
### Card Detail Modal (CardDetailModal.tsx)
- **Shared layout animation** — CardThumbnail gets `layoutId={card-${card.id}}`, modal wrapper gets same layoutId
- Card morphs into the modal on open — hero transition
- Backdrop blurs in with animated opacity + backdropFilter
- Modal content sections stagger in after layout animation
### Column Header (ColumnHeader.tsx)
- Dropdown menu items stagger in with `fadeSlideDown`
### TopBar
- Buttons have `whileHover` and `whileTap` micro-animations
- Saving status text fades in/out with AnimatePresence
### Toast Notifications (ToastContainer.tsx)
- Migrate to `wobbly` spring for extra personality
- Exit slides down + fades
### Settings Dialog
- Tab content crossfades with AnimatePresence
- Accent color swatches have `whileHover` scale pulse
### Command Palette
- Results stagger in as you type
---
## 3. Gesture-Reactive Drag & Drop
Override dnd-kit's default drag overlay with Framer Motion-powered custom overlay.
- **On drag start:** Card lifts with `scale: 1.05`, box-shadow, slight rotate based on grab offset
- **During drag:** Card tilts based on pointer velocity (useMotionValue + useTransform). Max tilt: ~5 degrees
- **On drop:** Spring back to `scale: 1, rotate: 0`. Target column cards spring apart using `layout` prop
- dnd-kit handles position/sorting logic; we layer gesture transforms on top
---
## 4. Dark Mode — Subtle Lift for HDR
### Pylon Dark Variables (in `.dark {}`)
| Variable | Current | New |
|----------|---------|-----|
| `--pylon-bg` | `oklch(18% 0.01 50)` | `oklch(25% 0.012 50)` |
| `--pylon-surface` | `oklch(22% 0.01 50)` | `oklch(29% 0.012 50)` |
| `--pylon-column` | `oklch(20% 0.012 50)` | `oklch(27% 0.014 50)` |
| `--pylon-text` | `oklch(90% 0.01 50)` | `oklch(92% 0.01 50)` |
| `--pylon-text-secondary` | `oklch(55% 0.01 50)` | `oklch(58% 0.01 50)` |
| `--pylon-accent` | `oklch(60% 0.12 160)` | `oklch(62% 0.13 160)` |
| `--pylon-danger` | `oklch(60% 0.18 25)` | `oklch(62% 0.18 25)` |
### Shadcn Dark Variables
| Variable | Current | New |
|----------|---------|-----|
| `--background` | `oklch(0.145 0 0)` | `oklch(0.22 0 0)` |
| `--card`, `--popover` | `oklch(0.205 0 0)` | `oklch(0.27 0 0)` |
| `--secondary`, `--muted`, `--accent` | `oklch(0.269 0 0)` | `oklch(0.32 0 0)` |
| `--border` | `oklch(1 0 0 / 10%)` | `oklch(1 0 0 / 12%)` |
| `--input` | `oklch(1 0 0 / 15%)` | `oklch(1 0 0 / 18%)` |
| `--sidebar` | `oklch(0.205 0 0)` | `oklch(0.27 0 0)` |
| `--sidebar-accent` | `oklch(0.269 0 0)` | `oklch(0.32 0 0)` |
Color scheme (hue 50 warmth) preserved. Slight chroma bump for HDR vibrancy.
---
## 5. Custom Window Titlebar
### Tauri Configuration
Set `"decorations": false` in `tauri.conf.json` to remove native OS titlebar.
### TopBar Integration
Window controls added to far right of existing TopBar, after a thin vertical separator:
```
[Back] .......... [Board Title] .......... [Undo][Redo][Settings][Save][Search][Gear] | [—][□][×]
```
### WindowControls Component
Inline in TopBar or extracted to `src/components/layout/WindowControls.tsx`.
**Buttons:**
- Minimize: `Minus` icon from Lucide → `getCurrentWindow().minimize()`
- Maximize/Restore: `Square` / `Copy` icon → `getCurrentWindow().toggleMaximize()`
- Close: `X` icon → `getCurrentWindow().close()`
**Styling:**
- 32x32px hit area, 16x16px icons
- Default: `text-pylon-text-secondary`
- Hover: Background with accent at 10% opacity
- Close hover: `pylon-danger` at 15% opacity (red highlight — convention)
- All have Framer Motion `whileHover` and `whileTap` springs
**State tracking:**
- Listen to `getCurrentWindow().onResized()` for maximize state
- Query `isMaximized()` on mount for initial icon
- `data-tauri-drag-region` stays on header; window buttons do NOT propagate drag
---
## Files Affected
### New Files
- `src/lib/motion.ts` — shared spring presets, variants, helpers
### Modified Files
- `src/App.tsx` — AnimatePresence page transitions
- `src/index.css` — dark mode color value updates
- `src/components/layout/TopBar.tsx` — window controls, motion on buttons
- `src/components/layout/AppShell.tsx` — support page transition wrapper
- `src/components/boards/BoardList.tsx` — stagger animation on board cards
- `src/components/boards/BoardCard.tsx` — fadeSlideUp entrance, hover/tap
- `src/components/board/BoardView.tsx` — column stagger, gesture-reactive drag overlay
- `src/components/board/KanbanColumn.tsx` — card stagger, layout animation for reorder
- `src/components/board/CardThumbnail.tsx` — shared layoutId, bouncy preset, hover/tap
- `src/components/board/ColumnHeader.tsx` — dropdown animation
- `src/components/card-detail/CardDetailModal.tsx` — shared layout animation (hero), content stagger
- `src/components/toast/ToastContainer.tsx` — wobbly spring
- `src/components/settings/SettingsDialog.tsx` — tab crossfade, swatch hover
- `src/components/command-palette/CommandPalette.tsx` — result stagger
- `src/components/shortcuts/ShortcutHelpModal.tsx` — entrance animation
- `src-tauri/tauri.conf.json` — decorations: false
## Implementation Order
1. Motion system foundation (`src/lib/motion.ts`)
2. Dark mode CSS variable updates
3. Custom titlebar (Tauri config + WindowControls)
4. Page transitions (App.tsx + AnimatePresence)
5. Board list animations (stagger + BoardCard)
6. Board view column stagger + card stagger
7. Card thumbnail hover/tap + shared layoutId
8. Card detail modal shared layout animation
9. Gesture-reactive drag overlay
10. Micro-interactions (TopBar, ColumnHeader dropdowns)
11. Toast, Settings, Command Palette, ShortcutHelp animations
12. Polish pass — verify all springs feel cohesive, test reduced-motion