diff --git a/docs/plans/2026-02-15-motion-darkmode-titlebar-implementation.md b/docs/plans/2026-02-15-motion-darkmode-titlebar-implementation.md
new file mode 100644
index 0000000..9a5a899
--- /dev/null
+++ b/docs/plans/2026-02-15-motion-darkmode-titlebar-implementation.md
@@ -0,0 +1,1273 @@
+# Motion, Dark Mode & Custom Titlebar Implementation Plan
+
+> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
+
+**Goal:** Add playful bouncy Framer Motion animations to every interaction, lighten dark mode for HDR monitors, and implement a custom window titlebar merged into the TopBar.
+
+**Architecture:** Centralized motion config (`src/lib/motion.ts`) defines shared spring presets and reusable variants. All components import from this single source. Dark mode is pure CSS variable tuning in `index.css`. Custom titlebar disables native decorations via Tauri config and embeds window controls into the existing TopBar component.
+
+**Tech Stack:** Framer Motion 12, React 19, TypeScript, Tauri v2 window API, OKLCH color space, dnd-kit, Tailwind CSS 4
+
+---
+
+### Task 1: Create shared motion config
+
+**Files:**
+- Create: `src/lib/motion.ts`
+
+**Step 1: Create the motion config file**
+
+Create `src/lib/motion.ts` with spring presets, reusable animation variants, and stagger helper:
+
+```typescript
+import type { Transition, Variants } from "framer-motion";
+
+// --- Spring presets ---
+
+export const springs = {
+ bouncy: { type: "spring", stiffness: 400, damping: 15, mass: 0.8 } as Transition,
+ snappy: { type: "spring", stiffness: 500, damping: 20 } as Transition,
+ gentle: { type: "spring", stiffness: 200, damping: 20 } as Transition,
+ wobbly: { type: "spring", stiffness: 300, damping: 10 } as Transition,
+};
+
+// --- Reusable variants ---
+
+export const fadeSlideUp: Variants = {
+ hidden: { opacity: 0, y: 12 },
+ visible: { opacity: 1, y: 0 },
+ exit: { opacity: 0, y: -8 },
+};
+
+export const fadeSlideDown: Variants = {
+ hidden: { opacity: 0, y: -12 },
+ visible: { opacity: 1, y: 0 },
+ exit: { opacity: 0, y: 8 },
+};
+
+export const fadeSlideLeft: Variants = {
+ hidden: { opacity: 0, x: 40 },
+ visible: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: -40 },
+};
+
+export const fadeSlideRight: Variants = {
+ hidden: { opacity: 0, x: -40 },
+ visible: { opacity: 1, x: 0 },
+ exit: { opacity: 0, x: 40 },
+};
+
+export const scaleIn: Variants = {
+ hidden: { opacity: 0, scale: 0.9 },
+ visible: { opacity: 1, scale: 1 },
+ exit: { opacity: 0, scale: 0.95 },
+};
+
+// --- Stagger container ---
+
+export function staggerContainer(staggerDelay = 0.04): Variants {
+ return {
+ hidden: {},
+ visible: {
+ transition: {
+ staggerChildren: staggerDelay,
+ },
+ },
+ };
+}
+
+// --- Micro-interaction presets ---
+
+export const microInteraction = {
+ hover: { scale: 1.05 },
+ tap: { scale: 0.95 },
+};
+
+export const subtleHover = {
+ hover: { scale: 1.02 },
+ tap: { scale: 0.98 },
+};
+```
+
+**Step 2: Verify TypeScript compiles**
+
+Run: `npx tsc --noEmit`
+Expected: No errors from `src/lib/motion.ts`
+
+**Step 3: Commit**
+
+```bash
+git add src/lib/motion.ts
+git commit -m "feat: add shared motion config with spring presets and variants"
+```
+
+---
+
+### Task 2: Update dark mode CSS variables
+
+**Files:**
+- Modify: `src/index.css:101-140` (the `.dark` block)
+
+**Step 1: Update the `.dark` block in `src/index.css`**
+
+Replace the entire `.dark { ... }` block (lines 101-140) with these lightened values:
+
+```css
+.dark {
+ --background: oklch(0.22 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.27 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.27 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.32 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.32 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.32 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 12%);
+ --input: oklch(1 0 0 / 18%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.27 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.32 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 12%);
+ --sidebar-ring: oklch(0.556 0 0);
+ --pylon-bg: oklch(25% 0.012 50);
+ --pylon-surface: oklch(29% 0.012 50);
+ --pylon-column: oklch(27% 0.014 50);
+ --pylon-accent: oklch(62% 0.13 160);
+ --pylon-text: oklch(92% 0.01 50);
+ --pylon-text-secondary: oklch(58% 0.01 50);
+ --pylon-danger: oklch(62% 0.18 25);
+}
+```
+
+**Step 2: Visual test**
+
+Run: `npm run tauri dev`
+Toggle dark mode in Settings. Verify backgrounds are noticeably lighter (charcoal, not cave-black). Colors should still feel warm.
+
+**Step 3: Commit**
+
+```bash
+git add src/index.css
+git commit -m "feat: lighten dark mode for HDR monitors — bump pylon + shadcn values"
+```
+
+---
+
+### Task 3: Custom window titlebar — Tauri config + WindowControls component
+
+**Files:**
+- Modify: `src-tauri/tauri.conf.json:13-19` (windows config)
+- Create: `src/components/layout/WindowControls.tsx`
+- Modify: `src/components/layout/TopBar.tsx`
+
+**Step 1: Disable native decorations in tauri.conf.json**
+
+In `src-tauri/tauri.conf.json`, add `"decorations": false` to the window config object:
+
+```json
+"windows": [
+ {
+ "title": "OpenPylon",
+ "width": 1200,
+ "height": 800,
+ "minWidth": 800,
+ "minHeight": 600,
+ "decorations": false
+ }
+]
+```
+
+**Step 2: Create WindowControls component**
+
+Create `src/components/layout/WindowControls.tsx`:
+
+```tsx
+import { useState, useEffect } from "react";
+import { getCurrentWindow } from "@tauri-apps/api/window";
+import { motion } from "framer-motion";
+import { Minus, Square, Copy, X } from "lucide-react";
+import { springs } from "@/lib/motion";
+
+export function WindowControls() {
+ const [isMaximized, setIsMaximized] = useState(false);
+
+ useEffect(() => {
+ const appWindow = getCurrentWindow();
+
+ // Check initial state
+ appWindow.isMaximized().then(setIsMaximized);
+
+ // Listen for resize events to track maximize state
+ const unlisten = appWindow.onResized(async () => {
+ const maximized = await appWindow.isMaximized();
+ setIsMaximized(maximized);
+ });
+
+ return () => {
+ unlisten.then((fn) => fn());
+ };
+ }, []);
+
+ const appWindow = getCurrentWindow();
+
+ return (
+
+ {/* Separator */}
+
+
+ {/* Minimize */}
+
appWindow.minimize()}
+ className="flex size-8 items-center justify-center rounded-md text-pylon-text-secondary transition-colors hover:bg-pylon-accent/10 hover:text-pylon-text"
+ whileHover={{ scale: 1.1 }}
+ whileTap={{ scale: 0.9 }}
+ transition={springs.snappy}
+ aria-label="Minimize"
+ >
+
+
+
+ {/* Maximize / Restore */}
+
appWindow.toggleMaximize()}
+ className="flex size-8 items-center justify-center rounded-md text-pylon-text-secondary transition-colors hover:bg-pylon-accent/10 hover:text-pylon-text"
+ whileHover={{ scale: 1.1 }}
+ whileTap={{ scale: 0.9 }}
+ transition={springs.snappy}
+ aria-label={isMaximized ? "Restore" : "Maximize"}
+ >
+ {isMaximized ? (
+
+ ) : (
+
+ )}
+
+
+ {/* Close */}
+
appWindow.close()}
+ className="flex size-8 items-center justify-center rounded-md text-pylon-text-secondary transition-colors hover:bg-pylon-danger/15 hover:text-pylon-danger"
+ whileHover={{ scale: 1.1 }}
+ whileTap={{ scale: 0.9 }}
+ transition={springs.snappy}
+ aria-label="Close"
+ >
+
+
+
+ );
+}
+```
+
+**Step 3: Integrate WindowControls into TopBar**
+
+In `src/components/layout/TopBar.tsx`, add this import at the top:
+
+```typescript
+import { WindowControls } from "@/components/layout/WindowControls";
+```
+
+Then, at the very end of the right section `` (after the Settings tooltip, before the closing `
` on line 241), add:
+
+```tsx
+
+```
+
+**Step 4: Test**
+
+Run: `npm run tauri dev`
+Verify: Native titlebar is gone. Custom minimize/maximize/close buttons appear in TopBar right side. Window dragging still works via the header. All three buttons function correctly.
+
+**Step 5: Commit**
+
+```bash
+git add src-tauri/tauri.conf.json src/components/layout/WindowControls.tsx src/components/layout/TopBar.tsx
+git commit -m "feat: custom window titlebar — remove native decorations, add WindowControls to TopBar"
+```
+
+---
+
+### Task 4: Page transitions in App.tsx
+
+**Files:**
+- Modify: `src/App.tsx`
+
+**Step 1: Add AnimatePresence page transitions**
+
+Replace the imports and the view rendering section in `src/App.tsx`.
+
+Add to imports:
+
+```typescript
+import { AnimatePresence, motion } from "framer-motion";
+import { springs, fadeSlideLeft, fadeSlideRight } from "@/lib/motion";
+```
+
+Replace line 76 (the view conditional):
+
+```tsx
+{view.type === "board-list" ? : }
+```
+
+With:
+
+```tsx
+
+ {view.type === "board-list" ? (
+
+
+
+ ) : (
+
+
+
+ )}
+
+```
+
+**Step 2: Test**
+
+Run: `npm run tauri dev`
+Navigate between board list and board view. Verify smooth slide transitions with gentle spring.
+
+**Step 3: Commit**
+
+```bash
+git add src/App.tsx
+git commit -m "feat: add AnimatePresence page transitions between views"
+```
+
+---
+
+### Task 5: Board list stagger animations
+
+**Files:**
+- Modify: `src/components/boards/BoardList.tsx`
+- Modify: `src/components/boards/BoardCard.tsx`
+
+**Step 1: Add stagger container to BoardList**
+
+In `src/components/boards/BoardList.tsx`, add imports:
+
+```typescript
+import { motion } from "framer-motion";
+import { staggerContainer, fadeSlideUp, springs, scaleIn } from "@/lib/motion";
+```
+
+Replace the empty state content (lines 27-44, the `` block) — wrap the inner content with motion for fade-in:
+
+```tsx
+
+
+
+ Welcome to OpenPylon
+
+
+ A local-first Kanban board that keeps your data on your machine.
+ Create your first board to get started.
+
+
+
+
setDialogOpen(true)}>
+
+ Create Board
+
+
+
+
+```
+
+Replace the board grid `
` (line 68):
+
+```tsx
+
+```
+
+With a `motion.div` stagger container:
+
+```tsx
+
+```
+
+**Step 2: Update BoardCard to use shared variants**
+
+In `src/components/boards/BoardCard.tsx`, add imports:
+
+```typescript
+import { fadeSlideUp, springs, subtleHover } from "@/lib/motion";
+```
+
+Replace the existing `` wrapper (lines 76-80):
+
+```tsx
+
+```
+
+With:
+
+```tsx
+
+```
+
+Remove the `index` prop from the `BoardCardProps` interface and function signature — stagger is now handled by the parent container. Also remove the `index = 0` default parameter.
+
+**Step 3: Commit**
+
+```bash
+git add src/components/boards/BoardList.tsx src/components/boards/BoardCard.tsx
+git commit -m "feat: add stagger animations to board list and board cards"
+```
+
+---
+
+### Task 6: Board view — column stagger + card stagger
+
+**Files:**
+- Modify: `src/components/board/BoardView.tsx`
+- Modify: `src/components/board/KanbanColumn.tsx`
+- Modify: `src/components/board/CardThumbnail.tsx`
+
+**Step 1: Add column stagger to BoardView**
+
+In `src/components/board/BoardView.tsx`, add imports:
+
+```typescript
+import { motion } from "framer-motion";
+import { staggerContainer, springs } from "@/lib/motion";
+```
+
+Wrap the column list container (line 323, the ``) — replace the `
` with ``:
+
+```tsx
+
+```
+
+Also change the closing `
` (line 378) to ``.
+
+**Step 2: Update KanbanColumn to use shared config**
+
+In `src/components/board/KanbanColumn.tsx`, update the import:
+
+```typescript
+import { motion, useReducedMotion } from "framer-motion";
+```
+
+Add:
+
+```typescript
+import { fadeSlideUp, springs, staggerContainer } from "@/lib/motion";
+```
+
+Replace the `
` props (lines 66-74):
+
+```tsx
+
+```
+
+With:
+
+```tsx
+
+```
+
+Wrap the `` card list (line 87) with stagger. Replace the ``:
+
+```tsx
+
+```
+
+With a `motion.ul`:
+
+```tsx
+
+```
+
+Change the closing ` ` to ``.
+
+**Step 3: Update CardThumbnail to use shared config**
+
+In `src/components/board/CardThumbnail.tsx`, add imports:
+
+```typescript
+import { fadeSlideUp, springs, subtleHover } from "@/lib/motion";
+```
+
+Replace the `` props (lines 49-56):
+
+```tsx
+`:
+
+After the `layout` prop, add:
+
+```tsx
+layoutId={`card-${card.id}`}
+```
+
+**Step 2: Replace Radix Dialog with custom Framer Motion modal in CardDetailModal**
+
+This is the most complex change. We replace the Radix `` with a custom modal using Framer Motion's `AnimatePresence` and `motion.div` with a matching `layoutId`.
+
+Rewrite `src/components/card-detail/CardDetailModal.tsx`. Replace the entire Dialog-based return block (lines 31-93) with:
+
+```tsx
+import { AnimatePresence, motion } from "framer-motion";
+import { springs, staggerContainer, fadeSlideUp } from "@/lib/motion";
+```
+
+Add these imports at the top of the file (alongside existing imports). Then replace the `return (...)` block:
+
+```tsx
+ return (
+
+ {open && card && cardId && (
+ <>
+ {/* Backdrop */}
+
+
+ {/* Modal */}
+
+
e.stopPropagation()}
+ >
+ {/* Close on Escape */}
+
+
+ {/* Hidden accessible description */}
+ Card detail editor
+
+
+ {/* Left panel: Title + Markdown (60%) */}
+
+
+
+
+
+
+
+
+ {/* Vertical separator */}
+
+
+ {/* Right sidebar (40%) */}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ )}
+
+ );
+```
+
+Add a small `EscapeHandler` component at the bottom of the file:
+
+```tsx
+function EscapeHandler({ onClose }: { onClose: () => void }) {
+ useEffect(() => {
+ function handleKeyDown(e: KeyboardEvent) {
+ if (e.key === "Escape") onClose();
+ }
+ document.addEventListener("keydown", handleKeyDown);
+ return () => document.removeEventListener("keydown", handleKeyDown);
+ }, [onClose]);
+ return null;
+}
+```
+
+Remove the Radix Dialog imports that are no longer needed:
+
+```typescript
+// REMOVE these imports:
+import {
+ Dialog,
+ DialogContent,
+ DialogHeader,
+ DialogTitle,
+ DialogDescription,
+} from "@/components/ui/dialog";
+```
+
+Keep `DialogTitle` if used by `InlineTitle` — actually `InlineTitle` uses `DialogTitle` on line 155. Replace that with a plain ``:
+
+In the `InlineTitle` component, change:
+
+```tsx
+ setEditing(true)}
+ className="cursor-pointer font-heading text-xl text-pylon-text transition-colors hover:text-pylon-accent"
+>
+ {title}
+
+```
+
+To:
+
+```tsx
+ setEditing(true)}
+ className="cursor-pointer font-heading text-xl text-pylon-text transition-colors hover:text-pylon-accent"
+>
+ {title}
+
+```
+
+**Step 3: Wrap the CardDetailModal in the parent with proper context**
+
+In `src/components/board/BoardView.tsx`, the `` is already rendered outside the ``. No changes needed — `AnimatePresence` is now inside `CardDetailModal` itself.
+
+**Step 4: Test**
+
+Run: `npm run tauri dev`
+Click a card. It should morph/expand from its position into the modal. The backdrop should blur in. Close should reverse the animation. Content sections should stagger in.
+
+**Step 5: Commit**
+
+```bash
+git add src/components/board/CardThumbnail.tsx src/components/card-detail/CardDetailModal.tsx
+git commit -m "feat: shared layout animation — card expands into detail modal"
+```
+
+---
+
+### Task 8: Gesture-reactive drag overlay
+
+**Files:**
+- Modify: `src/components/board/DragOverlayContent.tsx`
+- Modify: `src/components/board/BoardView.tsx`
+
+**Step 1: Create motion-powered drag overlay**
+
+Rewrite `src/components/board/DragOverlayContent.tsx` to use Framer Motion with gesture-reactive tilt:
+
+```tsx
+import { useRef } from "react";
+import { motion, useMotionValue, useTransform, animate } from "framer-motion";
+import type { Card, Column, Label } from "@/types/board";
+import { LabelDots } from "@/components/board/LabelDots";
+import { ChecklistBar } from "@/components/board/ChecklistBar";
+import { format, isPast, isToday } from "date-fns";
+import { springs } from "@/lib/motion";
+
+interface CardOverlayProps {
+ card: Card;
+ boardLabels: Label[];
+}
+
+export function CardOverlay({ card, boardLabels }: CardOverlayProps) {
+ const hasDueDate = card.dueDate != null;
+ const dueDate = hasDueDate ? new Date(card.dueDate!) : null;
+ const overdue = dueDate != null && isPast(dueDate) && !isToday(dueDate);
+
+ const rotate = useMotionValue(0);
+ const scale = useMotionValue(1.05);
+
+ // Track pointer movement for tilt
+ const lastX = useRef(0);
+
+ return (
+ {
+ const deltaX = e.clientX - lastX.current;
+ lastX.current = e.clientX;
+ // Tilt based on horizontal velocity, clamped to ±5 degrees
+ const tilt = Math.max(-5, Math.min(5, deltaX * 0.3));
+ animate(rotate, tilt, { type: "spring", stiffness: 300, damping: 20 });
+ }}
+ >
+ {/* Cover color bar */}
+ {card.coverColor && (
+
+ )}
+
+ {/* Label dots */}
+ {card.labels.length > 0 && (
+
+
+
+ )}
+
+ {/* Card title */}
+ {card.title}
+
+ {/* Footer row */}
+ {(hasDueDate || card.checklist.length > 0) && (
+
+ {dueDate && (
+
+ {format(dueDate, "MMM d")}
+
+ )}
+ {card.checklist.length > 0 && (
+
+ )}
+
+ )}
+
+ );
+}
+
+interface ColumnOverlayProps {
+ column: Column;
+}
+
+export function ColumnOverlay({ column }: ColumnOverlayProps) {
+ return (
+
+
+
+ {column.title}
+
+
+ {column.cardIds.length}
+
+
+
+ {column.cardIds.slice(0, 3).map((_, i) => (
+
+ ))}
+ {column.cardIds.length > 3 && (
+
+ +{column.cardIds.length - 3} more
+
+ )}
+
+
+ );
+}
+```
+
+**Step 2: Wrap DragOverlay in AnimatePresence**
+
+In `src/components/board/BoardView.tsx`, add `AnimatePresence` import:
+
+```typescript
+import { AnimatePresence } from "framer-motion";
+```
+
+Wrap the `` contents with AnimatePresence. Replace lines 382-388:
+
+```tsx
+
+ {activeCard ? (
+
+ ) : activeColumn ? (
+
+ ) : null}
+
+```
+
+With:
+
+```tsx
+
+
+ {activeCard ? (
+
+ ) : activeColumn ? (
+
+ ) : null}
+
+
+```
+
+Note: `dropAnimation={null}` disables dnd-kit's built-in drop animation since we handle it with Framer Motion.
+
+**Step 3: Commit**
+
+```bash
+git add src/components/board/DragOverlayContent.tsx src/components/board/BoardView.tsx
+git commit -m "feat: gesture-reactive drag overlay with tilt based on pointer velocity"
+```
+
+---
+
+### Task 9: TopBar micro-interactions
+
+**Files:**
+- Modify: `src/components/layout/TopBar.tsx`
+
+**Step 1: Add motion to TopBar buttons and saving status**
+
+In `src/components/layout/TopBar.tsx`, add imports:
+
+```typescript
+import { motion, AnimatePresence } from "framer-motion";
+import { springs } from "@/lib/motion";
+```
+
+Wrap each icon `` in the right section with `motion` micro-interactions. Since the `` component uses `asChild` pattern with Radix Tooltip, we wrap the `` itself. The simplest approach: convert each bare `` to a `motion.div` wrapper with `whileHover` and `whileTap`.
+
+Replace the saving status span (lines 201-204):
+
+```tsx
+{savingStatus && (
+
+ {savingStatus}
+
+)}
+```
+
+With AnimatePresence for fade in/out:
+
+```tsx
+
+ {savingStatus && (
+
+ {savingStatus}
+
+ )}
+
+```
+
+**Step 2: Commit**
+
+```bash
+git add src/components/layout/TopBar.tsx
+git commit -m "feat: add micro-animations to TopBar saving status"
+```
+
+---
+
+### Task 10: Toast animations — wobbly spring
+
+**Files:**
+- Modify: `src/components/toast/ToastContainer.tsx`
+
+**Step 1: Update toast spring to wobbly preset**
+
+In `src/components/toast/ToastContainer.tsx`, add import:
+
+```typescript
+import { springs } from "@/lib/motion";
+```
+
+Replace the `` transition (line 22):
+
+```tsx
+transition={{ type: "spring", stiffness: 400, damping: 25 }}
+```
+
+With:
+
+```tsx
+transition={springs.wobbly}
+```
+
+Also update the exit to slide down:
+
+```tsx
+exit={{ opacity: 0, y: 20, scale: 0.9 }}
+```
+
+**Step 2: Commit**
+
+```bash
+git add src/components/toast/ToastContainer.tsx
+git commit -m "feat: use wobbly spring for toast notifications"
+```
+
+---
+
+### Task 11: Settings dialog — tab crossfade + swatch hover
+
+**Files:**
+- Modify: `src/components/settings/SettingsDialog.tsx`
+
+**Step 1: Add AnimatePresence for tab content crossfade**
+
+In `src/components/settings/SettingsDialog.tsx`, add imports:
+
+```typescript
+import { motion, AnimatePresence } from "framer-motion";
+import { springs, scaleIn, microInteraction } from "@/lib/motion";
+```
+
+Wrap the tab content section (line 123, ``) with AnimatePresence:
+
+```tsx
+
+
+ {/* ... existing tab content unchanged ... */}
+
+
+```
+
+For the accent color swatch buttons (line 196-210), add `motion.button` with hover animation. Replace each `
` in the ACCENT_PRESETS map:
+
+```tsx
+ setAccentColor(hue)}
+ className="size-7 rounded-full"
+ style={{
+ backgroundColor: bg,
+ outline: settings.accentColor === hue ? "2px solid currentColor" : "none",
+ outlineOffset: "2px",
+ }}
+ whileHover={microInteraction.hover}
+ whileTap={microInteraction.tap}
+ transition={springs.snappy}
+ aria-label={label}
+ title={label}
+/>
+```
+
+Remove the `transition-transform hover:scale-110` Tailwind classes since Framer Motion handles it.
+
+**Step 2: Commit**
+
+```bash
+git add src/components/settings/SettingsDialog.tsx
+git commit -m "feat: add tab crossfade and swatch hover animations to settings"
+```
+
+---
+
+### Task 12: Shortcut help modal entrance animation
+
+**Files:**
+- Modify: `src/components/shortcuts/ShortcutHelpModal.tsx`
+
+**Step 1: Add entrance animation**
+
+In `src/components/shortcuts/ShortcutHelpModal.tsx`, add imports:
+
+```typescript
+import { motion, AnimatePresence } from "framer-motion";
+import { springs, scaleIn, staggerContainer, fadeSlideUp } from "@/lib/motion";
+```
+
+The current implementation uses Radix Dialog which handles its own open/close. We can add motion to the content inside. Replace the `` (line 45) with a motion stagger container:
+
+```tsx
+
+ {SHORTCUT_GROUPS.map((group) => (
+
+
+ {group.category}
+
+
+ {group.shortcuts.map(({ key, description }) => (
+
+ {description}
+
+ {key}
+
+
+ ))}
+
+
+ ))}
+
+```
+
+**Step 2: Commit**
+
+```bash
+git add src/components/shortcuts/ShortcutHelpModal.tsx
+git commit -m "feat: add stagger entrance animation to shortcut help modal"
+```
+
+---
+
+### Task 13: Polish pass — verify reduced-motion + TypeScript check
+
+**Files:**
+- Modify: `src/lib/motion.ts` (if needed)
+- Verify all files compile
+
+**Step 1: Run TypeScript check**
+
+Run: `npx tsc --noEmit`
+Expected: No errors. Fix any type issues that arise.
+
+**Step 2: Test reduced-motion**
+
+In the browser dev tools, run: `document.documentElement.style.setProperty('prefers-reduced-motion', 'reduce')` or use the browser's rendering tools to emulate reduced-motion.
+
+Verify: All Framer Motion animations respect `useReducedMotion()` where it's already used (KanbanColumn, CardThumbnail, BoardCard). The CSS `prefers-reduced-motion` media query in `index.css` already handles CSS transitions.
+
+**Step 3: Visual test all animations**
+
+Run: `npm run tauri dev`
+
+Test checklist:
+- [ ] Dark mode is lighter, warm tones preserved
+- [ ] Custom titlebar buttons work (minimize, maximize, close)
+- [ ] Window dragging works via TopBar
+- [ ] Page transitions slide between board list and board view
+- [ ] Board cards stagger in on the board list
+- [ ] Columns stagger in when opening a board
+- [ ] Cards stagger in within columns
+- [ ] Card hover scales up slightly with shadow
+- [ ] Card tap scales down
+- [ ] Clicking card morphs into detail modal (shared layout animation)
+- [ ] Closing modal morphs back to card position
+- [ ] Drag overlay lifts card with scale + shadow
+- [ ] Dragging card tilts based on movement direction
+- [ ] Toast notifications bounce in with wobbly spring
+- [ ] Settings tab switching crossfades
+- [ ] Accent color swatches bounce on hover
+- [ ] Shortcut help groups stagger in
+- [ ] Saving status fades in/out in TopBar
+
+**Step 4: Commit any polish fixes**
+
+```bash
+git add -A
+git commit -m "fix: polish pass — animation tweaks and reduced-motion compliance"
+```
+
+---
+
+## Quick Reference: File → Task Mapping
+
+| File | Tasks |
+|------|-------|
+| `src/lib/motion.ts` | 1 (create) |
+| `src/index.css` | 2 |
+| `src-tauri/tauri.conf.json` | 3 |
+| `src/components/layout/WindowControls.tsx` | 3 (create) |
+| `src/components/layout/TopBar.tsx` | 3, 9 |
+| `src/App.tsx` | 4 |
+| `src/components/boards/BoardList.tsx` | 5 |
+| `src/components/boards/BoardCard.tsx` | 5 |
+| `src/components/board/BoardView.tsx` | 6, 8 |
+| `src/components/board/KanbanColumn.tsx` | 6 |
+| `src/components/board/CardThumbnail.tsx` | 6, 7 |
+| `src/components/card-detail/CardDetailModal.tsx` | 7 |
+| `src/components/board/DragOverlayContent.tsx` | 8 |
+| `src/components/toast/ToastContainer.tsx` | 10 |
+| `src/components/settings/SettingsDialog.tsx` | 11 |
+| `src/components/shortcuts/ShortcutHelpModal.tsx` | 12 |