diff --git a/frontend/src/app/components/Layout/AppShell.tsx b/frontend/src/app/components/Layout/AppShell.tsx
index 5eadca46..4b267630 100644
--- a/frontend/src/app/components/Layout/AppShell.tsx
+++ b/frontend/src/app/components/Layout/AppShell.tsx
@@ -23,7 +23,8 @@ import { LayoutGrid } from 'lucide-react';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import { Settings as LucideSettings } from 'lucide-react';
import { Palette } from 'lucide-react';
-import { PanelLeft, ArrowLeft, ArrowRight, Plus } from 'lucide-react';
+import { ArrowLeft, ArrowRight } from 'lucide-react';
+import { AnimatedPanelLeft, AnimatedPlus } from './animatedIcons';
import RestartAltIcon from '@mui/icons-material/RestartAlt';
import SystemUpdateAltIcon from '@mui/icons-material/SystemUpdateAlt';
import CloseIcon from '@mui/icons-material/Close';
@@ -392,12 +393,10 @@ const AppShell: React.FC = () => {
color: c.text.tertiary,
p: 0.5,
borderRadius: 1,
- '& svg': { transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)' },
'&:hover': { color: c.text.secondary, bgcolor: `${c.text.tertiary}14` },
- '&:hover svg': { transform: 'translateX(-1.5px) scale(1.08)' },
}}
>
-
+
@@ -680,12 +679,10 @@ const AppShell: React.FC = () => {
p: 0.25,
mr: 0.25,
borderRadius: 1,
- '& svg': { transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)' },
'&:hover': { color: c.accent.primary, bgcolor: `${c.accent.primary}14` },
- '&:hover svg': { transform: 'rotate(90deg)' },
}}
>
-
+
{dashboardList.length > 0 && (
@@ -939,12 +936,10 @@ const AppShell: React.FC = () => {
p: 0.25,
mr: 0.25,
borderRadius: 1,
- '& svg': { transition: 'transform 0.2s cubic-bezier(0.34, 1.56, 0.64, 1)' },
'&:hover': { color: c.accent.primary, bgcolor: `${c.accent.primary}14` },
- '&:hover svg': { transform: 'rotate(90deg)' },
}}
>
-
+
{appsList.length > 0 && (
diff --git a/frontend/src/app/components/Layout/animatedIcons.tsx b/frontend/src/app/components/Layout/animatedIcons.tsx
new file mode 100644
index 00000000..4cafedfd
--- /dev/null
+++ b/frontend/src/app/components/Layout/animatedIcons.tsx
@@ -0,0 +1,51 @@
+import { motion } from 'framer-motion';
+
+// True path-drawing sidebar glyphs: the SVG strokes themselves animate on hover
+// (not just a transform), then settle. Always fully visible at rest, so a hover
+// that's interrupted mid-flight never leaves a half-drawn icon. Geometry matches
+// the lucide line-icons they replace so the static look is unchanged.
+
+const SPRING = { type: 'spring', stiffness: 380, damping: 20 } as const;
+
+type Props = { size?: number };
+
+const svgBase = {
+ fill: 'none',
+ stroke: 'currentColor',
+ strokeWidth: 2,
+ strokeLinecap: 'round' as const,
+ strokeLinejoin: 'round' as const,
+ viewBox: '0 0 24 24',
+};
+
+// Panel toggle: the inner divider sweeps left, like the panel collapsing.
+export function AnimatedPanelLeft({ size = 18 }: Props) {
+ return (
+
+
+
+
+ );
+}
+
+// Plus: both strokes redraw from nothing, the classic "new" flourish.
+export function AnimatedPlus({ size = 16 }: Props) {
+ return (
+
+
+
+
+ );
+}