Files
openpylon/docs/plans/2026-02-15-motion-darkmode-titlebar-design.md
Your Name 14c4e82070 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
2026-02-15 20:51:53 +02:00

189 lines
7.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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