Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 79 additions & 16 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@
--shadow-2xl: 0px 1px 2px 0px hsl(0 0% 0% / 0.45);
--tracking-normal: 0em;
--spacing: 0.25rem;
--motion-ease-out: cubic-bezier(0.23, 1, 0.32, 1);
--motion-ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);
--motion-ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);
}

.dark {
Expand Down Expand Up @@ -161,13 +164,13 @@
--shadow-xl: var(--shadow-xl);
--shadow-2xl: var(--shadow-2xl);

--animate-accordion-down: accordion-down 0.2s ease-out;
--animate-accordion-up: accordion-up 0.2s ease-out;
--animate-collapse-down: collapse-down 0.1s ease-in-out;
--animate-collapse-up: collapse-up 0.1s ease-in-out;
--animate-slide-in-right: slide-in-right 0.3s ease-out;
--animate-slide-out-right: slide-out-right 0.3s ease-out;
--animate-fade-in: fade-in 0.3s ease-out;
--animate-accordion-down: accordion-down 180ms var(--motion-ease-out);
--animate-accordion-up: accordion-up 140ms var(--motion-ease-out);
--animate-collapse-down: collapse-down 160ms var(--motion-ease-out);
--animate-collapse-up: collapse-up 120ms var(--motion-ease-out);
--animate-slide-in-right: slide-in-right 220ms var(--motion-ease-drawer);
--animate-slide-out-right: slide-out-right 160ms var(--motion-ease-out);
--animate-fade-in: fade-in 180ms var(--motion-ease-out);

@keyframes accordion-down {
from {
Expand Down Expand Up @@ -247,32 +250,36 @@
100% {
transform: translateX(0);
}
25% {
transform: translateX(10px);
18%,
30% {
transform: translateX(6px);
}
50% {
transform: translateX(0);
48%,
60% {
transform: translateX(-6px);
}
75% {
transform: translateX(-10px);
78% {
transform: translateX(0);
}
}
}

@utility animate-blink {
animation: blink 0.2s ease-in-out;
animation: blink 180ms var(--motion-ease-in-out);
}

/* Animation utilities for dropdown menu */
@utility animate-in {
animation-name: enter;
animation-duration: 150ms;
animation-duration: var(--tw-duration, 160ms);
animation-timing-function: var(--tw-ease, var(--motion-ease-out));
animation-fill-mode: both;
}

@utility animate-out {
animation-name: exit;
animation-duration: 150ms;
animation-duration: var(--tw-duration, 120ms);
animation-timing-function: var(--tw-ease, var(--motion-ease-out));
animation-fill-mode: both;
}

Expand Down Expand Up @@ -308,6 +315,38 @@
--enter-translate-x: 0.5rem;
}

@utility slide-in-from-top {
--enter-translate-y: -100%;
}

@utility slide-in-from-bottom {
--enter-translate-y: 100%;
}

@utility slide-in-from-left {
--enter-translate-x: -100%;
}

@utility slide-in-from-right {
--enter-translate-x: 100%;
}

@utility slide-out-to-top {
--exit-translate-y: -100%;
}

@utility slide-out-to-bottom {
--exit-translate-y: 100%;
}

@utility slide-out-to-left {
--exit-translate-x: -100%;
}

@utility slide-out-to-right {
--exit-translate-x: 100%;
}

@keyframes enter {
from {
opacity: var(--enter-opacity, 1);
Expand Down Expand Up @@ -335,6 +374,30 @@
}
}

@media (prefers-reduced-motion: reduce) {
@keyframes enter {
from {
opacity: var(--enter-opacity, 1);
transform: translate(0, 0) scale(1);
}
to {
opacity: 1;
transform: translate(0, 0) scale(1);
}
}

@keyframes exit {
from {
opacity: 1;
transform: translate(0, 0) scale(1);
}
to {
opacity: var(--exit-opacity, 1);
transform: translate(0, 0) scale(1);
}
}
}

@utility container {
margin-inline: auto;
padding-inline: 2rem;
Expand Down
8 changes: 4 additions & 4 deletions components/action-buttons.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ export function ActionButtons({
{/* Action buttons */}
<div
className={cn(
'absolute inset-0 flex items-start justify-center pt-2 transition-opacity duration-300',
'absolute inset-0 flex items-start justify-center pt-2 transition-opacity duration-[180ms] ease-[var(--motion-ease-out)]',
activeCategory ? 'opacity-0 pointer-events-none' : 'opacity-100'
)}
>
Expand Down Expand Up @@ -204,7 +204,7 @@ export function ActionButtons({
{/* Prompt samples */}
<div
className={cn(
'absolute inset-0 py-1 space-y-1 overflow-y-auto transition-opacity duration-300',
'absolute inset-0 space-y-1 overflow-y-auto py-1 transition-opacity duration-[180ms] ease-[var(--motion-ease-out)]',
!activeCategory ? 'opacity-0 pointer-events-none' : 'opacity-100'
)}
>
Expand All @@ -214,8 +214,8 @@ export function ActionButtons({
key={index}
type="button"
className={cn(
'w-full text-left px-3 py-2 rounded-md text-sm',
'hover:bg-muted transition-colors',
'w-full rounded-md px-3 py-2 text-left text-sm',
'transition-colors duration-[140ms] ease-[var(--motion-ease-out)] hover:bg-muted',
'flex items-center gap-2 group'
)}
onClick={() => handlePromptClick(prompt)}
Expand Down
4 changes: 2 additions & 2 deletions components/artifact/artifact-context.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ import type { Part } from '@/lib/types/ai'

import { useSidebar } from '../ui/sidebar'

// Animation duration should match CSS transition duration
const ANIMATION_DURATION = 300
// Animation duration should match the inspector panel exit transition.
const ANIMATION_DURATION = 260

interface ArtifactState {
part: Part | null
Expand Down
5 changes: 3 additions & 2 deletions components/artifact/chat-artifact-container.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function ChatArtifactContainer({

return (
<div className="flex-1 min-h-0 min-w-0 h-full flex">
<div className="absolute p-4 z-50 transition-opacity duration-1000">
<div className="absolute z-50 p-4 transition-opacity duration-[180ms] ease-[var(--motion-ease-out)]">
{hasUser && (!open || isMobileSidebar) && (
<SidebarTrigger className="animate-fade-in" />
)}
Expand Down Expand Up @@ -164,7 +164,8 @@ export function ChatArtifactContainer({
className={cn(
'bg-background overflow-hidden',
state.isOpen && state.part ? 'opacity-100' : 'w-0 opacity-0',
!isResizing && 'transition-all duration-300 ease-out'
!isResizing &&
'transition-[opacity,width] duration-[260ms] ease-[var(--motion-ease-in-out)]'
)}
style={{
width: state.isOpen && state.part ? `${width}px` : '0px'
Expand Down
8 changes: 4 additions & 4 deletions components/chat-panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ export function ChatPanel({
{messages.length > 0 && (
<div
className={cn(
'transition-opacity duration-100',
'transition-opacity duration-[120ms] ease-[var(--motion-ease-out)]',
showScrollToBottomButton
? 'opacity-100'
: 'pointer-events-none opacity-0'
Expand All @@ -291,7 +291,7 @@ export function ChatPanel({
{sections.length > 0 && (
<div
className={cn(
'transition-opacity duration-100',
'transition-opacity duration-[120ms] ease-[var(--motion-ease-out)]',
!showScrollToBottomButton && status === 'ready'
? 'opacity-100'
: 'pointer-events-none opacity-0'
Expand All @@ -303,7 +303,7 @@ export function ChatPanel({

<div
className={cn(
'relative flex flex-col w-full gap-2 bg-muted rounded-3xl border border-input transition-shadow',
'relative flex w-full flex-col gap-2 rounded-3xl border border-input bg-muted transition-[box-shadow] duration-[140ms] ease-[var(--motion-ease-out)]',
isInputFocused &&
'ring-1 ring-ring/20 ring-offset-1 ring-offset-background/50'
)}
Expand Down Expand Up @@ -441,7 +441,7 @@ export function ChatPanel({
type="button"
disabled={isLoading}
>
<MessageCirclePlus className="size-4 group-hover:rotate-12 transition-all" />
<MessageCirclePlus className="size-4 transition-transform duration-[140ms] ease-[var(--motion-ease-out)] group-hover:rotate-12" />
</Button>
)}
<Button
Expand Down
6 changes: 3 additions & 3 deletions components/model-selector-client.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ export function ModelSelectorClient({ data }: ModelSelectorClientProps) {
return (
<Button
variant="outline"
className="text-sm rounded-full shadow-none gap-1 transition-all px-3 py-2 h-auto bg-muted border-none"
className="h-auto gap-1 rounded-full border-none bg-muted px-3 py-2 text-sm shadow-none transition-[background-color,color,box-shadow,transform]"
disabled
title="No enabled models are available"
>
Expand All @@ -121,15 +121,15 @@ export function ModelSelectorClient({ data }: ModelSelectorClientProps) {
variant="outline"
role="combobox"
aria-expanded={open}
className="text-sm rounded-full shadow-none gap-1 transition-all px-3 py-2 h-auto bg-muted border-none"
className="h-auto gap-1 rounded-full border-none bg-muted px-3 py-2 text-sm shadow-none transition-[background-color,color,box-shadow,transform]"
>
<ProviderLogo providerId={selectedModel.providerId} />
<span className="truncate max-w-40 text-xs font-medium">
{selectedModel.name}
</span>
<ChevronDown
className={cn(
'h-3 w-3 ml-0.5 opacity-50 transition-transform duration-200',
'ml-0.5 h-3 w-3 opacity-50 transition-transform duration-[160ms] ease-[var(--motion-ease-out)]',
open && 'rotate-180'
)}
/>
Expand Down
8 changes: 4 additions & 4 deletions components/search-mode-selector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ export function SearchModeSelector({
<Button
variant="outline"
size="sm"
className="text-xs rounded-full shadow-none gap-1 transition-all"
className="gap-1 rounded-full text-xs shadow-none transition-[background-color,color,box-shadow,transform]"
>
{SelectedIcon && (
<SelectedIcon
Expand All @@ -118,7 +118,7 @@ export function SearchModeSelector({
<span className="text-xs font-medium">{selectedMode?.label}</span>
<ChevronDown
className={cn(
'size-3 ml-0.5 opacity-50 transition-transform duration-200',
'ml-0.5 size-3 opacity-50 transition-transform duration-[160ms] ease-[var(--motion-ease-out)]',
dropdownOpen && 'rotate-180'
)}
/>
Expand Down Expand Up @@ -160,7 +160,7 @@ export function SearchModeSelector({
<div className="relative inline-flex items-center rounded-full bg-background border p-1">
{/* Animated background indicator */}
<div
className="absolute inset-1 rounded-full bg-muted transition-all duration-200 ease-out"
className="absolute inset-1 rounded-full bg-muted transition-[transform,width] duration-[180ms] ease-[var(--motion-ease-in-out)]"
style={{
width: `calc(${100 / modeCount}% - 4px)`,
transform: `translateX(${selectedIndex * 100}%)`
Expand Down Expand Up @@ -190,7 +190,7 @@ export function SearchModeSelector({
type="button"
onClick={() => handleModeSelect(config.value)}
className={cn(
'relative z-10 flex-1 items-center justify-center rounded-full px-3 py-2 transition-colors duration-200',
'relative z-10 flex-1 items-center justify-center rounded-full px-3 py-2 transition-colors duration-[140ms] ease-[var(--motion-ease-out)]',
isSelected
? 'text-foreground'
: 'text-muted-foreground hover:text-foreground/80'
Expand Down
4 changes: 2 additions & 2 deletions components/ui/alert-dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ const AlertDialogOverlay = React.forwardRef<
>(({ className, ...props }, ref) => (
<AlertDialogPrimitive.Overlay
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-[180ms] data-[state=closed]:duration-[120ms] data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
Expand All @@ -38,7 +38,7 @@ const AlertDialogContent = React.forwardRef<
<AlertDialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg origin-center translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-[180ms] data-[state=closed]:duration-[120ms] data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg',
className
)}
{...props}
Expand Down
3 changes: 2 additions & 1 deletion components/ui/animated-logo.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ export function AnimatedLogo({
<g
className={cn(
'origin-center',
animate && 'animate-[lookAround_2s_ease-in-out_infinite]'
animate &&
'animate-[lookAround_2.8s_var(--motion-ease-in-out)_infinite] will-change-transform motion-reduce:animate-none motion-reduce:will-change-auto'
)}
>
<ellipse
Expand Down
2 changes: 1 addition & 1 deletion components/ui/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { cva, type VariantProps } from 'class-variance-authority'
import { cn } from '@/lib/utils/index'

const buttonVariants = cva(
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-[background-color,border-color,color,box-shadow,transform] duration-[140ms] ease-[var(--motion-ease-out)] active:scale-[0.98] focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
{
variants: {
variant: {
Expand Down
4 changes: 2 additions & 2 deletions components/ui/dialog.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const DialogOverlay = React.forwardRef<
<DialogPrimitive.Overlay
ref={ref}
className={cn(
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-[180ms] data-[state=closed]:duration-[120ms] data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
className
)}
{...props}
Expand All @@ -39,7 +39,7 @@ const DialogContent = React.forwardRef<
<DialogPrimitive.Content
ref={ref}
className={cn(
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg',
'fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg origin-center translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-[180ms] data-[state=closed]:duration-[120ms] data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 sm:rounded-lg',
className
)}
{...props}
Expand Down
4 changes: 2 additions & 2 deletions components/ui/dropdown-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ function DropdownMenuContent({
data-slot="dropdown-menu-content"
sideOffset={sideOffset}
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-[150ms] data-[state=closed]:duration-[100ms] data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md',
className
)}
{...props}
Expand Down Expand Up @@ -236,7 +236,7 @@ function DropdownMenuSubContent({
<DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content"
className={cn(
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
'bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=open]:duration-[150ms] data-[state=closed]:duration-[100ms] data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg',
className
)}
{...props}
Expand Down
4 changes: 2 additions & 2 deletions components/ui/hover-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@ const HoverCardContent = React.forwardRef<
align={align}
sideOffset={sideOffset}
className={cn(
'z-50 w-64 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
'z-50 w-64 origin-(--radix-hover-card-content-transform-origin) rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none',
'data-[state=open]:animate-in data-[state=closed]:animate-out',
'data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0',
'data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
'data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2',
'data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
'data-[state=open]:duration-150 data-[state=closed]:duration-100',
'data-[state=open]:duration-[140ms] data-[state=closed]:duration-[90ms]',
className
)}
{...props}
Expand Down
Loading
Loading