# 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