Files
SnapGo/landing/src/App.tsx
T
2026-06-12 08:02:55 +08:00

268 lines
9.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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;