文档: 按 Ant/Arco Design 风格重构官网首页,修正 API 参考,完善 i18n (#41)

重构:
- 首页 Hero 重设计:双列布局(标题+CTA+指标 / macOS 风代码窗口)
- 引入渐变文字、pulse 徽章、悬停带动画的主按钮
- 功能卡片加 SVG 图标、悬停提升效果、部分卡片变成可点击链接
- 新增 HomepageShowcase 截图轮播区:Tab 切换四个核心页面(仪表盘/任务/存储/多节点)
- 全站换 Arco 蓝 (#165dff) 作为主色,紫色 (#8f4bff) 作为辅助
- 导航栏加毛玻璃效果、表格加圆角与边框、菜单项圆角化
- 深色模式配色整体收敛

内容修正:
- API 参考补全遗漏的端点:auth logout/profile、records batch-delete、
  storage-targets star/usage/google-drive、notifications test、dashboard timeline、settings
- 把 API 表格改为"方法/端点/说明"三列,加响应结构说明
- 中英文 API 文档同步更新

i18n:
- code.json 补充 Hero、Features、Showcase 全部新翻译键
- 校对:16 个中英文档 frontmatter 完全对齐,无漏译

构建:双语 build 通过、产物 3.3MB
This commit is contained in:
Wu Qing
2026-04-17 13:39:27 +08:00
committed by GitHub
parent a6dd8033ed
commit 3a4c2edd9b
14 changed files with 1373 additions and 183 deletions

View File

@@ -1,46 +1,103 @@
import type {ReactNode} from 'react';
import clsx from 'clsx';
import Heading from '@theme/Heading';
import Translate from '@docusaurus/Translate';
import Link from '@docusaurus/Link';
import styles from './styles.module.css';
type FeatureItem = {
title: ReactNode;
description: ReactNode;
icon: ReactNode;
link?: string;
};
const DatabaseIcon = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<ellipse cx="12" cy="5" rx="9" ry="3" />
<path d="M3 5v6c0 1.66 4 3 9 3s9-1.34 9-3V5" />
<path d="M3 11v6c0 1.66 4 3 9 3s9-1.34 9-3v-6" />
</svg>
);
const CloudIcon = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M18 10h-1.26A8 8 0 109 20h9a5 5 0 000-10z" />
</svg>
);
const ClockIcon = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<circle cx="12" cy="12" r="10" />
<polyline points="12 6 12 12 16 14" />
</svg>
);
const NetworkIcon = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<rect x="9" y="2" width="6" height="6" rx="1" />
<rect x="2" y="16" width="6" height="6" rx="1" />
<rect x="16" y="16" width="6" height="6" rx="1" />
<path d="M12 8v4" />
<path d="M12 12H5v4" />
<path d="M12 12h7v4" />
</svg>
);
const ShieldIcon = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M12 2l9 4v6c0 5-3.5 9.5-9 10-5.5-.5-9-5-9-10V6l9-4z" />
<polyline points="9 12 11 14 15 10" />
</svg>
);
const RocketIcon = () => (
<svg width="28" height="28" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 00-2.91-.09z" />
<path d="M12 15l-3-3a22 22 0 012-3.95A12.88 12.88 0 0122 2c0 2.72-.78 7.5-6 11a22.35 22.35 0 01-4 2z" />
<path d="M9 12H4s.55-3.03 2-4c1.62-1.08 5 0 5 0" />
<path d="M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5" />
</svg>
);
const FEATURES: FeatureItem[] = [
{
title: <Translate id="feat.types.title">Many Backup Types</Translate>,
description: (
<Translate id="feat.types.desc">
Files &amp; directories with multi-path sources, plus MySQL, PostgreSQL, SQLite, and SAP HANA all in one place.
Files and directories with multi-path sources, plus MySQL, PostgreSQL, SQLite, and SAP HANA all in one place.
</Translate>
),
icon: <DatabaseIcon />,
link: '/docs/features/backup-types',
},
{
title: <Translate id="feat.storage.title">70+ Storage Backends</Translate>,
description: (
<Translate id="feat.storage.desc">
Native Alibaba OSS, Tencent COS, Qiniu, S3, Google Drive, WebDAV, FTP plus SFTP, Azure Blob, Dropbox, OneDrive and dozens more via rclone.
Native Alibaba OSS, Tencent COS, Qiniu, S3, Google Drive, WebDAV, FTP plus SFTP, Azure Blob, Dropbox and more via rclone.
</Translate>
),
icon: <CloudIcon />,
link: '/docs/features/storage-backends',
},
{
title: <Translate id="feat.scheduling.title">Scheduling &amp; Retention</Translate>,
title: <Translate id="feat.scheduling.title">Scheduling & Retention</Translate>,
description: (
<Translate id="feat.scheduling.desc">
Cron-based schedules with a visual editor and auto-retention (by days or count), plus empty-directory cleanup.
</Translate>
),
icon: <ClockIcon />,
},
{
title: <Translate id="feat.cluster.title">Multi-Node Cluster</Translate>,
description: (
<Translate id="feat.cluster.desc">
Master-Agent mode manages backups across multiple servers. Agents run tasks locally and upload straight to storage no reverse connectivity required.
Master-Agent via HTTP long-polling. Agents run tasks locally and upload directly to storage no reverse connectivity.
</Translate>
),
icon: <NetworkIcon />,
link: '/docs/features/multi-node',
},
{
title: <Translate id="feat.security.title">Secure by Default</Translate>,
@@ -49,33 +106,64 @@ const FEATURES: FeatureItem[] = [
JWT auth, bcrypt passwords, AES-256-GCM encrypted config, optional backup encryption, and a full audit log.
</Translate>
),
icon: <ShieldIcon />,
},
{
title: <Translate id="feat.deploy.title">Painless Deployment</Translate>,
description: (
<Translate id="feat.deploy.desc">
Single static binary with embedded SQLite. Docker one-click or bare-metal via install.sh zero external dependencies.
Single static binary with embedded SQLite. Docker one-click or bare-metal zero external dependencies.
</Translate>
),
icon: <RocketIcon />,
link: '/docs/getting-started/installation',
},
];
function Feature({title, description}: FeatureItem) {
return (
<div className={clsx('col col--4', styles.feature)}>
<Heading as="h3">{title}</Heading>
<p>{description}</p>
</div>
function Feature({title, description, icon, link}: FeatureItem) {
const content = (
<>
<div className={styles.iconWrap}>{icon}</div>
<Heading as="h3" className={styles.featureTitle}>{title}</Heading>
<p className={styles.featureDesc}>{description}</p>
{link && (
<span className={styles.featureLink}>
<Translate id="feat.learnMore">Learn more</Translate>
<span className={styles.featureArrow} aria-hidden="true"></span>
</span>
)}
</>
);
if (link) {
return (
<Link to={link} className={styles.featureCardLink}>
{content}
</Link>
);
}
return <div className={styles.featureCard}>{content}</div>;
}
export default function HomepageFeatures(): ReactNode {
return (
<section className={styles.features}>
<section className={styles.section}>
<div className="container">
<div className="row">
{FEATURES.map((props, idx) => (
<Feature key={idx} {...props} />
<div className={styles.sectionHead}>
<div className={styles.sectionTag}>
<Translate id="section.features.tag">FEATURES</Translate>
</div>
<Heading as="h2" className={styles.sectionTitle}>
<Translate id="section.features.title">Everything you need, nothing you don't</Translate>
</Heading>
<p className={styles.sectionSubtitle}>
<Translate id="section.features.subtitle">
Battle-tested building blocks backup runners, storage providers, scheduling, and clustering.
</Translate>
</p>
</div>
<div className={styles.grid}>
{FEATURES.map((feat, idx) => (
<Feature key={idx} {...feat} />
))}
</div>
</div>

View File

@@ -1,20 +1,148 @@
.features {
.section {
padding: 6rem 0 4rem;
}
.sectionHead {
text-align: center;
max-width: 720px;
margin: 0 auto 3rem;
}
.sectionTag {
display: inline-block;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.15em;
color: var(--ifm-color-primary);
padding: 4px 12px;
background: rgba(22, 93, 255, 0.08);
border-radius: 4px;
margin-bottom: 1rem;
}
[data-theme='dark'] .sectionTag {
background: rgba(96, 126, 255, 0.18);
color: var(--ifm-color-primary-lighter);
}
.sectionTitle {
font-size: clamp(1.8rem, 3vw, 2.5rem);
line-height: 1.2;
letter-spacing: -0.02em;
font-weight: 700;
margin: 0 0 1rem;
color: var(--ifm-heading-color);
}
.sectionSubtitle {
font-size: 1.05rem;
line-height: 1.65;
color: var(--ifm-color-content-secondary);
margin: 0;
}
.grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1.25rem;
}
@media (max-width: 996px) {
.section {
padding: 3.5rem 0 2rem;
}
.grid {
grid-template-columns: 1fr;
}
}
@media (min-width: 997px) and (max-width: 1200px) {
.grid {
grid-template-columns: repeat(2, 1fr);
}
}
.featureCard,
.featureCardLink {
position: relative;
display: flex;
flex-direction: column;
padding: 1.75rem;
background: var(--ifm-background-color);
border: 1px solid var(--ifm-color-emphasis-200);
border-radius: 12px;
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
text-decoration: none !important;
color: inherit;
height: 100%;
}
.featureCardLink:hover {
transform: translateY(-3px);
border-color: var(--ifm-color-primary);
box-shadow: 0 12px 30px -8px rgba(22, 93, 255, 0.18);
color: inherit;
}
[data-theme='dark'] .featureCard,
[data-theme='dark'] .featureCardLink {
background: rgba(255, 255, 255, 0.02);
border-color: rgba(255, 255, 255, 0.08);
}
[data-theme='dark'] .featureCardLink:hover {
background: rgba(64, 128, 255, 0.05);
border-color: var(--ifm-color-primary);
box-shadow: 0 12px 30px -8px rgba(64, 128, 255, 0.25);
}
.iconWrap {
width: 48px;
height: 48px;
border-radius: 10px;
display: flex;
align-items: center;
padding: 3rem 0;
width: 100%;
justify-content: center;
background: linear-gradient(135deg, rgba(22, 93, 255, 0.1) 0%, rgba(143, 75, 255, 0.08) 100%);
color: var(--ifm-color-primary);
margin-bottom: 1.25rem;
}
.feature {
padding: 1.2rem 1rem;
[data-theme='dark'] .iconWrap {
background: linear-gradient(135deg, rgba(96, 126, 255, 0.15) 0%, rgba(143, 75, 255, 0.12) 100%);
color: var(--ifm-color-primary-lighter);
}
.feature h3 {
.featureTitle {
font-size: 1.15rem;
margin-bottom: 0.5rem;
font-weight: 600;
margin: 0 0 0.6rem;
color: var(--ifm-heading-color);
letter-spacing: -0.01em;
}
.feature p {
color: var(--ifm-color-content-secondary);
.featureDesc {
font-size: 0.95rem;
line-height: 1.65;
color: var(--ifm-color-content-secondary);
margin: 0;
flex: 1;
}
.featureLink {
display: inline-flex;
align-items: center;
gap: 4px;
margin-top: 1rem;
font-size: 13px;
font-weight: 500;
color: var(--ifm-color-primary);
}
.featureArrow {
transition: transform 0.2s ease;
}
.featureCardLink:hover .featureArrow {
transform: translateX(4px);
}

View File

@@ -0,0 +1,120 @@
import type {ReactNode} from 'react';
import {useState} from 'react';
import clsx from 'clsx';
import Heading from '@theme/Heading';
import Translate from '@docusaurus/Translate';
import useBaseUrl from '@docusaurus/useBaseUrl';
import Link from '@docusaurus/Link';
import styles from './styles.module.css';
type Tab = {
id: string;
label: ReactNode;
image: string;
title: ReactNode;
description: ReactNode;
};
function useTabs(): Tab[] {
return [
{
id: 'dashboard',
label: <Translate id="showcase.tab.dashboard">Dashboard</Translate>,
image: useBaseUrl('/img/screenshots/dashboard.png'),
title: <Translate id="showcase.dashboard.title">Know at a glance</Translate>,
description: (
<Translate id="showcase.dashboard.desc">
Backup success rates, storage usage, recent runs and upcoming schedules all on one page with live data.
</Translate>
),
},
{
id: 'tasks',
label: <Translate id="showcase.tab.tasks">Backup Tasks</Translate>,
image: useBaseUrl('/img/screenshots/backup-tasks.png'),
title: <Translate id="showcase.tasks.title">Visual task editor</Translate>,
description: (
<Translate id="showcase.tasks.desc">
Files, MySQL, PostgreSQL, SQLite and SAP HANA with a three-step wizard. Cron editor, multi-target dispatch, retention, compression and encryption point and click.
</Translate>
),
},
{
id: 'storage',
label: <Translate id="showcase.tab.storage">Storage Targets</Translate>,
image: useBaseUrl('/img/screenshots/storage-targets.png'),
title: <Translate id="showcase.storage.title">70+ backends, one flow</Translate>,
description: (
<Translate id="showcase.storage.desc">
Alibaba OSS, Tencent COS, S3, Google Drive, WebDAV plus every rclone backend behind a uniform form. Test connection, favourite, and view live usage.
</Translate>
),
},
{
id: 'nodes',
label: <Translate id="showcase.tab.nodes">Multi-Node</Translate>,
image: useBaseUrl('/img/screenshots/nodes.png'),
title: <Translate id="showcase.nodes.title">Master-Agent in minutes</Translate>,
description: (
<Translate id="showcase.nodes.desc">
Create a node, copy the token, start the Agent on any remote host. Tasks routed to a node run locally there and upload directly to storage no reverse connectivity required.
</Translate>
),
},
];
}
export default function HomepageShowcase(): ReactNode {
const tabs = useTabs();
const [active, setActive] = useState(tabs[0].id);
const current = tabs.find(t => t.id === active) ?? tabs[0];
return (
<section className={styles.section}>
<div className="container">
<div className={styles.sectionHead}>
<div className={styles.sectionTag}>
<Translate id="showcase.tag">PRODUCT</Translate>
</div>
<Heading as="h2" className={styles.sectionTitle}>
<Translate id="showcase.title">A polished console, not a DIY script</Translate>
</Heading>
<p className={styles.sectionSubtitle}>
<Translate id="showcase.subtitle">
Every screen designed for day-2 operations visibility first, configuration second.
</Translate>
</p>
</div>
<div className={styles.tabs}>
{tabs.map(tab => (
<button
key={tab.id}
type="button"
className={clsx(styles.tabBtn, active === tab.id && styles.tabBtnActive)}
onClick={() => setActive(tab.id)}>
{tab.label}
</button>
))}
</div>
<div className={styles.stage}>
<div className={styles.browser}>
<div className={styles.browserBar}>
<span className={clsx(styles.browserDot, styles.browserDotRed)} />
<span className={clsx(styles.browserDot, styles.browserDotYellow)} />
<span className={clsx(styles.browserDot, styles.browserDotGreen)} />
<div className={styles.browserUrl}>backupx.local</div>
</div>
<img src={current.image} alt="" className={styles.screenshot} />
</div>
<div className={styles.caption}>
<Heading as="h3" className={styles.captionTitle}>{current.title}</Heading>
<p className={styles.captionDesc}>{current.description}</p>
<Link to="/docs/getting-started/quick-start" className={styles.captionLink}>
<Translate id="showcase.cta">Explore the docs</Translate>
<span aria-hidden="true"> </span>
</Link>
</div>
</div>
</div>
</section>
);
}

View File

@@ -0,0 +1,196 @@
.section {
padding: 4rem 0 6rem;
background: linear-gradient(180deg, transparent 0%, rgba(22, 93, 255, 0.03) 100%);
}
[data-theme='dark'] .section {
background: linear-gradient(180deg, transparent 0%, rgba(64, 128, 255, 0.04) 100%);
}
.sectionHead {
text-align: center;
max-width: 720px;
margin: 0 auto 2.5rem;
}
.sectionTag {
display: inline-block;
font-size: 12px;
font-weight: 600;
letter-spacing: 0.15em;
color: #8f4bff;
padding: 4px 12px;
background: rgba(143, 75, 255, 0.08);
border-radius: 4px;
margin-bottom: 1rem;
}
[data-theme='dark'] .sectionTag {
background: rgba(143, 75, 255, 0.18);
}
.sectionTitle {
font-size: clamp(1.8rem, 3vw, 2.5rem);
line-height: 1.2;
letter-spacing: -0.02em;
font-weight: 700;
margin: 0 0 1rem;
color: var(--ifm-heading-color);
}
.sectionSubtitle {
font-size: 1.05rem;
line-height: 1.65;
color: var(--ifm-color-content-secondary);
margin: 0;
}
/* Tab bar */
.tabs {
display: flex;
justify-content: center;
gap: 8px;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.tabBtn {
padding: 8px 18px;
background: transparent;
border: 1px solid var(--ifm-color-emphasis-300);
border-radius: 999px;
color: var(--ifm-color-content-secondary);
font-size: 14px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
}
.tabBtn:hover {
color: var(--ifm-color-primary);
border-color: var(--ifm-color-primary);
}
.tabBtnActive,
.tabBtnActive:hover {
background: linear-gradient(90deg, #165dff 0%, #4080ff 100%);
color: #fff !important;
border-color: transparent;
box-shadow: 0 4px 14px rgba(22, 93, 255, 0.3);
}
/* Stage */
.stage {
display: grid;
grid-template-columns: 1.4fr 1fr;
gap: 3rem;
align-items: center;
}
@media (max-width: 996px) {
.stage {
grid-template-columns: 1fr;
gap: 1.5rem;
}
}
.browser {
background: var(--ifm-background-color);
border-radius: 12px;
overflow: hidden;
box-shadow:
0 30px 60px -20px rgba(22, 93, 255, 0.25),
0 0 0 1px var(--ifm-color-emphasis-200);
}
[data-theme='dark'] .browser {
box-shadow:
0 30px 60px -20px rgba(0, 0, 0, 0.5),
0 0 0 1px rgba(255, 255, 255, 0.06);
}
.browserBar {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 14px;
background: var(--ifm-color-emphasis-100);
border-bottom: 1px solid var(--ifm-color-emphasis-200);
}
[data-theme='dark'] .browserBar {
background: rgba(255, 255, 255, 0.03);
border-bottom-color: rgba(255, 255, 255, 0.06);
}
.browserDot {
width: 11px;
height: 11px;
border-radius: 50%;
}
.browserDotRed { background: #ff5f56; }
.browserDotYellow { background: #ffbd2e; }
.browserDotGreen { background: #27c93f; }
.browserUrl {
margin: 0 auto;
padding: 3px 14px;
background: var(--ifm-background-color);
border-radius: 999px;
font-size: 12px;
color: var(--ifm-color-content-secondary);
font-family: 'SFMono-Regular', Menlo, monospace;
border: 1px solid var(--ifm-color-emphasis-200);
}
[data-theme='dark'] .browserUrl {
background: rgba(255, 255, 255, 0.04);
border-color: rgba(255, 255, 255, 0.06);
}
.screenshot {
display: block;
width: 100%;
height: auto;
background: var(--ifm-color-emphasis-100);
}
.caption {
padding: 0 1rem;
}
@media (max-width: 996px) {
.caption {
padding: 0;
}
}
.captionTitle {
font-size: 1.7rem;
line-height: 1.2;
letter-spacing: -0.02em;
font-weight: 700;
margin: 0 0 1rem;
color: var(--ifm-heading-color);
}
.captionDesc {
font-size: 1.05rem;
line-height: 1.7;
color: var(--ifm-color-content-secondary);
margin: 0 0 1.25rem;
}
.captionLink {
display: inline-flex;
align-items: center;
gap: 4px;
font-weight: 500;
color: var(--ifm-color-primary);
text-decoration: none !important;
}
.captionLink:hover {
color: var(--ifm-color-primary-dark);
}

View File

@@ -1,19 +1,56 @@
/**
* BackupX 官方文档站样式
* 灵感Ant Design / Arco Design
*/
:root {
/* Primary palette (Arco blue) */
--ifm-color-primary: #165dff;
--ifm-color-primary-dark: #0e4fe6;
--ifm-color-primary-darker: #0e4bd9;
--ifm-color-primary-darkest: #0b3eb3;
--ifm-color-primary-darker: #0b4bd9;
--ifm-color-primary-darkest: #093eb3;
--ifm-color-primary-light: #2f6cff;
--ifm-color-primary-lighter: #3d75ff;
--ifm-color-primary-lightest: #668eff;
--ifm-code-font-size: 92%;
--docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.08);
--ifm-font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
/* Surfaces */
--ifm-background-color: #ffffff;
--ifm-background-surface-color: #ffffff;
--ifm-color-emphasis-100: #f7f9fc;
--ifm-color-emphasis-200: #eef1f6;
--ifm-color-emphasis-300: #dde3ec;
/* Typography */
--ifm-font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
--ifm-font-family-monospace: 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
--ifm-heading-font-weight: 600;
--ifm-code-font-size: 92%;
--ifm-h1-font-size: 2.25rem;
--ifm-h2-font-size: 1.75rem;
--ifm-h3-font-size: 1.35rem;
--ifm-line-height-base: 1.7;
--ifm-color-content: #1d2129;
--ifm-color-content-secondary: #4e5969;
--ifm-heading-color: #1d2129;
/* Navbar */
--ifm-navbar-height: 64px;
--ifm-navbar-background-color: rgba(255, 255, 255, 0.82);
--ifm-navbar-link-color: #4e5969;
--ifm-navbar-link-hover-color: var(--ifm-color-primary);
/* Sidebar */
--ifm-menu-color: #4e5969;
--ifm-menu-color-background-active: rgba(22, 93, 255, 0.08);
--ifm-menu-color-background-hover: var(--ifm-color-emphasis-100);
/* Code */
--ifm-code-background: rgba(22, 93, 255, 0.06);
--docusaurus-highlighted-code-line-bg: rgba(22, 93, 255, 0.08);
/* Hero background helper (consumed in index.module.css) */
--bx-hero-bg: transparent;
}
[data-theme='dark'] {
@@ -24,14 +61,174 @@
--ifm-color-primary-light: #5a93ff;
--ifm-color-primary-lighter: #74a5ff;
--ifm-color-primary-lightest: #9dbfff;
--docusaurus-highlighted-code-line-bg: rgba(255, 255, 255, 0.08);
--ifm-background-color: #0f1115;
--ifm-background-surface-color: #16181d;
--ifm-color-emphasis-100: #1a1d23;
--ifm-color-emphasis-200: #23272f;
--ifm-color-emphasis-300: #2e343d;
--ifm-color-content: #e6e9ef;
--ifm-color-content-secondary: #9aa3b2;
--ifm-heading-color: #f0f2f5;
--ifm-navbar-background-color: rgba(15, 17, 21, 0.82);
--ifm-navbar-link-color: #c9d1db;
--ifm-menu-color: #c9d1db;
--ifm-menu-color-background-active: rgba(64, 128, 255, 0.15);
--ifm-menu-color-background-hover: rgba(255, 255, 255, 0.04);
--ifm-code-background: rgba(64, 128, 255, 0.14);
--docusaurus-highlighted-code-line-bg: rgba(64, 128, 255, 0.18);
}
.hero--primary {
background: linear-gradient(135deg, #165dff 0%, #0b3eb3 100%);
/* Frosted-glass navbar */
.navbar {
backdrop-filter: saturate(180%) blur(10px);
-webkit-backdrop-filter: saturate(180%) blur(10px);
border-bottom: 1px solid var(--ifm-color-emphasis-200);
box-shadow: none;
}
[data-theme='dark'] .navbar {
border-bottom-color: rgba(255, 255, 255, 0.06);
}
.navbar__title {
font-weight: 700;
letter-spacing: -0.01em;
}
.navbar__link {
font-weight: 500;
font-size: 14px;
}
/* Sidebar tweaks */
.menu__link {
font-size: 14px;
border-radius: 6px;
padding: 6px 10px;
line-height: 1.4;
}
.menu__link--active,
.menu__link--active:hover {
font-weight: 600;
}
.theme-doc-sidebar-container {
border-right: 1px solid var(--ifm-color-emphasis-200) !important;
}
[data-theme='dark'] .theme-doc-sidebar-container {
border-right-color: rgba(255, 255, 255, 0.06) !important;
}
/* Article: better heading rhythm */
.markdown h2 {
margin-top: 2.5rem;
padding-top: 0.5rem;
border-top: 1px solid var(--ifm-color-emphasis-200);
}
[data-theme='dark'] .markdown h2 {
border-top-color: rgba(255, 255, 255, 0.06);
}
.markdown h3 {
margin-top: 2rem;
}
/* Tables */
.markdown table {
border-radius: 8px;
overflow: hidden;
box-shadow: 0 0 0 1px var(--ifm-color-emphasis-200);
border-collapse: separate;
border-spacing: 0;
}
.markdown table thead tr {
background: var(--ifm-color-emphasis-100);
}
.markdown table th,
.markdown table td {
border: none;
border-bottom: 1px solid var(--ifm-color-emphasis-200);
padding: 10px 14px;
}
.markdown table tr:last-child td {
border-bottom: none;
}
/* Inline code */
code {
background: var(--ifm-code-background);
border: none;
padding: 2px 6px;
border-radius: 4px;
font-size: 0.92em;
}
/* Admonitions: softer */
.theme-admonition {
border-radius: 8px;
border-width: 1px;
border-left-width: 4px;
}
/* Footer */
.footer {
--ifm-footer-background-color: #141720;
--ifm-footer-color: #9aa3b2;
--ifm-footer-link-color: #c9d1db;
--ifm-footer-link-hover-color: #ffffff;
--ifm-footer-title-color: #f0f2f5;
padding: 3.5rem 0 2.5rem;
}
.footer__title {
font-size: 13px;
letter-spacing: 0.08em;
text-transform: uppercase;
font-weight: 600;
}
.footer__link-item {
font-size: 14px;
transition: color 0.15s ease;
}
.footer__bottom {
border-top: 1px solid rgba(255, 255, 255, 0.06);
padding-top: 2rem;
margin-top: 2.5rem;
}
.footer__copyright {
font-size: 13px;
color: #6b7280;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 10px;
height: 10px;
}
::-webkit-scrollbar-thumb {
background: var(--ifm-color-emphasis-300);
border-radius: 10px;
}
::-webkit-scrollbar-thumb:hover {
background: var(--ifm-color-emphasis-400, #adb5bd);
}
[data-theme='dark'] ::-webkit-scrollbar-thumb {
background: rgba(255, 255, 255, 0.15);
}

View File

@@ -1,31 +1,273 @@
.heroBanner {
padding: 5rem 0 4rem;
text-align: center;
/* ── Hero ───────────────────────────────────────────── */
.hero {
position: relative;
padding: 7rem 0 6rem;
overflow: hidden;
background: var(--bx-hero-bg);
}
@media screen and (max-width: 996px) {
.heroBanner {
padding: 3rem 1rem;
.heroBg {
position: absolute;
inset: 0;
background:
radial-gradient(circle at 15% 20%, rgba(104, 127, 255, 0.18) 0%, transparent 45%),
radial-gradient(circle at 85% 70%, rgba(22, 93, 255, 0.15) 0%, transparent 50%),
linear-gradient(180deg, #f7f9ff 0%, #ffffff 100%);
z-index: 0;
}
[data-theme='dark'] .heroBg {
background:
radial-gradient(circle at 15% 20%, rgba(96, 126, 255, 0.22) 0%, transparent 45%),
radial-gradient(circle at 85% 70%, rgba(118, 70, 255, 0.18) 0%, transparent 50%),
linear-gradient(180deg, #0f1115 0%, #0b0d10 100%);
}
.heroInner {
position: relative;
z-index: 1;
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 4rem;
align-items: center;
}
@media (max-width: 996px) {
.hero {
padding: 4rem 0 3rem;
}
.heroInner {
grid-template-columns: 1fr;
gap: 2.5rem;
text-align: left;
}
}
.buttons {
.heroContent {
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 1.25rem;
}
.badge {
display: inline-flex;
align-items: center;
justify-content: center;
gap: 1rem;
gap: 8px;
padding: 4px 14px;
background: rgba(22, 93, 255, 0.08);
border: 1px solid rgba(22, 93, 255, 0.15);
border-radius: 999px;
font-size: 13px;
color: var(--ifm-color-primary);
font-weight: 500;
}
[data-theme='dark'] .badge {
background: rgba(96, 126, 255, 0.15);
border-color: rgba(96, 126, 255, 0.3);
color: var(--ifm-color-primary-lighter);
}
.badgeDot {
width: 6px;
height: 6px;
background: var(--ifm-color-primary);
border-radius: 50%;
box-shadow: 0 0 0 4px rgba(22, 93, 255, 0.18);
animation: pulse 2s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
.heroTitle {
font-size: clamp(2.25rem, 4vw, 3.4rem);
line-height: 1.15;
letter-spacing: -0.025em;
font-weight: 700;
margin: 0;
color: var(--ifm-heading-color);
}
.heroTitleAccent {
display: block;
background: linear-gradient(90deg, #4080ff 0%, #8f4bff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin-top: 6px;
}
.heroSubtitle {
font-size: 1.15rem;
line-height: 1.65;
color: var(--ifm-color-content-secondary);
max-width: 540px;
margin: 0;
}
.actions {
display: flex;
gap: 12px;
margin-top: 8px;
flex-wrap: wrap;
margin-top: 1.5rem;
}
.primaryBtn {
background: linear-gradient(90deg, #165dff 0%, #4080ff 100%);
border: none;
color: #fff;
display: inline-flex;
align-items: center;
gap: 6px;
font-weight: 600;
box-shadow: 0 6px 20px rgba(22, 93, 255, 0.3);
transition: transform 0.2s ease, box-shadow 0.2s ease;
}
.primaryBtn:hover {
transform: translateY(-1px);
box-shadow: 0 10px 25px rgba(22, 93, 255, 0.4);
color: #fff;
}
.btnArrow {
transition: transform 0.2s ease;
}
.primaryBtn:hover .btnArrow {
transform: translateX(4px);
}
.secondaryBtn {
color: #fff !important;
border-color: #fff;
background: var(--ifm-background-color);
border: 1px solid var(--ifm-color-emphasis-300);
color: var(--ifm-font-color-base);
display: inline-flex;
align-items: center;
font-weight: 500;
transition: all 0.2s ease;
}
.secondaryBtn:hover {
background-color: rgba(255, 255, 255, 0.15);
color: #fff;
border-color: var(--ifm-color-primary);
color: var(--ifm-color-primary);
background: var(--ifm-background-color);
}
.metrics {
display: flex;
align-items: center;
gap: 1.75rem;
padding-top: 1.5rem;
margin-top: 0.5rem;
}
.metric {
display: flex;
flex-direction: column;
gap: 2px;
}
.metricValue {
font-size: 1.6rem;
font-weight: 700;
color: var(--ifm-heading-color);
line-height: 1.1;
letter-spacing: -0.02em;
}
.metricLabel {
font-size: 12px;
color: var(--ifm-color-content-secondary);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.metricDivider {
width: 1px;
height: 30px;
background: var(--ifm-color-emphasis-300);
}
/* ── Code window (macOS-style) ─────────────────────── */
.heroCode {
position: relative;
}
.codeWindow {
background: #0f1622;
border-radius: 12px;
box-shadow:
0 20px 50px -10px rgba(15, 22, 34, 0.35),
0 0 0 1px rgba(255, 255, 255, 0.05);
overflow: hidden;
border: 1px solid rgba(255, 255, 255, 0.06);
}
[data-theme='light'] .codeWindow {
box-shadow: 0 20px 50px -10px rgba(22, 93, 255, 0.2), 0 0 0 1px rgba(22, 93, 255, 0.06);
}
.codeHeader {
display: flex;
align-items: center;
gap: 6px;
padding: 10px 14px;
background: #161f2e;
border-bottom: 1px solid rgba(255, 255, 255, 0.04);
}
.codeDot {
width: 11px;
height: 11px;
border-radius: 50%;
}
.codeDotRed { background: #ff5f56; }
.codeDotYellow { background: #ffbd2e; }
.codeDotGreen { background: #27c93f; }
.codeTitle {
margin-left: auto;
font-size: 11px;
color: #7b8696;
letter-spacing: 0.05em;
text-transform: uppercase;
}
.codeBody {
margin: 0;
padding: 18px 20px;
font-family: 'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace;
font-size: 13px;
line-height: 1.65;
color: #e1e7ef;
background: transparent;
overflow-x: auto;
}
.codeBody code {
background: transparent;
padding: 0;
border: 0;
color: inherit;
}
.codePrompt {
color: #4080ff;
margin-right: 6px;
user-select: none;
}
.codeComment {
color: #6e7889;
font-style: italic;
}
.codeString {
color: #82d1ff;
}

View File

@@ -6,47 +6,106 @@ import useDocusaurusContext from '@docusaurus/useDocusaurusContext';
import Layout from '@theme/Layout';
import Heading from '@theme/Heading';
import HomepageFeatures from '@site/src/components/HomepageFeatures';
import HomepageShowcase from '@site/src/components/HomepageShowcase';
import styles from './index.module.css';
function HomepageHeader() {
return (
<header className={clsx('hero hero--primary', styles.heroBanner)}>
<div className="container">
<Heading as="h1" className="hero__title">
BackupX
</Heading>
<p className="hero__subtitle">
<Translate id="home.tagline">
Self-hosted server backup management one binary, one command, manage every backup
</Translate>
</p>
<div className={styles.buttons}>
<Link
className="button button--secondary button--lg"
to="/docs/getting-started/quick-start">
<Translate id="home.getStarted">Get Started</Translate>
</Link>
<Link
className={clsx('button button--outline button--secondary button--lg', styles.secondaryBtn)}
to="https://github.com/Awuqing/BackupX">
GitHub
</Link>
<header className={styles.hero}>
<div className={styles.heroBg} aria-hidden="true" />
<div className={clsx('container', styles.heroInner)}>
<div className={styles.heroContent}>
<div className={styles.badge}>
<span className={styles.badgeDot} />
<Translate id="home.badge">Open-source · v1.6.0</Translate>
</div>
<Heading as="h1" className={styles.heroTitle}>
<Translate id="home.title.part1">Self-hosted backup management</Translate>
<span className={styles.heroTitleAccent}>
<Translate id="home.title.part2">for every server.</Translate>
</span>
</Heading>
<p className={styles.heroSubtitle}>
<Translate id="home.tagline">
One binary, one command. File / database / SAP HANA backups routed to 70+ storage backends.
</Translate>
</p>
<div className={styles.actions}>
<Link className={clsx('button button--primary button--lg', styles.primaryBtn)} to="/docs/getting-started/quick-start">
<Translate id="home.getStarted">Get Started</Translate>
<span className={styles.btnArrow} aria-hidden="true"></span>
</Link>
<Link className={clsx('button button--lg', styles.secondaryBtn)} to="https://github.com/Awuqing/BackupX">
<svg width="18" height="18" viewBox="0 0 16 16" fill="currentColor" aria-hidden="true" style={{marginRight: 6}}>
<path d="M8 0C3.58 0 0 3.58 0 8a8 8 0 005.47 7.59c.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27s1.36.09 2 .27c1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.013 8.013 0 0016 8c0-4.42-3.58-8-8-8z" />
</svg>
GitHub
</Link>
</div>
<div className={styles.metrics}>
<MetricItem labelId="home.metric.backends" valueClass={styles.metricValue}>70+</MetricItem>
<div className={styles.metricDivider} />
<MetricItem labelId="home.metric.backupTypes" valueClass={styles.metricValue}>5</MetricItem>
<div className={styles.metricDivider} />
<MetricItem labelId="home.metric.license" valueClass={styles.metricValue}>Apache 2.0</MetricItem>
</div>
</div>
<div className={styles.heroCode}>
<div className={styles.codeWindow}>
<div className={styles.codeHeader}>
<span className={clsx(styles.codeDot, styles.codeDotRed)} />
<span className={clsx(styles.codeDot, styles.codeDotYellow)} />
<span className={clsx(styles.codeDot, styles.codeDotGreen)} />
<span className={styles.codeTitle}>bash</span>
</div>
<pre className={styles.codeBody}>
<code>
<span className={styles.codeComment}># Docker one-liner</span>{'\n'}
<span className={styles.codePrompt}>$</span> docker run -d --name backupx \{'\n'}
{' '}-p 8340:8340 \{'\n'}
{' '}-v backupx-data:/app/data \{'\n'}
{' '}awuqing/backupx:latest{'\n'}
{'\n'}
<span className={styles.codeComment}># Open http://localhost:8340</span>{'\n'}
<span className={styles.codeComment}># Deploy an Agent on a remote host</span>{'\n'}
<span className={styles.codePrompt}>$</span> backupx agent \{'\n'}
{' '}--master <span className={styles.codeString}>http://master:8340</span> \{'\n'}
{' '}--token <span className={styles.codeString}>&lt;token&gt;</span>
</code>
</pre>
</div>
</div>
</div>
</header>
);
}
function MetricItem({children, labelId, valueClass}: {children: ReactNode; labelId: string; valueClass: string}) {
return (
<div className={styles.metric}>
<div className={valueClass}>{children}</div>
<div className={styles.metricLabel}>
<Translate id={labelId}>
{labelId === 'home.metric.backends' ? 'Storage backends'
: labelId === 'home.metric.backupTypes' ? 'Backup types'
: 'License'}
</Translate>
</div>
</div>
);
}
export default function Home(): ReactNode {
const {siteConfig} = useDocusaurusContext();
return (
<Layout
title={translate({id: 'home.title', message: 'Self-hosted backup management'})}
title={translate({id: 'home.pageTitle', message: 'Self-hosted backup management'})}
description={siteConfig.tagline}>
<HomepageHeader />
<main>
<HomepageFeatures />
<HomepageShowcase />
</main>
</Layout>
);