Merge pull request #28 from manmohan659/fix/ui-redesign

fix(ui): redesign landing page + chat UI
This commit is contained in:
Manmohan 2026-04-16 17:05:16 -04:00 committed by GitHub
commit 40ce6c1a89
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 231 additions and 150 deletions

View File

@ -1,18 +1,24 @@
import LandingNav from '@/components/LandingNav';
import LandingFooter from '@/components/LandingFooter';
import Hero from '@/components/landing/Hero';
import { SamosaIllustration, KettleIllustration } from '@/components/landing/Illustrations';
import Features from '@/components/landing/Features';
import Doodles from '@/components/svg/Doodles';
export default function LandingPage() {
return (
<main className="relative flex min-h-dvh flex-col bg-white overflow-x-hidden">
<LandingNav />
<main className="relative flex min-h-dvh flex-col overflow-x-hidden bg-gradient-to-br from-[#fff8e7] via-white to-[#fff8e7]">
<Doodles />
<Hero />
<SamosaIllustration />
<KettleIllustration />
<div className="flex-1" />
{/* Hero section: full viewport height with warm gradient */}
<div className="relative min-h-dvh flex flex-col">
<LandingNav />
<Hero />
</div>
{/* Features section */}
<Features />
{/* Footer */}
<LandingFooter />
</main>
);

View File

@ -3,16 +3,7 @@ export default function LandingFooter() {
<footer className="flex flex-col sm:flex-row justify-between items-center gap-1 px-4 md:px-9 py-3 font-caveat text-sm text-gray-400 flex-shrink-0">
<span>&copy; 2026 samosachaat.art · Crafted with care. For India, from India.</span>
<span className="text-xs text-gray-400">
Built on{' '}
<a
href="https://github.com/karpathy/nanochat"
target="_blank"
rel="noopener noreferrer"
className="text-warm-grey hover:text-gray-600"
>
nanochat
</a>{' '}
by Andrej Karpathy
Built by Manmohan
</span>
<a href="#" className="hover:text-gray-600">Terms and Policies</a>
</footer>

View File

@ -1,7 +1,6 @@
'use client';
import Link from 'next/link';
import ToranSvg from './svg/ToranSvg';
import { useAuth } from '@/hooks/useAuth';
export default function LandingNav() {
@ -27,10 +26,6 @@ export default function LandingNav() {
</Link>
</div>
<div className="absolute left-1/2 top-0 origin-top transform -translate-x-1/2 animate-pendulum z-[5]">
<ToranSvg />
</div>
<div className="flex items-center gap-4 font-caveat text-[1.05rem] text-gray-600 pt-1">
<a
href="https://instagram.com/samosachaat.art"
@ -43,14 +38,14 @@ export default function LandingNav() {
{authenticated ? (
<Link
href="/chat"
className="px-3 py-1 rounded-full border border-warm-grey text-brown bg-cream-light hover:bg-cream transition-colors"
className="px-5 py-2 rounded-full bg-gold text-white font-semibold hover:bg-gold-dark transition-colors shadow-sm"
>
Chat
</Link>
) : (
<Link
href="/login"
className="px-3 py-1 rounded-full border border-warm-grey text-brown bg-cream-light hover:bg-cream transition-colors"
className="px-5 py-2 rounded-full bg-gold text-white font-semibold hover:bg-gold-dark transition-colors shadow-sm"
>
Sign in
</Link>

View File

@ -37,7 +37,7 @@ export default function ChatInput({ value, onChange, onSubmit, onStop, isStreami
};
return (
<div className="sticky bottom-0 bg-white pt-3 pb-[calc(1rem+env(safe-area-inset-bottom))] px-4">
<div className="sticky bottom-0 bg-white pt-3 pb-[calc(1rem+env(safe-area-inset-bottom))] px-4 border-t border-cream-border/50 shadow-[0_-2px_8px_rgba(0,0,0,0.04)]">
<div className="max-w-3xl mx-auto flex items-end gap-3">
<div className="flex-1 relative">
<textarea

View File

@ -162,12 +162,11 @@ export default function ChatWindow() {
<PanelLeftOpen size={18} />
</button>
)}
<h1 className="font-baloo font-semibold text-lg text-gray-900">Chat Completions</h1>
<span className="hidden sm:inline text-xs px-2 py-0.5 rounded-full border border-warm-grey bg-cream-light text-brown">
<span className="text-xs px-2 py-0.5 rounded-full border border-warm-grey bg-cream-light text-brown">
{model}
</span>
</div>
<div className="text-xs text-gray-500">
<div className="text-sm text-gray-600 font-medium">
{user?.name ? `Hi, ${user.name.split(' ')[0]}` : ''}
</div>
</header>

View File

@ -1,35 +1,64 @@
'use client';
import { Sparkles, BookOpen, Code2, Smile } from 'lucide-react';
import SamosaLogo from '@/components/svg/SamosaLogo';
const CHIPS = [
{ icon: BookOpen, label: 'Summarize a topic', prompt: 'Summarize the history of samosas in 3 paragraphs.' },
{ icon: Sparkles, label: 'Explain a concept', prompt: 'Explain transformers to a curious beginner.' },
{ icon: Code2, label: 'Write some code', prompt: 'Write a Python function that reverses a linked list.' },
{ icon: Smile, label: 'Tell me a joke', prompt: 'Tell me a joke about chai.' },
const SUGGESTIONS = [
{
icon: '📚',
label: 'Summarize a topic',
description: 'Get a concise overview of any subject',
prompt: 'Summarize the history of samosas in 3 paragraphs.',
},
{
icon: '✨',
label: 'Explain a concept',
description: 'Break down complex ideas simply',
prompt: 'Explain transformers to a curious beginner.',
},
{
icon: '💻',
label: 'Write some code',
description: 'Get help with any programming task',
prompt: 'Write a Python function that reverses a linked list.',
},
{
icon: '😄',
label: 'Tell me a joke',
description: 'Lighten the mood with some humor',
prompt: 'Tell me a joke about chai.',
},
];
export default function EmptyState({ onPick }: { onPick: (prompt: string) => void }) {
return (
<div className="flex-1 flex flex-col items-center justify-center px-4 text-center">
<h2 className="font-baloo font-bold text-3xl md:text-4xl text-gray-900 mb-2">
<div className="flex flex-col items-center justify-center flex-1 px-4 -mt-20">
{/* Small logo */}
<div className="w-16 h-16 mb-6 opacity-20">
<SamosaLogo size={64} />
</div>
<h2 className="font-baloo font-bold text-3xl text-gray-800 mb-2">
How can I help you today?
</h2>
<p className="font-caveat text-lg text-brown-light mb-8">
<p className="font-caveat text-lg text-brown/60 mb-10">
Ask anything a doubt, a recipe, a code snippet, or a fresh idea.
</p>
{/* Bigger suggestion cards - 2x2 grid */}
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 w-full max-w-xl">
{CHIPS.map(({ icon: Icon, label, prompt }) => (
{SUGGESTIONS.map((s) => (
<button
key={label}
key={s.label}
type="button"
onClick={() => onPick(prompt)}
className="flex items-center gap-3 px-4 py-3 rounded-xl border border-cream-border bg-cream-light hover:bg-cream text-left transition-colors"
onClick={() => onPick(s.prompt)}
className="flex items-start gap-3 p-4 rounded-xl border border-cream-border bg-white hover:bg-cream/50 hover:border-gold/30 transition-all text-left group"
>
<Icon size={18} className="text-gold flex-shrink-0" />
<span className="text-xl mt-0.5">{s.icon}</span>
<div>
<div className="text-sm font-medium text-gray-800">{label}</div>
<div className="text-xs text-gray-500 truncate">{prompt}</div>
<div className="font-medium text-sm text-gray-800 group-hover:text-brown">
{s.label}
</div>
<div className="text-xs text-gray-500 mt-0.5">{s.description}</div>
</div>
</button>
))}

View File

@ -56,7 +56,7 @@ export default function Sidebar() {
<button
type="button"
onClick={() => createConversation()}
className="w-full flex items-center gap-2 px-3 py-2 rounded-lg border border-gold/60 bg-white hover:bg-cream text-brown font-medium text-sm transition-colors"
className="w-full flex items-center justify-center gap-2 px-3 py-2.5 rounded-lg bg-gold/10 border border-gold/40 hover:bg-gold/20 text-brown font-baloo font-semibold text-sm transition-colors"
>
<Plus size={16} className="text-gold" />
New chat
@ -64,46 +64,54 @@ export default function Sidebar() {
</div>
<div className="flex-1 overflow-y-auto px-2 nice-scrollbar">
{Object.entries(grouped).map(([group, items]) => {
if (items.length === 0) return null;
return (
<div key={group} className="mb-4">
<div className="px-2 mb-1 text-[11px] uppercase tracking-wider text-gray-400 font-medium">
{group}
{conversations.length === 0 ? (
<div className="flex flex-col items-center justify-center h-full px-4 text-center py-12">
<div className="text-3xl mb-3 opacity-30">💬</div>
<p className="text-sm text-gray-400 font-medium">No conversations yet.</p>
<p className="text-xs text-gray-400 mt-1">Start your first chat!</p>
</div>
) : (
Object.entries(grouped).map(([group, items]) => {
if (items.length === 0) return null;
return (
<div key={group} className="mb-4">
<div className="px-2 mb-1 text-[11px] uppercase tracking-wider text-gray-400 font-medium">
{group}
</div>
<ul className="space-y-0.5">
{items.map((c) => (
<li key={c.id} className="group relative">
<button
type="button"
onClick={() => selectConversation(c.id)}
className={clsx(
'w-full text-left px-2.5 py-1.5 rounded text-sm truncate transition-colors pr-8',
c.id === currentConversationId
? 'bg-cream text-brown font-medium'
: 'text-gray-700 hover:bg-cream/70',
)}
title={c.title}
>
{c.title}
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
deleteConversation(c.id);
}}
className="absolute right-1 top-1/2 -translate-y-1/2 p-1 rounded opacity-0 group-hover:opacity-100 hover:bg-cream text-gray-400 hover:text-chutney-red transition-all"
aria-label={`Delete ${c.title}`}
>
<Trash2 size={14} />
</button>
</li>
))}
</ul>
</div>
<ul className="space-y-0.5">
{items.map((c) => (
<li key={c.id} className="group relative">
<button
type="button"
onClick={() => selectConversation(c.id)}
className={clsx(
'w-full text-left px-2.5 py-1.5 rounded text-sm truncate transition-colors pr-8',
c.id === currentConversationId
? 'bg-cream text-brown font-medium'
: 'text-gray-700 hover:bg-cream/70',
)}
title={c.title}
>
{c.title}
</button>
<button
type="button"
onClick={(e) => {
e.stopPropagation();
deleteConversation(c.id);
}}
className="absolute right-1 top-1/2 -translate-y-1/2 p-1 rounded opacity-0 group-hover:opacity-100 hover:bg-cream text-gray-400 hover:text-chutney-red transition-all"
aria-label={`Delete ${c.title}`}
>
<Trash2 size={14} />
</button>
</li>
))}
</ul>
</div>
);
})}
);
})
)}
</div>
<div className="px-3 py-3 border-t border-cream-border space-y-3">

View File

@ -0,0 +1,40 @@
export default function Features() {
return (
<section className="bg-[#fff8e7]/60 py-20 px-4">
<div className="max-w-4xl mx-auto">
<h3 className="text-center font-baloo text-2xl text-brown mb-12">
Why samosaChaat?
</h3>
<div className="grid grid-cols-1 md:grid-cols-3 gap-8">
<div className="bg-white rounded-2xl p-6 shadow-sm border border-[#f0e0b8]/50 text-center">
<div className="text-3xl mb-3">💬</div>
<h4 className="font-baloo font-bold text-lg text-brown mb-2">
Conversations that stick
</h4>
<p className="text-sm text-brown/70">
Your chats are saved and organized. Pick up right where you left off.
</p>
</div>
<div className="bg-white rounded-2xl p-6 shadow-sm border border-[#f0e0b8]/50 text-center">
<div className="text-3xl mb-3">🔄</div>
<h4 className="font-baloo font-bold text-lg text-brown mb-2">
Swap models anytime
</h4>
<p className="text-sm text-brown/70">
Switch between different AI models with a click. Your choice, your style.
</p>
</div>
<div className="bg-white rounded-2xl p-6 shadow-sm border border-[#f0e0b8]/50 text-center">
<div className="text-3xl mb-3">🇮🇳</div>
<h4 className="font-baloo font-bold text-lg text-brown mb-2">
Desi at heart
</h4>
<p className="text-sm text-brown/70">
Built with love, inspired by Indian culture. A little desi, a lot thoughtful.
</p>
</div>
</div>
</div>
</section>
);
}

View File

@ -3,56 +3,89 @@
import Link from 'next/link';
import { motion } from 'framer-motion';
import { useAuth } from '@/hooks/useAuth';
import SamosaSvg from '@/components/svg/SamosaSvg';
import KettleSvg from '@/components/svg/KettleSvg';
import KettleSteam from '@/components/svg/KettleSteam';
import ToranSvg from '@/components/svg/ToranSvg';
export default function Hero() {
const { authenticated } = useAuth();
const ctaHref = authenticated ? '/chat' : '/login';
return (
<section className="relative z-[2] text-center px-4 pt-6">
<motion.h1
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="font-baloo font-extrabold text-[clamp(3rem,7vw,5.5rem)] text-[#1a1a1a] leading-[1.1] -rotate-1 -mb-[0.35em] relative z-[2]"
>
</motion.h1>
<motion.h2
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.15 }}
className="font-vibes text-[clamp(2rem,5vw,3.8rem)] text-[rgba(30,30,30,0.55)] leading-none rotate-[0.5deg] relative z-[1] -mt-[0.1em]"
>
samosaChaat
</motion.h2>
<section className="flex-1 flex flex-col items-center justify-center relative px-4">
{/* Toran animation at top center */}
<div className="absolute left-1/2 top-0 origin-top transform -translate-x-1/2 animate-pendulum z-[5]">
<ToranSvg />
</div>
<motion.p
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
className="mt-6 font-caveat text-lg md:text-xl text-brown-light max-w-xl mx-auto"
>
A warm, desi-flavored chat brewed from the nanochat research model, served with a side of chai.
</motion.p>
<div className="flex items-center justify-center gap-8 md:gap-16 lg:gap-24 w-full max-w-6xl">
{/* Left illustration - Samosa */}
<div className="hidden md:block flex-shrink-0 animate-float">
<SamosaSvg className="w-44 h-44 lg:w-56 lg:h-56" width={224} height={224} />
<span className="mt-1.5 block text-center font-caveat text-[1.1rem] text-brown-light bg-[#f5edd6] px-4 py-0.5 border border-[#d4c4a0] rounded-sm -rotate-3 shadow-sm">
Samosa
</span>
</div>
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.45 }}
className="mt-6"
>
<Link
href={ctaHref}
className="inline-flex items-center gap-2 px-6 py-3 rounded-full bg-gold hover:bg-gold-dark text-white font-baloo font-semibold text-base shadow-md transition-colors"
>
Start Chatting
<svg width={18} height={18} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round">
<path d="M5 12h14" />
<path d="M13 5l7 7-7 7" />
</svg>
</Link>
</motion.div>
{/* Center hero text */}
<div className="text-center relative z-[2]">
<motion.h1
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
className="font-baloo font-extrabold text-[clamp(3rem,7vw,5.5rem)] text-[#1a1a1a] leading-[1.1] -rotate-1 -mb-[0.35em] relative z-[2]"
>
</motion.h1>
<motion.h2
initial={{ opacity: 0, y: 12 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.15 }}
className="font-vibes text-[clamp(2rem,5vw,3.8rem)] text-[rgba(30,30,30,0.55)] leading-none rotate-[0.5deg] relative z-[1] -mt-[0.1em]"
>
samosaChaat
</motion.h2>
<motion.p
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.3 }}
className="mt-6 font-caveat text-lg md:text-xl text-brown-light max-w-xl mx-auto"
>
Your AI, with a dash of masala
</motion.p>
<motion.div
initial={{ opacity: 0, y: 8 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.45 }}
className="mt-6"
>
<Link
href={ctaHref}
className="inline-flex items-center gap-2 px-8 py-3.5 rounded-full bg-gold hover:bg-gold-dark text-white font-baloo font-semibold text-lg shadow-lg shadow-gold/25 hover:shadow-xl hover:shadow-gold/30 transition-all"
>
Start Chatting
<svg width={18} height={18} viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2.5} strokeLinecap="round" strokeLinejoin="round">
<path d="M5 12h14" />
<path d="M13 5l7 7-7 7" />
</svg>
</Link>
</motion.div>
</div>
{/* Right illustration - Chai Kettle */}
<div className="hidden md:block flex-shrink-0 animate-wobble">
<div className="relative">
<KettleSteam />
<KettleSvg className="w-40 h-40 lg:w-48 lg:h-48" width={192} height={192} />
</div>
<span className="mt-1.5 block text-center font-caveat text-[1.1rem] text-brown-light bg-[#f5edd6] px-4 py-0.5 border border-[#d4c4a0] rounded-sm rotate-2 shadow-sm">
Chai
</span>
</div>
</div>
</section>
);
}

View File

@ -1,30 +1,10 @@
import SamosaSvg from '@/components/svg/SamosaSvg';
import KettleSvg from '@/components/svg/KettleSvg';
import KettleSteam from '@/components/svg/KettleSteam';
// Illustrations are now inline in Hero.tsx as flanking elements.
// This file is kept for backwards compatibility but no longer renders fixed-position illustrations.
export function SamosaIllustration() {
return (
<div className="fixed bottom-[5%] left-[5%] flex flex-col items-center z-[5] pointer-events-none hidden md:flex">
<div className="animate-float">
<SamosaSvg />
</div>
<span className="mt-1.5 inline-block font-caveat text-[1.1rem] text-brown-light bg-[#f5edd6] px-4 py-0.5 border border-[#d4c4a0] rounded-sm -rotate-3 shadow-sm">
Samosa
</span>
</div>
);
return null;
}
export function KettleIllustration() {
return (
<div className="fixed bottom-[5%] right-[5%] flex flex-col items-center z-[5] pointer-events-none hidden md:flex">
<div className="relative animate-wobble">
<KettleSteam />
<KettleSvg />
</div>
<span className="mt-1.5 inline-block font-caveat text-[1.1rem] text-brown-light bg-[#f5edd6] px-4 py-0.5 border border-[#d4c4a0] rounded-sm rotate-2 shadow-sm">
Chai
</span>
</div>
);
return null;
}