's'
This commit is contained in:
@@ -8,10 +8,10 @@ import { TRACKING_EVENTS } from "@/lib/tracking-events";
|
||||
export function AppMetaInfo({ app }: { app: AppResource }) {
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-3 text-sm md:grid-cols-4">
|
||||
<div className="card-glass"><p>版本</p><p className="mt-1 font-semibold">{app.version}</p></div>
|
||||
<div className="card-glass"><p>大小</p><p className="mt-1 font-semibold">{app.size}</p></div>
|
||||
<div className="card-glass"><p>更新日期</p><p className="mt-1 font-semibold">{app.updatedAt}</p></div>
|
||||
<div className="card-glass"><p>上传日期</p><p className="mt-1 font-semibold">{app.uploadedAt}</p></div>
|
||||
<div className="panel-strong"><p className="text-xs text-slate-400">版本</p><p className="mt-1 font-semibold">{app.version}</p></div>
|
||||
<div className="panel-strong"><p className="text-xs text-slate-400">大小</p><p className="mt-1 font-semibold">{app.size}</p></div>
|
||||
<div className="panel-strong"><p className="text-xs text-slate-400">更新</p><p className="mt-1 font-semibold">{app.updatedAt}</p></div>
|
||||
<div className="panel-strong"><p className="text-xs text-slate-400">上传</p><p className="mt-1 font-semibold">{app.uploadedAt}</p></div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -19,15 +19,15 @@ export function AppMetaInfo({ app }: { app: AppResource }) {
|
||||
export function DownloadButtons({ app }: { app: AppResource }) {
|
||||
const { track } = useTrack();
|
||||
return (
|
||||
<div className="grid grid-cols-2 gap-3 md:grid-cols-4">
|
||||
<div className="grid grid-cols-1 gap-2 sm:grid-cols-2 xl:grid-cols-4">
|
||||
{app.channels.map((c) => (
|
||||
<a
|
||||
key={c.id}
|
||||
href={buildChannelUrl(c.href, c.name, app.slug)}
|
||||
onClick={() => track(TRACKING_EVENTS.downloadClick, { slug: app.slug, channel: c.name, sourceType: c.type, location: "detail" })}
|
||||
className="rounded-2xl border border-cyan-300/20 bg-gradient-to-br from-cyan-400/15 to-violet-400/10 p-3 text-center text-sm transition hover:-translate-y-1 hover:shadow-[0_10px_30px_rgba(34,211,238,.22)]"
|
||||
className="panel-strong text-center text-sm transition hover:border-cyan-300/40 hover:bg-cyan-400/10"
|
||||
>
|
||||
{c.name}
|
||||
<p className="font-medium">{c.name}</p>
|
||||
{c.hint && <p className="mt-1 text-xs text-slate-400">{c.hint}</p>}
|
||||
</a>
|
||||
))}
|
||||
@@ -37,10 +37,10 @@ export function DownloadButtons({ app }: { app: AppResource }) {
|
||||
|
||||
export function VersionHistory({ app }: { app: AppResource }) {
|
||||
return (
|
||||
<div className="card-glass space-y-3">
|
||||
<h3 className="text-lg font-semibold">历史版本</h3>
|
||||
<div className="panel space-y-3">
|
||||
<h3 className="text-base font-semibold">历史版本</h3>
|
||||
{app.versionHistory.map((v) => (
|
||||
<div key={v.version} className="flex items-center justify-between rounded-2xl border border-white/10 bg-slate-900/20 p-3 text-sm">
|
||||
<div key={v.version} className="flex items-center justify-between rounded-xl border border-white/10 bg-slate-950/20 p-3 text-sm">
|
||||
<div><p>{v.version}</p><p className="text-xs text-slate-400">{v.updatedAt} · {v.size}</p></div>
|
||||
<a href={v.downloadHref} className="pill pill-active">下载此版本</a>
|
||||
</div>
|
||||
@@ -51,8 +51,8 @@ export function VersionHistory({ app }: { app: AppResource }) {
|
||||
|
||||
export function ChangelogTimeline({ app }: { app: AppResource }) {
|
||||
return (
|
||||
<div className="card-glass space-y-4">
|
||||
<h3 className="text-lg font-semibold">更新日志</h3>
|
||||
<div className="panel space-y-4">
|
||||
<h3 className="text-base font-semibold">更新日志</h3>
|
||||
{app.changelog.map((item) => (
|
||||
<div key={item.date + item.title} className="relative border-l border-cyan-300/40 pl-4">
|
||||
<span className="absolute -left-[5px] top-1 h-2.5 w-2.5 rounded-full bg-cyan-300" />
|
||||
@@ -67,8 +67,8 @@ export function ChangelogTimeline({ app }: { app: AppResource }) {
|
||||
|
||||
export function QRCodeSection() {
|
||||
return (
|
||||
<div className="card-glass text-center">
|
||||
<div className="mx-auto grid h-40 w-40 place-items-center rounded-2xl border border-dashed border-cyan-300/40 bg-slate-900/70 text-xs text-slate-400">
|
||||
<div className="panel text-center">
|
||||
<div className="mx-auto grid h-40 w-40 place-items-center rounded-2xl border border-dashed border-cyan-300/40 bg-slate-900/60 text-xs text-slate-400">
|
||||
二维码区域
|
||||
</div>
|
||||
<p className="mt-3 text-sm text-slate-300">关注公众号并发送关键词“下载”获取解锁码。</p>
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
"use client";
|
||||
|
||||
import { useMemo, useState } from "react";
|
||||
import { AppGrid, CategoryTabs } from "@/components/sections";
|
||||
import Link from "next/link";
|
||||
import { CategoryTabs } from "@/components/sections";
|
||||
import { AppResource } from "@/lib/types";
|
||||
|
||||
export function AppsExplorer({ apps }: { apps: AppResource[] }) {
|
||||
@@ -24,21 +25,29 @@ export function AppsExplorer({ apps }: { apps: AppResource[] }) {
|
||||
}, [apps, query, category, sort]);
|
||||
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<div className="card-glass space-y-3">
|
||||
<input
|
||||
className="w-full rounded-xl border border-white/15 bg-slate-900/50 px-3 py-2 text-sm outline-none"
|
||||
placeholder="搜索资源、标签或名称"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
<section className="space-y-4 md:space-y-5">
|
||||
<div className="panel space-y-4">
|
||||
<div className="grid gap-3 md:grid-cols-12 md:items-center">
|
||||
<div className="md:col-span-8">
|
||||
<input
|
||||
className="input-fancy"
|
||||
placeholder="搜索资源、标签或名称"
|
||||
value={query}
|
||||
onChange={(e) => setQuery(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="flex items-center justify-between rounded-xl border border-white/10 px-3 py-2 text-xs text-slate-300 md:col-span-4 md:text-sm">
|
||||
<span>已找到资源</span>
|
||||
<strong className="text-cyan-200">{filtered.length}</strong>
|
||||
</div>
|
||||
</div>
|
||||
<CategoryTabs active={category} onChange={setCategory} />
|
||||
<div className="flex items-center gap-2 text-sm">
|
||||
<span>排序:</span>
|
||||
<div className="flex flex-wrap items-center gap-2 text-sm">
|
||||
<span className="text-slate-300">排序方式:</span>
|
||||
{[
|
||||
{ id: "latest", label: "最新" },
|
||||
{ id: "hot", label: "最热" },
|
||||
{ id: "update", label: "更新日期" },
|
||||
{ id: "latest", label: "最新上架" },
|
||||
{ id: "hot", label: "热度优先" },
|
||||
{ id: "update", label: "最近更新" },
|
||||
].map((s) => (
|
||||
<button key={s.id} className={`pill ${sort === s.id ? "pill-active" : ""}`} onClick={() => setSort(s.id)}>
|
||||
{s.label}
|
||||
@@ -46,11 +55,58 @@ export function AppsExplorer({ apps }: { apps: AppResource[] }) {
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<AppGrid apps={filtered.slice(0, count)} />
|
||||
|
||||
{filtered.length > 0 ? (
|
||||
<div className="space-y-3">
|
||||
{filtered.slice(0, count).map((app) => (
|
||||
<div key={app.id} className="panel-strong flex min-w-0 flex-col gap-3 md:flex-row md:items-center md:justify-between">
|
||||
<div className="flex min-w-0 flex-1 items-start gap-3">
|
||||
<span className="grid h-10 w-10 shrink-0 place-items-center rounded-xl border border-white/10 bg-slate-900/50 text-lg">{app.icon}</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<div className="flex items-center gap-2">
|
||||
<Link href={`/apps/${app.slug}`} className="truncate text-sm font-semibold transition hover:text-cyan-300 md:text-base">
|
||||
{app.name}
|
||||
</Link>
|
||||
<span className="rounded-md border border-cyan-300/35 bg-cyan-400/10 px-1.5 py-0.5 text-[10px] text-cyan-100">{app.version}</span>
|
||||
</div>
|
||||
<p className="mt-1 truncate text-xs text-slate-400 md:text-sm">{app.subtitle}</p>
|
||||
<div className="mt-2 flex flex-wrap gap-1.5">
|
||||
{app.tags.slice(0, 3).map((tag) => (
|
||||
<span key={tag} className="pill py-1">{tag}</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-3 gap-2 text-xs text-slate-400 md:w-[320px] md:text-sm">
|
||||
<div className="rounded-lg border border-white/10 px-2 py-2 text-center">
|
||||
<p className="text-[10px] uppercase tracking-wide text-slate-500">更新</p>
|
||||
<p className="mt-1 text-slate-200">{app.updatedAt}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-white/10 px-2 py-2 text-center">
|
||||
<p className="text-[10px] uppercase tracking-wide text-slate-500">体积</p>
|
||||
<p className="mt-1 text-slate-200">{app.size}</p>
|
||||
</div>
|
||||
<div className="rounded-lg border border-white/10 px-2 py-2 text-center">
|
||||
<p className="text-[10px] uppercase tracking-wide text-slate-500">下载</p>
|
||||
<p className="mt-1 text-slate-200">{app.downloads}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="panel text-center">
|
||||
<p className="text-sm text-slate-300">没有找到匹配资源,换个关键词试试。</p>
|
||||
<button onClick={() => setQuery("")} className="mt-3 cta-sub">清空搜索</button>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{count < filtered.length && (
|
||||
<button onClick={() => setCount((c) => c + 6)} className="mx-auto block rounded-xl border border-white/20 px-4 py-2 text-sm">
|
||||
加载更多
|
||||
</button>
|
||||
<div className="pt-1">
|
||||
<button onClick={() => setCount((c) => c + 6)} className="mx-auto block cta-sub">
|
||||
加载更多资源
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
|
||||
@@ -1,48 +1,43 @@
|
||||
"use client";
|
||||
|
||||
import Link from "next/link";
|
||||
import { motion } from "framer-motion";
|
||||
import { ThemeToggle } from "@/components/theme-toggle";
|
||||
import { Search, Sparkles } from "lucide-react";
|
||||
import { Search } from "lucide-react";
|
||||
import { HiddenEasterEggTrigger } from "@/components/hidden-easter-egg-trigger";
|
||||
import { useAuth } from "@/components/auth-provider";
|
||||
|
||||
export function Navbar() {
|
||||
const { session, loginAs, logout } = useAuth();
|
||||
return (
|
||||
<motion.header
|
||||
initial={{ y: -24, opacity: 0 }}
|
||||
animate={{ y: 0, opacity: 1 }}
|
||||
className="sticky top-0 z-40 border-b border-white/10 bg-slate-950/70 backdrop-blur-xl"
|
||||
>
|
||||
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 md:px-6">
|
||||
<div className="flex items-center gap-6">
|
||||
<header className="sticky top-0 z-40 border-b border-white/10 bg-slate-950/70 backdrop-blur-2xl">
|
||||
<div className="mx-auto flex h-16 max-w-7xl items-center justify-between px-4 md:h-18 md:px-6">
|
||||
<div className="flex min-w-0 items-center gap-4 md:gap-8">
|
||||
<HiddenEasterEggTrigger>
|
||||
<Link href="/" className="flex items-center gap-2 text-sm font-semibold">
|
||||
<Sparkles className="text-cyan-400" size={18} />
|
||||
Download Nova
|
||||
<Link href="/" className="flex items-center gap-2.5">
|
||||
<span className="grid h-8 w-8 place-items-center rounded-xl border border-cyan-300/40 bg-cyan-400/10 text-sm">D</span>
|
||||
<span className="text-sm font-semibold tracking-wide md:text-base">Download Nova</span>
|
||||
</Link>
|
||||
</HiddenEasterEggTrigger>
|
||||
<nav className="hidden items-center gap-4 text-sm text-slate-300 md:flex">
|
||||
<Link href="/apps">资源库</Link>
|
||||
<Link href="/insights">数据洞察</Link>
|
||||
<Link href="/unlock">解锁中心</Link>
|
||||
<Link href="/pricing">会员套餐</Link>
|
||||
<nav className="hidden items-center gap-5 text-sm text-slate-300 lg:flex">
|
||||
<Link href="/apps" className="transition hover:text-cyan-200">资源库</Link>
|
||||
<Link href="/insights" className="transition hover:text-cyan-200">数据洞察</Link>
|
||||
<Link href="/unlock" className="transition hover:text-cyan-200">解锁中心</Link>
|
||||
<Link href="/pricing" className="transition hover:text-cyan-200">会员套餐</Link>
|
||||
</nav>
|
||||
</div>
|
||||
<div className="flex items-center gap-2">
|
||||
<div className="flex shrink-0 items-center gap-2.5">
|
||||
<button className="pill hidden md:inline-flex" onClick={session.isLoggedIn ? logout : () => loginAs()}>
|
||||
{session.isLoggedIn ? `${session.userName ?? "用户"} · ${session.accessLevel}` : "未登录"}
|
||||
</button>
|
||||
<Link href="/apps" className="rounded-full border border-white/20 p-2">
|
||||
<Link href="/apps" className="rounded-full border border-white/20 p-2 transition hover:border-cyan-300/40 hover:bg-cyan-400/10">
|
||||
<Search size={16} />
|
||||
</Link>
|
||||
<ThemeToggle />
|
||||
<Link href="/pricing" className="rounded-full bg-cyan-400/90 px-4 py-1.5 text-xs font-semibold text-slate-900 shadow-[0_0_20px_rgba(34,211,238,0.45)]">
|
||||
<Link href="/pricing" className="cta-main rounded-full px-4 py-1.5 text-xs shadow-none md:text-sm">
|
||||
登录 / 会员
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
</motion.header>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -42,18 +42,39 @@ export function CategoryTabs({
|
||||
export function HeroSection() {
|
||||
const { track } = useTrack();
|
||||
return (
|
||||
<motion.section initial={{ opacity: 0, y: 24 }} animate={{ opacity: 1, y: 0 }} className="w-full min-w-0 space-y-4 py-4">
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight md:text-4xl">找软件,直接下载</h1>
|
||||
<p className="max-w-2xl text-sm text-slate-400">每日更新,版本清晰,来源可查。</p>
|
||||
<SearchBar placeholder="试试搜索:AI 绘图、剪辑、开发工具..." />
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{hotKeywords.map((k) => (
|
||||
<span key={k} className="pill">{k}</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Link href="/apps" onClick={() => track(TRACKING_EVENTS.ctaClick, { position: "hero", target: "apps" })} className="cta-main">进入资源库</Link>
|
||||
<Link href="/unlock" onClick={() => track(TRACKING_EVENTS.ctaClick, { position: "hero", target: "unlock" })} className="cta-sub">输入解锁码</Link>
|
||||
<motion.section initial={{ opacity: 0, y: 24 }} animate={{ opacity: 1, y: 0 }} className="panel-strong relative w-full min-w-0 overflow-hidden">
|
||||
<div className="pointer-events-none absolute -top-20 right-[-60px] h-56 w-56 rounded-full bg-cyan-300/20 blur-3xl" />
|
||||
<div className="pointer-events-none absolute -bottom-24 left-[-80px] h-60 w-60 rounded-full bg-indigo-400/15 blur-3xl" />
|
||||
<div className="relative grid gap-6 lg:grid-cols-12 lg:items-center">
|
||||
<div className="space-y-4 lg:col-span-8">
|
||||
<p className="text-xs uppercase tracking-[0.3em] text-cyan-200/80">Download Nova</p>
|
||||
<h1 className="text-3xl font-bold leading-tight tracking-tight md:text-5xl">网盘资源下载,一站直达</h1>
|
||||
<p className="max-w-2xl text-sm text-slate-300 md:text-base">每日更新,版本清晰,渠道可查。热门软件、学习资料、影音资源快速下载。</p>
|
||||
<SearchBar placeholder="试试搜索:AI 绘图、剪辑、开发工具..." />
|
||||
<div className="flex flex-wrap gap-1.5">
|
||||
{hotKeywords.map((k) => (
|
||||
<span key={k} className="pill">{k}</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-3">
|
||||
<Link href="/apps" onClick={() => track(TRACKING_EVENTS.ctaClick, { position: "hero", target: "apps" })} className="cta-main">进入资源库</Link>
|
||||
<Link href="/unlock" onClick={() => track(TRACKING_EVENTS.ctaClick, { position: "hero", target: "unlock" })} className="cta-sub">输入解锁码</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid gap-2 sm:grid-cols-3 lg:col-span-4 lg:grid-cols-1">
|
||||
<div className="rounded-xl border border-white/10 bg-slate-900/35 p-3">
|
||||
<p className="text-xs text-slate-400">今日更新</p>
|
||||
<p className="mt-1 text-2xl font-bold text-cyan-200">27</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-white/10 bg-slate-900/35 p-3">
|
||||
<p className="text-xs text-slate-400">可用资源</p>
|
||||
<p className="mt-1 text-2xl font-bold text-cyan-200">1,286</p>
|
||||
</div>
|
||||
<div className="rounded-xl border border-white/10 bg-slate-900/35 p-3">
|
||||
<p className="text-xs text-slate-400">总下载量</p>
|
||||
<p className="mt-1 text-2xl font-bold text-cyan-200">8.4M</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</motion.section>
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user