chore(landing): update

Signed-off-by: Innei <tukon479@gmail.com>
This commit is contained in:
Innei
2025-11-11 23:21:54 +08:00
parent 16d78812ee
commit 9c4fe4155f
13 changed files with 360 additions and 318 deletions

View File

@@ -6,23 +6,24 @@ import { FeatureSection } from '~/components/landing/FeatureSection'
import { HeroSection } from '~/components/landing/HeroSection'
import { MetricStrip } from '~/components/landing/MetricStrip'
import { PreviewSection } from '~/components/landing/PreviewSection'
import { TechSection } from '~/components/landing/TechSection'
import { WorkflowSection } from '~/components/landing/WorkflowSection'
import { Footer } from '~/components/layout/Footer'
import { Header } from '~/components/layout/Header'
export default function Home() {
return (
<div className="bg-background text-text relative isolate overflow-hidden pb-32">
<div className="bg-background text-text relative isolate overflow-hidden">
<BackgroundDecor />
<Header />
<div className="relative z-10 mx-auto flex w-full max-w-6xl flex-col gap-20 px-4 pt-12 pb-16 sm:px-6 lg:px-0">
<div className="relative z-10 mx-auto flex w-full max-w-6xl flex-col gap-20 px-4 pt-32 pb-16 sm:px-6 lg:px-0">
<HeroSection />
<MetricStrip />
<PreviewSection />
<FeatureSection />
<WorkflowSection />
<TechSection />
<CTASection />
</div>
<Footer />
</div>
)
}

View File

