's'
This commit is contained in:
@@ -15,19 +15,28 @@ export default async function AppDetailPage({ params }: { params: Promise<{ slug
|
||||
const appResources = await getApps();
|
||||
const related = appResources.filter((i) => i.slug !== slug).slice(0, 3);
|
||||
return (
|
||||
<div className="space-y-6 py-8">
|
||||
<div className="space-y-5 py-6 md:space-y-6 md:py-8">
|
||||
<ScrollReveal>
|
||||
<section className="card-glass relative overflow-hidden">
|
||||
<div className="pointer-events-none absolute -right-8 -top-8 h-36 w-36 rounded-full bg-cyan-300/20 blur-3xl" />
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="text-4xl">{app.icon}</div>
|
||||
<div>
|
||||
<h1 className="text-3xl font-bold">{app.name}</h1>
|
||||
<p className="text-slate-300">{app.subtitle}</p>
|
||||
<section className="panel-strong">
|
||||
<div className="grid gap-4 md:grid-cols-12 md:items-center">
|
||||
<div className="md:col-span-8">
|
||||
<div className="flex items-center gap-4">
|
||||
<div className="grid h-14 w-14 place-items-center rounded-2xl border border-white/10 bg-slate-900/45 text-3xl">{app.icon}</div>
|
||||
<div>
|
||||
<h1 className="text-2xl font-bold md:text-3xl">{app.name}</h1>
|
||||
<p className="text-sm text-slate-300 md:text-base">{app.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{app.tags.map((t) => <span key={t} className="pill">{t}</span>)}
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:col-span-4">
|
||||
<div className="rounded-xl border border-cyan-300/25 bg-cyan-400/10 p-3 text-sm">
|
||||
<p className="text-xs uppercase tracking-wide text-cyan-200/80">下载提示</p>
|
||||
<p className="mt-2 text-slate-100">建议优先使用主线路,若下载慢可切换备用源或历史版本。</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-3 flex flex-wrap gap-2">
|
||||
{app.tags.map((t) => <span key={t} className="pill">{t}</span>)}
|
||||
</div>
|
||||
</section>
|
||||
</ScrollReveal>
|
||||
@@ -46,31 +55,36 @@ export default async function AppDetailPage({ params }: { params: Promise<{ slug
|
||||
<ScrollReveal delay={0.1}>
|
||||
<ChangelogTimeline app={app} />
|
||||
</ScrollReveal>
|
||||
|
||||
<ScrollReveal delay={0.12}>
|
||||
<section className="card-glass">
|
||||
<h3 className="text-lg font-semibold">软件介绍</h3>
|
||||
<p className="mt-2 text-sm text-slate-300">{app.description}</p>
|
||||
<h4 className="mt-4 font-medium">使用说明</h4>
|
||||
<ul className="mt-2 list-disc space-y-1 pl-5 text-sm text-slate-300">
|
||||
{app.usage.map((u) => <li key={u}>{u}</li>)}
|
||||
</ul>
|
||||
<section className="grid gap-4 lg:grid-cols-12">
|
||||
<div className="space-y-4 lg:col-span-8">
|
||||
<ScrollReveal delay={0.12}>
|
||||
<section className="panel">
|
||||
<h3 className="text-base font-semibold">软件介绍</h3>
|
||||
<p className="mt-2 text-sm text-slate-300">{app.description}</p>
|
||||
<h4 className="mt-4 text-sm font-medium">使用说明</h4>
|
||||
<ul className="mt-2 list-disc space-y-1 pl-5 text-sm text-slate-300">
|
||||
{app.usage.map((u) => <li key={u}>{u}</li>)}
|
||||
</ul>
|
||||
</section>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.14}>
|
||||
<ScreenshotGallery slug={app.slug} shots={app.screenshots} />
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
<div className="space-y-4 lg:col-span-4">
|
||||
<ScrollReveal delay={0.16}>
|
||||
<QRCodeSection />
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.18}>
|
||||
<UnlockSection />
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
</section>
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.14}>
|
||||
<ScreenshotGallery slug={app.slug} shots={app.screenshots} />
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.16}>
|
||||
<QRCodeSection />
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.18}>
|
||||
<UnlockSection />
|
||||
</ScrollReveal>
|
||||
<ScrollReveal delay={0.2}>
|
||||
<section className="space-y-3">
|
||||
<h3 className="text-lg font-semibold">相关推荐</h3>
|
||||
<AppGrid apps={related} />
|
||||
</section>
|
||||
<section className="space-y-3">
|
||||
<h3 className="text-base font-semibold">相关推荐</h3>
|
||||
<AppGrid apps={related} />
|
||||
</section>
|
||||
</ScrollReveal>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -4,9 +4,14 @@ import { getApps } from "@/services/apps.service";
|
||||
export default async function AppsPage() {
|
||||
const apps = await getApps();
|
||||
return (
|
||||
<div className="space-y-6 py-8">
|
||||
<h1 className="text-3xl font-bold">资源列表</h1>
|
||||
<p className="text-sm text-slate-300">支持搜索、分类筛选、排序与加载更多(本地 mock 过滤)。</p>
|
||||
<div className="space-y-5 py-6 md:space-y-6 md:py-8">
|
||||
<section className="panel">
|
||||
<p className="text-xs uppercase tracking-[0.25em] text-cyan-200/80">Download Center</p>
|
||||
<h1 className="mt-2 text-2xl font-bold md:text-3xl">网盘资源下载站</h1>
|
||||
<p className="mt-2 max-w-2xl text-sm text-slate-300 md:text-base">
|
||||
聚合热门工具、影视和学习资源,支持多渠道下载与版本回溯,快速定位你需要的资源。
|
||||
</p>
|
||||
</section>
|
||||
<AppsExplorer apps={apps} />
|
||||
</div>
|
||||
);
|
||||
|
||||
134
app/globals.css
134
app/globals.css
@@ -1,11 +1,17 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
:root {
|
||||
--background: #050816;
|
||||
--foreground: #e6edf8;
|
||||
--card: rgba(15, 23, 42, 0.56);
|
||||
--card-border: rgba(148, 163, 184, 0.24);
|
||||
--ring: rgba(56, 189, 248, 0.45);
|
||||
--background: #060b19;
|
||||
--background-soft: #0b1329;
|
||||
--foreground: #e8eefc;
|
||||
--muted: #94a3b8;
|
||||
--card: rgba(11, 19, 41, 0.72);
|
||||
--card-strong: rgba(15, 25, 50, 0.9);
|
||||
--card-border: rgba(148, 163, 184, 0.26);
|
||||
--ring: rgba(56, 189, 248, 0.38);
|
||||
--accent: #22d3ee;
|
||||
--accent-2: #818cf8;
|
||||
--accent-3: #f472b6;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
@@ -17,17 +23,40 @@
|
||||
|
||||
@media (prefers-color-scheme: dark) {
|
||||
:root {
|
||||
--background: #020617;
|
||||
--foreground: #e2e8f0;
|
||||
--background: #050915;
|
||||
--foreground: #e2e8f8;
|
||||
}
|
||||
}
|
||||
|
||||
html.light {
|
||||
--background: #f4f7ff;
|
||||
--foreground: #111827;
|
||||
--card: rgba(255, 255, 255, 0.74);
|
||||
--card-border: rgba(148, 163, 184, 0.24);
|
||||
--ring: rgba(14, 116, 144, 0.28);
|
||||
--background: #f3f6ff;
|
||||
--background-soft: #e9efff;
|
||||
--foreground: #0f172a;
|
||||
--muted: #475569;
|
||||
--card: rgba(255, 255, 255, 0.84);
|
||||
--card-strong: rgba(255, 255, 255, 0.96);
|
||||
--card-border: rgba(148, 163, 184, 0.34);
|
||||
--ring: rgba(14, 116, 144, 0.22);
|
||||
--accent: #0891b2;
|
||||
--accent-2: #4f46e5;
|
||||
--accent-3: #db2777;
|
||||
}
|
||||
|
||||
@keyframes bg-shift {
|
||||
0% { background-position: 0% 0%, 100% 0%, 50% 0%; }
|
||||
50% { background-position: 10% 20%, 90% 16%, 50% 100%; }
|
||||
100% { background-position: 0% 0%, 100% 0%, 50% 0%; }
|
||||
}
|
||||
|
||||
@keyframes panel-glow {
|
||||
0% { box-shadow: 0 12px 30px rgba(15, 23, 42, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.06); }
|
||||
50% { box-shadow: 0 14px 34px rgba(34, 211, 238, 0.16), inset 0 1px 0 rgba(255, 255, 255, 0.08); }
|
||||
100% { box-shadow: 0 12px 30px rgba(15, 23, 42, 0.28), inset 0 1px 0 rgba(255, 255, 255, 0.06); }
|
||||
}
|
||||
|
||||
@keyframes neon-pulse {
|
||||
0%, 100% { box-shadow: 0 10px 24px rgba(34, 211, 238, 0.28); }
|
||||
50% { box-shadow: 0 14px 30px rgba(244, 114, 182, 0.3); }
|
||||
}
|
||||
|
||||
body {
|
||||
@@ -35,9 +64,12 @@ body {
|
||||
color: var(--foreground);
|
||||
font-family: var(--font-geist-sans), Arial, sans-serif;
|
||||
background-image:
|
||||
radial-gradient(circle at 10% -10%, rgba(34, 211, 238, 0.18), transparent 35%),
|
||||
radial-gradient(circle at 90% 0%, rgba(139, 92, 246, 0.2), transparent 36%),
|
||||
radial-gradient(circle at 50% 100%, rgba(59, 130, 246, 0.1), transparent 45%);
|
||||
radial-gradient(1000px 440px at 10% -10%, rgba(34, 211, 238, 0.2), transparent 64%),
|
||||
radial-gradient(900px 460px at 88% -16%, rgba(129, 140, 248, 0.22), transparent 60%),
|
||||
linear-gradient(180deg, var(--background-soft), var(--background));
|
||||
background-size: 120% 120%, 120% 120%, 100% 100%;
|
||||
animation: bg-shift 18s ease-in-out infinite;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
* {
|
||||
@@ -45,48 +77,92 @@ body {
|
||||
}
|
||||
|
||||
.card-glass {
|
||||
@apply rounded-xl border p-3 backdrop-blur-xl transition duration-300;
|
||||
background: var(--card);
|
||||
@apply rounded-2xl border p-4 backdrop-blur-xl transition duration-300;
|
||||
background:
|
||||
linear-gradient(140deg, color-mix(in srgb, var(--accent) 10%, transparent), transparent 40%),
|
||||
linear-gradient(320deg, color-mix(in srgb, var(--accent-2) 12%, transparent), transparent 50%),
|
||||
var(--card);
|
||||
border-color: var(--card-border);
|
||||
box-shadow: 0 20px 55px rgba(2, 8, 23, 0.32), inset 0 1px 0 rgba(255, 255, 255, 0.05);
|
||||
animation: panel-glow 6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.card-glass:hover {
|
||||
box-shadow: 0 24px 62px rgba(2, 8, 23, 0.42), 0 0 0 1px rgba(125, 211, 252, 0.16);
|
||||
box-shadow: 0 18px 42px rgba(34, 211, 238, 0.2);
|
||||
transform: translateY(-3px) scale(1.01);
|
||||
}
|
||||
|
||||
.pill {
|
||||
@apply rounded-full border px-3.5 py-1.5 text-xs font-medium transition;
|
||||
background: rgba(148, 163, 184, 0.12);
|
||||
@apply rounded-full border px-3.5 py-1.5 text-xs font-medium transition duration-200;
|
||||
background: rgba(148, 163, 184, 0.1);
|
||||
border-color: rgba(148, 163, 184, 0.24);
|
||||
}
|
||||
|
||||
.pill-active {
|
||||
@apply text-cyan-100;
|
||||
@apply text-cyan-50;
|
||||
background: linear-gradient(135deg, rgba(6, 182, 212, 0.46), rgba(99, 102, 241, 0.42));
|
||||
border-color: rgba(125, 211, 252, 0.5);
|
||||
}
|
||||
|
||||
.cta-main {
|
||||
@apply rounded-xl px-5 py-2.5 text-sm font-semibold text-slate-900 transition duration-300 hover:-translate-y-0.5;
|
||||
background: linear-gradient(135deg, #22d3ee, #60a5fa 52%, #a78bfa);
|
||||
box-shadow: 0 10px 28px rgba(56, 189, 248, 0.35), 0 0 0 1px rgba(255, 255, 255, 0.2) inset;
|
||||
@apply rounded-xl px-4 py-2 text-sm font-semibold text-slate-950 transition duration-200;
|
||||
background: linear-gradient(120deg, #67e8f9 0%, #a5b4fc 50%, #f9a8d4 100%);
|
||||
box-shadow: 0 10px 24px rgba(34, 211, 238, 0.28);
|
||||
animation: neon-pulse 3.8s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.cta-main:hover {
|
||||
transform: translateY(-2px) scale(1.015);
|
||||
box-shadow: 0 14px 32px rgba(99, 102, 241, 0.4);
|
||||
}
|
||||
|
||||
.cta-sub {
|
||||
@apply rounded-xl border px-5 py-2.5 text-sm transition duration-300 hover:-translate-y-0.5;
|
||||
background: rgba(15, 23, 42, 0.35);
|
||||
@apply rounded-xl border px-4 py-2 text-sm transition duration-200;
|
||||
background: linear-gradient(180deg, rgba(15, 23, 42, 0.32), rgba(15, 23, 42, 0.2));
|
||||
}
|
||||
|
||||
.cta-sub:hover {
|
||||
border-color: rgba(125, 211, 252, 0.45);
|
||||
background: rgba(15, 23, 42, 0.4);
|
||||
}
|
||||
|
||||
.section-title {
|
||||
@apply text-lg font-semibold tracking-tight md:text-xl;
|
||||
@apply text-xl font-semibold tracking-tight md:text-2xl;
|
||||
}
|
||||
|
||||
.input-fancy {
|
||||
@apply w-full rounded-2xl border bg-slate-950/30 px-4 py-3 text-sm outline-none transition placeholder:text-slate-400;
|
||||
@apply w-full rounded-2xl border px-4 py-3 text-sm outline-none transition placeholder:text-slate-400;
|
||||
background: rgba(15, 23, 42, 0.36);
|
||||
border-color: rgba(125, 211, 252, 0.2);
|
||||
}
|
||||
|
||||
.input-fancy:focus {
|
||||
box-shadow: 0 0 0 4px var(--ring);
|
||||
}
|
||||
|
||||
.panel {
|
||||
@apply rounded-2xl border p-4 md:p-5;
|
||||
background:
|
||||
linear-gradient(145deg, color-mix(in srgb, var(--accent) 7%, transparent), transparent 48%),
|
||||
linear-gradient(315deg, color-mix(in srgb, var(--accent-2) 9%, transparent), transparent 52%),
|
||||
var(--card);
|
||||
border-color: var(--card-border);
|
||||
animation: panel-glow 7s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.panel-strong {
|
||||
@apply rounded-2xl border p-4 md:p-5;
|
||||
background:
|
||||
linear-gradient(140deg, color-mix(in srgb, var(--accent) 12%, transparent), transparent 42%),
|
||||
linear-gradient(320deg, color-mix(in srgb, var(--accent-3) 12%, transparent), transparent 55%),
|
||||
var(--card-strong);
|
||||
border-color: var(--card-border);
|
||||
animation: panel-glow 5.5s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.text-muted {
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
a, button {
|
||||
transition: all 0.22s ease;
|
||||
}
|
||||
|
||||
79
app/page.tsx
79
app/page.tsx
@@ -1,5 +1,5 @@
|
||||
import { categoryLabels } from "@/lib/mock-data";
|
||||
import { HeroSection, UnlockSection } from "@/components/sections";
|
||||
import { AppGrid, HeroSection, StatsPanel, UnlockSection } from "@/components/sections";
|
||||
import { ScrollReveal } from "@/components/scroll-reveal";
|
||||
import { getApps } from "@/services/apps.service";
|
||||
import Link from "next/link";
|
||||
@@ -7,15 +7,23 @@ import Link from "next/link";
|
||||
export default async function Home() {
|
||||
const appResources = await getApps();
|
||||
const latest = [...appResources].sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));
|
||||
const featured = appResources.filter((a) => a.featured).slice(0, 4);
|
||||
const featured = appResources.filter((a) => a.featured).slice(0, 6);
|
||||
return (
|
||||
<div className="space-y-8 py-6">
|
||||
<div className="space-y-8 py-6 md:space-y-10 md:py-8">
|
||||
<ScrollReveal>
|
||||
<HeroSection />
|
||||
</ScrollReveal>
|
||||
|
||||
<section className="border-y border-white/10 py-3">
|
||||
<div className="-mx-1 flex flex-wrap items-center gap-2">
|
||||
<ScrollReveal delay={0.02}>
|
||||
<StatsPanel />
|
||||
</ScrollReveal>
|
||||
|
||||
<section className="panel">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h2 className="text-base font-semibold md:text-lg">热门分类</h2>
|
||||
<Link href="/apps" className="text-xs text-cyan-300 md:text-sm">按分类浏览</Link>
|
||||
</div>
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className="pill pill-active shrink-0">全部</span>
|
||||
{Object.values(categoryLabels).map((label) => (
|
||||
<span key={label} className="pill shrink-0">{label}</span>
|
||||
@@ -23,30 +31,44 @@ export default async function Home() {
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="space-y-3">
|
||||
<div className="flex min-w-0 flex-wrap items-baseline justify-between gap-2">
|
||||
<h2 className="section-title shrink-0">精选资源推荐</h2>
|
||||
<Link href="/apps" className="shrink-0 text-sm text-cyan-300">查看更多</Link>
|
||||
</div>
|
||||
<AppGrid apps={featured} />
|
||||
</section>
|
||||
|
||||
<section className="grid min-w-0 grid-cols-1 gap-6 lg:grid-cols-12">
|
||||
<div className="min-w-0 lg:col-span-8">
|
||||
<div className="mb-3 flex min-w-0 flex-wrap items-baseline justify-between gap-2">
|
||||
<div className="mb-3 flex min-w-0 flex-wrap items-end justify-between gap-2">
|
||||
<h2 className="section-title shrink-0">最新更新</h2>
|
||||
<Link href="/apps" className="shrink-0 text-sm text-cyan-300">查看全部</Link>
|
||||
</div>
|
||||
<div className="divide-y divide-white/10 overflow-hidden rounded-xl border border-white/10">
|
||||
<div className="panel overflow-hidden p-0">
|
||||
<div className="hidden grid-cols-12 border-b border-white/10 bg-slate-900/25 px-4 py-2 text-xs text-slate-400 md:grid">
|
||||
<span className="col-span-6">资源信息</span>
|
||||
<span className="col-span-2 text-center">版本</span>
|
||||
<span className="col-span-2 text-center">大小</span>
|
||||
<span className="col-span-2 text-center">更新日期</span>
|
||||
</div>
|
||||
{latest.slice(0, 8).map((app) => (
|
||||
<Link
|
||||
key={app.id}
|
||||
href={`/apps/${app.slug}`}
|
||||
className="flex min-w-0 flex-col gap-1 px-3 py-3 transition hover:bg-white/5 sm:flex-row sm:items-center sm:gap-3"
|
||||
className="grid gap-2 border-b border-white/10 px-4 py-3 transition hover:bg-white/5 last:border-b-0 md:grid-cols-12 md:items-center"
|
||||
>
|
||||
<div className="flex min-w-0 flex-1 items-start gap-3 sm:items-center">
|
||||
<span className="shrink-0 text-lg leading-none">{app.icon}</span>
|
||||
<div className="col-span-6 flex min-w-0 items-start gap-3 sm:items-center">
|
||||
<span className="grid h-9 w-9 shrink-0 place-items-center rounded-lg border border-white/10 bg-slate-900/45 text-lg leading-none">{app.icon}</span>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium">{app.name}</p>
|
||||
<p className="truncate text-sm font-semibold">{app.name}</p>
|
||||
<p className="truncate text-xs text-slate-400">{app.subtitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex shrink-0 flex-wrap items-center gap-x-3 gap-y-1 pl-9 text-xs text-slate-400 sm:pl-0">
|
||||
<span className="hidden whitespace-nowrap sm:inline">{app.version}</span>
|
||||
<span className="hidden whitespace-nowrap sm:inline">{app.size}</span>
|
||||
<span className="whitespace-nowrap tabular-nums">{app.updatedAt}</span>
|
||||
<div className="col-span-6 grid grid-cols-3 pl-12 text-xs text-slate-300 sm:pl-0 md:text-sm">
|
||||
<span className="text-center tabular-nums">{app.version}</span>
|
||||
<span className="text-center tabular-nums">{app.size}</span>
|
||||
<span className="text-center tabular-nums">{app.updatedAt}</span>
|
||||
</div>
|
||||
</Link>
|
||||
))}
|
||||
@@ -54,26 +76,29 @@ export default async function Home() {
|
||||
</div>
|
||||
|
||||
<aside className="min-w-0 space-y-4 lg:col-span-4">
|
||||
<div className="rounded-xl border border-white/10 p-3">
|
||||
<p className="text-sm font-medium">精选</p>
|
||||
<div className="mt-2 space-y-2">
|
||||
{featured.map((app) => (
|
||||
<div className="panel">
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<p className="text-sm font-semibold">下载热榜</p>
|
||||
<span className="text-xs text-slate-400">按下载量</span>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
{latest.slice(0, 6).map((app, index) => (
|
||||
<Link
|
||||
key={app.id}
|
||||
href={`/apps/${app.slug}`}
|
||||
className="flex min-w-0 items-center justify-between gap-3 text-xs text-slate-300 hover:text-white"
|
||||
className="flex min-w-0 items-center justify-between gap-3 rounded-xl border border-white/10 bg-slate-900/20 px-3 py-2 text-xs text-slate-200 transition hover:border-cyan-300/30 hover:bg-slate-900/35"
|
||||
>
|
||||
<span className="min-w-0 truncate">{app.name}</span>
|
||||
<span className="shrink-0 tabular-nums">{app.downloads.toLocaleString()}</span>
|
||||
<span className="flex min-w-0 items-center gap-2.5 truncate">
|
||||
<span className="grid h-6 w-6 shrink-0 place-items-center rounded-full border border-cyan-300/35 bg-cyan-400/10 text-[10px] font-semibold text-cyan-200">
|
||||
{String(index + 1).padStart(2, "0")}
|
||||
</span>
|
||||
<span className="truncate">{app.name}</span>
|
||||
</span>
|
||||
<span className="shrink-0 tabular-nums text-cyan-200">{app.downloads.toLocaleString()}</span>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="rounded-xl border border-white/10 p-3 text-xs leading-relaxed text-slate-300">
|
||||
<p>免费资源约 70%</p>
|
||||
<p className="mt-1">解锁资源约 20%</p>
|
||||
<p className="mt-1">会员资源约 10%</p>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
|
||||
@@ -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