This commit is contained in:
eric
2026-04-02 07:38:11 -05:00
parent d401a36b71
commit d53ede2ea5
8 changed files with 350 additions and 158 deletions

View File

@@ -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>
);

View File

@@ -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>
);

View File

@@ -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;
}

View File

@@ -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>