feat: update hero with cards carousel (#18)
This commit is contained in:
parent
dabae60bad
commit
24f4e3f68d
8 changed files with 743 additions and 22 deletions
53
apps/www/components/ui/avatar.tsx
Normal file
53
apps/www/components/ui/avatar.tsx
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
"use client"
|
||||
|
||||
import * as React from "react"
|
||||
import * as AvatarPrimitive from "@radix-ui/react-avatar"
|
||||
|
||||
import { cn } from "@/lib/utils"
|
||||
|
||||
function Avatar({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Root>) {
|
||||
return (
|
||||
<AvatarPrimitive.Root
|
||||
data-slot="avatar"
|
||||
className={cn(
|
||||
"relative flex size-8 shrink-0 overflow-hidden rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarImage({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Image>) {
|
||||
return (
|
||||
<AvatarPrimitive.Image
|
||||
data-slot="avatar-image"
|
||||
className={cn("aspect-square size-full", className)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
function AvatarFallback({
|
||||
className,
|
||||
...props
|
||||
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
|
||||
return (
|
||||
<AvatarPrimitive.Fallback
|
||||
data-slot="avatar-fallback"
|
||||
className={cn(
|
||||
"bg-muted flex size-full items-center justify-center rounded-full",
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export { Avatar, AvatarImage, AvatarFallback }
|
||||
625
apps/www/components/ui/hero-cards.tsx
Normal file
625
apps/www/components/ui/hero-cards.tsx
Normal file
|
|
@ -0,0 +1,625 @@
|
|||
"use client"
|
||||
|
||||
import { useState, useEffect } from "react"
|
||||
import { Card, CardContent } from "@/components/ui/card"
|
||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar"
|
||||
|
||||
const emailContent = `Hi Sarah,
|
||||
|
||||
Thanks for yesterday's meeting! Here are our 3 follow-up items:
|
||||
|
||||
1. Technical Integration - Implementation roadmap by Friday
|
||||
2. Custom Dashboard - Analytics mockups for your KPIs
|
||||
3. Pricing Proposal - ROI analysis by next week
|
||||
|
||||
Available for a call Tuesday to review?
|
||||
|
||||
Best,
|
||||
Alex`
|
||||
|
||||
const slackMessage = `Hey Alex, thank you for helping me out with the OKRs, you are a lifesaver! 🙌`
|
||||
|
||||
const jiraComment = `Bug resolved - closing the task. Please publish the changelog. Thanks!`
|
||||
|
||||
const whatsappMessage = `Thanks for the wonderful evening 🙏, loved the pot roast 😋, see you next week at the game 🏈!`
|
||||
|
||||
const cards = ["gmail", "slack", "jira", "whatsapp"] as const
|
||||
|
||||
export default function HeroCards() {
|
||||
const [currentCardIndex, setCurrentCardIndex] = useState(0)
|
||||
const [isTransitioning, setIsTransitioning] = useState(false)
|
||||
const [isMobile, setIsMobile] = useState(false)
|
||||
|
||||
// Email states
|
||||
const [typedContent, setTypedContent] = useState("")
|
||||
const [currentIndex, setCurrentIndex] = useState(0)
|
||||
const [isTyping, setIsTyping] = useState(true)
|
||||
|
||||
// Slack states
|
||||
const [slackTypedContent, setSlackTypedContent] = useState("")
|
||||
const [slackCurrentIndex, setSlackCurrentIndex] = useState(0)
|
||||
const [slackIsTyping, setSlackIsTyping] = useState(true)
|
||||
|
||||
// Jira states
|
||||
const [jiraTypedContent, setJiraTypedContent] = useState("")
|
||||
const [jiraCurrentIndex, setJiraCurrentIndex] = useState(0)
|
||||
const [jiraIsTyping, setJiraIsTyping] = useState(true)
|
||||
|
||||
// WhatsApp states
|
||||
const [whatsappTypedContent, setWhatsappTypedContent] = useState("")
|
||||
const [whatsappCurrentIndex, setWhatsappCurrentIndex] = useState(0)
|
||||
const [whatsappIsTyping, setWhatsappIsTyping] = useState(true)
|
||||
|
||||
// Check for mobile screen size
|
||||
useEffect(() => {
|
||||
const checkScreenSize = () => {
|
||||
setIsMobile(window.innerWidth < 640)
|
||||
}
|
||||
|
||||
checkScreenSize()
|
||||
window.addEventListener('resize', checkScreenSize)
|
||||
|
||||
return () => window.removeEventListener('resize', checkScreenSize)
|
||||
}, [])
|
||||
|
||||
// Carousel rotation function
|
||||
const rotateCarousel = () => {
|
||||
if (!isTransitioning) {
|
||||
setIsTransitioning(true)
|
||||
setTimeout(() => {
|
||||
setCurrentCardIndex((prev) => (prev + 1) % cards.length)
|
||||
setIsTransitioning(false)
|
||||
}, 300)
|
||||
}
|
||||
}
|
||||
|
||||
// Email typing animation
|
||||
useEffect(() => {
|
||||
if (cards[currentCardIndex] === "gmail") {
|
||||
if (currentIndex < emailContent.length && isTyping) {
|
||||
const timeout = setTimeout(() => {
|
||||
setTypedContent(emailContent.slice(0, currentIndex + 1))
|
||||
setCurrentIndex(currentIndex + 1)
|
||||
}, 3)
|
||||
return () => clearTimeout(timeout)
|
||||
} else if (currentIndex >= emailContent.length && isTyping) {
|
||||
setIsTyping(false)
|
||||
setTimeout(() => {
|
||||
rotateCarousel()
|
||||
setTimeout(() => {
|
||||
setCurrentIndex(0)
|
||||
setTypedContent("")
|
||||
setIsTyping(true)
|
||||
}, 400)
|
||||
}, 1000)
|
||||
}
|
||||
}
|
||||
}, [currentIndex, isTyping, currentCardIndex])
|
||||
|
||||
// Slack typing animation
|
||||
useEffect(() => {
|
||||
if (cards[currentCardIndex] === "slack") {
|
||||
if (slackCurrentIndex < slackMessage.length && slackIsTyping) {
|
||||
const timeout = setTimeout(() => {
|
||||
setSlackTypedContent(slackMessage.slice(0, slackCurrentIndex + 1))
|
||||
setSlackCurrentIndex(slackCurrentIndex + 1)
|
||||
}, 25)
|
||||
return () => clearTimeout(timeout)
|
||||
} else if (slackCurrentIndex >= slackMessage.length && slackIsTyping) {
|
||||
setSlackIsTyping(false)
|
||||
setTimeout(() => {
|
||||
rotateCarousel()
|
||||
setTimeout(() => {
|
||||
setSlackCurrentIndex(0)
|
||||
setSlackTypedContent("")
|
||||
setSlackIsTyping(true)
|
||||
}, 400)
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
}, [slackCurrentIndex, slackIsTyping, currentCardIndex])
|
||||
|
||||
// Jira typing animation
|
||||
useEffect(() => {
|
||||
if (cards[currentCardIndex] === "jira") {
|
||||
if (jiraCurrentIndex < jiraComment.length && jiraIsTyping) {
|
||||
const timeout = setTimeout(() => {
|
||||
setJiraTypedContent(jiraComment.slice(0, jiraCurrentIndex + 1))
|
||||
setJiraCurrentIndex(jiraCurrentIndex + 1)
|
||||
}, 30)
|
||||
return () => clearTimeout(timeout)
|
||||
} else if (jiraCurrentIndex >= jiraComment.length && jiraIsTyping) {
|
||||
setJiraIsTyping(false)
|
||||
setTimeout(() => {
|
||||
rotateCarousel()
|
||||
setTimeout(() => {
|
||||
setJiraCurrentIndex(0)
|
||||
setJiraTypedContent("")
|
||||
setJiraIsTyping(true)
|
||||
}, 400)
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
}, [jiraCurrentIndex, jiraIsTyping, currentCardIndex])
|
||||
|
||||
// WhatsApp typing animation
|
||||
useEffect(() => {
|
||||
if (cards[currentCardIndex] === "whatsapp") {
|
||||
if (whatsappCurrentIndex < whatsappMessage.length && whatsappIsTyping) {
|
||||
const timeout = setTimeout(() => {
|
||||
setWhatsappTypedContent(whatsappMessage.slice(0, whatsappCurrentIndex + 1))
|
||||
setWhatsappCurrentIndex(whatsappCurrentIndex + 1)
|
||||
}, 20)
|
||||
return () => clearTimeout(timeout)
|
||||
} else if (whatsappCurrentIndex >= whatsappMessage.length && whatsappIsTyping) {
|
||||
setWhatsappIsTyping(false)
|
||||
setTimeout(() => {
|
||||
rotateCarousel()
|
||||
setTimeout(() => {
|
||||
setWhatsappCurrentIndex(0)
|
||||
setWhatsappTypedContent("")
|
||||
setWhatsappIsTyping(true)
|
||||
}, 400)
|
||||
}, 2000)
|
||||
}
|
||||
}
|
||||
}, [whatsappCurrentIndex, whatsappIsTyping, currentCardIndex])
|
||||
|
||||
const renderGmailCard = () => (
|
||||
<Card className="shadow-xl border border-neutral-800 bg-neutral-950 overflow-hidden flex flex-col w-full h-full py-0 gap-0">
|
||||
{/* Gmail Header */}
|
||||
<div className="bg-neutral-900 px-3 py-1.5 flex items-center justify-between border-b border-neutral-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src="/integrations/gmail.svg"
|
||||
alt="Gmail"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
<span className="text-sm font-medium text-neutral-300">New Email</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent className="p-0 flex flex-col flex-1">
|
||||
{/* Email Fields */}
|
||||
<div className="space-y-0">
|
||||
<div className="flex items-center border-b border-neutral-800 px-3 py-1.5">
|
||||
<label className="text-sm text-neutral-400 w-12 flex-shrink-0">To</label>
|
||||
<div className="flex-1 bg-neutral-950 px-2 py-0.5">
|
||||
<div className="h-4 bg-neutral-700 rounded w-48"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center border-b border-neutral-800 px-3 py-1.5">
|
||||
<label className="text-sm text-neutral-400 w-12 flex-shrink-0">Subject</label>
|
||||
<div className="flex-1 bg-neutral-950 px-2 py-0.5">
|
||||
<div className="h-4 bg-neutral-700 rounded w-40"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Email Body */}
|
||||
<div className="px-3 py-0.5 flex-1 bg-neutral-950">
|
||||
<div className="whitespace-pre-wrap text-xs leading-relaxed text-neutral-100">
|
||||
{typedContent}
|
||||
{isTyping && <span className="animate-pulse bg-blue-500 w-0.5 h-4 inline-block ml-1" />}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Bottom Actions */}
|
||||
<div className="px-3 py-1 bg-neutral-900 border-t border-neutral-800 flex items-center justify-between mt-auto">
|
||||
<div className="flex items-center gap-2">
|
||||
<button className="bg-blue-600 text-white px-4 py-1.5 rounded text-sm font-medium hover:bg-blue-700">
|
||||
Send
|
||||
</button>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-4 h-4 bg-neutral-700 rounded cursor-pointer"></div>
|
||||
<div className="w-4 h-4 bg-neutral-700 rounded cursor-pointer"></div>
|
||||
<div className="w-4 h-4 bg-neutral-700 rounded cursor-pointer"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-4 h-4 bg-neutral-700 rounded cursor-pointer"></div>
|
||||
<div className="w-4 h-4 bg-neutral-700 rounded cursor-pointer"></div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
const renderSlackCard = () => (
|
||||
<Card className="shadow-lg border border-neutral-800 bg-neutral-950 overflow-hidden flex flex-col w-full h-full py-0 gap-0">
|
||||
{/* Slack Header */}
|
||||
<div className="bg-neutral-900 px-3 py-1.5 flex items-center justify-between border-b border-neutral-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src="/integrations/slack.svg"
|
||||
alt="Slack"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
<span className="text-sm font-medium text-neutral-300">New Message</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Channel Header */}
|
||||
<div className="bg-neutral-950 border-b border-neutral-800 px-4 py-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-md text-neutral-100"># dev-team</span>
|
||||
<div className="w-4 h-4 bg-neutral-700 rounded-full"></div>
|
||||
<span className="text-sm text-neutral-400">42 members</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent className="p-0 bg-neutral-950 flex-1 flex flex-col">
|
||||
{/* Messages Area */}
|
||||
<div className="px-4 py-1.5 h-[80px] overflow-y-auto space-y-2 flex-1">
|
||||
<div className="flex gap-3 hover:bg-neutral-900 px-2 py-1 rounded">
|
||||
<Avatar className="w-9 h-9 mt-1">
|
||||
<AvatarFallback className="text-sm bg-blue-500 text-white font-semibold">AM</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-baseline gap-2 mb-2">
|
||||
<span className="text-sm font-bold text-neutral-100">Alex Miller</span>
|
||||
<span className="text-xs text-neutral-400">2:34 PM</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 bg-neutral-700 rounded w-full"></div>
|
||||
<div className="h-4 bg-neutral-700 rounded w-3/4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 hover:bg-neutral-900 px-2 py-1 rounded">
|
||||
<Avatar className="w-9 h-9 mt-1">
|
||||
<AvatarFallback className="text-sm bg-green-500 text-white font-semibold">JD</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-baseline gap-2 mb-2">
|
||||
<span className="text-sm font-bold text-neutral-100">Jane Doe</span>
|
||||
<span className="text-xs text-neutral-400">2:35 PM</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 bg-neutral-700 rounded w-2/3"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Message Input */}
|
||||
<div className="border-t border-neutral-800 p-2 mt-auto">
|
||||
<div className="border border-neutral-700 rounded-lg bg-neutral-950">
|
||||
<div className="px-3 py-1.5 min-h-[48px] flex items-start">
|
||||
<span className="text-sm text-neutral-100 leading-relaxed">
|
||||
{slackTypedContent}
|
||||
{slackIsTyping && <span className="animate-pulse bg-neutral-400 w-0.5 h-4 inline-block ml-1" />}
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-t border-neutral-700 px-3 py-1.5 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-5 h-5 bg-neutral-800 rounded"></div>
|
||||
<div className="w-5 h-5 bg-neutral-800 rounded"></div>
|
||||
<div className="w-5 h-5 bg-neutral-800 rounded"></div>
|
||||
</div>
|
||||
<div className="w-8 h-6 bg-green-600 rounded text-white flex items-center justify-center">
|
||||
<span className="text-xs">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
const renderJiraCard = () => (
|
||||
<Card className="shadow-lg border border-neutral-800 bg-neutral-950 overflow-hidden flex flex-col w-full h-full py-0 gap-0">
|
||||
{/* Jira Header */}
|
||||
<div className="bg-neutral-900 px-3 py-1.5 flex items-center justify-between border-b border-neutral-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<img src="/integrations/jira.svg" alt="Jira" className="w-5 h-5" />
|
||||
<span className="text-sm font-medium text-neutral-300">Add Comment</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Task Header */}
|
||||
<div className="bg-neutral-950 border-b border-neutral-800 px-4 py-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-4 h-4 bg-red-500 rounded-sm flex items-center justify-center">
|
||||
<span className="text-xs text-white font-bold">🐛</span>
|
||||
</div>
|
||||
<span className="text-sm text-neutral-100">BUG-1234</span>
|
||||
<div className="h-4 bg-neutral-700 rounded w-48"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent className="p-0 bg-neutral-950 flex flex-col flex-1">
|
||||
{/* Task Details */}
|
||||
<div className="px-4 py-1.5 space-y-1.5 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-neutral-400 w-16">Status:</span>
|
||||
<div className="bg-green-900 text-green-300 px-2 py-1 rounded text-xs font-medium">Done</div>
|
||||
</div>
|
||||
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-xs text-neutral-400 w-16">Assignee:</span>
|
||||
<Avatar className="w-6 h-6">
|
||||
<AvatarFallback className="text-xs bg-blue-500 text-white font-semibold">JD</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-xs text-neutral-300">John Developer</span>
|
||||
</div>
|
||||
|
||||
<div className="space-y-2">
|
||||
<div className="border-l-2 border-neutral-700 pl-3">
|
||||
<div className="flex items-center gap-2 mb-1">
|
||||
<Avatar className="w-5 h-5">
|
||||
<AvatarFallback className="text-xs bg-purple-500 text-white font-semibold">PM</AvatarFallback>
|
||||
</Avatar>
|
||||
<span className="text-xs font-medium text-neutral-300">Product Manager</span>
|
||||
<span className="text-xs text-neutral-400">2 hours ago</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-3 bg-neutral-700 rounded w-full"></div>
|
||||
<div className="h-3 bg-neutral-700 rounded w-3/4"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Comment Input */}
|
||||
<div className="border-t border-neutral-800 p-2 mt-auto">
|
||||
<div className="border border-neutral-700 rounded-md bg-neutral-950">
|
||||
<div className="px-3 py-1.5 min-h-[48px] flex items-start">
|
||||
<span className="text-sm text-neutral-100 leading-relaxed">
|
||||
{jiraTypedContent}
|
||||
{jiraIsTyping && <span className="animate-pulse bg-blue-500 w-0.5 h-4 inline-block ml-1" />}
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-t border-neutral-700 px-3 py-1.5 flex items-center justify-between bg-neutral-900">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-6 h-6 bg-neutral-800 rounded flex items-center justify-center">
|
||||
<span className="text-xs text-neutral-300">B</span>
|
||||
</div>
|
||||
<div className="w-6 h-6 bg-neutral-800 rounded flex items-center justify-center">
|
||||
<span className="text-xs text-neutral-300">I</span>
|
||||
</div>
|
||||
<div className="w-6 h-6 bg-neutral-800 rounded flex items-center justify-center">
|
||||
<span className="text-xs text-neutral-300">@</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="bg-blue-600 text-white px-3 py-1.5 rounded text-xs font-medium">Comment</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
const renderWhatsAppCard = () => (
|
||||
<Card className="shadow-lg border border-neutral-800 bg-neutral-950 overflow-hidden flex flex-col w-full h-full py-0 gap-0">
|
||||
{/* WhatsApp Header */}
|
||||
<div className="bg-neutral-900 px-3 py-1.5 flex items-center justify-between border-b border-neutral-800">
|
||||
<div className="flex items-center gap-2">
|
||||
<img
|
||||
src="/integrations/whatsapp.svg"
|
||||
alt="WhatsApp"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
<span className="text-sm font-medium text-neutral-300">Send IM</span>
|
||||
</div>
|
||||
<div className="flex items-center gap-1">
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
<div className="w-4 h-4 bg-neutral-600 rounded-sm"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Chat Header */}
|
||||
<div className="bg-neutral-950 border-b border-neutral-800 px-4 py-1.5">
|
||||
<div className="flex items-center gap-2">
|
||||
<span className="text-md text-neutral-100">Family Group</span>
|
||||
<div className="w-4 h-4 bg-neutral-700 rounded-full"></div>
|
||||
<span className="text-sm text-neutral-400">5 members</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<CardContent className="p-0 bg-neutral-950 flex-1 flex flex-col">
|
||||
{/* Messages Area */}
|
||||
<div className="px-4 py-1.5 h-[80px] overflow-y-auto space-y-2 flex-1">
|
||||
<div className="flex gap-3 hover:bg-neutral-900 px-2 py-1 rounded">
|
||||
<Avatar className="w-9 h-9 mt-1">
|
||||
<AvatarFallback className="text-sm bg-purple-500 text-white font-semibold">MO</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-baseline gap-2 mb-2">
|
||||
<span className="text-sm font-bold text-neutral-100">Mom</span>
|
||||
<span className="text-xs text-neutral-400">7:45 PM</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 bg-neutral-700 rounded w-full"></div>
|
||||
<div className="h-4 bg-neutral-700 rounded w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex gap-3 hover:bg-neutral-900 px-2 py-1 rounded">
|
||||
<Avatar className="w-9 h-9 mt-1">
|
||||
<AvatarFallback className="text-sm bg-blue-500 text-white font-semibold">DA</AvatarFallback>
|
||||
</Avatar>
|
||||
<div className="flex-1 min-w-0">
|
||||
<div className="flex items-baseline gap-2 mb-2">
|
||||
<span className="text-sm font-bold text-neutral-100">Dad</span>
|
||||
<span className="text-xs text-neutral-400">8:12 PM</span>
|
||||
</div>
|
||||
<div className="space-y-1">
|
||||
<div className="h-4 bg-neutral-700 rounded w-3/4"></div>
|
||||
<div className="h-4 bg-neutral-700 rounded w-1/2"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
{/* Message Input */}
|
||||
<div className="border-t border-neutral-800 p-2 mt-auto">
|
||||
<div className="border border-neutral-700 rounded-lg bg-neutral-950">
|
||||
<div className="px-3 py-1.5 min-h-[48px] flex items-start">
|
||||
<span className="text-sm text-neutral-100 leading-relaxed">
|
||||
{whatsappTypedContent}
|
||||
{whatsappIsTyping && <span className="animate-pulse bg-green-500 w-0.5 h-4 inline-block ml-1" />}
|
||||
</span>
|
||||
</div>
|
||||
<div className="border-t border-neutral-700 px-3 py-1.5 flex items-center justify-between">
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="w-5 h-5 bg-neutral-800 rounded"></div>
|
||||
<div className="w-5 h-5 bg-neutral-800 rounded"></div>
|
||||
<div className="w-5 h-5 bg-neutral-800 rounded"></div>
|
||||
</div>
|
||||
<div className="w-8 h-6 bg-green-600 rounded text-white flex items-center justify-center">
|
||||
<span className="text-xs">→</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)
|
||||
|
||||
const renderCard = (cardType: string) => {
|
||||
switch (cardType) {
|
||||
case "gmail":
|
||||
return renderGmailCard()
|
||||
case "slack":
|
||||
return renderSlackCard()
|
||||
case "jira":
|
||||
return renderJiraCard()
|
||||
case "whatsapp":
|
||||
return renderWhatsAppCard()
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
const renderCurrentCardIcon = () => {
|
||||
switch (cards[currentCardIndex]) {
|
||||
case "gmail":
|
||||
return (
|
||||
<img
|
||||
src="/integrations/gmail.svg"
|
||||
alt="Gmail"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
)
|
||||
case "slack":
|
||||
return (
|
||||
<img
|
||||
src="/integrations/slack.svg"
|
||||
alt="Slack"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
)
|
||||
case "jira":
|
||||
return <img src="/integrations/jira.svg" alt="Jira" className="w-5 h-5" />
|
||||
case "whatsapp":
|
||||
return (
|
||||
<img
|
||||
src="/integrations/whatsapp.svg"
|
||||
alt="WhatsApp"
|
||||
className="w-5 h-5"
|
||||
/>
|
||||
)
|
||||
default:
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full flex flex-col items-center justify-center bg-neutral-950 overflow-hidden py-4 sm:py-8">
|
||||
{/* Carousel Container */}
|
||||
<div className="relative w-full max-w-4xl h-[380px] sm:h-[320px] md:h-[400px]">
|
||||
<div className="relative w-full h-full overflow-hidden">
|
||||
{/* Fade Gradients */}
|
||||
<div className="absolute left-0 top-0 w-8 sm:w-24 md:w-32 h-full bg-gradient-to-r from-neutral-950 to-transparent z-10 pointer-events-none"></div>
|
||||
<div className="absolute right-0 top-0 w-8 sm:w-24 md:w-32 h-full bg-gradient-to-l from-neutral-950 to-transparent z-10 pointer-events-none"></div>
|
||||
|
||||
{/* Cards Container with responsive scaling */}
|
||||
<div
|
||||
className={`flex transition-transform duration-300 ease-in-out h-full ${isTransitioning ? "opacity-90" : "opacity-100"} scale-65 sm:scale-75 md:scale-90 lg:scale-100 origin-center`}
|
||||
style={{
|
||||
transform: `translateX(calc(-${currentCardIndex * (isMobile ? 57 : 40)}% + ${isMobile ? '-22' : '15'}%))`,
|
||||
width: `${cards.length * (isMobile ? 50 : 35)}%`,
|
||||
}}
|
||||
>
|
||||
{cards.map((cardType, index) => (
|
||||
<div
|
||||
key={cardType}
|
||||
className="w-3/5 sm:w-2/5 px-1 sm:px-2 h-full flex-shrink-0"
|
||||
style={{
|
||||
opacity: index === currentCardIndex ? 1 : 0.3,
|
||||
transform: index === currentCardIndex ? "scale(1)" : "scale(0.9)",
|
||||
transition: "opacity 300ms ease-in-out, transform 300ms ease-in-out",
|
||||
}}
|
||||
>
|
||||
{renderCard(cardType)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Recording Pill - Fixed below cards with stronger indigo glow */}
|
||||
<div className="-mt-10 sm:mt-0 md:mt-8 mb-8">
|
||||
<div
|
||||
className="flex items-center gap-2 sm:gap-3 md:gap-4 bg-black rounded-full px-4 sm:px-5 md:px-6 py-2 sm:py-2.5 md:py-3 border border-neutral-700 relative"
|
||||
style={{
|
||||
boxShadow:
|
||||
"0 0 30px rgba(99, 102, 241, 0.6), 0 0 60px rgba(99, 102, 241, 0.3), 0 0 90px rgba(99, 102, 241, 0.1)",
|
||||
}}
|
||||
>
|
||||
{/* Dynamic App Icon */}
|
||||
<div>
|
||||
{renderCurrentCardIcon()}
|
||||
</div>
|
||||
|
||||
{/* Extended Recording Wave Animation */}
|
||||
<div className="flex items-center gap-[1px] sm:gap-[2px] h-4 sm:h-5">
|
||||
{[1, 2, 3, 4, 5, 6, 7, 8, 9, 10].map((i) => (
|
||||
<div
|
||||
key={i}
|
||||
className="w-[2px] sm:w-[3px] bg-green-500 rounded-full"
|
||||
style={{
|
||||
height: `${Math.max(30, 50 + Math.sin(i) * 20)}%`,
|
||||
animation: `waveAnimation ${0.5 + i * 0.1}s ease-in-out infinite alternate`,
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* CSS for wave animation */}
|
||||
<style jsx global>{`
|
||||
@keyframes waveAnimation {
|
||||
0% {
|
||||
height: 30%;
|
||||
}
|
||||
100% {
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
29
apps/www/components/ui/hero-old.tsx
Normal file
29
apps/www/components/ui/hero-old.tsx
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import { WavyBackground } from "../ui/wavy-background";
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<WavyBackground
|
||||
className="max-w-6xl mx-auto pb-10"
|
||||
backgroundFill="#0A0A0A"
|
||||
>
|
||||
<h1 className="text-2xl md:text-4xl lg:text-6xl text-white font-bold inter-var text-center leading-tight -mt-20">
|
||||
Open Source AI Dictation App
|
||||
</h1>
|
||||
<h2 className="text-base md:text-lg mt-4 text-white font-normal inter-var text-center">
|
||||
Type 10x faster, no keyboard needed. Fast, Accurate, Context-aware and Private.
|
||||
</h2>
|
||||
<div className="flex justify-center">
|
||||
<a
|
||||
href="https://github.com/amicalhq/amical"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block mt-8 px-8 py-3 bg-white text-black font-semibold rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</WavyBackground>
|
||||
);
|
||||
}
|
||||
|
|
@ -1,29 +1,36 @@
|
|||
"use client";
|
||||
import React from "react";
|
||||
import { WavyBackground } from "../ui/wavy-background";
|
||||
import HeroCards from "./hero-cards";
|
||||
|
||||
export function Hero() {
|
||||
return (
|
||||
<WavyBackground
|
||||
className="max-w-6xl mx-auto pb-10"
|
||||
backgroundFill="#0A0A0A"
|
||||
>
|
||||
<h1 className="text-2xl md:text-4xl lg:text-6xl text-white font-bold inter-var text-center leading-tight -mt-20">
|
||||
Open Source AI Dictation App
|
||||
</h1>
|
||||
<h2 className="text-base md:text-lg mt-4 text-white font-normal inter-var text-center">
|
||||
Type 10x faster, no keyboard needed. Fast, Accurate, Context-aware and Private.
|
||||
</h2>
|
||||
<div className="flex justify-center">
|
||||
<a
|
||||
href="https://github.com/amicalhq/amical"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block mt-8 px-8 py-3 bg-white text-black font-semibold rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-20">
|
||||
<div className="grid grid-cols-1 lg:grid-cols-5 gap-0 lg:gap-12 items-center">
|
||||
{/* Left side - Title and content */}
|
||||
<div className="space-y-6 lg:col-span-2 text-center lg:text-left">
|
||||
<h1 className="text-3xl md:text-4xl lg:text-5xl text-white font-bold inter-var leading-tight">
|
||||
Open Source <br /> AI Dictation App
|
||||
</h1>
|
||||
<h2 className="text-base md:text-lg text-neutral-300 font-normal inter-var leading-relaxed">
|
||||
Type 10x faster, no keyboard needed. Fast, Accurate, Context-aware and Private.
|
||||
</h2>
|
||||
<div className="pt-4">
|
||||
<a
|
||||
href="https://github.com/amicalhq/amical"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-block px-8 py-3 bg-white text-black font-semibold rounded-lg hover:bg-gray-100 transition-colors"
|
||||
>
|
||||
Get Started
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</WavyBackground>
|
||||
|
||||
{/* Right side - Hero cards */}
|
||||
<div className="flex justify-center lg:col-span-3 order-2 lg:order-1">
|
||||
<HeroCards />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"dependencies": {
|
||||
"@next/third-parties": "^15.3.2",
|
||||
"@radix-ui/react-accordion": "^1.2.10",
|
||||
"@radix-ui/react-avatar": "^1.1.9",
|
||||
"@radix-ui/react-checkbox": "^1.3.1",
|
||||
"@radix-ui/react-dialog": "^1.1.13",
|
||||
"@radix-ui/react-label": "^2.1.6",
|
||||
|
|
|
|||
|
|
@ -1 +1,4 @@
|
|||
<svg viewBox="-0.003 -293.41895027729095 1172.923 1474.5159502772908" xmlns="http://www.w3.org/2000/svg" fill-rule="evenodd" clip-rule="evenodd" stroke-linejoin="round" stroke-miterlimit="2" width="1659" height="2500"><path d="M308.678 1021.49l19.153 9.576a499.739 499.739 0 0 0 258.244 70.227c279.729-.638 509.563-231.016 509.563-510.744 0-135.187-53.692-265.012-149.169-360.713-95.35-96.69-225.62-151.18-361.383-151.18-278.451 0-507.552 229.133-507.552 507.552 0 2.203 0 4.373.032 6.576a523.81 523.81 0 0 0 76.612 268.14l12.768 19.153-51.074 188.337 192.806-46.925z" fill="#00E676" fill-rule="nonzero"/><path d="M1003.29 172.378C894.597 61.482 745.49-.732 590.225 0h-.99C269.479.001 6.35 263.131 6.35 582.888c0 1.5.032 2.969.032 4.47a616.759 616.759 0 0 0 76.612 290.485L-.003 1181.097l309.32-79.804a569.202 569.202 0 0 0 278.993 70.228c320.939-1.756 584.036-266.385 583.844-587.356.766-154.213-60.044-302.52-168.864-411.787m-413.065 900.186a473.935 473.935 0 0 1-245.476-67.035l-19.153-9.577-184.187 47.883 47.882-181.953-12.768-19.153a484.242 484.242 0 0 1-72.558-254.957c0-265.65 218.599-484.25 484.25-484.25 265.65 0 484.248 218.6 484.248 484.25 0 167.269-86.666 323.11-228.781 411.372a464.838 464.838 0 0 1-251.86 73.42m280.59-354.329l-35.114-15.96s-51.075-22.346-82.996-38.306c-3.192 0-6.384-3.192-9.577-3.192a46.308 46.308 0 0 0-22.345 6.384c-6.799 3.99-3.192 3.192-47.882 54.266-3.032 5.97-9.257 9.705-15.96 9.577h-3.193a24.328 24.328 0 0 1-12.768-6.384l-15.961-6.385a309.91 309.91 0 0 1-92.573-60.65c-6.384-6.385-15.96-12.77-22.345-19.154a357.13 357.13 0 0 1-60.65-76.611l-3.193-6.384a46.475 46.475 0 0 1-6.384-12.769 23.915 23.915 0 0 1 3.192-15.96c2.905-4.789 12.769-15.962 22.345-25.538 9.577-9.576 9.577-15.96 15.961-22.345a39.33 39.33 0 0 0 6.384-31.922 1246.398 1246.398 0 0 0-51.074-121.301 37.099 37.099 0 0 0-22.345-15.961H380.82c-6.384 0-12.768 3.192-19.153 3.192l-3.192 3.192c-6.384 3.192-12.768 9.577-19.153 12.769-6.384 3.192-9.576 12.769-15.96 19.153a162.752 162.752 0 0 0-35.114 98.956 189.029 189.029 0 0 0 15.96 73.42l3.193 9.576a532.111 532.111 0 0 0 118.11 162.8l12.768 12.769a193.037 193.037 0 0 1 25.537 25.537c66.141 57.554 144.7 99.052 229.516 121.302 9.576 3.192 22.345 3.192 31.921 6.384h31.922a118.126 118.126 0 0 0 47.882-12.769c7.82-3.543 15.29-7.82 22.345-12.768l6.384-6.385c6.385-6.384 12.769-9.576 19.153-15.96a84.393 84.393 0 0 0 15.961-19.153c6.129-14.301 10.438-29.304 12.769-44.69V724.62a40.107 40.107 0 0 0-9.577-6.385" fill="#fff" fill-rule="nonzero"/></svg>
|
||||
<svg width="360" height="362" viewBox="0 0 360 362" fill="none" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M307.546 52.5655C273.709 18.685 228.706 0.0171895 180.756 0C81.951 0 1.53846 80.404 1.50408 179.235C1.48689 210.829 9.74646 241.667 25.4319 268.844L0 361.736L95.0236 336.811C121.203 351.096 150.683 358.616 180.679 358.625H180.756C279.544 358.625 359.966 278.212 360 179.381C360.017 131.483 341.392 86.4547 307.546 52.5741V52.5655ZM180.756 328.354H180.696C153.966 328.346 127.744 321.16 104.865 307.589L99.4242 304.358L43.034 319.149L58.0834 264.168L54.5423 258.53C39.6304 234.809 31.749 207.391 31.7662 179.244C31.8006 97.1036 98.6334 30.2707 180.817 30.2707C220.61 30.2879 258.015 45.8015 286.145 73.9665C314.276 102.123 329.755 139.562 329.738 179.364C329.703 261.513 262.871 328.346 180.756 328.346V328.354ZM262.475 216.777C257.997 214.534 235.978 203.704 231.869 202.209C227.761 200.713 224.779 199.966 221.796 204.452C218.814 208.939 210.228 219.029 207.615 222.011C205.002 225.002 202.389 225.372 197.911 223.128C193.434 220.885 179.003 216.158 161.891 200.902C148.578 189.024 139.587 174.362 136.975 169.875C134.362 165.389 136.7 162.965 138.934 160.739C140.945 158.728 143.412 155.505 145.655 152.892C147.899 150.279 148.638 148.406 150.133 145.423C151.629 142.432 150.881 139.82 149.764 137.576C148.646 135.333 139.691 113.287 135.952 104.323C132.316 95.5909 128.621 96.777 125.879 96.6309C123.266 96.5019 120.284 96.4762 117.293 96.4762C114.302 96.4762 109.454 97.5935 105.346 102.08C101.238 106.566 89.6691 117.404 89.6691 139.441C89.6691 161.478 105.716 182.785 107.959 185.776C110.202 188.767 139.544 234.001 184.469 253.408C195.153 258.023 203.498 260.782 210.004 262.845C220.731 266.257 230.494 265.776 238.212 264.624C246.816 263.335 264.71 253.786 268.44 243.326C272.17 232.866 272.17 223.893 271.053 222.028C269.936 220.163 266.945 219.037 262.467 216.794L262.475 216.777Z" fill="#25D366">
|
||||
</path>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 2.4 KiB After Width: | Height: | Size: 2 KiB |
3
pnpm-lock.yaml
generated
3
pnpm-lock.yaml
generated
|
|
@ -288,6 +288,9 @@ importers:
|
|||
'@radix-ui/react-accordion':
|
||||
specifier: ^1.2.10
|
||||
version: 1.2.11(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-avatar':
|
||||
specifier: ^1.1.9
|
||||
version: 1.1.10(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
'@radix-ui/react-checkbox':
|
||||
specifier: ^1.3.1
|
||||
version: 1.3.2(@types/react-dom@19.1.5(@types/react@19.1.5))(@types/react@19.1.5)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue