feat: add animated transitions and micro-interactions

Add entrance animations to overlays, modals, chat messages, sidebar
session items, and approval banners:
- overlay-slide-enter: settings and project settings panels
- overlay-backdrop-enter + overlay-panel-enter: modals
- message-enter: chat message fade-up on mount
- session-item-enter: sidebar item slide-in
- banner-slide-enter: approval banner slide-down

All animations use cubic-bezier(0.16, 1, 0.3, 1) for a snappy feel
and stay under 250ms. Respects prefers-reduced-motion.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-03-29 17:57:05 +02:00
co-authored by Copilot
parent ea58f7d66a
commit 05dded9b37
7 changed files with 95 additions and 9 deletions
+2 -2
View File
@@ -345,7 +345,7 @@ export function ChatPane({
phase === 'thinking' ? 'Thinking' : phase === 'final' ? 'Final' : undefined;
return (
<div className="group py-3" data-message-id={message.id} key={message.id}>
<div className="message-enter group py-3" data-message-id={message.id} key={message.id}>
<div className="flex gap-3">
<div
className={`mt-0.5 flex size-7 shrink-0 items-center justify-center rounded-full ${
@@ -448,7 +448,7 @@ export function ChatPane({
<div className="mx-auto max-w-3xl">
{/* Pending approval banner */}
{pendingApproval && (
<div className="mb-3 space-y-2">
<div className="banner-slide-enter mb-3 space-y-2">
<ApprovalBanner
approval={pendingApproval}
isResolving={isResolvingApproval}
@@ -81,10 +81,10 @@ export function DiscoveredToolingModal({
<div
aria-labelledby="discovered-tooling-title"
aria-modal="true"
className="fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm"
className="overlay-backdrop-enter fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm"
role="dialog"
>
<div className="flex max-h-[80vh] w-full max-w-lg flex-col rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
<div className="overlay-panel-enter flex max-h-[80vh] w-full max-w-lg flex-col rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
{/* Header */}
<div className="flex items-center justify-between border-b border-[var(--color-border)] px-5 py-4">
<div className="flex items-center gap-2.5">
+2 -2
View File
@@ -57,8 +57,8 @@ export function NewSessionModal({
const canCreate = projectId && patternId;
return (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="new-session-title">
<div className="w-full max-w-md rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
<div className="overlay-backdrop-enter fixed inset-0 z-50 flex items-center justify-center bg-[#07080e]/90 backdrop-blur-sm" role="dialog" aria-modal="true" aria-labelledby="new-session-title">
<div className="overlay-panel-enter w-full max-w-md rounded-xl border border-[var(--color-border)] bg-[var(--color-surface-1)] shadow-[0_16px_64px_rgba(0,0,0,0.5)]">
{/* Header */}
<div className="flex items-center justify-between border-b border-[var(--color-border)] px-5 py-4">
<h2 id="new-session-title" className="font-display text-[13px] font-semibold text-[var(--color-text-primary)]">New Session</h2>
@@ -111,7 +111,7 @@ export function ProjectSettingsPanel({
}
return (
<div className="fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]">
<div className="overlay-slide-enter fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]">
{/* Header */}
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
<button
+1 -1
View File
@@ -212,7 +212,7 @@ export function SettingsPanel({
}
return (
<div className="fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]">
<div className="overlay-slide-enter fixed inset-0 z-50 flex flex-col bg-[var(--color-surface-0)]">
<div className="drag-region flex items-center gap-3 border-b border-[var(--color-border)] px-5 pb-3 pt-3">
<button
className="no-drag flex size-8 items-center justify-center rounded-lg text-[var(--color-text-secondary)] transition-all duration-200 hover:bg-[var(--color-surface-3)] hover:text-[var(--color-text-primary)]"
+1 -1
View File
@@ -213,7 +213,7 @@ function SessionItem({
return (
<div
className={`group relative flex w-full cursor-pointer items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-200 ${
className={`session-item-enter group relative flex w-full cursor-pointer items-start gap-2.5 rounded-lg px-2.5 py-2 text-left transition-all duration-200 ${
isActive
? 'bg-[var(--color-accent-muted)] ring-1 ring-[var(--color-border-glow)]'
: 'hover:bg-[var(--color-surface-2)]/60'
+86
View File
@@ -542,6 +542,92 @@ body {
animation: palette-backdrop-enter 0.15s ease-out;
}
/* ── Overlay transitions ─────────────────────────────────────── */
@keyframes overlay-backdrop-in {
from { opacity: 0; }
}
@keyframes overlay-panel-in {
from {
opacity: 0;
transform: translateY(6px) scale(0.99);
}
}
@keyframes overlay-slide-in {
from {
opacity: 0;
transform: translateX(12px);
}
}
.overlay-backdrop-enter {
animation: overlay-backdrop-in 0.18s ease-out;
}
.overlay-panel-enter {
animation: overlay-panel-in 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
.overlay-slide-enter {
animation: overlay-slide-in 0.22s cubic-bezier(0.16, 1, 0.3, 1);
}
/* ── Chat message appearance ─────────────────────────────────── */
@keyframes message-in {
from {
opacity: 0;
transform: translateY(4px);
}
}
.message-enter {
animation: message-in 0.2s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* ── Sidebar session item ────────────────────────────────────── */
@keyframes session-item-in {
from {
opacity: 0;
transform: translateX(-6px);
}
}
.session-item-enter {
animation: session-item-in 0.18s cubic-bezier(0.16, 1, 0.3, 1) both;
}
/* ── Approval banner slide ───────────────────────────────────── */
@keyframes banner-slide-in {
from {
opacity: 0;
transform: translateY(-100%);
}
}
.banner-slide-enter {
animation: banner-slide-in 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
/* ── Respect reduced motion ──────────────────────────────────── */
@media (prefers-reduced-motion: reduce) {
.palette-enter,
.palette-backdrop-enter,
.overlay-backdrop-enter,
.overlay-panel-enter,
.overlay-slide-enter,
.message-enter,
.session-item-enter,
.banner-slide-enter {
animation: none;
}
}
/* ── Markdown prose styles ───────────────────────────────────── */
.markdown-content {