mirror of
https://github.com/Afilmory/afilmory
synced 2026-02-01 22:48:17 +00:00
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -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">
|
||||
结合 builder、apps/web、apps/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>
|
||||
|
||||
@@ -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 共享 UI:apps/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>
|
||||
|
||||
|
||||
@@ -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">
|
||||
以 WebGL、Motion 与 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>
|
||||
|
||||
@@ -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 = () => (
|
||||
|
||||
@@ -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 Viewer、EXIF 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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
@@ -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 Sync:S3 / GitHub / Eagle 增量拉取',
|
||||
'Format & Thumbnail:HEIC/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>
|
||||
)
|
||||
@@ -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'
|
||||
|
||||
174
apps/landing/src/components/layout/Footer.tsx
Normal file
174
apps/landing/src/components/layout/Footer.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
75
apps/landing/src/components/layout/Header.tsx
Normal file
75
apps/landing/src/components/layout/Header.tsx
Normal 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>
|
||||
)
|
||||
}
|
||||
7
apps/landing/src/components/layout/index.ts
Normal file
7
apps/landing/src/components/layout/index.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* Layout Components
|
||||
* Header 和 Footer 等布局组件
|
||||
*/
|
||||
|
||||
export { Footer } from './Footer'
|
||||
export { Header } from './Header'
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user