feat(landing): add audience tabs to hero

This commit is contained in:
DIYgod 2026-03-17 16:44:49 +08:00
parent 4cc68d34cf
commit 312ec968ef
5 changed files with 121 additions and 52 deletions

View File

@ -23,6 +23,7 @@ export interface SegmentTabProps<T = string> {
inactiveClassName?: string
size?: 'sm' | 'md' | 'lg'
variant?: 'default' | 'compact'
distribution?: 'fill' | 'fit'
disabled?: boolean
responsiveWrap?: boolean
ref?: Ref<HTMLDivElement>
@ -69,6 +70,7 @@ export function SegmentTab<T = string>({
inactiveClassName,
size = 'md',
variant = 'default',
distribution = 'fill',
disabled = false,
responsiveWrap = false,
ref,
@ -82,7 +84,8 @@ export function SegmentTab<T = string>({
role="tablist"
aria-disabled={disabled || undefined}
className={cn(
'container-type-[inline-size] relative flex w-full items-center',
'container-type-[inline-size] relative flex items-center',
distribution === 'fill' ? 'w-full' : 'w-fit max-w-full',
styles.container,
disabled && 'pointer-events-none opacity-60',
containerClassName,
@ -90,7 +93,8 @@ export function SegmentTab<T = string>({
>
<div
className={cn(
'relative flex w-full items-center',
'relative flex items-center',
distribution === 'fill' ? 'w-full' : 'w-fit max-w-full',
responsiveWrap && 'flex-wrap',
)}
>
@ -110,12 +114,14 @@ export function SegmentTab<T = string>({
'focus-visible:ring-accent/30 focus:outline-none focus-visible:ring-2',
sizeClass,
styles.button,
responsiveWrap
? clsx(
'@[0px]:flex-none @[0px]:basis-[calc(50%-0.125rem)] @[0px]:px-2',
'@[420px]:flex-1 @[420px]:basis-auto @[420px]:px-3',
)
: clsx('flex-1', '@[0px]:px-2 @[420px]:px-3'),
distribution === 'fit'
? 'flex-none'
: responsiveWrap
? clsx(
'@[0px]:flex-none @[0px]:basis-[calc(50%-0.125rem)] @[0px]:px-2',
'@[420px]:flex-1 @[420px]:basis-auto @[420px]:px-3',
)
: clsx('flex-1', '@[0px]:px-2 @[420px]:px-3'),
isActive
? cn('text-text', activeClassName)
: cn(
@ -150,7 +156,6 @@ export function SegmentTab<T = string>({
// Floating pill indicator with subtle border & shadow
'pointer-events-none absolute inset-x-0 inset-y-0.5 z-[-1]',
styles.indicator,
responsiveWrap && 'hidden @[420px]:block',
className,
)}
/>

View File

@ -15,6 +15,7 @@ import { ParticlesAura } from '~/components/ui/effects/ParticlesAura'
import { TiltCard } from '~/components/ui/effects/TiltCard'
import { Highlighter } from '~/components/ui/highlighter'
import { PanelSplitter } from '~/components/ui/panel/PanelSplitter'
import { SegmentTab } from '~/components/ui/segment-tab'
import { Link as LocalizedLink } from '~/i18n/routing'
import type { HeroTimelineItem } from '~/lib/landing-data'
@ -26,6 +27,8 @@ type LandingHeroProps = {
items: HeroTimelineItem[]
}
type AudienceKey = 'human' | 'agent'
export const LandingHero: Component<LandingHeroProps> = ({ items }) => {
const ref = React.useRef<HTMLElement | null>(null)
const { scrollYProgress } = useScroll({
@ -35,6 +38,28 @@ export const LandingHero: Component<LandingHeroProps> = ({ items }) => {
const bgY = useTransform(scrollYProgress, [0, 1], [0, -150])
const heroT = useTranslations('landing.hero')
const actionsT = useTranslations('common.actions')
const [audience, setAudience] = React.useState<AudienceKey>('human')
const audienceItems: {
value: AudienceKey
label: string
icon: React.ReactNode
}[] = [
{
value: 'human',
label: heroT('humanTab'),
icon: (
<i className="i-mingcute-user-3-line size-4 shrink-0" aria-hidden />
),
},
{
value: 'agent',
label: heroT('agentTab'),
icon: (
<i className="i-mingcute-android-2-line size-4 shrink-0" aria-hidden />
),
},
]
return (
<section ref={ref} className="relative isolate w-full">
@ -75,48 +100,75 @@ export const LandingHero: Component<LandingHeroProps> = ({ items }) => {
<p>{heroT('bodyLine2')}</p>
</div>
<div className="mt-8 flex items-center">
<NextLink
href="https://app.folo.is"
target="_blank"
rel="noreferrer noopener"
>
<span className="relative inline-flex">
<Button className="group relative text-base overflow-hidden px-5 py-2.5 bg-accent text-accent-foreground shadow-[0_0_0_1px_var(--color-accent-40)] ![filter:drop-shadow(0_0_24px_color-mix(in_oklab,var(--color-accent)_35%,transparent))] rounded-xl">
<span
aria-hidden
className={clsx(
'pointer-events-none absolute -inset-1 rounded-[inherit] opacity-70 blur-md',
'bg-[radial-gradient(closest-side,color-mix(in_oklab,var(--color-accent)_55%,transparent)_0%,transparent_70%)]',
)}
/>
<div className="mt-8 max-w-xl">
<SegmentTab
items={audienceItems}
value={audience}
onChange={(value) => setAudience(value as AudienceKey)}
containerClassName="inline-flex w-fit max-w-full rounded-full border-border/70 bg-background/45 shadow-[0_20px_40px_-32px_rgba(255,107,0,0.4)]"
className="!inset-y-0 !rounded-full !border-accent/20 !bg-background !shadow-[0_12px_24px_-16px_rgba(255,107,0,0.6)]"
activeClassName="text-accent"
inactiveClassName="text-text-secondary/75 hover:text-text"
distribution="fit"
size="lg"
responsiveWrap
/>
<span className="relative z-10 inline-flex items-center">
{actionsT('getStarted')}
{audience === 'human' ? (
<div className="mt-4 flex flex-wrap items-center gap-3">
<NextLink
href="https://app.folo.is"
target="_blank"
rel="noreferrer noopener"
>
<span className="relative inline-flex">
<Button className="group relative overflow-hidden rounded-xl bg-accent px-5 py-2.5 text-base text-accent-foreground shadow-[0_0_0_1px_var(--color-accent-40)] ![filter:drop-shadow(0_0_24px_color-mix(in_oklab,var(--color-accent)_35%,transparent))]">
<span
aria-hidden
className={clsx(
'pointer-events-none absolute -inset-1 rounded-[inherit] opacity-70 blur-md',
'bg-[radial-gradient(closest-side,color-mix(in_oklab,var(--color-accent)_55%,transparent)_0%,transparent_70%)]',
)}
/>
<span className="relative z-10 inline-flex items-center">
{actionsT('getStarted')}
</span>
<BorderBeam colorFrom="#fff" colorTo="#ff5c00" />
</Button>
<ParticlesAura className="-inset-2" />
</span>
<BorderBeam colorFrom="#fff" colorTo="#ff5c00" />
</Button>
<ParticlesAura className="-inset-2" />
</span>
</NextLink>
<LocalizedLink href="/download">
<Button variant="ghost" className="ml-4">
<span className="relative z-10 inline-flex items-center text-base">
{actionsT('download')}
</span>
</Button>
</LocalizedLink>
{/* <a
href="#learn-more"
className="text-text-secondary hover:text-text inline-flex items-center gap-1 text-sm"
>
<span className="rounded-full border border-border bg-fill px-2 py-1 text-xs">
New
</span>
<span>Product Intelligence</span>
<i className="i-mingcute-right-line" aria-hidden />
</a> */}
</NextLink>
<LocalizedLink href="/download">
<Button variant="ghost">
<span className="relative z-10 inline-flex items-center text-base">
{actionsT('download')}
</span>
</Button>
</LocalizedLink>
</div>
) : (
<div className="bg-material-medium/60 border-border mt-4 rounded-2xl border p-5 backdrop-blur-md">
<p className="text-text text-sm font-semibold">
{heroT('agentTitle')}
</p>
<p className="text-text-secondary mt-2 text-sm leading-6">
{heroT.rich('agentBody', {
skill: (chunks) => (
<a
href="https://api.folo.is/skill.md"
target="_blank"
rel="noreferrer noopener"
className="text-accent underline decoration-accent/40 underline-offset-4 transition-colors hover:text-accent/80"
>
{chunks}
</a>
),
})}
</p>
</div>
)}
</div>
</div>

View File

@ -22,7 +22,11 @@
"eyebrow": "AI RSS Reader · Clean Timeline",
"title": "<brand>Folo</brand> is the <highlight>AI RSS Reader</highlight>",
"bodyLine1": "The AI that reads the internet for you,",
"bodyLine2": "cutting through noise to surface the knowledge you actually care about."
"bodyLine2": "cutting through noise to surface the knowledge you actually care about.",
"humanTab": "I'm a Human",
"agentTab": "I'm an Agent",
"agentTitle": "Use Folo",
"agentBody": "Read <skill>https://api.folo.is/skill.md</skill> and follow the instructions to use Folo."
},
"features": {
"headline": "Reading reimagined in the AI era",

View File

@ -22,7 +22,11 @@
"eyebrow": "AI RSS Reader · クリーンタイムライン",
"title": "<brand>Folo</brand> はあなたの <highlight>AI RSS Reader</highlight>",
"bodyLine1": "インターネットの情報を AI が代わりに読み、",
"bodyLine2": "雑音を取り除いて、本当に必要な知識だけを届けます。"
"bodyLine2": "雑音を取り除いて、本当に必要な知識だけを届けます。",
"humanTab": "I'm a Human",
"agentTab": "I'm an Agent",
"agentTitle": "Use Folo",
"agentBody": "Read <skill>https://api.folo.is/skill.md</skill> and follow the instructions to use Folo."
},
"features": {
"headline": "AI 時代のための新しい読書体験",

View File

@ -22,7 +22,11 @@
"eyebrow": "AI RSS Reader · 洁净时间线",
"title": "<brand>Folo</brand> 是你的 <highlight>AI RSS Reader</highlight>",
"bodyLine1": "这款 AI 为你读遍全网,",
"bodyLine2": "穿透噪音,只呈现你真正关心的洞见。"
"bodyLine2": "穿透噪音,只呈现你真正关心的洞见。",
"humanTab": "I'm a Human",
"agentTab": "I'm an Agent",
"agentTitle": "Use Folo",
"agentBody": "Read <skill>https://api.folo.is/skill.md</skill> and follow the instructions to use Folo."
},
"features": {
"headline": "在 AI 时代重新定义阅读",