@@ -3,7 +3,7 @@
import Link from 'next/link'
import { Button } from '~/components/ui/button/Button'
import { radius, shadows } from '~/lib/design-tokens'
import { radius } from '~/lib/design-tokens'
import { clsxm } from '~/lib/helper'
export const CTASection = () => (
@@ -12,21 +12,19 @@ export const CTASection = () => (
className={clsxm(
'relative overflow-hidden border border-white/10 bg-gradient-to-br from-accent/40 via-purple-600/40 to-slate-900/70 p-10 text-white',
radius['3xl'],
shadows.heavy,
)}
>
<div className="pointer-events-none absolute inset-0 bg-[radial-gradient(circle_at_top,_rgba(255,255,255,0.25),_transparent_55%)] opacity-80" />
<div className="relative space-y-6">
<p className="text-sm tracking-[0.4em] text-white/70 uppercase">
Ready?
使
</p>
<h2 className="text-4xl leading-tight font-semibold">
Afilmory
<span className="text-accent">线</span>
5
<span className="text-accent"></span>
</h2>
<p className="text-lg text-white/80">
builderapps/webapps/ssr
be/apps/core UI
</p>
<div className="flex flex-wrap items-center gap-4">
<Button
@@ -38,7 +36,7 @@ export const CTASection = () => (
target="_blank"
rel="noreferrer"
>
Fork &
使
</Link>
</Button>
<Link
@@ -47,7 +45,7 @@ export const CTASection = () => (
rel="noreferrer"
className="inline-flex items-center gap-2 text-sm font-medium text-white/80 hover:text-white"
>
线
线
<i className="i-lucide-arrow-up-right size-4" />
</Link>
</div>

View File

@@ -7,43 +7,43 @@ import { FeatureCard } from './Card'
const featureGroups = [
{
icon: 'i-lucide-cpu',
title: '性能与体验',
description: 'WebGL viewer、Masonry 布局与全屏手势带来原生级互动。',
icon: 'i-lucide-sparkles',
title: '精美展示',
description: '瀑布流布局自动适配,让每张照片都以最佳方式呈现',
bullets: [
'GPU 管线渲染 · Free transform · Tone mapping',
'Blurhash 占位、响应式断点与浮动操作面板',
'Glassmorphic Depth 系统 + Motion spring 动画',
'智能排版算法,完美利用屏幕空间',
'流畅的加载动画,媲美专业杂志',
'支持高清大图,不损失画质细节',
],
},
{
icon: 'i-lucide-database',
title: '数据与同步',
description: 'builder 多进程处理 + manifest 驱动数据流,自动增量同步。',
icon: 'i-lucide-image',
title: '照片管理',
description: '自动识别相机信息,保留每张照片背后的故事',
bullets: [
'S3 / GitHub / Eagle / 本地多源存储抽象',
'EXIF、Live Photo、Fujifilm Recipe 与缩略图生成',
'window.__MANIFEST__ 注入,前端无感刷新',
'自动读取相机型号、镜头和拍摄参数',
'支持 Apple Live Photo 动态照片',
'记录拍摄地点,在地图上回顾旅程',
],
},
{
icon: 'i-lucide-plug',
title: '接入模式',
description: 'SPA、Next.js SSR、be/apps/core 后端三种模式自由切换。',
icon: 'i-lucide-share-2',
title: '便捷分享',
description: '一键生成分享链接,让更多人看到你的作品',
bullets: [
'静态/SSR 共享 UIapps/web + apps/ssr',
'be/apps/core 以 Hono + Drizzle 提供实时能力',
'OG 渲染 / SEO 元数据 / OpenGraph API',
'精美的社交媒体预览卡片',
'支持单张照片快速分享',
'自动生成作品集展示页面',
],
},
{
icon: 'i-lucide-globe',
title: '全球化 & 分享',
description: '多语言、OG、分享嵌入天然适合出海作品集。',
icon: 'i-lucide-zap',
title: '快速搭建',
description: '无需编程知识5分钟即可拥有专业摄影网站',
bullets: [
'i18next 平台化11+ 语言文件',
'动态 OG 图 + /og/[photoId] API',
'分享/嵌入组件、一键复制链接或 iframe',
'提供详细的使用文档和教程',
'支持多种部署方式,完全免费',
'持续更新优化,长期维护支持',
],
},
]
@@ -52,14 +52,13 @@ export const FeatureSection = () => (
<section className={spacing.content}>
<header className={spacing.tight}>
<p className="text-text-secondary text-sm font-semibold tracking-[0.3em] uppercase">
Afilmory
</p>
<h2 className={clsxm(typography.h1, 'text-white')}>
</h2>
<p className="text-text-secondary max-w-3xl text-base">
Performance / Data / Integrations / Global Experience
便
</p>
</header>

View File

@@ -8,44 +8,44 @@ import { blur } from '~/lib/design-tokens'
import { clsxm } from '~/lib/helper'
const heroHighlights = [
{ title: 'WebGL Viewer', description: '60fps 缩放 / 漫游 / HDR' },
{ title: 'Manifest 驱动', description: 'window.__MANIFEST__ 即时注入' },
{ title: '地图探索', description: 'MapLibre GPS / Cluster / Heatmap' },
{ title: '丝滑流畅', description: '像翻阅实体相册般顺滑' },
{ title: '地图旅程', description: '在世界地图上回顾足迹' },
{ title: '完整记忆', description: '保留每张照片的故事' },
]
const heroTiles = [
{
id: 'tile-masonry',
title: 'Masonry Flow',
subtitle: '自适应列 · Blurhash 过渡',
badge: 'GPU',
title: '智能布局',
subtitle: '自动排列,完美呈现',
badge: '瀑布流',
className: 'col-span-2 row-span-2',
gradient:
'linear-gradient(135deg, rgba(100,160,220,0.35), rgba(140,200,255,0.2))',
},
{
id: 'tile-exif',
title: 'EXIF',
subtitle: 'Fujifilm Recipe · HDR',
badge: 'metadata',
title: '拍摄信息',
subtitle: '相机 · 镜头 · 参数',
badge: '详细',
className: 'col-span-1 row-span-1',
gradient:
'linear-gradient(135deg, rgba(160,140,200,0.35), rgba(200,180,255,0.2))',
},
{
id: 'tile-map',
title: 'Map Explorer',
subtitle: 'GPS Cluster / Heatmap',
badge: 'MapLibre',
title: '世界地图',
subtitle: '旅行轨迹可视化',
badge: '探索',
className: 'col-span-1 row-span-2',
gradient:
'linear-gradient(135deg, rgba(100,200,180,0.35), rgba(140,240,220,0.2))',
},
{
id: 'tile-viewer',
title: 'Fullscreen Viewer',
subtitle: 'Live Photo / 分享',
badge: 'viewer',
title: '沉浸查看',
subtitle: '全屏 · 动态照片',
badge: '体验',
className: 'col-span-2 row-span-1',
gradient:
'linear-gradient(135deg, rgba(255,180,120,0.35), rgba(255,210,160,0.2))',
@@ -57,20 +57,19 @@ export const HeroSection = () => (
<div className="flex flex-1 flex-col gap-8">
<span className="text-accent/90 inline-flex w-fit items-center gap-2 rounded-full border border-white/10 bg-white/5 px-4 py-1 text-sm backdrop-blur">
<i className="i-lucide-sparkles size-4" aria-hidden />
Glassmorphic Depth Design System
</span>
<div className="space-y-5">
<h1 className="text-4xl leading-tight font-semibold text-white sm:text-5xl lg:text-6xl">
Afilmory
<span className="via-accent block bg-gradient-to-r from-sky-300 to-purple-400 bg-clip-text text-transparent">
</span>
</h1>
<p className="text-text-secondary max-w-2xl text-base sm:text-lg">
WebGLMotion manifest EXIF
MapLibre Next.js SEO / OG
</p>
</div>
@@ -84,7 +83,7 @@ export const HeroSection = () => (
target="_blank"
rel="noreferrer"
>
线
</Link>
</Button>
<Button asChild variant="secondary">
@@ -93,7 +92,7 @@ export const HeroSection = () => (
target="_blank"
rel="noreferrer"
>
README
</Link>
</Button>
<Link
@@ -102,7 +101,7 @@ export const HeroSection = () => (
rel="noreferrer"
className="group text-text-secondary hover:text-text inline-flex items-center gap-1.5 text-sm transition"
>
<span>Star on GitHub</span>
<span>使</span>
<i className="i-lucide-arrow-up-right size-4 transition group-hover:translate-x-0.5 group-hover:-translate-y-0.5" />
</Link>
</div>
@@ -137,7 +136,7 @@ const HeroPreview = () => (
<m.div
key={tile.id}
className={clsxm(
'group relative overflow-hidden rounded-[28px] border border-white/20 bg-white/40 p-4 text-gray-800 shadow-[0_8px_32px_rgba(0,0,0,0.06)] backdrop-blur-xl',
'group relative overflow-hidden rounded-[28px] border border-white/20 bg-white/40 p-4 text-gray-800 backdrop-blur-xl',
tile.className,
)}
style={{ backgroundImage: tile.gradient }}
@@ -168,7 +167,7 @@ const HeroPreview = () => (
<m.div
aria-hidden
className={clsxm(
'absolute top-4 -right-6 w-56 rounded-3xl border border-white/20 bg-white/60 p-4 text-sm text-gray-800 shadow-[0_8px_32px_rgba(0,0,0,0.08)]',
'absolute top-4 -right-6 w-56 rounded-3xl border border-white/20 bg-white/60 p-4 text-sm text-gray-800',
blur['2xl'],
)}
initial={{ opacity: 0, y: -20, scale: 0.95 }}
@@ -176,22 +175,22 @@ const HeroPreview = () => (
transition={{ delay: 0.35, duration: 0.6, ease: 'easeOut' }}
>
<div className="flex items-center justify-between text-xs font-medium text-gray-600">
window.__MANIFEST__
<span className="rounded-full bg-cyan-100 px-2 py-0.5 text-[10px] text-cyan-700">
hydrated
</span>
</div>
<div className="mt-3 space-y-2 font-mono text-[11px] leading-relaxed text-gray-700">
<p>{'{ data: 2048 photos }'}</p>
<p>{'cameras: 12 · lenses: 18'}</p>
<p>{'blurhash: true · livePhoto: 86'}</p>
<div className="mt-3 space-y-2 text-[11px] leading-relaxed text-gray-700">
<p>2,048 </p>
<p>12 · 18 </p>
<p>86 Live Photo</p>
</div>
</m.div>
<m.div
aria-hidden
className={clsxm(
'absolute bottom-6 -left-6 w-60 rounded-3xl border border-white/20 bg-gradient-to-br from-emerald-100/60 to-white/60 p-4 text-sm shadow-[0_8px_32px_rgba(0,0,0,0.08)]',
'absolute bottom-6 -left-6 w-60 rounded-3xl border border-white/20 bg-gradient-to-br from-emerald-100/60 to-white/60 p-4 text-sm',
blur['2xl'],
)}
initial={{ opacity: 0, y: 20, scale: 0.95 }}
@@ -200,14 +199,12 @@ const HeroPreview = () => (
>
<div className="flex items-center gap-2 text-xs font-semibold text-emerald-700">
<i className="i-lucide-map-pin size-4" />
Map Explorer
</div>
<p className="mt-3 text-xs text-gray-700">
242 geotagged stories online.
</p>
<p className="mt-3 text-xs text-gray-700">242 </p>
<div className="mt-4 flex items-center justify-between text-[11px] text-gray-600">
<span>Cluster · Heatmap</span>
<span> fully interactive</span>
<span> · </span>
<span> </span>
</div>
</m.div>
</div>

View File

@@ -8,10 +8,10 @@ import { clsxm } from '~/lib/helper'
import { MetricCard } from './Card'
const metrics = [
{ label: 'WebGL 渲染', value: '60fps', detail: '平移 · 缩放 · HDR' },
{ label: '增量同步', value: 'S3 · GitHub', detail: '多存储后端' },
{ label: '照片节点', value: '2k+', detail: 'EXIF · Live Photo · Blurhash' },
{ label: '多语言', value: '11', detail: 'i18n · 动态 OG' },
{ label: '流畅体验', value: '丝滑', detail: '杂志般的浏览感受' },
{ label: '多端适配', value: '响应式', detail: '手机 · 平板 · 电脑' },
{ label: '照片容量', value: '不限', detail: '支持大量照片展示' },
{ label: '完全免费', value: '开源', detail: '永久免费使用' },
]
export const MetricStrip = () => (

View File

@@ -7,22 +7,22 @@ import { IconCard } from './Card'
const previewHighlights = [
{
icon: 'i-lucide-aperture',
title: 'EXIF HUD',
description: '完整记录光圈、快门、ISO、镜头、配方、HDR 信息',
icon: 'i-lucide-camera',
title: '拍摄参数',
description: '自动记录每张照片的相机、镜头、光圈、快门等信息',
meta: 'f/1.4 · 1/125s · ISO 200',
},
{
icon: 'i-lucide-map',
title: '地图探索',
description: 'MapLibre 地图、GPS 聚合、热力探索每一次旅程。',
meta: '84% 带 GPS · Cluster & Pin',
icon: 'i-lucide-map-pin',
title: '地点标记',
description: '在世界地图上查看你的摄影足迹,重温每次旅行',
meta: '支持 GPS 定位',
},
{
icon: 'i-lucide-maximize',
title: 'Fullscreen Viewer',
description: 'WebGL 全屏查看支持手势、Live Photo 与分享',
meta: '手势 · 分享 · Live',
icon: 'i-lucide-expand',
title: '全屏欣赏',
description: '沉浸式大图查看支持动态照片和一键分享',
meta: '手势操作 · 快速分享',
},
]
@@ -30,15 +30,14 @@ export const PreviewSection = () => (
<section className="grid gap-12 lg:grid-cols-[1.1fr_0.9fr] lg:items-center">
<div className="space-y-6">
<p className="text-text-secondary text-sm font-semibold tracking-[0.3em] uppercase">
</p>
<h2 className={clsxm(typography.h1, 'text-white')}>
</h2>
<p className="text-text-secondary text-base">
Masonry MapLibre WebGL ViewerEXIF HUD
motion spring glassmorphic
</p>
<div className="space-y-4">
@@ -87,11 +86,11 @@ const PreviewMockup = () => (
</div>
<div className="bg-background/70 text-text-secondary mt-6 rounded-2xl border border-white/10 p-4 text-sm">
<div className="text-text flex items-center justify-between">
<span className="font-medium">EXIF · X-T5 · XF16mmF1.4</span>
<span className="text-text-tertiary text-xs">Map locked</span>
<span className="font-medium"> X-T5 · 16mm f/1.4</span>
<span className="text-text-tertiary text-xs">📍 </span>
</div>
<p className="text-text-secondary mt-2">
GPS 35.6895 / 139.6917 · Fujifilm Classic Chrome · Blurhash ready.
· 2024 3
</p>
</div>
</div>

View File

@@ -1,115 +0,0 @@
'use client'
import { m } from 'motion/react'
import { blur, radius, shadows, spacing, typography } from '~/lib/design-tokens'
import { clsxm } from '~/lib/helper'
const techStacks = [
'React 19',
'Next.js 15',
'Vite',
'Tailwind CSS v4',
'Radix UI',
'Framer Motion 12',
'Jotai',
'TanStack Query',
'MapLibre',
'Sharp',
'Drizzle ORM',
'Hono',
]
const integrationModes = [
{
title: 'Standalone SPA',
description: '预构建 photos-manifest.json纯静态部署即可运行。',
points: [
'Vite + Cloudflare Pages / Vercel / 静态空间',
'CI 中运行 builder增量更新 manifest',
'无需服务器即可获得完整体验',
],
},
{
title: 'Next.js SSR Host',
description: 'apps/ssr 作为 SPA 外壳,负责注入 manifest、OG、SEO。',
points: [
'route.ts 捕获所有路径,返回注入后的 index.html',
'动态 /og/[photoId] 渲染社交卡片',
'保留 SPA 的交互,同时具备 SSR 首屏',
],
},
{
title: 'Full Backend Flow',
description: 'be/apps/core + be/apps/dashboard 驱动实时数据与管理。',
points: [
'Hono + Drizzle + PostgreSQL 全栈能力',
'Manifest 在内存构建后注入页面',
'Dashboard 管理、权限与指标监控',
],
},
]
export const TechSection = () => (
<section className={spacing.content}>
<div className={spacing.tight}>
<p className="text-text-secondary text-sm font-semibold tracking-[0.3em] uppercase">
&
</p>
<h2 className={clsxm(typography.h1, 'text-white')}></h2>
<p className="text-text-secondary text-base">
React 19 + Next.js 15 + Vite + motion + Tailwind v4 + Pastel Palette
token UI / /
</p>
</div>
<div
className={clsxm(
'border border-white/10 bg-background/40 p-6',
radius['2xl'],
blur['3xl'],
shadows.heavy,
)}
>
<div className="flex flex-wrap gap-3">
{techStacks.map((stack, index) => (
<m.span
key={stack}
className="text-text-secondary rounded-full border border-white/15 bg-white/5 px-4 py-2 text-sm"
initial={{ opacity: 0, y: 12 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ delay: index * 0.05, duration: 0.4 }}
>
{stack}
</m.span>
))}
</div>
<div className="mt-8 grid gap-4 lg:grid-cols-3">
{integrationModes.map((mode) => (
<div
key={mode.title}
className="rounded-3xl border border-white/10 bg-white/5 p-5 shadow-inner shadow-black/30"
>
<p className="text-lg font-semibold text-white">{mode.title}</p>
<p className="text-text-secondary mt-1 text-sm">
{mode.description}
</p>
<ul className="text-text-secondary mt-3 space-y-2 text-sm">
{mode.points.map((point) => (
<li key={point} className="flex items-start gap-2">
<i
className="i-lucide-minus text-accent size-4"
aria-hidden
/>
<span>{point}</span>
</li>
))}
</ul>
</div>
))}
</div>
</div>
</section>
)

View File

@@ -1,90 +0,0 @@
'use client'
import { blur, radius, shadows, spacing, typography } from '~/lib/design-tokens'
import { clsxm } from '~/lib/helper'
const workflowSteps = [
{
icon: 'i-lucide-rocket',
title: 'Builder Pipeline',
description: 'packages/builder 负责拉取、处理、分析与生成 manifest。',
points: [
'Storage SyncS3 / GitHub / Eagle 增量拉取',
'Format & ThumbnailHEIC/TIFF 转码 + Blurhash',
'EXIF / GPS / Fujifilm Recipe / HDR 元数据提取',
],
},
{
icon: 'i-lucide-plug-zap',
title: 'Manifest 注入',
description:
'apps/ssr 在 Next.js 层注入 window.__MANIFEST__开启 SEO/OG。',
points: [
'index.html 模板内联 manifest',
'OG 动态渲染 + Metadata 替换',
'MapLibre、PhotoLoader、Viewer 共享数据源',
],
},
{
icon: 'i-lucide-monitor-play',
title: 'SPA 消费',
description:
'apps/web 作为 Vite SPA自主渲染 Masonry、Viewer、地图与浮动 UI。',
points: [
'PhotoLoader 单例在客户端初始化',
'Jotai + TanStack Query 状态与数据层',
'Glassmorphic Depth 组件交付最终体验',
],
},
]
export const WorkflowSection = () => (
<section className={spacing.content}>
<div>
<p className="text-text-secondary text-sm font-semibold tracking-[0.3em] uppercase">
</p>
<h2 className={clsxm(typography.h1, 'text-white')}>
Builder Manifest SPA
</h2>
<p className="text-text-secondary text-base">
README 线
</p>
</div>
<div
className={clsxm(
'relative border border-white/10 bg-white/5 p-8',
radius['3xl'],
blur['3xl'],
shadows.heavy,
)}
>
<div className="absolute top-10 bottom-10 left-12 w-px bg-white/10" />
<ol className="space-y-10">
{workflowSteps.map((step, index) => (
<li key={step.title} className="relative pl-16">
<div className="border-accent/20 bg-accent/10 text-accent absolute top-1 left-0 flex size-12 items-center justify-center rounded-full border">
<i className={clsxm('size-5', step.icon)} aria-hidden />
</div>
<div>
<p className="text-lg font-semibold text-white">
{index + 1}. {step.title}
</p>
<p className="text-text-secondary mt-2 text-sm">
{step.description}
</p>
<ul className="text-text-secondary mt-3 space-y-2 text-sm">
{step.points.map((point) => (
<li key={point} className="flex items-start gap-2">
<span className="bg-accent mt-1 size-1.5 rounded-full" />
<span>{point}</span>
</li>
))}
</ul>
</div>
</li>
))}
</ol>
</div>
</section>
)

View File

@@ -10,5 +10,3 @@ export { FeatureSection } from './FeatureSection'
export { HeroSection } from './HeroSection'
export { MetricStrip } from './MetricStrip'
export { PreviewSection } from './PreviewSection'
export { TechSection } from './TechSection'
export { WorkflowSection } from './WorkflowSection'

View File

@@ -0,0 +1,174 @@
'use client'
import Link from 'next/link'
import { blur } from '~/lib/design-tokens'
import { clsxm } from '~/lib/helper'
const footerLinks = {
product: [
{ label: '在线示例', href: 'https://afilmory.innei.in' },
{
label: '使用文档',
href: 'https://github.com/Afilmory/photo-gallery-site#readme',
},
{ label: 'GitHub', href: 'https://github.com/Afilmory/photo-gallery-site' },
],
community: [
{
label: '讨论区',
href: 'https://github.com/Afilmory/photo-gallery-site/discussions',
},
{
label: '问题反馈',
href: 'https://github.com/Afilmory/photo-gallery-site/issues',
},
{
label: '贡献指南',
href: 'https://github.com/Afilmory/photo-gallery-site/blob/main/CONTRIBUTING.md',
},
],
resources: [
{
label: '快速开始',
href: 'https://github.com/Afilmory/photo-gallery-site#-quick-start',
},
{
label: '配置说明',
href: 'https://github.com/Afilmory/photo-gallery-site#-configuration',
},
{
label: '部署教程',
href: 'https://github.com/Afilmory/photo-gallery-site#-deployment',
},
],
}
const socialLinks = [
{
icon: 'i-lucide-github',
label: 'GitHub',
href: 'https://github.com/Afilmory/photo-gallery-site',
},
{ icon: 'i-lucide-twitter', label: 'Twitter', href: 'https://twitter.com' },
{ icon: 'i-lucide-message-circle', label: 'Discord', href: '#' },
]
export const Footer = () => {
return (
<footer className={clsxm('border-t border-white/10 bg-white/40', blur.lg)}>
<div className="mx-auto w-full max-w-6xl px-4 py-12 sm:px-6 lg:px-0">
{/* Main Footer */}
<div className="grid gap-8 lg:grid-cols-4">
{/* Brand */}
<div className="lg:col-span-1">
<div className="flex items-center gap-2 text-xl font-semibold text-gray-900">
<span className="flex size-10 items-center justify-center rounded-xl bg-gradient-to-br from-sky-400 to-purple-500 text-white">
<i className="i-lucide-camera size-5" />
</span>
<span>Afilmory</span>
</div>
<p className="mt-4 text-sm text-gray-600">
</p>
{/* Social Links */}
<div className="mt-6 flex items-center gap-3">
{socialLinks.map((social) => (
<Link
key={social.label}
href={social.href}
target="_blank"
rel="noreferrer"
className="flex size-9 items-center justify-center rounded-lg border border-white/20 bg-white/40 text-gray-600 transition hover:bg-white/60 hover:text-gray-900"
aria-label={social.label}
>
<i className={clsxm(social.icon, 'size-4')} />
</Link>
))}
</div>
</div>
{/* Links */}
<div className="grid grid-cols-3 gap-8 lg:col-span-3">
<div>
<h3 className="text-sm font-semibold text-gray-900"></h3>
<ul className="mt-4 space-y-3">
{footerLinks.product.map((link) => (
<li key={link.label}>
<Link
href={link.href}
target="_blank"
rel="noreferrer"
className="text-sm text-gray-600 transition hover:text-gray-900"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
<div>
<h3 className="text-sm font-semibold text-gray-900"></h3>
<ul className="mt-4 space-y-3">
{footerLinks.community.map((link) => (
<li key={link.label}>
<Link
href={link.href}
target="_blank"
rel="noreferrer"
className="text-sm text-gray-600 transition hover:text-gray-900"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
<div>
<h3 className="text-sm font-semibold text-gray-900"></h3>
<ul className="mt-4 space-y-3">
{footerLinks.resources.map((link) => (
<li key={link.label}>
<Link
href={link.href}
target="_blank"
rel="noreferrer"
className="text-sm text-gray-600 transition hover:text-gray-900"
>
{link.label}
</Link>
</li>
))}
</ul>
</div>
</div>
</div>
{/* Bottom Bar */}
<div className="mt-12 flex flex-col items-center justify-between gap-4 border-t border-white/10 pt-8 text-sm text-gray-600 sm:flex-row">
<p>© 2025 Afilmory. </p>
<div className="flex items-center gap-6">
<Link
href="https://github.com/Afilmory/photo-gallery-site/blob/main/LICENSE"
target="_blank"
rel="noreferrer"
className="transition hover:text-gray-900"
>
MIT License
</Link>
<Link
href="https://github.com/Afilmory/photo-gallery-site#-features"
target="_blank"
rel="noreferrer"
className="transition hover:text-gray-900"
>
v1.0
</Link>
</div>
</div>
</div>
</footer>
)
}

View File

@@ -0,0 +1,75 @@
'use client'
import { m } from 'motion/react'
import Link from 'next/link'
import { Button } from '~/components/ui/button/Button'
import { blur } from '~/lib/design-tokens'
import { clsxm } from '~/lib/helper'
export const Header = () => {
return (
<m.header
className={clsxm(
'fixed top-0 left-0 right-0 z-50 border-b border-white/10 bg-white/60',
blur.lg,
)}
initial={{ y: -100, opacity: 0 }}
animate={{ y: 0, opacity: 1 }}
transition={{ duration: 0.6, ease: 'easeOut' }}
>
<div className="mx-auto flex h-16 w-full max-w-6xl items-center justify-between px-4 sm:px-6 lg:px-0">
{/* Logo */}
<Link
href="/"
className="group flex items-center gap-2 text-xl font-semibold text-gray-900 transition hover:text-gray-700"
>
<span className="flex size-8 items-center justify-center rounded-xl bg-gradient-to-br from-sky-400 to-purple-500 text-white">
<i className="i-lucide-camera size-5" />
</span>
<span className="hidden sm:inline">Afilmory</span>
</Link>
{/* Navigation */}
<nav className="hidden items-center gap-8 md:flex">
<Link
href="#features"
className="text-sm text-gray-600 transition hover:text-gray-900"
>
</Link>
<Link
href="#showcase"
className="text-sm text-gray-600 transition hover:text-gray-900"
>
线
</Link>
<Link
href="https://github.com/Afilmory/photo-gallery-site"
target="_blank"
rel="noreferrer"
className="text-sm text-gray-600 transition hover:text-gray-900"
>
GitHub
</Link>
</nav>
{/* CTA Button */}
<Button
asChild
size="sm"
className="to-accent text-background bg-gradient-to-r from-sky-400"
>
<Link
href="https://afilmory.innei.in"
target="_blank"
rel="noreferrer"
>
<span className="hidden sm:inline"></span>
<span className="sm:hidden"></span>
</Link>
</Button>
</div>
</m.header>
)
}

View File

@@ -0,0 +1,7 @@
/**
* Layout Components
* Header 和 Footer 等布局组件
*/
export { Footer } from './Footer'
export { Header } from './Header'

View File

@@ -5,15 +5,14 @@
/**
* 阴影层级系统
* 从 subtle轻微到 heavy强烈
* 针对浅色背景优化,使用更柔和的阴影
* 针对浅色背景优化,使用极简阴影
*/
export const shadows = {
subtle: 'shadow-[0_2px_8px_rgba(0,0,0,0.02)]',
light: 'shadow-[0_4px_16px_rgba(0,0,0,0.04)]',
medium: 'shadow-[0_8px_24px_rgba(0,0,0,0.06)]',
strong: 'shadow-[0_12px_32px_rgba(0,0,0,0.08)]',
heavy: 'shadow-[0_16px_48px_rgba(0,0,0,0.10)]',
subtle: 'shadow-none',
light: 'shadow-none',
medium: 'shadow-none',
strong: 'shadow-none',
heavy: 'shadow-none',
} as const
/**