1 Commits

3 changed files with 25 additions and 13 deletions

View File

@@ -204,19 +204,22 @@ export function AIChatPanel({
{/* Scrollable messages area */} {/* Scrollable messages area */}
<div className="relative flex-1 min-h-0"> <div className="relative flex-1 min-h-0">
{/* Gradual blur at the top of messages */} {/* Gradual blur at the bottom of messages */}
{hasMessages && ( {hasMessages && (
<GradualBlur <GradualBlur
position="top" position="bottom"
strength={2} strength={0.6}
height="5rem" height="4rem"
divCount={6} divCount={10}
curve="ease-out"
opacity={0.8}
zIndex={20} zIndex={20}
/> />
)} )}
<ScrollArea <ScrollArea
className="h-full" className="h-full"
viewportRef={messagesContainerRef} viewportRef={messagesContainerRef}
scrollbarClassName={hasMessages ? 'z-30' : undefined}
viewportClassName={ viewportClassName={
isHomePage && !hasMessages isHomePage && !hasMessages
? '[&>div]:!flex [&>div]:!flex-col [&>div]:!min-h-full [&>div]:!justify-center' ? '[&>div]:!flex [&>div]:!flex-col [&>div]:!min-h-full [&>div]:!justify-center'
@@ -382,10 +385,9 @@ export function AIChatPanel({
</ScrollArea> </ScrollArea>
</div> </div>
{/* Fixed input — pinned to the bottom (hidden on initial state) */} {/* Fixed input — pinned to the bottom, above the blur */}
{hasMessages && ( {hasMessages && (
<div className="absolute bottom-0 left-0 right-0 z-10 px-6 pb-5 pt-16 pointer-events-none"> <div className="absolute bottom-0 left-0 right-0 z-30 px-6 pb-5 pt-4 pointer-events-none">
<div className="absolute inset-x-0 top-0 h-full bg-gradient-to-b from-transparent via-background/60 to-background/90" />
<div className="relative pointer-events-auto mx-auto max-w-3xl"> <div className="relative pointer-events-auto mx-auto max-w-3xl">
<ChatInput <ChatInput
input={input} input={input}

View File

@@ -13,6 +13,8 @@ interface GradualBlurProps {
divCount?: number; divCount?: number;
/** Use exponential progression for stronger end blur */ /** Use exponential progression for stronger end blur */
exponential?: boolean; exponential?: boolean;
/** Distribution curve: linear | ease-out */
curve?: 'linear' | 'ease-out';
/** Opacity applied to each blur layer */ /** Opacity applied to each blur layer */
opacity?: number; opacity?: number;
/** z-index for the overlay */ /** z-index for the overlay */
@@ -30,6 +32,7 @@ export function GradualBlur({
height = '6rem', height = '6rem',
divCount = 5, divCount = 5,
exponential = false, exponential = false,
curve = 'linear',
opacity = 1, opacity = 1,
zIndex = 10, zIndex = 10,
className = '', className = '',
@@ -39,14 +42,19 @@ export function GradualBlur({
const increment = 100 / divCount; const increment = 100 / divCount;
const direction = getGradientDirection(position); const direction = getGradientDirection(position);
const curveFunc = curve === 'ease-out'
? (p: number) => 1 - Math.pow(1 - p, 2)
: (p: number) => p;
for (let i = 1; i <= divCount; i++) { for (let i = 1; i <= divCount; i++) {
const progress = i / divCount; let progress = i / divCount;
progress = curveFunc(progress);
let blurValue: number; let blurValue: number;
if (exponential) { if (exponential) {
blurValue = Math.pow(2, progress * 4) * 0.0625 * strength; blurValue = Math.pow(2, progress * 4) * 0.0625 * strength;
} else { } else {
blurValue = 0.0625 * (progress * divCount + 1) * strength; blurValue = progress * strength;
} }
const p1 = Math.round((increment * i - increment) * 10) / 10; const p1 = Math.round((increment * i - increment) * 10) / 10;
@@ -77,7 +85,7 @@ export function GradualBlur({
} }
return divs; return divs;
}, [position, strength, divCount, exponential, opacity]); }, [position, strength, divCount, exponential, curve, opacity]);
return ( return (
<div <div

View File

@@ -8,10 +8,12 @@ function ScrollArea({
children, children,
viewportRef, viewportRef,
viewportClassName, viewportClassName,
scrollbarClassName,
...props ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root> & { }: React.ComponentProps<typeof ScrollAreaPrimitive.Root> & {
viewportRef?: React.Ref<HTMLDivElement>; viewportRef?: React.Ref<HTMLDivElement>;
viewportClassName?: string; viewportClassName?: string;
scrollbarClassName?: string;
}) { }) {
return ( return (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
@@ -29,7 +31,7 @@ function ScrollArea({
> >
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar className={scrollbarClassName} />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
) )
@@ -45,7 +47,7 @@ function ScrollBar({
data-slot="scroll-area-scrollbar" data-slot="scroll-area-scrollbar"
orientation={orientation} orientation={orientation}
className={cn( className={cn(
"flex touch-none p-px transition-colors select-none", "flex touch-none p-px transition-colors select-none z-50",
orientation === "vertical" && orientation === "vertical" &&
"h-full w-2.5 border-l border-l-transparent", "h-full w-2.5 border-l border-l-transparent",
orientation === "horizontal" && orientation === "horizontal" &&