Files
openpylon/docs/plans/2026-02-15-motion-darkmode-titlebar-design.md
Your Name 1e487e95a1 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

7.5 KiB
Raw Blame History

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