feat: add landing page

This commit is contained in:
2026-06-12 06:58:06 +08:00
parent 050f2b74c3
commit 7fe3a0fc93
13 changed files with 3067 additions and 0 deletions
+267
View File
@@ -0,0 +1,267 @@
import {
Apple,
ArrowRight,
CheckCircle2,
ClipboardCheck,
Cloud,
Command,
Download,
FolderInput,
Gauge,
Globe2,
HardDriveUpload,
LockKeyhole,
MousePointer2,
Network,
Server,
Sparkles,
TerminalSquare,
UploadCloud,
} from 'lucide-react';
import appIcon from '../../build/appicon.png';
const defaultDownloadUrl =
'https://github.com/mamamiyear/SnapGo/releases/latest/download/SnapGo-0.1.0-arm64.dmg';
const downloadUrl = import.meta.env.VITE_DOWNLOAD_URL || defaultDownloadUrl;
const providers = ['AWS S3', 'MinIO', 'Cloudflare R2', 'Backblaze B2'];
const workflow = [
{
title: '全局快捷键截图',
detail: '菜单栏常驻,按下 cmd+shift+a 后直接框选当前屏幕内容。',
icon: Command,
},
{
title: '选择上传目标',
detail: '对象存储得到公开 URL,远端服务器得到 SSH 家目录下的路径。',
icon: Network,
},
{
title: '自动复制结果',
detail: '上传完成后,URL 或 ~/snapgo/...png 已经在剪贴板里。',
icon: ClipboardCheck,
},
];
const featureCards = [
{
title: '截图直传对象存储',
copy: '接入 S3 兼容端点,支持路径前缀和自定义公开地址,适合自有图床和 CDN。',
icon: Cloud,
},
{
title: '远端服务器保存',
copy: '通过 SSH/SCP 写入远端主机,复制相对家目录路径,方便登录服务器后立刻定位。',
icon: Server,
},
{
title: '失败也有兜底',
copy: '上传失败时保住截图文件,避免截了一次图却丢了上下文。',
icon: FolderInput,
},
{
title: '为高频沟通而轻',
copy: '截图、上传、复制三步合一,发给聊天、工单、Issue、PR 都不打断手上的事。',
icon: Gauge,
},
];
const comparison = [
'不用打开图床网页',
'不用手动保存再拖拽',
'不用把图片交给陌生服务',
'不用给 CLI Agent 拼 base64',
];
function App() {
return (
<div className="site-shell">
<header className="hero">
<nav className="top-nav" aria-label="主导航">
<a className="brand" href="#top" aria-label="SnapGo 首页">
<img src={appIcon} alt="" />
<span>SnapGo</span>
</a>
<div className="nav-actions">
<a href="#workflow"></a>
<a href="#destinations"></a>
<a className="nav-download" href={downloadUrl}>
<Download size={16} aria-hidden="true" />
</a>
</div>
</nav>
<div className="hero-grid" id="top">
<div className="hero-copy">
<div className="eyebrow">
<Sparkles size={16} aria-hidden="true" />
macOS
</div>
<h1></h1>
<p className="hero-lede">
SnapGo
</p>
<div className="hero-actions">
<a className="primary-cta" href={downloadUrl}>
<Apple size={19} aria-hidden="true" />
macOS Apple silicon
</a>
<a className="secondary-cta" href="#destinations">
<ArrowRight size={17} aria-hidden="true" />
</a>
</div>
<div className="hero-proof" aria-label="核心能力">
<span>S3 </span>
<span>SSH/SCP </span>
<span> URL/</span>
</div>
</div>
<div className="hero-visual" aria-label="SnapGo 截图上传工作流示意">
<img className="hero-app-icon" src={appIcon} alt="SnapGo 应用图标" />
<div className="capture-scene">
<div className="desktop-bar">
<span />
<span />
<span />
</div>
<div className="selection-box">
<div className="selection-label">1280 x 720</div>
<div className="toolbar" aria-hidden="true">
<MousePointer2 size={16} />
<UploadCloud size={16} />
<HardDriveUpload size={16} />
<ClipboardCheck size={16} />
</div>
</div>
<div className="result-stack">
<div className="result-line">
<Globe2 size={15} aria-hidden="true" />
<span>https://cdn.example.com/snapgo/2026/06/shot.png</span>
</div>
<div className="result-line">
<TerminalSquare size={15} aria-hidden="true" />
<span>~/snapgo/2026/06/shot.png</span>
</div>
</div>
</div>
</div>
</div>
</header>
<main>
<section className="quick-strip" aria-label="上传目标">
{providers.map((provider) => (
<span key={provider}>{provider}</span>
))}
<span>Custom CDN</span>
<span>SSH Host</span>
</section>
<section className="section workflow-section" id="workflow">
<div className="section-heading">
<p></p>
<h2></h2>
</div>
<div className="workflow-grid">
{workflow.map((item, index) => {
const Icon = item.icon;
return (
<article className="workflow-card" key={item.title}>
<div className="step-index">0{index + 1}</div>
<Icon size={25} aria-hidden="true" />
<h3>{item.title}</h3>
<p>{item.detail}</p>
</article>
);
})}
</div>
</section>
<section className="section split-section" id="destinations">
<div className="destination-copy">
<p className="section-kicker"></p>
<h2> URL</h2>
<p>
SnapGo
PR Agent
</p>
<ul className="check-list">
{comparison.map((item) => (
<li key={item}>
<CheckCircle2 size={18} aria-hidden="true" />
{item}
</li>
))}
</ul>
</div>
<div className="destination-panel" aria-label="远端配置示意">
<div className="panel-head">
<span>Destination</span>
<strong>Ready</strong>
</div>
<div className="route-row active">
<Cloud size={20} aria-hidden="true" />
<div>
<strong>S3 compatible</strong>
<span>snapgo/2026/06/*.png public URL</span>
</div>
</div>
<div className="route-row">
<Server size={20} aria-hidden="true" />
<div>
<strong>SSH remote</strong>
<span>~/snapgo/2026/06/*.png clipboard path</span>
</div>
</div>
<div className="clipboard-preview">
<ClipboardCheck size={19} aria-hidden="true" />
<code>Copied: https://cdn.example.com/snapgo/shot.png</code>
</div>
</div>
</section>
<section className="section feature-section">
<div className="section-heading">
<p></p>
<h2></h2>
</div>
<div className="feature-grid">
{featureCards.map((feature) => {
const Icon = feature.icon;
return (
<article className="feature-card" key={feature.title}>
<Icon size={24} aria-hidden="true" />
<h3>{feature.title}</h3>
<p>{feature.copy}</p>
</article>
);
})}
</div>
</section>
<section className="final-cta" aria-labelledby="download-title">
<div>
<p className="section-kicker">SnapGo for macOS</p>
<h2 id="download-title"></h2>
<p>
macOS Apple silicon 使
</p>
</div>
<a className="primary-cta final-button" href={downloadUrl}>
<Download size={19} aria-hidden="true" />
macOS Apple silicon
</a>
</section>
</main>
</div>
);
}
export default App;