# 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 |