diff --git a/docs-site/docusaurus.config.ts b/docs-site/docusaurus.config.ts index 3cf703b..22e7d12 100644 --- a/docs-site/docusaurus.config.ts +++ b/docs-site/docusaurus.config.ts @@ -76,20 +76,25 @@ const config: Config = { label: 'Docs', }, { - href: 'https://github.com/Awuqing/BackupX/releases', - label: 'Downloads', + to: '/docs/deployment/docker', + label: 'Deployment', position: 'left', }, { - to: '/community', - label: 'Community', + to: '/docs/operations/monitoring', + label: 'Operations', position: 'left', }, { - to: '/sponsors', - label: 'Sponsors', + to: '/docs/reference/api', + label: 'API', position: 'left', }, + { + to: '/community', + label: 'Community', + position: 'right', + }, { type: 'localeDropdown', position: 'right', @@ -105,44 +110,27 @@ const config: Config = { style: 'dark', links: [ { - title: 'Docs', + title: 'Documentation', items: [ {label: 'Introduction', to: '/docs/intro'}, {label: 'Quick Start', to: '/docs/getting-started/quick-start'}, - {label: 'Upgrade & Recovery', to: '/docs/operations/upgrade-recovery'}, + {label: 'Configuration', to: '/docs/deployment/configuration'}, ], }, { - title: 'Features', + title: 'Operations', items: [ - {label: 'SAP HANA', to: '/docs/features/sap-hana'}, - {label: 'Multi-Node Cluster', to: '/docs/features/multi-node'}, - {label: 'API Reference', to: '/docs/reference/api'}, + {label: 'Monitoring', to: '/docs/operations/monitoring'}, + {label: 'Security', to: '/docs/operations/security'}, + {label: 'Troubleshooting', to: '/docs/operations/troubleshooting'}, ], }, { - title: 'More', + title: 'Project', items: [ {label: 'GitHub', href: 'https://github.com/Awuqing/BackupX'}, {label: 'Releases', href: 'https://github.com/Awuqing/BackupX/releases'}, - {label: 'Docker Hub', href: 'https://hub.docker.com/r/awuqing/backupx'}, - {label: 'Issues', href: 'https://github.com/Awuqing/BackupX/issues'}, - ], - }, - { - title: 'Community', - items: [ - {label: 'Contributors', href: 'https://github.com/Awuqing/BackupX/graphs/contributors'}, - {label: 'Pull Requests', href: 'https://github.com/Awuqing/BackupX/pulls'}, - {label: 'Sponsor', to: '/sponsors'}, - ], - }, - { - title: 'Sponsors', - items: [ - {label: 'Sponsor BackupX', href: 'https://github.com/sponsors/Awuqing'}, - {label: 'Partnership', href: 'https://github.com/Awuqing/BackupX/issues/new/choose'}, - {label: 'Sponsor tiers', to: '/sponsors'}, + {label: 'Community', to: '/community'}, ], }, ], diff --git a/docs-site/i18n/zh-CN/code.json b/docs-site/i18n/zh-CN/code.json index e22bb85..0d158c3 100644 --- a/docs-site/i18n/zh-CN/code.json +++ b/docs-site/i18n/zh-CN/code.json @@ -1,160 +1,115 @@ { - "home.badge": { - "message": "开源备份控制平面 · v2.2.1", - "description": "Version badge on the hero" - }, - "home.title.part1": { - "message": "面向自托管服务器的", - "description": "Hero title, first line" - }, - "home.title.part2": { - "message": "备份编排平台。", - "description": "Hero title accent second line" - }, - "home.tagline": { - "message": "在一个清爽控制台中管理文件、数据库、SAP HANA 和远程节点备份。控制平面自己掌握,存储后端灵活选择。", - "description": "Tagline on the home page" - }, - "home.pageTitle": { - "message": "面向自托管服务器的备份编排", - "description": "Page element on the home page" - }, - "home.getStarted": { - "message": "快速开始", - "description": "Primary CTA on the home page" - }, - "home.metric.backends": { - "message": "存储后端", - "description": "Hero metric label: storage backends" - }, - "home.metric.backupTypes": { - "message": "远程执行", - "description": "Hero metric label: backup types" - }, - "home.metric.license": { - "message": "开源协议", - "description": "Hero metric label: license" - }, - "home.visual.eyebrow": {"message": "BackupX 控制台"}, - "home.visual.title": {"message": "运维概览"}, - "home.visual.status": {"message": "健康"}, - "home.visual.success": {"message": "成功率"}, - "home.visual.nodes": {"message": "活跃节点"}, - "home.visual.targets": {"message": "存储目标"}, - "home.visual.row1.title": {"message": "PostgreSQL 夜间备份"}, - "home.visual.row1.desc": {"message": "加密归档已上传至 S3"}, - "home.visual.row2.title": {"message": "SAP HANA 快照"}, - "home.visual.row2.desc": {"message": "正在 agent-shanghai-02 上运行"}, - "home.visual.row3.title": {"message": "保留策略清理"}, - "home.visual.row3.desc": {"message": "下一次执行在 4 小时后"}, - "home.command.title": {"message": "使用 Docker 启动"}, + "home.badge": {"message": "BackupX 文档 · v2.2.1"}, + "home.title.part1": {"message": "可靠地运维 BackupX,"}, + "home.title.part2": {"message": "每一步都有依据。"}, + "home.tagline": {"message": "从部署控制平面、连接存储与远程 Agent,到持续观测和验证恢复,一套务实文档覆盖完整运维路径。"}, + "home.pageTitle": {"message": "面向自托管服务器的备份编排"}, + "home.getStarted": {"message": "使用 Docker 开始"}, + "home.viewSource": {"message": "查看源码"}, + "home.supported.label": {"message": "支持的运行环境"}, + "home.supported.docker": {"message": "Docker"}, + "home.supported.linux": {"message": "Linux"}, + "home.supported.windows": {"message": "Windows Agent"}, + "home.guide.label": {"message": "推荐文档路径"}, + "home.guide.kicker": {"message": "从这里开始"}, + "home.guide.hint": {"message": "为下一项工作选择指南"}, + "home.guide.install.title": {"message": "安装 BackupX"}, + "home.guide.install.desc": {"message": "Docker、Compose 或独立二进制"}, + "home.guide.cluster.title": {"message": "连接远程节点"}, + "home.guide.cluster.desc": {"message": "Agent、代理、私有 CA 与堡垒机"}, + "home.guide.security.title": {"message": "加固运维环境"}, + "home.guide.security.desc": {"message": "安全控制、监控与审计记录"}, + "home.guide.recovery.title": {"message": "准备恢复方案"}, + "home.guide.recovery.desc": {"message": "升级、回滚、恢复与故障排查"}, - "section.features.tag": { - "message": "核心能力", - "description": "FEATURES section tag" - }, - "section.features.title": { - "message": "该有的都有,多余的没有", - "description": "Features section title" - }, - "section.features.subtitle": { - "message": "备份 Runner、存储 Provider、调度、集群 — 每一块都经过打磨。", - "description": "Features section subtitle" - }, - - "feat.types.title": {"message": "多种备份类型"}, - "feat.types.desc": {"message": "文件与目录(支持多源路径),以及 MySQL、PostgreSQL、SQLite、SAP HANA 统一管理。"}, - "feat.storage.title": {"message": "70+ 存储后端"}, - "feat.storage.desc": {"message": "内置阿里云 OSS、腾讯云 COS、七牛、S3、Google Drive、WebDAV、FTP,以及 SFTP、Azure Blob、Dropbox 等 rclone 后端。"}, - "feat.scheduling.title": {"message": "调度与保留策略"}, - "feat.scheduling.desc": {"message": "基于 Cron 的可视化调度编辑器,支持按天数/份数自动保留和空目录清理。"}, - "feat.cluster.title": {"message": "多节点集群"}, - "feat.cluster.desc": {"message": "Master-Agent 采用 Agent 主动出站轮询。支持代理、私有 CA 与 SSH 堡垒机,无需反向连通性。"}, - "feat.security.title": {"message": "默认安全"}, - "feat.security.desc": {"message": "JWT 认证、bcrypt、AES-256-GCM 加密配置、可选备份加密、完整审计日志。"}, - "feat.deploy.title": {"message": "部署轻量"}, - "feat.deploy.desc": {"message": "单个静态二进制 + 内嵌 SQLite。Docker 一键启动或裸机 — 零外部依赖。"}, - "feat.learnMore": {"message": "了解更多"}, + "section.features.tag": {"message": "文档路径"}, + "section.features.title": {"message": "从部署到恢复,不依赖猜测"}, + "section.features.subtitle": {"message": "每条路径都把产品能力与生产环境所需的配置、安全和运维决策直接关联。"}, + "feat.install.title": {"message": "安装与升级"}, + "feat.install.desc": {"message": "选择 Docker、Compose 或独立二进制,并保持部署过程可重复。"}, + "feat.types.title": {"message": "保护文件与数据库"}, + "feat.types.desc": {"message": "配置文件、MySQL、PostgreSQL、SQLite 和 SAP HANA 备份工作负载。"}, + "feat.storage.title": {"message": "连接存储目标"}, + "feat.storage.desc": {"message": "通过一致流程使用原生 Provider 或任意受支持的 rclone 后端。"}, + "feat.cluster.title": {"message": "构建远程节点集群"}, + "feat.cluster.desc": {"message": "通过代理、私有 CA 或 SSH 堡垒机部署仅出站连接的 Agent。"}, + "feat.monitor.title": {"message": "监控日常运行"}, + "feat.monitor.desc": {"message": "持续查看任务健康、存储容量、通知、日志和服务就绪状态。"}, + "feat.recovery.title": {"message": "按验证过的方案恢复"}, + "feat.recovery.desc": {"message": "预先准备升级、回滚点、恢复验证和事件故障排查。"}, "showcase.tag": {"message": "产品界面"}, - "showcase.title": {"message": "精心打磨的控制台,而非 DIY 脚本"}, - "showcase.subtitle": {"message": "每个页面都为运维而生 — 可观测优先,可配置次之。"}, + "showcase.title": {"message": "部署前先了解完整操作流程"}, + "showcase.subtitle": {"message": "每张产品截图都连接到对应指南,说明背后的任务、配置和运行模型。"}, + "showcase.tabs.label": {"message": "BackupX 产品界面"}, + "showcase.preview.label": {"message": "BackupX 控制台"}, "showcase.tab.dashboard": {"message": "仪表盘"}, "showcase.tab.tasks": {"message": "备份任务"}, "showcase.tab.storage": {"message": "存储目标"}, "showcase.tab.nodes": {"message": "多节点"}, + "showcase.dashboard.alt": {"message": "显示备份健康和存储使用情况的 BackupX 仪表盘"}, "showcase.dashboard.title": {"message": "一眼掌握全局"}, - "showcase.dashboard.desc": {"message": "备份成功率、存储使用量、最近执行记录、即将触发的计划 — 一页实时数据。"}, + "showcase.dashboard.desc": {"message": "备份成功率、存储使用量、最近执行记录和即将触发的计划集中显示在一个实时页面。"}, + "showcase.tasks.alt": {"message": "BackupX 备份任务管理界面"}, "showcase.tasks.title": {"message": "可视化任务编辑器"}, - "showcase.tasks.desc": {"message": "文件、MySQL、PostgreSQL、SQLite、SAP HANA — 三步完成。Cron 编辑器、多目标分发、保留策略、压缩、加密 — 点击即用。"}, - "showcase.storage.title": {"message": "70+ 后端,统一体验"}, - "showcase.storage.desc": {"message": "阿里云 OSS、腾讯云 COS、S3、Google Drive、WebDAV — 加上每一种 rclone 后端。测试连接、收藏、查看实时容量。"}, - "showcase.nodes.title": {"message": "几分钟搭起 Master-Agent"}, - "showcase.nodes.desc": {"message": "创建节点、复制令牌、在任意远程主机启动 Agent。路由到节点的任务在本地执行并直接上传到存储 — 无需反向连通性。"}, + "showcase.tasks.desc": {"message": "通过三步向导配置文件、MySQL、PostgreSQL、SQLite 和 SAP HANA,并绑定调度、多目标、保留、压缩和加密。"}, + "showcase.storage.alt": {"message": "BackupX 存储目标管理界面"}, + "showcase.storage.title": {"message": "多种后端,统一流程"}, + "showcase.storage.desc": {"message": "以一致表单管理阿里云 OSS、腾讯云 COS、S3、Google Drive、WebDAV 及 rclone 后端,并测试连接和查看容量。"}, + "showcase.nodes.alt": {"message": "BackupX 远程节点管理界面"}, + "showcase.nodes.title": {"message": "快速搭建 Master-Agent"}, + "showcase.nodes.desc": {"message": "创建节点、复制令牌并启动远程 Agent。任务在节点本地执行并直接上传存储,无需反向网络连通。"}, "showcase.cta": {"message": "开始阅读文档"}, "community.tag": {"message": "社区"}, - "community.pageTitle": {"message": "社区、赞助商与贡献者"}, - "community.pageDescription": {"message": "赞助 BackupX,了解贡献者,并找到务实的参与方式。"}, - "community.title": {"message": "开放协作,面向长期运维"}, - "community.subtitle": {"message": "备份软件的信任来自透明发布、真实部署反馈,以及足够务实的贡献路径。"}, - "community.sponsor.kicker": {"message": "赞助商"}, - "community.sponsor.wallTitle": {"message": "赞助商"}, - "community.sponsor.title": {"message": "支持你依赖的备份基础设施"}, + "community.pageTitle": {"message": "社区、赞助与贡献者"}, + "community.pageDescription": {"message": "反馈真实部署约束、完善文档,或为 BackupX 提交可验证的改进。"}, + "community.title": {"message": "开放协作让运维知识持续完善"}, + "community.subtitle": {"message": "反馈真实部署约束、完善运行手册,或通过可复现验证提交聚焦的改动。"}, + "community.path.kicker": {"message": "贡献路径"}, + "community.path.guide": {"message": "贡献指南"}, + "community.path.issues.title": {"message": "反馈生产环境问题"}, + "community.path.issues.desc": {"message": "提交日志、部署拓扑和恢复预期。"}, + "community.path.docs.title": {"message": "完善文档与示例"}, + "community.path.docs.desc": {"message": "贡献存储、Agent 和数据库部署指南。"}, + "community.path.code.title": {"message": "提交聚焦的 Pull Request"}, + "community.path.code.desc": {"message": "保持改动可测试、可审查,并贴合现有架构。"}, + "community.contributor.kicker": {"message": "贡献者"}, + "community.contributor.all": {"message": "查看全部"}, + "community.contributor.source": {"message": "通过 GitHub contributors API 加载,并提供本地回退数据。"}, + "community.contributor.botRole": {"message": "自动化贡献者"}, + "community.contributor.githubRole": {"message": "GitHub 贡献者"}, + "community.contributor.contributions": {"message": "{count} 次贡献"}, + "community.sponsor.bandTitle": {"message": "支持长期维护"}, + "community.sponsor.bandDesc": {"message": "支持兼容性测试、恢复工作和面向运维者的文档。"}, + "community.sponsor.learnMore": {"message": "了解赞助方案"}, + "community.sponsor.title": {"message": "支持可靠的备份基础设施"}, + "community.sponsor.programDesc": {"message": "赞助将用于测试覆盖、恢复可信度、Provider 兼容性,以及运维人员可以直接应用的文档。"}, "community.sponsor.cta": {"message": "赞助 BackupX"}, - "community.sponsor.openSlot": {"message": "赞助席位开放"}, - "community.sponsor.logo.project": {"message": "项目赞助"}, - "community.sponsor.logo.cloud": {"message": "云服务伙伴"}, - "community.sponsor.logo.object": {"message": "对象存储"}, - "community.sponsor.logo.cdn": {"message": "CDN 伙伴"}, - "community.sponsor.logo.database": {"message": "数据库伙伴"}, - "community.sponsor.logo.security": {"message": "安全审计"}, - "community.sponsor.logo.agent": {"message": "远程节点实验室"}, - "community.sponsor.logo.docs": {"message": "文档赞助"}, - "community.sponsor.logo.release": {"message": "发布赞助"}, - "community.sponsor.logo.s3": {"message": "S3 兼容"}, - "community.sponsor.logo.webdav": {"message": "WebDAV 伙伴"}, - "community.sponsor.logo.sftp": {"message": "SFTP 伙伴"}, - "community.sponsor.logo.docker": {"message": "容器伙伴"}, - "community.sponsor.logo.mirror": {"message": "镜像伙伴"}, - "community.sponsor.logo.restore": {"message": "恢复演练"}, - "community.sponsor.logo.qa": {"message": "测试实验室"}, - "community.sponsor.logo.oss": {"message": "开源支持"}, - "community.sponsor.logo.open": {"message": "赞助席位开放"}, "community.sponsor.infrastructure.label": {"message": "基础设施"}, - "community.sponsor.infrastructure.title": {"message": "云与存储生态伙伴"}, - "community.sponsor.infrastructure.desc": {"message": "帮助 BackupX 覆盖对象存储、WebDAV、SFTP 以及区域云平台的真实验证。"}, - "community.sponsor.security.label": {"message": "安全"}, - "community.sponsor.security.title": {"message": "审计与可靠性支持者"}, - "community.sponsor.security.desc": {"message": "支持加密、恢复演练、发布签名和运维检查等强化工作。"}, + "community.sponsor.infrastructure.title": {"message": "云与存储兼容性"}, + "community.sponsor.infrastructure.desc": {"message": "支持对象存储、WebDAV、SFTP 和区域云平台的真实验证。"}, + "community.sponsor.security.label": {"message": "可靠性"}, + "community.sponsor.security.title": {"message": "安全与恢复工作"}, + "community.sponsor.security.desc": {"message": "支持加密审查、恢复演练、发布签名和运维检查。"}, "community.sponsor.community.label": {"message": "社区"}, - "community.sponsor.community.title": {"message": "开源支持者"}, - "community.sponsor.community.desc": {"message": "支持文档、示例、平台测试和贡献者引导。"}, + "community.sponsor.community.title": {"message": "文档与贡献者支持"}, + "community.sponsor.community.desc": {"message": "改善指南、示例、平台测试和贡献者体验。"}, + "community.sponsor.tier.title": {"message": "支持方式"}, + "community.sponsor.tier.subtitle": {"message": "根据团队依赖 BackupX 的方式选择合适层级。"}, "community.sponsor.tier.backer.name": {"message": "Backer"}, "community.sponsor.tier.backer.amount": {"message": "适合个人与小团队"}, - "community.sponsor.tier.backer.desc": {"message": "支持文档、Issue 分流、兼容性测试和小型体验改进。"}, + "community.sponsor.tier.backer.desc": {"message": "支持文档、Issue 分流、兼容性测试和聚焦的易用性改进。"}, "community.sponsor.tier.partner.name": {"message": "Partner"}, "community.sponsor.tier.partner.amount": {"message": "适合存储与基础设施厂商"}, "community.sponsor.tier.partner.desc": {"message": "支持 Provider 验证、部署示例、基准说明和集成指南。"}, "community.sponsor.tier.enterprise.name": {"message": "Enterprise"}, "community.sponsor.tier.enterprise.amount": {"message": "适合生产环境使用方"}, - "community.sponsor.tier.enterprise.desc": {"message": "赞助恢复演练、发布加固、审计和长期维护等可靠性工作。"}, - "community.contributor.kicker": {"message": "贡献者"}, - "community.contributor.all": {"message": "查看全部"}, - "community.contributor.source": {"message": "浏览器端通过 GitHub contributors API 获取。"}, - "community.contributor.botRole": {"message": "自动化贡献者"}, - "community.contributor.githubRole": {"message": "GitHub 贡献者"}, - "community.contributor.contributions": {"message": "{count} 次贡献"}, - "community.path.kicker": {"message": "贡献路径"}, - "community.path.issues.title": {"message": "反馈生产问题"}, - "community.path.issues.desc": {"message": "提交日志、部署拓扑和恢复预期。"}, - "community.path.docs.title": {"message": "完善文档与示例"}, - "community.path.docs.desc": {"message": "贡献存储、Agent 和数据库部署指南。"}, - "community.path.code.title": {"message": "提交聚焦的 PR"}, - "community.path.code.desc": {"message": "保持改动小而可测,并贴合现有架构。"}, - "sponsors.pageTitle": {"message": "赞助商"}, - "sponsors.pageDescription": {"message": "赞助 BackupX 的可靠性、文档、存储兼容性和长期维护。"}, - "sponsors.tag": {"message": "赞助商"}, - "sponsors.title": {"message": "赞助 BackupX 生态"}, - "sponsors.subtitle": {"message": "赞助帮助 BackupX 更贴近真实运维:经过验证的存储 Provider、可靠发布、恢复信心和更完善的文档。"} + "community.sponsor.tier.enterprise.desc": {"message": "支持恢复演练、发布加固、审计和长期维护。"}, + + "sponsors.pageTitle": {"message": "赞助 BackupX"}, + "sponsors.pageDescription": {"message": "支持 BackupX 的可靠性、文档、存储兼容性和长期维护。"}, + "sponsors.tag": {"message": "赞助"}, + "sponsors.title": {"message": "让关键维护工作持续进行"}, + "sponsors.subtitle": {"message": "赞助用于真实 Provider 验证、可靠发布、恢复演练和更完善的运维文档。"} } diff --git a/docs-site/i18n/zh-CN/docusaurus-theme-classic/footer.json b/docs-site/i18n/zh-CN/docusaurus-theme-classic/footer.json index f84afbe..ff1fab9 100644 --- a/docs-site/i18n/zh-CN/docusaurus-theme-classic/footer.json +++ b/docs-site/i18n/zh-CN/docusaurus-theme-classic/footer.json @@ -1,23 +1,14 @@ { - "link.title.Docs": {"message": "文档"}, - "link.title.Features": {"message": "功能"}, - "link.title.More": {"message": "更多"}, - "link.title.Community": {"message": "社区"}, - "link.title.Sponsors": {"message": "赞助商"}, + "link.title.Documentation": {"message": "文档"}, + "link.title.Operations": {"message": "运维"}, + "link.title.Project": {"message": "项目"}, "link.item.label.Introduction": {"message": "简介"}, "link.item.label.Quick Start": {"message": "快速开始"}, - "link.item.label.Upgrade & Recovery": {"message": "升级与恢复"}, - "link.item.label.SAP HANA": {"message": "SAP HANA"}, - "link.item.label.Multi-Node Cluster": {"message": "多节点集群"}, - "link.item.label.API Reference": {"message": "API 参考"}, + "link.item.label.Configuration": {"message": "配置"}, + "link.item.label.Monitoring": {"message": "监控"}, + "link.item.label.Security": {"message": "安全"}, + "link.item.label.Troubleshooting": {"message": "故障排查"}, "link.item.label.GitHub": {"message": "GitHub"}, - "link.item.label.Releases": {"message": "Releases"}, - "link.item.label.Docker Hub": {"message": "Docker Hub"}, - "link.item.label.Issues": {"message": "Issues"}, - "link.item.label.Contributors": {"message": "贡献者"}, - "link.item.label.Pull Requests": {"message": "Pull Requests"}, - "link.item.label.Sponsor": {"message": "赞助"}, - "link.item.label.Sponsor BackupX": {"message": "赞助 BackupX"}, - "link.item.label.Partnership": {"message": "合作伙伴"}, - "link.item.label.Sponsor tiers": {"message": "赞助层级"} + "link.item.label.Releases": {"message": "发布版本"}, + "link.item.label.Community": {"message": "社区"} } diff --git a/docs-site/i18n/zh-CN/docusaurus-theme-classic/navbar.json b/docs-site/i18n/zh-CN/docusaurus-theme-classic/navbar.json index 96c2044..d2e2e51 100644 --- a/docs-site/i18n/zh-CN/docusaurus-theme-classic/navbar.json +++ b/docs-site/i18n/zh-CN/docusaurus-theme-classic/navbar.json @@ -1,22 +1,8 @@ { - "item.label.Docs": { - "message": "文档", - "description": "Navbar item: Docs" - }, - "item.label.Downloads": { - "message": "下载", - "description": "Navbar item: Downloads" - }, - "item.label.Community": { - "message": "社区", - "description": "Navbar item: Community" - }, - "item.label.Sponsors": { - "message": "赞助商", - "description": "Navbar item: Sponsors" - }, - "item.label.GitHub": { - "message": "GitHub", - "description": "Navbar item: GitHub" - } + "item.label.Docs": {"message": "文档"}, + "item.label.Deployment": {"message": "部署"}, + "item.label.Operations": {"message": "运维"}, + "item.label.API": {"message": "API"}, + "item.label.Community": {"message": "社区"}, + "item.label.GitHub": {"message": "GitHub"} } diff --git a/docs-site/src/components/DocIcon/index.tsx b/docs-site/src/components/DocIcon/index.tsx new file mode 100644 index 0000000..b2f1ade --- /dev/null +++ b/docs-site/src/components/DocIcon/index.tsx @@ -0,0 +1,172 @@ +import type {ReactNode} from 'react'; + +export type DocIconName = + | 'arrowRight' + | 'bookOpen' + | 'box' + | 'check' + | 'clock' + | 'cloud' + | 'database' + | 'download' + | 'external' + | 'github' + | 'heart' + | 'monitor' + | 'network' + | 'restore' + | 'server' + | 'shield' + | 'storage' + | 'terminal' + | 'users' + | 'wrench'; + +type DocIconProps = { + name: DocIconName; + size?: number; + className?: string; +}; + +const ICON_PATHS: Record<Exclude<DocIconName, 'github'>, ReactNode> = { + arrowRight: ( + <> + <path d="M5 12h14" /> + <path d="m13 6 6 6-6 6" /> + </> + ), + bookOpen: ( + <> + <path d="M3 5.5A3.5 3.5 0 0 1 6.5 2H11v17H6.5A3.5 3.5 0 0 0 3 22Z" /> + <path d="M21 5.5A3.5 3.5 0 0 0 17.5 2H13v17h4.5A3.5 3.5 0 0 1 21 22Z" /> + </> + ), + box: ( + <> + <path d="m12 3 8 4.5v9L12 21l-8-4.5v-9Z" /> + <path d="m4.4 7.7 7.6 4.4 7.6-4.4" /> + <path d="M12 12.1V21" /> + </> + ), + check: <path d="m5 12 4 4L19 6" />, + clock: ( + <> + <circle cx="12" cy="12" r="9" /> + <path d="M12 7v5l3 2" /> + </> + ), + cloud: <path d="M17.5 19H7a5 5 0 0 1-.6-9.96A6.5 6.5 0 0 1 18.7 8.2 5.5 5.5 0 0 1 17.5 19Z" />, + database: ( + <> + <ellipse cx="12" cy="5" rx="8" ry="3" /> + <path d="M4 5v6c0 1.7 3.6 3 8 3s8-1.3 8-3V5" /> + <path d="M4 11v6c0 1.7 3.6 3 8 3s8-1.3 8-3v-6" /> + </> + ), + download: ( + <> + <path d="M12 3v12" /> + <path d="m7 10 5 5 5-5" /> + <path d="M4 20h16" /> + </> + ), + external: ( + <> + <path d="M14 4h6v6" /> + <path d="m20 4-9 9" /> + <path d="M19 13v6a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V6a1 1 0 0 1 1-1h6" /> + </> + ), + heart: <path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.7l-1.1-1.1a5.5 5.5 0 0 0-7.8 7.8l1.1 1.1L12 21l7.8-7.5 1.1-1.1a5.5 5.5 0 0 0-.1-7.8Z" />, + monitor: ( + <> + <rect x="3" y="4" width="18" height="13" rx="1" /> + <path d="M8 21h8" /> + <path d="M12 17v4" /> + </> + ), + network: ( + <> + <rect x="9" y="3" width="6" height="5" rx="1" /> + <rect x="3" y="16" width="6" height="5" rx="1" /> + <rect x="15" y="16" width="6" height="5" rx="1" /> + <path d="M12 8v4M6 16v-4h12v4" /> + </> + ), + restore: ( + <> + <path d="M4 7v5h5" /> + <path d="M5.6 17a8 8 0 1 0 .4-10L4 9" /> + <path d="M12 8v4l3 2" /> + </> + ), + server: ( + <> + <rect x="3" y="4" width="18" height="6" rx="1" /> + <rect x="3" y="14" width="18" height="6" rx="1" /> + <path d="M7 7h.01M7 17h.01M11 7h7M11 17h7" /> + </> + ), + shield: ( + <> + <path d="M12 3 20 6v6c0 5-3.3 8.2-8 9-4.7-.8-8-4-8-9V6Z" /> + <path d="m8.5 12 2.2 2.2 4.8-5" /> + </> + ), + storage: ( + <> + <path d="M4 6h16v12H4z" /> + <path d="M8 10h8M8 14h5" /> + </> + ), + terminal: ( + <> + <rect x="3" y="4" width="18" height="16" rx="1" /> + <path d="m7 9 3 3-3 3M13 15h4" /> + </> + ), + users: ( + <> + <circle cx="9" cy="8" r="3" /> + <path d="M3 20a6 6 0 0 1 12 0" /> + <path d="M16 5.2a3 3 0 0 1 0 5.6M17 14a5 5 0 0 1 4 4.9" /> + </> + ), + wrench: ( + <> + <path d="M14.7 6.3a4 4 0 0 0-5-5L12 3.6 8.4 7.2 6.1 4.9a4 4 0 0 0 5 5L19 17.8a1.6 1.6 0 0 1-2.2 2.2l-7.9-7.9" /> + </> + ), +}; + +export default function DocIcon({name, size = 20, className}: DocIconProps): ReactNode { + if (name === 'github') { + return ( + <svg + aria-hidden="true" + className={className} + width={size} + height={size} + viewBox="0 0 24 24" + fill="currentColor"> + <path d="M12 2C6.48 2 2 6.59 2 12.25c0 4.53 2.87 8.37 6.84 9.73.5.1.68-.22.68-.49v-1.92c-2.78.62-3.37-1.21-3.37-1.21-.45-1.18-1.11-1.49-1.11-1.49-.91-.64.07-.63.07-.63 1 .08 1.53 1.06 1.53 1.06.89 1.57 2.34 1.11 2.91.85.09-.67.35-1.11.63-1.37-2.22-.26-4.56-1.14-4.56-5.07 0-1.12.39-2.03 1.03-2.75-.1-.26-.45-1.3.1-2.71 0 0 .84-.28 2.75 1.05A9.3 9.3 0 0 1 12 6.95a9.3 9.3 0 0 1 2.5.35c1.91-1.33 2.75-1.05 2.75-1.05.55 1.41.2 2.45.1 2.71.64.72 1.03 1.63 1.03 2.75 0 3.94-2.34 4.8-4.57 5.06.36.32.68.94.68 1.9v2.82c0 .27.18.59.69.49A10.27 10.27 0 0 0 22 12.25C22 6.59 17.52 2 12 2Z" /> + </svg> + ); + } + + return ( + <svg + aria-hidden="true" + className={className} + width={size} + height={size} + viewBox="0 0 24 24" + fill="none" + stroke="currentColor" + strokeWidth="1.6" + strokeLinecap="round" + strokeLinejoin="round"> + {ICON_PATHS[name]} + </svg> + ); +} diff --git a/docs-site/src/components/HomepageCommunity/index.tsx b/docs-site/src/components/HomepageCommunity/index.tsx index b363eee..ecc3e0f 100644 --- a/docs-site/src/components/HomepageCommunity/index.tsx +++ b/docs-site/src/components/HomepageCommunity/index.tsx @@ -3,14 +3,9 @@ import {useEffect, useState} from 'react'; import Heading from '@theme/Heading'; import Translate from '@docusaurus/Translate'; import Link from '@docusaurus/Link'; +import DocIcon, {type DocIconName} from '@site/src/components/DocIcon'; import styles from './styles.module.css'; -type SponsorSlot = { - brand: ReactNode; - name: ReactNode; - href?: string; -}; - type Contributor = { login: string; avatarUrl?: string; @@ -28,86 +23,26 @@ type GitHubContributor = { }; type CommunityPath = { + icon: DocIconName; title: ReactNode; description: ReactNode; href: string; }; -const SPONSOR_SLOTS: SponsorSlot[] = [ - { - brand: 'BackupX', - name: <Translate id="community.sponsor.logo.project">Project backer</Translate>, - href: 'https://github.com/sponsors/Awuqing', - }, - { - brand: 'Cloud', - name: <Translate id="community.sponsor.logo.cloud">Cloud partner</Translate>, - }, - { - brand: 'Object', - name: <Translate id="community.sponsor.logo.object">Object storage</Translate>, - }, - { - brand: 'CDN', - name: <Translate id="community.sponsor.logo.cdn">CDN partner</Translate>, - }, - { - brand: 'DB', - name: <Translate id="community.sponsor.logo.database">Database partner</Translate>, - }, - { - brand: 'Security', - name: <Translate id="community.sponsor.logo.security">Security audit</Translate>, - }, - { - brand: 'Agent', - name: <Translate id="community.sponsor.logo.agent">Remote node lab</Translate>, - }, - { - brand: 'Docs', - name: <Translate id="community.sponsor.logo.docs">Docs sponsor</Translate>, - }, - { - brand: 'Release', - name: <Translate id="community.sponsor.logo.release">Release sponsor</Translate>, - }, - { - brand: 'S3', - name: <Translate id="community.sponsor.logo.s3">S3 compatible</Translate>, - }, - { - brand: 'WebDAV', - name: <Translate id="community.sponsor.logo.webdav">WebDAV partner</Translate>, - }, - { - brand: 'SFTP', - name: <Translate id="community.sponsor.logo.sftp">SFTP partner</Translate>, - }, - { - brand: 'Docker', - name: <Translate id="community.sponsor.logo.docker">Container partner</Translate>, - }, - { - brand: 'Mirror', - name: <Translate id="community.sponsor.logo.mirror">Mirror partner</Translate>, - }, - { - brand: 'Restore', - name: <Translate id="community.sponsor.logo.restore">Restore drill</Translate>, - }, - { - brand: 'QA', - name: <Translate id="community.sponsor.logo.qa">Test lab</Translate>, - }, - { - brand: 'OSS', - name: <Translate id="community.sponsor.logo.oss">Open source</Translate>, - }, - { - brand: 'Open Slot', - name: <Translate id="community.sponsor.logo.open">Sponsor slot open</Translate>, - }, -]; +type SponsorFocus = { + id: string; + icon: DocIconName; + label: ReactNode; + title: ReactNode; + description: ReactNode; +}; + +type SponsorTier = { + id: string; + name: ReactNode; + audience: ReactNode; + description: ReactNode; +}; const FALLBACK_CONTRIBUTORS: Contributor[] = [ { @@ -126,30 +61,69 @@ const FALLBACK_CONTRIBUTORS: Contributor[] = [ const COMMUNITY_PATHS: CommunityPath[] = [ { + icon: 'wrench', title: <Translate id="community.path.issues.title">Report production issues</Translate>, - description: <Translate id="community.path.issues.desc">Share logs, deployment topology and restore expectations.</Translate>, + description: <Translate id="community.path.issues.desc">Share logs, deployment topology, and restore expectations.</Translate>, href: 'https://github.com/Awuqing/BackupX/issues', }, { + icon: 'bookOpen', title: <Translate id="community.path.docs.title">Improve docs and examples</Translate>, - description: <Translate id="community.path.docs.desc">Contribute deployment guides for storage, agents and databases.</Translate>, + description: <Translate id="community.path.docs.desc">Contribute deployment guides for storage, agents, and databases.</Translate>, href: '/docs/development/contributing', }, { - title: <Translate id="community.path.code.title">Ship focused PRs</Translate>, - description: <Translate id="community.path.code.desc">Keep changes small, tested and aligned with the existing architecture.</Translate>, + icon: 'github', + title: <Translate id="community.path.code.title">Ship focused pull requests</Translate>, + description: <Translate id="community.path.code.desc">Keep changes tested, reviewable, and aligned with the existing architecture.</Translate>, href: 'https://github.com/Awuqing/BackupX/pulls', }, ]; -function SponsorLogoCard({brand, name, href}: SponsorSlot) { - return ( - <Link className={styles.sponsorLogoTile} to={href ?? 'https://github.com/sponsors/Awuqing'}> - <span className={styles.sponsorLogoMark}>{brand}</span> - <span className={styles.sponsorLogoName}>{name}</span> - </Link> - ); -} +const SPONSOR_FOCUS: SponsorFocus[] = [ + { + id: 'infrastructure', + icon: 'cloud', + label: <Translate id="community.sponsor.infrastructure.label">Infrastructure</Translate>, + title: <Translate id="community.sponsor.infrastructure.title">Cloud and storage compatibility</Translate>, + description: <Translate id="community.sponsor.infrastructure.desc">Support validation across object storage, WebDAV, SFTP, and regional cloud providers.</Translate>, + }, + { + id: 'reliability', + icon: 'shield', + label: <Translate id="community.sponsor.security.label">Reliability</Translate>, + title: <Translate id="community.sponsor.security.title">Security and recovery work</Translate>, + description: <Translate id="community.sponsor.security.desc">Fund encryption reviews, restore drills, release signing, and operational checks.</Translate>, + }, + { + id: 'community', + icon: 'users', + label: <Translate id="community.sponsor.community.label">Community</Translate>, + title: <Translate id="community.sponsor.community.title">Documentation and contributor support</Translate>, + description: <Translate id="community.sponsor.community.desc">Improve guides, examples, platform testing, and the contributor experience.</Translate>, + }, +]; + +const SPONSOR_TIERS: SponsorTier[] = [ + { + id: 'backer', + name: <Translate id="community.sponsor.tier.backer.name">Backer</Translate>, + audience: <Translate id="community.sponsor.tier.backer.amount">For individuals and small teams</Translate>, + description: <Translate id="community.sponsor.tier.backer.desc">Supports documentation, issue triage, compatibility testing, and focused usability work.</Translate>, + }, + { + id: 'partner', + name: <Translate id="community.sponsor.tier.partner.name">Partner</Translate>, + audience: <Translate id="community.sponsor.tier.partner.amount">For storage and infrastructure vendors</Translate>, + description: <Translate id="community.sponsor.tier.partner.desc">Supports provider validation, deployment examples, benchmarks, and integration guides.</Translate>, + }, + { + id: 'enterprise', + name: <Translate id="community.sponsor.tier.enterprise.name">Enterprise</Translate>, + audience: <Translate id="community.sponsor.tier.enterprise.amount">For production BackupX operators</Translate>, + description: <Translate id="community.sponsor.tier.enterprise.desc">Funds recovery drills, release hardening, audits, and long-term maintenance.</Translate>, + }, +]; function getInitials(login: string): string { return login @@ -180,11 +154,9 @@ function useGitHubContributors(): Contributor[] { useEffect(() => { const controller = new AbortController(); - fetch('https://api.github.com/repos/Awuqing/BackupX/contributors?per_page=12', { + fetch('https://api.github.com/repos/Awuqing/BackupX/contributors?per_page=8', { signal: controller.signal, - headers: { - Accept: 'application/vnd.github+json', - }, + headers: {Accept: 'application/vnd.github+json'}, }) .then(response => { if (!response.ok) { @@ -213,28 +185,28 @@ function useGitHubContributors(): Contributor[] { return contributors; } -function ContributorCard({login, avatarUrl, contributions, type, href}: Contributor) { +function ContributorRow({login, avatarUrl, contributions, type, href}: Contributor): ReactNode { return ( - <Link className={styles.contributorCard} to={href}> + <Link className={styles.contributorRow} to={href}> {avatarUrl ? ( <img className={styles.avatarImage} src={avatarUrl} alt="" loading="lazy" /> ) : ( <span className={styles.avatar} aria-hidden="true">{getInitials(login)}</span> )} <span className={styles.contributorBody}> - <strong>{login}</strong> - <span> + <span className={styles.contributorName}>{login}</span> + <span className={styles.contributorRole}> {type === 'Bot' ? ( <Translate id="community.contributor.botRole">Automation contributor</Translate> ) : ( <Translate id="community.contributor.githubRole">GitHub contributor</Translate> )} </span> - <em> - <Translate id="community.contributor.contributions" values={{count: contributions}}> - {'{count} contributions'} - </Translate> - </em> + </span> + <span className={styles.contributionCount}> + <Translate id="community.contributor.contributions" values={{count: contributions}}> + {'{count} contributions'} + </Translate> </span> </Link> ); @@ -242,22 +214,54 @@ function ContributorCard({login, avatarUrl, contributions, type, href}: Contribu export function HomepageSponsors(): ReactNode { return ( - <div className={styles.sponsorWall}> - <div className={styles.sponsorWallHeader}> - <Heading as="h3" className={styles.sponsorWallTitle}> - <Translate id="community.sponsor.wallTitle">Sponsors</Translate> - </Heading> - <Link className={styles.sponsorWallAction} to="https://github.com/sponsors/Awuqing"> + <div className={styles.sponsorProgram}> + <div className={styles.sponsorProgramHeader}> + <span className={styles.sponsorProgramIcon}><DocIcon name="heart" size={23} /></span> + <div className={styles.sponsorProgramCopy}> + <Heading as="h2" className={styles.sponsorProgramTitle}> + <Translate id="community.sponsor.title">Support reliable backup infrastructure</Translate> + </Heading> + <p> + <Translate id="community.sponsor.programDesc"> + Sponsorship is directed toward test coverage, restore confidence, provider compatibility, and documentation that operators can apply directly. + </Translate> + </p> + </div> + <Link className={styles.sponsorAction} to="https://github.com/sponsors/Awuqing"> + <DocIcon name="github" size={17} /> <Translate id="community.sponsor.cta">Sponsor BackupX</Translate> - <span aria-hidden="true">-></span> + <DocIcon name="external" size={15} /> </Link> </div> - <div className={styles.sponsorLogoGrid}> - {SPONSOR_SLOTS.map((slot, index) => ( - <SponsorLogoCard key={index} {...slot} /> + <div className={styles.sponsorFocusList}> + {SPONSOR_FOCUS.map(focus => ( + <div key={focus.id} className={styles.sponsorFocusItem}> + <span className={styles.sponsorFocusIcon}><DocIcon name={focus.icon} size={20} /></span> + <span className={styles.sponsorFocusBody}> + <span className={styles.sponsorFocusLabel}>{focus.label}</span> + <span className={styles.sponsorFocusTitle}>{focus.title}</span> + <span className={styles.sponsorFocusDescription}>{focus.description}</span> + </span> + </div> ))} </div> + + <div className={styles.tierSection}> + <div className={styles.tierSectionHeader}> + <Heading as="h3"><Translate id="community.sponsor.tier.title">Ways to support</Translate></Heading> + <p><Translate id="community.sponsor.tier.subtitle">Choose a level that matches how your team depends on BackupX.</Translate></p> + </div> + <div className={styles.tierGrid}> + {SPONSOR_TIERS.map(tier => ( + <div key={tier.id} className={styles.tierItem}> + <span className={styles.tierName}>{tier.name}</span> + <span className={styles.tierAudience}>{tier.audience}</span> + <span className={styles.tierDescription}>{tier.description}</span> + </div> + ))} + </div> + </div> </div> ); } @@ -269,59 +273,74 @@ export default function HomepageCommunity(): ReactNode { <section id="community" className={styles.section}> <div className="container"> <div className={styles.sectionHead}> - <div className={styles.sectionTag}> - <Translate id="community.tag">COMMUNITY</Translate> + <div> + <div className={styles.sectionTag}> + <Translate id="community.tag">Community</Translate> + </div> + <Heading as="h2" className={styles.sectionTitle}> + <Translate id="community.title">Operational knowledge improves in the open</Translate> + </Heading> </div> - <Heading as="h2" className={styles.sectionTitle}> - <Translate id="community.title">Built in the open, ready for long-term operators</Translate> - </Heading> <p className={styles.sectionSubtitle}> <Translate id="community.subtitle"> - Backup software earns trust through transparent releases, real deployment feedback and a contributor path that stays practical. + Report real deployment constraints, improve the runbooks, or contribute a focused change with reproducible validation. </Translate> </p> </div> - <HomepageSponsors /> - - <div className={styles.communityGrid}> - <div className={styles.panel}> + <div className={styles.communityShell}> + <div className={styles.pathPanel}> <div className={styles.panelHeader}> - <span> - <Translate id="community.contributor.kicker">Contributors</Translate> - </span> - <Link to="https://github.com/Awuqing/BackupX/graphs/contributors"> - <Translate id="community.contributor.all">View all</Translate> + <span><Translate id="community.path.kicker">Contribution paths</Translate></span> + <Link to="/docs/development/contributing"> + <Translate id="community.path.guide">Contribution guide</Translate> + <DocIcon name="arrowRight" size={15} /> </Link> </div> - <div className={styles.panelNote}> - <Translate id="community.contributor.source">Loaded from GitHub contributors API in the browser.</Translate> - </div> - <div className={styles.contributorList}> - {contributors.map(contributor => ( - <ContributorCard key={contributor.login} {...contributor} /> + <div className={styles.pathList}> + {COMMUNITY_PATHS.map((path, index) => ( + <Link key={path.href} className={styles.pathItem} to={path.href}> + <span className={styles.pathIndex}>{String(index + 1).padStart(2, '0')}</span> + <span className={styles.pathIcon}><DocIcon name={path.icon} size={19} /></span> + <span className={styles.pathBody}> + <span className={styles.pathTitle}>{path.title}</span> + <span className={styles.pathDescription}>{path.description}</span> + </span> + <DocIcon name="arrowRight" size={17} className={styles.rowArrow} /> + </Link> ))} </div> </div> - <div className={styles.panel}> + <div className={styles.contributorPanel}> <div className={styles.panelHeader}> - <span> - <Translate id="community.path.kicker">Contributor paths</Translate> - </span> + <span><Translate id="community.contributor.kicker">Contributors</Translate></span> + <Link to="https://github.com/Awuqing/BackupX/graphs/contributors"> + <Translate id="community.contributor.all">View all</Translate> + <DocIcon name="external" size={14} /> + </Link> </div> - <div className={styles.pathList}> - {COMMUNITY_PATHS.map((path, index) => ( - <Link key={index} className={styles.pathItem} to={path.href}> - <span className={styles.pathIndex}>{String(index + 1).padStart(2, '0')}</span> - <span> - <strong>{path.title}</strong> - <em>{path.description}</em> - </span> - </Link> + <p className={styles.panelNote}> + <Translate id="community.contributor.source">Loaded from the GitHub contributors API with a local fallback.</Translate> + </p> + <div className={styles.contributorList}> + {contributors.slice(0, 5).map(contributor => ( + <ContributorRow key={contributor.login} {...contributor} /> ))} </div> </div> + + <div className={styles.sponsorBand}> + <span className={styles.sponsorBandIcon}><DocIcon name="heart" size={20} /></span> + <span className={styles.sponsorBandBody}> + <span className={styles.sponsorBandTitle}><Translate id="community.sponsor.bandTitle">Support long-term maintenance</Translate></span> + <span className={styles.sponsorBandDescription}><Translate id="community.sponsor.bandDesc">Fund compatibility testing, recovery work, and operator-focused documentation.</Translate></span> + </span> + <Link to="/sponsors"> + <Translate id="community.sponsor.learnMore">Sponsorship details</Translate> + <DocIcon name="arrowRight" size={16} /> + </Link> + </div> </div> </div> </section> diff --git a/docs-site/src/components/HomepageCommunity/styles.module.css b/docs-site/src/components/HomepageCommunity/styles.module.css index 3753a33..5435e3d 100644 --- a/docs-site/src/components/HomepageCommunity/styles.module.css +++ b/docs-site/src/components/HomepageCommunity/styles.module.css @@ -1,367 +1,436 @@ .section { - padding: 5.5rem 0 6rem; - background: - linear-gradient(180deg, rgba(245, 247, 250, 0) 0%, rgba(245, 247, 250, 0.86) 100%), - var(--ifm-background-color); -} - -[data-theme='dark'] .section { - background: - linear-gradient(180deg, rgba(15, 17, 21, 0) 0%, rgba(255, 255, 255, 0.03) 100%), - var(--ifm-background-color); + background: var(--ifm-background-color); + padding: 5rem 0 5.5rem; } .sectionHead { - max-width: 760px; - margin: 0 auto 2.5rem; - text-align: center; + align-items: end; + display: grid; + gap: 3rem; + grid-template-columns: minmax(0, 1fr) minmax(320px, 0.7fr); + margin-bottom: 2.25rem; } .sectionTag { - display: inline-flex; - align-items: center; - min-height: 28px; - margin-bottom: 1rem; - padding: 4px 10px; - color: #00a870; - background: rgba(0, 180, 42, 0.1); - border: 1px solid rgba(0, 180, 42, 0.18); - border-radius: 8px; + color: var(--ifm-color-primary); font-size: 12px; - font-weight: 750; - letter-spacing: 0; + font-weight: 500; + letter-spacing: 0.06em; + margin-bottom: 0.75rem; + text-transform: uppercase; } .sectionTitle { - margin: 0 0 1rem; color: var(--ifm-heading-color); - font-size: 2.35rem; - font-weight: 750; - letter-spacing: 0; - line-height: 1.2; + font-size: clamp(1.9rem, 4vw, 2.55rem); + font-weight: 500; + letter-spacing: -0.025em; + line-height: 1.18; + margin: 0; + max-width: 720px; } .sectionSubtitle { - margin: 0; color: var(--ifm-color-content-secondary); - font-size: 1.04rem; + font-size: 1rem; line-height: 1.7; + margin: 0; } -.sponsorWall { +.communityShell, +.sponsorProgram { + border: 1px solid var(--bx-border); + border-radius: 4px; overflow: hidden; - margin-bottom: 1rem; - background: var(--ifm-background-color); - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; - box-shadow: 0 12px 28px rgba(29, 33, 41, 0.06); } -[data-theme='dark'] .sponsorWall { - background: rgba(255, 255, 255, 0.02); - border-color: rgba(255, 255, 255, 0.08); - box-shadow: none; +.communityShell { + display: grid; + grid-template-columns: minmax(0, 1.35fr) minmax(310px, 0.8fr); } -.sponsorWallHeader { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; - min-height: 60px; - padding: 0 1.25rem; - border-bottom: 1px solid var(--ifm-color-emphasis-200); +.pathPanel, +.contributorPanel { + min-width: 0; } -[data-theme='dark'] .sponsorWallHeader { - border-bottom-color: rgba(255, 255, 255, 0.08); +.pathPanel { + border-right: 1px solid var(--bx-border); } -.sponsorWallTitle { - position: relative; - margin: 0; - padding-left: 14px; - color: var(--ifm-heading-color); - font-size: 1.05rem; - font-weight: 750; - letter-spacing: 0; -} - -.sponsorWallTitle::before { - position: absolute; - top: 50%; - left: 0; - width: 3px; - height: 18px; - content: ""; - background: #52c41a; - border-radius: 3px; - transform: translateY(-50%); +.panelHeader { + align-items: center; + background: var(--bx-surface-subtle); + border-bottom: 1px solid var(--bx-border); + display: flex; + gap: 1rem; + justify-content: space-between; + min-height: 50px; + padding: 10px 14px; } -.sponsorWallAction { - display: inline-flex; - align-items: center; - gap: 6px; - min-height: 36px; - padding: 0 12px; - color: #52c41a; - background: rgba(82, 196, 26, 0.08); - border: 1px solid rgba(82, 196, 26, 0.2); - border-radius: 8px; +.panelHeader > span { + color: var(--ifm-heading-color); font-size: 13px; - font-weight: 700; - text-decoration: none !important; - white-space: nowrap; - transition: background 0.2s ease, border-color 0.2s ease, transform 0.2s ease; + font-weight: 500; } -.sponsorWallAction:hover, -.sponsorWallAction:focus-visible { - color: #389e0d; - background: rgba(82, 196, 26, 0.14); - border-color: #52c41a; - transform: translateY(-1px); +.panelHeader a { + align-items: center; + color: var(--ifm-color-primary); + display: inline-flex; + font-size: 12px; + gap: 5px; + text-decoration: none !important; } -.sponsorLogoGrid { +.pathList, +.contributorList { display: grid; - grid-template-columns: repeat(6, minmax(0, 1fr)); - background: var(--ifm-color-emphasis-200); - gap: 1px; - padding: 1px; } -[data-theme='dark'] .sponsorLogoGrid { - background: rgba(255, 255, 255, 0.08); -} - -.sponsorLogoTile { - display: flex; +.pathItem { align-items: center; - justify-content: center; - min-width: 0; - min-height: 106px; - padding: 14px 10px; - flex-direction: column; + border-bottom: 1px solid var(--bx-border); color: inherit; - background: var(--ifm-background-color); - text-align: center; + display: grid; + gap: 11px; + grid-template-columns: auto auto minmax(0, 1fr) auto; + min-height: 100px; + padding: 13px 14px; text-decoration: none !important; - transition: background 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease; } -[data-theme='dark'] .sponsorLogoTile { - background: rgba(15, 17, 21, 0.78); +.pathItem:last-child { + border-bottom: 0; } -.sponsorLogoTile:hover, -.sponsorLogoTile:focus-visible { - z-index: 1; +.pathItem:hover, +.pathItem:focus-visible, +.contributorRow:hover, +.contributorRow:focus-visible { + background: var(--bx-surface-selected); color: inherit; - background: rgba(82, 196, 26, 0.04); - box-shadow: inset 0 0 0 1px rgba(82, 196, 26, 0.5); - transform: translateY(-1px); } -.sponsorLogoMark { - display: block; - max-width: 100%; - overflow-wrap: anywhere; +.pathIndex { + color: var(--ifm-color-content-secondary); + font-family: var(--ifm-font-family-monospace); + font-size: 11px; +} + +.pathIcon, +.sponsorBandIcon, +.sponsorProgramIcon, +.sponsorFocusIcon { + align-items: center; + background: var(--bx-surface-selected); + border: 1px solid color-mix(in srgb, var(--ifm-color-primary) 25%, var(--bx-border)); + border-radius: 3px; color: var(--ifm-color-primary); - font-size: 1.45rem; - font-weight: 850; - letter-spacing: 0; - line-height: 1.1; + display: inline-flex; + justify-content: center; +} + +.pathIcon { + height: 36px; + width: 36px; +} + +.pathBody, +.contributorBody, +.sponsorBandBody, +.sponsorFocusBody { + display: grid; + min-width: 0; } -.sponsorLogoTile:nth-child(2n) .sponsorLogoMark { - color: #ff7d00; +.pathBody { + gap: 3px; } -.sponsorLogoTile:nth-child(3n) .sponsorLogoMark { - color: #14c9c9; +.pathTitle, +.contributorName, +.sponsorBandTitle, +.sponsorFocusTitle, +.tierName { + color: var(--ifm-heading-color); + font-weight: 500; } -.sponsorLogoTile:nth-child(4n) .sponsorLogoMark { - color: #722ed1; +.pathTitle { + font-size: 14px; } -.sponsorLogoTile:nth-child(5n) .sponsorLogoMark { - color: #52c41a; +.pathDescription { + color: var(--ifm-color-content-secondary); + font-size: 12px; + line-height: 1.55; } -.sponsorLogoName { - display: block; - max-width: 100%; - margin-top: 10px; +.rowArrow { color: var(--ifm-color-content-secondary); - font-size: 0.86rem; - font-weight: 600; - line-height: 1.35; } -.panel { - background: var(--ifm-background-color); - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; - box-shadow: 0 12px 28px rgba(29, 33, 41, 0.06); +.pathItem:hover .rowArrow, +.pathItem:focus-visible .rowArrow { + color: var(--ifm-color-primary); } -[data-theme='dark'] .panel { - background: rgba(255, 255, 255, 0.02); - border-color: rgba(255, 255, 255, 0.08); - box-shadow: none; +.panelNote { + border-bottom: 1px solid var(--bx-border); + color: var(--ifm-color-content-secondary); + font-size: 12px; + line-height: 1.5; + margin: 0; + padding: 10px 14px; } -.communityGrid { +.contributorRow { + align-items: center; + border-bottom: 1px solid var(--bx-border); + color: inherit; display: grid; - grid-template-columns: 1fr 1fr; - gap: 1rem; + gap: 10px; + grid-template-columns: auto minmax(0, 1fr) auto; + min-height: 62px; + padding: 9px 14px; + text-decoration: none !important; } -.panel { - min-width: 0; - padding: 1.25rem; +.contributorRow:last-child { + border-bottom: 0; } -.panelHeader { - display: flex; +.avatar, +.avatarImage { + border-radius: 4px; + height: 34px; + width: 34px; +} + +.avatar { align-items: center; - justify-content: space-between; - gap: 1rem; - margin-bottom: 1rem; - color: var(--ifm-color-content-secondary); - font-size: 12px; - font-weight: 750; - letter-spacing: 0; - text-transform: uppercase; + background: var(--ifm-color-primary); + color: #ffffff; + display: inline-flex; + font-size: 11px; + justify-content: center; } -.panelHeader a { - color: var(--ifm-color-primary); - text-decoration: none !important; +.avatarImage { + border: 1px solid var(--bx-border); + object-fit: cover; } -.panelNote { - margin: -0.35rem 0 1rem; +.contributorBody { + gap: 1px; +} + +.contributorName { + font-size: 13px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.contributorRole, +.contributionCount { color: var(--ifm-color-content-secondary); - font-size: 0.82rem; - line-height: 1.5; + font-size: 11px; } -.contributorList, -.pathList { - display: grid; - gap: 10px; +.contributionCount { + white-space: nowrap; } -.contributorCard, -.pathItem { +.sponsorBand { + align-items: center; + background: var(--bx-surface-subtle); + border-top: 1px solid var(--bx-border); display: grid; - min-width: 0; - color: inherit; - background: var(--ifm-color-emphasis-100); - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; - text-decoration: none !important; - transition: border-color 0.2s ease, transform 0.2s ease, background 0.2s ease; + gap: 12px; + grid-column: 1 / -1; + grid-template-columns: auto minmax(0, 1fr) auto; + min-height: 82px; + padding: 13px 14px; } -.contributorCard:hover, -.contributorCard:focus-visible, -.pathItem:hover, -.pathItem:focus-visible { - color: inherit; - background: var(--ifm-background-color); - border-color: var(--ifm-color-primary); - transform: translateY(-1px); +.sponsorBandIcon { + height: 38px; + width: 38px; } -[data-theme='dark'] .contributorCard, -[data-theme='dark'] .pathItem { - background: rgba(255, 255, 255, 0.03); - border-color: rgba(255, 255, 255, 0.08); +.sponsorBandBody { + gap: 2px; } -.contributorCard { - grid-template-columns: auto minmax(0, 1fr); - gap: 12px; - align-items: center; - padding: 12px; +.sponsorBandTitle { + font-size: 14px; } -.avatar { - display: inline-flex; +.sponsorBandDescription { + color: var(--ifm-color-content-secondary); + font-size: 12px; + line-height: 1.5; +} + +.sponsorBand > a, +.sponsorAction { align-items: center; - justify-content: center; - width: 44px; - height: 44px; - color: #fff; - background: #165dff; - border-radius: 8px; + border: 1px solid var(--bx-border-strong); + border-radius: 4px; + color: var(--ifm-color-primary); + display: inline-flex; font-size: 13px; - font-weight: 800; + gap: 6px; + min-height: 38px; + padding: 0 11px; + text-decoration: none !important; + white-space: nowrap; } -.avatarImage { - width: 44px; - height: 44px; - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; - object-fit: cover; +.sponsorBand > a:hover, +.sponsorBand > a:focus-visible, +.sponsorAction:hover, +.sponsorAction:focus-visible { + background: var(--bx-surface-selected); + border-color: var(--ifm-color-primary); + color: var(--ifm-color-primary); } -.contributorCard:nth-child(2) .avatar { - background: #00a870; +.sponsorProgramHeader { + align-items: center; + background: var(--bx-surface-subtle); + border-bottom: 1px solid var(--bx-border); + display: grid; + gap: 14px; + grid-template-columns: auto minmax(0, 1fr) auto; + padding: 1.4rem; } -.contributorCard:nth-child(3) .avatar { - background: #ff7d00; +.sponsorProgramIcon { + height: 44px; + width: 44px; } -.contributorBody { - display: grid; +.sponsorProgramCopy { min-width: 0; - gap: 2px; } -.contributorBody strong { +.sponsorProgramTitle { color: var(--ifm-heading-color); - font-size: 0.98rem; + font-size: 1.25rem; + font-weight: 500; + margin: 0 0 4px; } -.contributorBody span { - color: var(--ifm-color-content); - font-size: 0.88rem; +.sponsorProgramCopy p { + color: var(--ifm-color-content-secondary); + font-size: 0.9rem; + line-height: 1.55; + margin: 0; + max-width: 740px; } -.contributorBody em, -.pathItem em { - color: var(--ifm-color-content-secondary); - font-size: 0.82rem; - font-style: normal; - line-height: 1.45; +.sponsorFocusList { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); } -.pathItem { +.sponsorFocusItem { + display: grid; + gap: 11px; grid-template-columns: auto minmax(0, 1fr); - gap: 12px; - padding: 14px; + min-height: 190px; + padding: 1.4rem; } -.pathIndex { +.sponsorFocusItem + .sponsorFocusItem { + border-left: 1px solid var(--bx-border); +} + +.sponsorFocusIcon { + height: 36px; + width: 36px; +} + +.sponsorFocusBody { + align-content: start; + gap: 5px; +} + +.sponsorFocusLabel { color: var(--ifm-color-primary); - font-family: var(--ifm-font-family-monospace); - font-size: 0.86rem; - font-weight: 800; + font-size: 11px; + font-weight: 500; + letter-spacing: 0.05em; + text-transform: uppercase; +} + +.sponsorFocusTitle { + font-size: 14px; + line-height: 1.4; +} + +.sponsorFocusDescription, +.tierDescription { + color: var(--ifm-color-content-secondary); + font-size: 12px; + line-height: 1.6; +} + +.tierSection { + border-top: 1px solid var(--bx-border); } -.pathItem strong { - display: block; - margin-bottom: 4px; +.tierSectionHeader { + align-items: baseline; + background: var(--bx-surface-subtle); + border-bottom: 1px solid var(--bx-border); + display: flex; + gap: 14px; + justify-content: space-between; + padding: 14px; +} + +.tierSectionHeader h3 { color: var(--ifm-heading-color); - font-size: 0.96rem; + font-size: 14px; + font-weight: 500; + margin: 0; +} + +.tierSectionHeader p { + color: var(--ifm-color-content-secondary); + font-size: 12px; + margin: 0; + text-align: right; +} + +.tierGrid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); +} + +.tierItem { + display: grid; + gap: 5px; + min-height: 150px; + padding: 1.25rem; +} + +.tierItem + .tierItem { + border-left: 1px solid var(--bx-border); +} + +.tierName { + font-size: 15px; +} + +.tierAudience { + color: var(--ifm-color-primary); + font-size: 12px; } @media (max-width: 996px) { @@ -369,20 +438,35 @@ padding: 4rem 0; } - .sectionTitle { - font-size: 2rem; + .sectionHead { + align-items: start; + gap: 1rem; + grid-template-columns: 1fr; } - .communityGrid { + .communityShell { grid-template-columns: 1fr; } - .sponsorLogoGrid { - grid-template-columns: repeat(3, minmax(0, 1fr)); + .pathPanel { + border-bottom: 1px solid var(--bx-border); + border-right: 0; } - .sponsorLogoTile { - min-height: 96px; + .sponsorFocusList, + .tierGrid { + grid-template-columns: 1fr; + } + + .sponsorFocusItem, + .tierItem { + min-height: 0; + } + + .sponsorFocusItem + .sponsorFocusItem, + .tierItem + .tierItem { + border-left: 0; + border-top: 1px solid var(--bx-border); } } @@ -391,39 +475,35 @@ padding: 3.25rem 0; } - .sectionTitle { - font-size: 1.75rem; + .pathItem { + grid-template-columns: auto minmax(0, 1fr) auto; } - .sponsorWallHeader { - display: grid; - min-height: auto; - padding: 1rem; + .pathIndex, + .contributionCount { + display: none; } - .sponsorWallAction { - justify-content: center; - width: 100%; + .sponsorBand, + .sponsorProgramHeader { + align-items: start; + grid-template-columns: auto minmax(0, 1fr); } - .sponsorLogoGrid { - grid-template-columns: repeat(2, minmax(0, 1fr)); - } - - .sponsorLogoMark { - font-size: 1.15rem; + .sponsorBand > a, + .sponsorAction { + grid-column: 1 / -1; + justify-content: center; + width: 100%; } - .panel { - padding: 1rem; + .tierSectionHeader { + align-items: flex-start; + flex-direction: column; + gap: 2px; } -} -@media (prefers-reduced-motion: reduce) { - .sponsorWallAction, - .sponsorLogoTile, - .contributorCard, - .pathItem { - transition: none; + .tierSectionHeader p { + text-align: left; } } diff --git a/docs-site/src/components/HomepageFeatures/index.tsx b/docs-site/src/components/HomepageFeatures/index.tsx index e4baf12..2f35d35 100644 --- a/docs-site/src/components/HomepageFeatures/index.tsx +++ b/docs-site/src/components/HomepageFeatures/index.tsx @@ -1,169 +1,87 @@ import type {ReactNode} from 'react'; -import Heading from '@theme/Heading'; -import Translate from '@docusaurus/Translate'; import Link from '@docusaurus/Link'; +import Translate from '@docusaurus/Translate'; +import Heading from '@theme/Heading'; +import DocIcon, {type DocIconName} from '@site/src/components/DocIcon'; import styles from './styles.module.css'; -type FeatureItem = { +type DocumentationPath = { + icon: DocIconName; title: ReactNode; description: ReactNode; - icon: ReactNode; - link?: string; + to: 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[] = [ +const DOCUMENTATION_PATHS: DocumentationPath[] = [ { - title: <Translate id="feat.types.title">Many Backup Types</Translate>, - description: ( - <Translate id="feat.types.desc"> - 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', + icon: 'box', + title: <Translate id="feat.install.title">Install and upgrade</Translate>, + description: <Translate id="feat.install.desc">Choose Docker, Compose, or a standalone binary and keep the deployment repeatable.</Translate>, + to: '/docs/getting-started/installation', }, { - 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 and more via rclone. - </Translate> - ), - icon: <CloudIcon />, - link: '/docs/features/storage-backends', + icon: 'database', + title: <Translate id="feat.types.title">Protect files and databases</Translate>, + description: <Translate id="feat.types.desc">Configure file, MySQL, PostgreSQL, SQLite, and SAP HANA backup workloads.</Translate>, + to: '/docs/features/backup-types', }, { - 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 />, + icon: 'storage', + title: <Translate id="feat.storage.title">Connect storage targets</Translate>, + description: <Translate id="feat.storage.desc">Use native providers or any supported rclone backend through one consistent flow.</Translate>, + to: '/docs/features/storage-backends', }, { - title: <Translate id="feat.cluster.title">Multi-Node Cluster</Translate>, - description: ( - <Translate id="feat.cluster.desc"> - Outbound-only Master-Agent polling with proxy, private-CA, and SSH-bastion support. No reverse connectivity is required. - </Translate> - ), - icon: <NetworkIcon />, - link: '/docs/features/multi-node', + icon: 'network', + title: <Translate id="feat.cluster.title">Build a remote-node cluster</Translate>, + description: <Translate id="feat.cluster.desc">Deploy outbound-only agents through proxies, private CAs, or SSH bastion hosts.</Translate>, + to: '/docs/features/multi-node', }, { - title: <Translate id="feat.security.title">Secure by Default</Translate>, - description: ( - <Translate id="feat.security.desc"> - JWT auth, bcrypt passwords, AES-256-GCM encrypted config, optional backup encryption, and a full audit log. - </Translate> - ), - icon: <ShieldIcon />, + icon: 'monitor', + title: <Translate id="feat.monitor.title">Monitor daily operations</Translate>, + description: <Translate id="feat.monitor.desc">Track task health, storage capacity, notifications, logs, and service readiness.</Translate>, + to: '/docs/operations/monitoring', }, { - 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 — zero external dependencies. - </Translate> - ), - icon: <RocketIcon />, - link: '/docs/getting-started/installation', + icon: 'restore', + title: <Translate id="feat.recovery.title">Recover with a tested plan</Translate>, + description: <Translate id="feat.recovery.desc">Prepare upgrades, rollback points, restore validation, and incident troubleshooting.</Translate>, + to: '/docs/operations/upgrade-recovery', }, ]; -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.section}> <div className="container"> <div className={styles.sectionHead}> - <div className={styles.sectionTag}> - <Translate id="section.features.tag">FEATURES</Translate> + <div> + <div className={styles.sectionTag}> + <Translate id="section.features.tag">Documentation paths</Translate> + </div> + <Heading as="h2" className={styles.sectionTitle}> + <Translate id="section.features.title">Move from deployment to recovery without guesswork</Translate> + </Heading> </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. + Each path connects product capability to the configuration, security, and operational decisions required in production. </Translate> </p> </div> - <div className={styles.grid}> - {FEATURES.map((feat, idx) => ( - <Feature key={idx} {...feat} /> + + <div className={styles.pathGrid}> + {DOCUMENTATION_PATHS.map((path, index) => ( + <Link key={path.to} to={path.to} className={styles.pathItem}> + <span className={styles.pathIndex}>{String(index + 1).padStart(2, '0')}</span> + <span className={styles.pathIcon}><DocIcon name={path.icon} size={21} /></span> + <span className={styles.pathBody}> + <span className={styles.pathTitle}>{path.title}</span> + <span className={styles.pathDescription}>{path.description}</span> + </span> + <DocIcon name="arrowRight" size={18} className={styles.pathArrow} /> + </Link> ))} </div> </div> diff --git a/docs-site/src/components/HomepageFeatures/styles.module.css b/docs-site/src/components/HomepageFeatures/styles.module.css index 9659f03..931c224 100644 --- a/docs-site/src/components/HomepageFeatures/styles.module.css +++ b/docs-site/src/components/HomepageFeatures/styles.module.css @@ -1,169 +1,171 @@ .section { - padding: 5.5rem 0 4.25rem; background: var(--ifm-background-color); + padding: 5rem 0; } .sectionHead { - text-align: center; - max-width: 720px; - margin: 0 auto 3rem; + align-items: end; + display: grid; + gap: 3rem; + grid-template-columns: minmax(0, 1fr) minmax(320px, 0.7fr); + margin-bottom: 2.25rem; } .sectionTag { - display: inline-flex; - align-items: center; - min-height: 28px; - font-size: 12px; - font-weight: 750; - letter-spacing: 0; color: var(--ifm-color-primary); - padding: 4px 12px; - background: rgba(22, 93, 255, 0.08); - border: 1px solid rgba(22, 93, 255, 0.16); - border-radius: 8px; - margin-bottom: 1rem; -} - -[data-theme='dark'] .sectionTag { - background: rgba(96, 126, 255, 0.18); - color: var(--ifm-color-primary-lighter); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.06em; + margin-bottom: 0.75rem; + text-transform: uppercase; } .sectionTitle { - font-size: 2.35rem; - line-height: 1.2; - letter-spacing: 0; - font-weight: 750; - margin: 0 0 1rem; color: var(--ifm-heading-color); + font-size: clamp(1.9rem, 4vw, 2.55rem); + font-weight: 500; + letter-spacing: -0.025em; + line-height: 1.18; + margin: 0; + max-width: 710px; } .sectionSubtitle { - font-size: 1.05rem; - line-height: 1.65; color: var(--ifm-color-content-secondary); + font-size: 1rem; + line-height: 1.7; margin: 0; } -.grid { +.pathGrid { + border: 1px solid var(--bx-border); + border-radius: 4px; display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 1.25rem; + grid-template-columns: repeat(2, minmax(0, 1fr)); + overflow: hidden; } -@media (max-width: 996px) { - .section { - padding: 3.5rem 0 2rem; - } - .sectionTitle { - font-size: 2rem; - } - .grid { - grid-template-columns: 1fr; - } +.pathItem { + align-items: start; + border-bottom: 1px solid var(--bx-border); + color: inherit; + display: grid; + gap: 12px; + grid-template-columns: auto auto minmax(0, 1fr) auto; + min-height: 154px; + padding: 1.4rem; + text-decoration: none !important; } -@media (min-width: 997px) and (max-width: 1200px) { - .grid { - grid-template-columns: repeat(2, 1fr); - } +.pathItem:nth-child(odd) { + border-right: 1px solid var(--bx-border); } -.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: 8px; - transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease; - text-decoration: none !important; - color: inherit; - height: 100%; +.pathItem:nth-last-child(-n + 2) { + border-bottom: 0; } -.featureCardLink:hover { - transform: translateY(-2px); - border-color: var(--ifm-color-primary); - box-shadow: 0 12px 30px -8px rgba(22, 93, 255, 0.18); +.pathItem:hover, +.pathItem:focus-visible { + background: var(--bx-surface-subtle); 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); +.pathIndex { + color: var(--ifm-color-content-secondary); + font-family: var(--ifm-font-family-monospace); + font-size: 11px; + line-height: 38px; } -.iconWrap { - width: 48px; - height: 48px; - border-radius: 8px; - display: flex; +.pathIcon { align-items: center; - justify-content: center; - background: linear-gradient(135deg, rgba(22, 93, 255, 0.1) 0%, rgba(20, 201, 201, 0.12) 100%); + background: var(--bx-surface-selected); + border: 1px solid color-mix(in srgb, var(--ifm-color-primary) 25%, var(--bx-border)); + border-radius: 3px; color: var(--ifm-color-primary); - margin-bottom: 1.25rem; + display: inline-flex; + height: 38px; + justify-content: center; + width: 38px; } -[data-theme='dark'] .iconWrap { - background: linear-gradient(135deg, rgba(96, 126, 255, 0.15) 0%, rgba(20, 201, 201, 0.12) 100%); - color: var(--ifm-color-primary-lighter); +.pathBody { + display: grid; + gap: 7px; + min-width: 0; + padding-top: 7px; } -.featureTitle { - font-size: 1.15rem; - font-weight: 700; - margin: 0 0 0.6rem; +.pathTitle { color: var(--ifm-heading-color); - letter-spacing: 0; + font-size: 1rem; + font-weight: 500; + line-height: 1.35; } -.featureDesc { - font-size: 0.95rem; +.pathDescription { + color: var(--ifm-color-content-secondary); + font-size: 0.9rem; line-height: 1.65; +} + +.pathArrow { color: var(--ifm-color-content-secondary); - margin: 0; - flex: 1; + margin-top: 10px; } -.featureLink { - display: inline-flex; - align-items: center; - gap: 4px; - margin-top: 1rem; - font-size: 13px; - font-weight: 500; +.pathItem:hover .pathArrow, +.pathItem:focus-visible .pathArrow { color: var(--ifm-color-primary); } -.featureArrow { - transition: transform 0.2s ease; -} +@media (max-width: 996px) { + .section { + padding: 4rem 0; + } -.featureCardLink:hover .featureArrow { - transform: translateX(4px); + .sectionHead { + align-items: start; + gap: 1rem; + grid-template-columns: 1fr; + } } -@media (max-width: 640px) { - .sectionTitle { - font-size: 1.75rem; +@media (max-width: 760px) { + .pathGrid { + grid-template-columns: 1fr; + } + + .pathItem, + .pathItem:nth-child(odd), + .pathItem:nth-last-child(-n + 2) { + border-bottom: 1px solid var(--bx-border); + border-right: 0; + } + + .pathItem:last-child { + border-bottom: 0; } } -@media (prefers-reduced-motion: reduce) { - .featureCard, - .featureCardLink, - .featureArrow { - transition: none; +@media (max-width: 520px) { + .section { + padding: 3.25rem 0; + } + + .pathItem { + gap: 10px; + grid-template-columns: auto minmax(0, 1fr) auto; + min-height: 0; + padding: 1rem; + } + + .pathIndex { + display: none; + } + + .pathBody { + padding-top: 6px; } } diff --git a/docs-site/src/components/HomepageHero/index.tsx b/docs-site/src/components/HomepageHero/index.tsx new file mode 100644 index 0000000..54800c6 --- /dev/null +++ b/docs-site/src/components/HomepageHero/index.tsx @@ -0,0 +1,103 @@ +import type {ReactNode} from 'react'; +import Link from '@docusaurus/Link'; +import Translate, {translate} from '@docusaurus/Translate'; +import Heading from '@theme/Heading'; +import DocIcon, {type DocIconName} from '@site/src/components/DocIcon'; +import styles from './styles.module.css'; + +type GuideLink = { + icon: DocIconName; + title: ReactNode; + description: ReactNode; + to: string; +}; + +const GUIDE_LINKS: GuideLink[] = [ + { + icon: 'box', + title: <Translate id="home.guide.install.title">Install BackupX</Translate>, + description: <Translate id="home.guide.install.desc">Docker, Compose, or a standalone binary</Translate>, + to: '/docs/getting-started/installation', + }, + { + icon: 'network', + title: <Translate id="home.guide.cluster.title">Connect remote nodes</Translate>, + description: <Translate id="home.guide.cluster.desc">Agents, proxies, private CAs, and bastion hosts</Translate>, + to: '/docs/features/multi-node', + }, + { + icon: 'shield', + title: <Translate id="home.guide.security.title">Harden operations</Translate>, + description: <Translate id="home.guide.security.desc">Security controls, monitoring, and audit trails</Translate>, + to: '/docs/operations/security', + }, + { + icon: 'restore', + title: <Translate id="home.guide.recovery.title">Prepare recovery</Translate>, + description: <Translate id="home.guide.recovery.desc">Upgrade, rollback, restore, and troubleshoot</Translate>, + to: '/docs/operations/upgrade-recovery', + }, +]; + +export default function HomepageHero(): ReactNode { + return ( + <header className={styles.hero}> + <div className={`container ${styles.heroGrid}`}> + <div className={styles.heroContent}> + <div className={styles.badge}> + <DocIcon name="bookOpen" size={16} /> + <Translate id="home.badge">BackupX documentation · v2.2.1</Translate> + </div> + + <Heading as="h1" className={styles.heroTitle}> + <span className={styles.heroTitleLine}><Translate id="home.title.part1">Operate BackupX</Translate></span> + <span className={styles.heroTitleAccent}><Translate id="home.title.part2">with confidence.</Translate></span> + </Heading> + + <p className={styles.heroSubtitle}> + <Translate id="home.tagline"> + Deploy the control plane, connect storage and remote agents, then keep backups observable and recoverable with one practical guide set. + </Translate> + </p> + + <div className={styles.actions}> + <Link className={styles.primaryAction} to="/docs/getting-started/quick-start"> + <DocIcon name="terminal" size={18} /> + <Translate id="home.getStarted">Start with Docker</Translate> + <DocIcon name="arrowRight" size={17} /> + </Link> + <Link className={styles.secondaryAction} to="https://github.com/Awuqing/BackupX"> + <DocIcon name="github" size={18} /> + <Translate id="home.viewSource">View source</Translate> + </Link> + </div> + + <div className={styles.supported} aria-label={translate({id: 'home.supported.label', message: 'Supported environments'})}> + <span><DocIcon name="check" size={16} /><Translate id="home.supported.docker">Docker</Translate></span> + <span><DocIcon name="check" size={16} /><Translate id="home.supported.linux">Linux</Translate></span> + <span><DocIcon name="check" size={16} /><Translate id="home.supported.windows">Windows agents</Translate></span> + </div> + </div> + + <nav className={styles.guidePanel} aria-label={translate({id: 'home.guide.label', message: 'Recommended documentation paths'})}> + <div className={styles.guidePanelHeader}> + <span><Translate id="home.guide.kicker">Start here</Translate></span> + <span><Translate id="home.guide.hint">Choose a guide for the next task</Translate></span> + </div> + <div className={styles.guideList}> + {GUIDE_LINKS.map(guide => ( + <Link key={guide.to} className={styles.guideLink} to={guide.to}> + <span className={styles.guideIcon}><DocIcon name={guide.icon} size={20} /></span> + <span className={styles.guideBody}> + <span className={styles.guideTitle}>{guide.title}</span> + <span className={styles.guideDescription}>{guide.description}</span> + </span> + <DocIcon name="arrowRight" size={17} className={styles.guideArrow} /> + </Link> + ))} + </div> + </nav> + </div> + </header> + ); +} diff --git a/docs-site/src/components/HomepageHero/styles.module.css b/docs-site/src/components/HomepageHero/styles.module.css new file mode 100644 index 0000000..6235b4a --- /dev/null +++ b/docs-site/src/components/HomepageHero/styles.module.css @@ -0,0 +1,273 @@ +.hero { + background: var(--bx-surface-subtle); + border-bottom: 1px solid var(--bx-border); + padding: 5.5rem 0 4.75rem; +} + +.heroGrid { + align-items: center; + display: grid; + gap: 4.5rem; + grid-template-columns: minmax(0, 1fr) minmax(410px, 0.88fr); +} + +.heroContent { + align-items: flex-start; + display: flex; + flex-direction: column; +} + +.badge { + align-items: center; + background: var(--bx-surface-selected); + border: 1px solid color-mix(in srgb, var(--ifm-color-primary) 28%, var(--bx-border)); + border-radius: 3px; + color: var(--ifm-color-primary); + display: inline-flex; + font-size: 13px; + font-weight: 400; + gap: 7px; + margin-bottom: 1.5rem; + min-height: 30px; + padding: 4px 9px; +} + +.heroTitle { + color: var(--ifm-heading-color); + font-size: clamp(2.55rem, 5vw, 3.7rem); + font-weight: 500; + letter-spacing: -0.035em; + line-height: 1.06; + margin: 0; + max-width: 720px; +} + +.heroTitleLine, +.heroTitleAccent { + display: block; +} + +.heroTitleLine { + color: var(--ifm-heading-color); +} + +.heroTitleAccent { + color: var(--ifm-color-primary); +} + +.heroSubtitle { + color: var(--ifm-color-content-secondary); + font-size: 1.08rem; + line-height: 1.72; + margin: 1.5rem 0 0; + max-width: 650px; +} + +.actions { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 1.75rem; +} + +.primaryAction, +.secondaryAction { + align-items: center; + border: 1px solid var(--ifm-color-primary); + border-radius: 4px; + display: inline-flex; + font-size: 14px; + font-weight: 400; + gap: 8px; + justify-content: center; + min-height: 44px; + padding: 0 15px; + text-decoration: none !important; +} + +.primaryAction { + background: var(--ifm-color-primary); + color: #ffffff; +} + +.primaryAction:hover, +.primaryAction:focus-visible { + background: var(--ifm-color-primary-dark); + border-color: var(--ifm-color-primary-dark); + color: #ffffff; +} + +.secondaryAction { + background: var(--ifm-background-color); + border-color: var(--bx-border-strong); + color: var(--ifm-color-content); +} + +.secondaryAction:hover, +.secondaryAction:focus-visible { + background: var(--bx-surface-selected); + border-color: var(--ifm-color-primary); + color: var(--ifm-color-primary); +} + +.supported { + color: var(--ifm-color-content-secondary); + display: flex; + flex-wrap: wrap; + font-size: 13px; + gap: 10px 18px; + margin-top: 1.4rem; +} + +.supported span { + align-items: center; + display: inline-flex; + gap: 5px; +} + +.supported svg { + color: var(--bx-success); +} + +.guidePanel { + background: var(--ifm-background-color); + border: 1px solid var(--bx-border-strong); + border-radius: 4px; + overflow: hidden; +} + +.guidePanelHeader { + align-items: baseline; + border-bottom: 1px solid var(--bx-border); + display: flex; + gap: 12px; + justify-content: space-between; + padding: 14px 16px; +} + +.guidePanelHeader span:first-child { + color: var(--ifm-heading-color); + font-size: 14px; + font-weight: 500; +} + +.guidePanelHeader span:last-child { + color: var(--ifm-color-content-secondary); + font-size: 12px; + text-align: right; +} + +.guideList { + display: grid; +} + +.guideLink { + align-items: center; + border-bottom: 1px solid var(--bx-border); + color: inherit; + display: grid; + gap: 12px; + grid-template-columns: auto minmax(0, 1fr) auto; + min-height: 82px; + padding: 13px 16px; + text-decoration: none !important; +} + +.guideLink:last-child { + border-bottom: 0; +} + +.guideLink:hover, +.guideLink:focus-visible { + background: var(--bx-surface-selected); + color: inherit; +} + +.guideIcon { + align-items: center; + background: var(--bx-surface-subtle); + border: 1px solid var(--bx-border); + border-radius: 3px; + color: var(--ifm-color-primary); + display: inline-flex; + height: 38px; + justify-content: center; + width: 38px; +} + +.guideBody { + display: grid; + gap: 2px; + min-width: 0; +} + +.guideTitle { + color: var(--ifm-heading-color); + font-size: 14px; + font-weight: 500; +} + +.guideDescription { + color: var(--ifm-color-content-secondary); + font-size: 12px; + line-height: 1.5; +} + +.guideArrow { + color: var(--ifm-color-content-secondary); +} + +.guideLink:hover .guideArrow, +.guideLink:focus-visible .guideArrow { + color: var(--ifm-color-primary); +} + +@media (max-width: 996px) { + .hero { + padding: 4.25rem 0 3.75rem; + } + + .heroGrid { + gap: 2.75rem; + grid-template-columns: 1fr; + } + + .heroContent { + max-width: 760px; + } +} + +@media (max-width: 640px) { + .hero { + padding: 3.25rem 0 2.75rem; + } + + .heroTitle { + font-size: 2.35rem; + } + + .heroSubtitle { + font-size: 1rem; + } + + .actions, + .primaryAction, + .secondaryAction { + width: 100%; + } + + .guidePanelHeader { + align-items: flex-start; + flex-direction: column; + gap: 2px; + } + + .guidePanelHeader span:last-child { + text-align: left; + } + + .guideLink { + min-height: 78px; + padding: 12px; + } +} diff --git a/docs-site/src/components/HomepageShowcase/index.tsx b/docs-site/src/components/HomepageShowcase/index.tsx index 44378ab..04e6149 100644 --- a/docs-site/src/components/HomepageShowcase/index.tsx +++ b/docs-site/src/components/HomepageShowcase/index.tsx @@ -2,15 +2,18 @@ import type {ReactNode} from 'react'; import {useState} from 'react'; import clsx from 'clsx'; import Heading from '@theme/Heading'; -import Translate from '@docusaurus/Translate'; +import Translate, {translate} from '@docusaurus/Translate'; import useBaseUrl from '@docusaurus/useBaseUrl'; import Link from '@docusaurus/Link'; +import DocIcon, {type DocIconName} from '@site/src/components/DocIcon'; import styles from './styles.module.css'; type Tab = { id: string; label: ReactNode; + icon: DocIconName; image: string; + imageAlt: string; title: ReactNode; description: ReactNode; }; @@ -20,7 +23,9 @@ function useTabs(): Tab[] { { id: 'dashboard', label: <Translate id="showcase.tab.dashboard">Dashboard</Translate>, + icon: 'monitor', image: useBaseUrl('/img/screenshots/dashboard.png'), + imageAlt: translate({id: 'showcase.dashboard.alt', message: 'BackupX dashboard showing backup health and storage usage'}), title: <Translate id="showcase.dashboard.title">Know at a glance</Translate>, description: ( <Translate id="showcase.dashboard.desc"> @@ -31,7 +36,9 @@ function useTabs(): Tab[] { { id: 'tasks', label: <Translate id="showcase.tab.tasks">Backup Tasks</Translate>, + icon: 'database', image: useBaseUrl('/img/screenshots/backup-tasks.png'), + imageAlt: translate({id: 'showcase.tasks.alt', message: 'BackupX backup task management screen'}), title: <Translate id="showcase.tasks.title">Visual task editor</Translate>, description: ( <Translate id="showcase.tasks.desc"> @@ -42,7 +49,9 @@ function useTabs(): Tab[] { { id: 'storage', label: <Translate id="showcase.tab.storage">Storage Targets</Translate>, + icon: 'storage', image: useBaseUrl('/img/screenshots/storage-targets.png'), + imageAlt: translate({id: 'showcase.storage.alt', message: 'BackupX storage target management screen'}), title: <Translate id="showcase.storage.title">70+ backends, one flow</Translate>, description: ( <Translate id="showcase.storage.desc"> @@ -53,7 +62,9 @@ function useTabs(): Tab[] { { id: 'nodes', label: <Translate id="showcase.tab.nodes">Multi-Node</Translate>, + icon: 'network', image: useBaseUrl('/img/screenshots/nodes.png'), + imageAlt: translate({id: 'showcase.nodes.alt', message: 'BackupX remote node management screen'}), title: <Translate id="showcase.nodes.title">Master-Agent in minutes</Translate>, description: ( <Translate id="showcase.nodes.desc"> @@ -72,45 +83,55 @@ export default function HomepageShowcase(): ReactNode { <section className={styles.section}> <div className="container"> <div className={styles.sectionHead}> - <div className={styles.sectionTag}> - <Translate id="showcase.tag">PRODUCT</Translate> + <div> + <div className={styles.sectionTag}> + <Translate id="showcase.tag">Product interface</Translate> + </div> + <Heading as="h2" className={styles.sectionTitle}> + <Translate id="showcase.title">See the workflow before you deploy</Translate> + </Heading> </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. + Screenshots stay connected to the guide that explains the underlying task, configuration, and operating model. </Translate> </p> </div> - <div className={styles.tabs}> + <div className={styles.tabs} role="tablist" aria-label={translate({id: 'showcase.tabs.label', message: 'BackupX product screens'})}> {tabs.map(tab => ( <button key={tab.id} + id={`showcase-tab-${tab.id}`} type="button" + role="tab" + aria-selected={active === tab.id} + aria-controls="showcase-panel" className={clsx(styles.tabBtn, active === tab.id && styles.tabBtnActive)} onClick={() => setActive(tab.id)}> + <DocIcon name={tab.icon} size={17} /> {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 + id="showcase-panel" + role="tabpanel" + aria-labelledby={`showcase-tab-${current.id}`} + className={styles.stage}> + <div className={styles.preview}> + <div className={styles.previewBar}> + <span><DocIcon name="monitor" size={16} /><Translate id="showcase.preview.label">BackupX console</Translate></span> + <code>backupx.local</code> </div> - <img src={current.image} alt="" className={styles.screenshot} /> + <img src={current.image} alt={current.imageAlt} className={styles.screenshot} /> </div> <div className={styles.caption}> + <div className={styles.captionKicker}>{current.label}</div> <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> + <DocIcon name="arrowRight" size={17} /> </Link> </div> </div> diff --git a/docs-site/src/components/HomepageShowcase/styles.module.css b/docs-site/src/components/HomepageShowcase/styles.module.css index 22de943..33223a0 100644 --- a/docs-site/src/components/HomepageShowcase/styles.module.css +++ b/docs-site/src/components/HomepageShowcase/styles.module.css @@ -1,247 +1,224 @@ .section { - padding: 4.5rem 0 5.5rem; - background: - linear-gradient(180deg, rgba(245, 247, 250, 0) 0%, rgba(245, 247, 250, 0.72) 100%), - var(--ifm-background-color); -} - -[data-theme='dark'] .section { - background: - linear-gradient(180deg, rgba(15, 17, 21, 0) 0%, rgba(255, 255, 255, 0.03) 100%), - var(--ifm-background-color); + background: var(--bx-surface-subtle); + border-bottom: 1px solid var(--bx-border); + border-top: 1px solid var(--bx-border); + padding: 5rem 0; } .sectionHead { - text-align: center; - max-width: 720px; - margin: 0 auto 2.5rem; + align-items: end; + display: grid; + gap: 3rem; + grid-template-columns: minmax(0, 1fr) minmax(320px, 0.7fr); + margin-bottom: 2.25rem; } .sectionTag { - display: inline-flex; - align-items: center; - min-height: 28px; + color: var(--ifm-color-primary); font-size: 12px; - font-weight: 750; - letter-spacing: 0; - color: #0e7490; - padding: 4px 12px; - background: rgba(20, 201, 201, 0.1); - border: 1px solid rgba(20, 201, 201, 0.2); - border-radius: 8px; - margin-bottom: 1rem; -} - -[data-theme='dark'] .sectionTag { - background: rgba(20, 201, 201, 0.16); - color: #67e8f9; + font-weight: 500; + letter-spacing: 0.06em; + margin-bottom: 0.75rem; + text-transform: uppercase; } .sectionTitle { - font-size: 2.35rem; - line-height: 1.2; - letter-spacing: 0; - font-weight: 750; - margin: 0 0 1rem; color: var(--ifm-heading-color); + font-size: clamp(1.9rem, 4vw, 2.55rem); + font-weight: 500; + letter-spacing: -0.025em; + line-height: 1.18; + margin: 0; + max-width: 700px; } .sectionSubtitle { - font-size: 1.05rem; - line-height: 1.65; color: var(--ifm-color-content-secondary); + font-size: 1rem; + line-height: 1.7; margin: 0; } -/* Tab bar */ .tabs { + border: 1px solid var(--bx-border); + border-bottom: 0; + border-radius: 4px 4px 0 0; display: flex; - justify-content: center; - gap: 6px; - margin-bottom: 2rem; - flex-wrap: wrap; + gap: 4px; + overflow-x: auto; padding: 6px; - background: var(--ifm-color-emphasis-100); - border: 1px solid var(--ifm-color-emphasis-200); - border-radius: 8px; } .tabBtn { - min-height: 40px; - padding: 8px 18px; + align-items: center; background: transparent; border: 1px solid transparent; - border-radius: 8px; + border-radius: 3px; color: var(--ifm-color-content-secondary); - font-size: 14px; - font-weight: 650; cursor: pointer; - transition: color 0.2s ease, border-color 0.2s ease, background 0.2s ease, box-shadow 0.2s ease; + display: inline-flex; + flex: 1 0 auto; + font-size: 14px; + font-weight: 400; + gap: 7px; + justify-content: center; + min-height: 40px; + padding: 7px 14px; } .tabBtn:hover { - color: var(--ifm-color-primary); background: var(--ifm-background-color); + border-color: var(--bx-border); + color: var(--ifm-color-primary); } .tabBtnActive, .tabBtnActive:hover { - background: var(--ifm-background-color); - color: var(--ifm-color-primary) !important; - border-color: rgba(22, 93, 255, 0.18); - box-shadow: 0 6px 16px rgba(22, 93, 255, 0.12); + background: var(--bx-surface-selected); + border-color: color-mix(in srgb, var(--ifm-color-primary) 30%, var(--bx-border)); + color: var(--ifm-color-primary); } -/* 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: 8px; + border: 1px solid var(--bx-border); + border-radius: 0 0 4px 4px; + display: grid; + grid-template-columns: minmax(0, 1.6fr) minmax(300px, 0.74fr); overflow: hidden; - box-shadow: - 0 24px 58px -22px rgba(22, 93, 255, 0.28), - 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); +.preview { + border-right: 1px solid var(--bx-border); + min-width: 0; } -.browserBar { - display: flex; +.previewBar { 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); + background: var(--bx-surface-subtle); + border-bottom: 1px solid var(--bx-border); + color: var(--ifm-color-content-secondary); + display: flex; + font-size: 12px; + justify-content: space-between; + min-height: 42px; + padding: 8px 12px; } -.browserDot { - width: 11px; - height: 11px; - border-radius: 50%; +.previewBar span { + align-items: center; + display: inline-flex; + gap: 7px; } -.browserDotRed { background: #ff5f56; } -.browserDotYellow { background: #ffbd2e; } -.browserDotGreen { background: #27c93f; } - -.browserUrl { - margin: 0 auto; - padding: 3px 14px; +.previewBar code { background: var(--ifm-background-color); - border-radius: 8px; - font-size: 12px; + border: 1px solid var(--bx-border); + border-radius: 3px; 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); + font-size: 11px; + padding: 2px 7px; } .screenshot { + background: var(--ifm-background-color); display: block; - width: 100%; height: auto; - background: var(--ifm-color-emphasis-100); + width: 100%; } .caption { - padding: 0 1rem; + align-content: center; + display: grid; + padding: 2rem; } -@media (max-width: 996px) { - .caption { - padding: 0; - } +.captionKicker { + color: var(--ifm-color-primary); + font-size: 12px; + font-weight: 500; + letter-spacing: 0.05em; + margin-bottom: 0.75rem; + text-transform: uppercase; } .captionTitle { - font-size: 1.7rem; - line-height: 1.2; - letter-spacing: 0; - font-weight: 750; - margin: 0 0 1rem; color: var(--ifm-heading-color); + font-size: 1.65rem; + font-weight: 500; + letter-spacing: -0.02em; + line-height: 1.25; + margin: 0 0 0.9rem; } .captionDesc { - font-size: 1.05rem; - line-height: 1.7; color: var(--ifm-color-content-secondary); + font-size: 0.96rem; + line-height: 1.7; margin: 0 0 1.25rem; } .captionLink { - display: inline-flex; align-items: center; - gap: 4px; - min-height: 40px; - padding: 0 12px; - border: 1px solid rgba(22, 93, 255, 0.18); - border-radius: 8px; - font-weight: 650; color: var(--ifm-color-primary); + display: inline-flex; + font-size: 14px; + font-weight: 500; + gap: 6px; + justify-self: start; text-decoration: none !important; - transition: border-color 0.2s ease, background 0.2s ease; } -.captionLink:hover { +.captionLink:hover, +.captionLink:focus-visible { color: var(--ifm-color-primary-dark); - background: rgba(22, 93, 255, 0.06); - border-color: var(--ifm-color-primary); } @media (max-width: 996px) { - .sectionTitle { - font-size: 2rem; + .section { + padding: 4rem 0; + } + + .sectionHead { + align-items: start; + gap: 1rem; + grid-template-columns: 1fr; + } + + .stage { + grid-template-columns: 1fr; + } + + .preview { + border-bottom: 1px solid var(--bx-border); + border-right: 0; + } + + .caption { + padding: 1.5rem; } } @media (max-width: 640px) { .section { - padding: 3.25rem 0 4rem; - } - - .sectionTitle { - font-size: 1.75rem; + padding: 3.25rem 0; } .tabs { - justify-content: stretch; + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); } .tabBtn { - flex: 1 1 130px; + min-width: 0; + padding: 7px 8px; } -} -@media (prefers-reduced-motion: reduce) { - .tabBtn, - .captionLink { - transition: none; + .previewBar code { + display: none; + } + + .caption { + padding: 1.25rem; } } diff --git a/docs-site/src/css/custom.css b/docs-site/src/css/custom.css index 1721433..c0b5625 100644 --- a/docs-site/src/css/custom.css +++ b/docs-site/src/css/custom.css @@ -1,255 +1,492 @@ /** - * BackupX 官方文档站样式 - * 灵感:Ant Design / Arco Design + * BackupX documentation UI + * Flat, operational and consistent with the product interface. */ :root { - /* Primary palette (Arco blue) */ - --ifm-color-primary: #165dff; - --ifm-color-primary-dark: #0e4fe6; - --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; - - /* Surfaces */ + --ifm-color-primary: #175cd3; + --ifm-color-primary-dark: #1452bd; + --ifm-color-primary-darker: #124bad; + --ifm-color-primary-darkest: #0e3c8a; + --ifm-color-primary-light: #2a6bd7; + --ifm-color-primary-lighter: #3978dc; + --ifm-color-primary-lightest: #6d9be5; + --ifm-background-color: #ffffff; --ifm-background-surface-color: #ffffff; - --ifm-color-emphasis-100: #f5f7fa; - --ifm-color-emphasis-200: #e5e6eb; - --ifm-color-emphasis-300: #c9cdd4; - --ifm-color-emphasis-400: #a9aeb8; - - /* 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: 700; + --ifm-color-emphasis-100: #f6f7f9; + --ifm-color-emphasis-200: #e4e7ec; + --ifm-color-emphasis-300: #d0d5dd; + --ifm-color-emphasis-400: #98a2b3; + --ifm-color-emphasis-600: #475467; + --ifm-color-content: #202939; + --ifm-color-content-secondary: #596579; + --ifm-heading-color: #111827; + + --ifm-font-family-base: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif; + --ifm-font-family-monospace: ui-monospace, 'SFMono-Regular', Menlo, Monaco, Consolas, monospace; + --ifm-font-weight-bold: 500; + --ifm-heading-font-weight: 500; + --ifm-h1-font-size: 2.15rem; + --ifm-h2-font-size: 1.6rem; + --ifm-h3-font-size: 1.24rem; + --ifm-line-height-base: 1.72; --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; - --ifm-global-radius: 8px; - - /* Navbar */ - --ifm-navbar-height: 64px; - --ifm-navbar-background-color: rgba(255, 255, 255, 0.9); - --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; + --ifm-global-radius: 4px; + --ifm-global-shadow-lw: none; + --ifm-global-shadow-md: none; + --ifm-global-shadow-tl: none; + --ifm-container-width: 1160px; + --ifm-container-width-xl: 1280px; + + --ifm-navbar-height: 58px; + --ifm-navbar-background-color: #ffffff; + --ifm-navbar-link-color: #475467; + --ifm-navbar-link-hover-color: #175cd3; + --ifm-navbar-shadow: none; + + --ifm-menu-color: #475467; + --ifm-menu-color-background-active: #eef4ff; + --ifm-menu-color-background-hover: #f6f7f9; + --ifm-toc-border-color: transparent; + + --ifm-code-background: #f2f4f7; + --docusaurus-highlighted-code-line-bg: #eaf1fb; + + --bx-border: #e4e7ec; + --bx-border-strong: #d0d5dd; + --bx-surface-subtle: #f8f9fb; + --bx-surface-selected: #eef4ff; + --bx-success: #168453; + --bx-warning: #b54708; } [data-theme='dark'] { - --ifm-color-primary: #4080ff; - --ifm-color-primary-dark: #3371f2; - --ifm-color-primary-darker: #2c6ae6; - --ifm-color-primary-darkest: #2359c7; - --ifm-color-primary-light: #5a93ff; - --ifm-color-primary-lighter: #74a5ff; - --ifm-color-primary-lightest: #9dbfff; - - --ifm-background-color: #0f1115; - --ifm-background-surface-color: #16181d; - --ifm-color-emphasis-100: #1d2129; - --ifm-color-emphasis-200: #272e3b; - --ifm-color-emphasis-300: #384252; - --ifm-color-emphasis-400: #4e5969; + --ifm-color-primary: #84adff; + --ifm-color-primary-dark: #6f9df5; + --ifm-color-primary-darker: #5f8fe9; + --ifm-color-primary-darkest: #4776c9; + --ifm-color-primary-light: #98bbff; + --ifm-color-primary-lighter: #aac7ff; + --ifm-color-primary-lightest: #c7d8ff; + + --ifm-background-color: #0f141c; + --ifm-background-surface-color: #151b24; + --ifm-color-emphasis-100: #1a222d; + --ifm-color-emphasis-200: #273240; + --ifm-color-emphasis-300: #344054; + --ifm-color-emphasis-400: #667085; + --ifm-color-emphasis-600: #98a2b3; + --ifm-color-content: #d0d5dd; + --ifm-color-content-secondary: #98a2b3; + --ifm-heading-color: #f2f4f7; + + --ifm-navbar-background-color: #0f141c; + --ifm-navbar-link-color: #c7ced8; + --ifm-menu-color: #c7ced8; + --ifm-menu-color-background-active: #1c2c48; + --ifm-menu-color-background-hover: #1a222d; + + --ifm-code-background: #1c2531; + --docusaurus-highlighted-code-line-bg: #213352; + + --bx-border: #273240; + --bx-border-strong: #344054; + --bx-surface-subtle: #131a23; + --bx-surface-selected: #1c2c48; + --bx-success: #6ce9a6; + --bx-warning: #fec84b; +} + +html { + scroll-padding-top: calc(var(--ifm-navbar-height) + 20px); +} + +body { + text-rendering: optimizeLegibility; +} + +strong, +b { + font-weight: 500; +} - --ifm-color-content: #e6e9ef; - --ifm-color-content-secondary: #9aa3b2; - --ifm-heading-color: #f0f2f5; +a, +button, +.button { + transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease; +} - --ifm-navbar-background-color: rgba(15, 17, 21, 0.9); - --ifm-navbar-link-color: #c9d1db; +:focus-visible { + outline: 2px solid var(--ifm-color-primary); + outline-offset: 2px; +} - --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); +.button { + border-radius: 4px; + font-weight: 400; +} - --ifm-code-background: rgba(64, 128, 255, 0.14); - --docusaurus-highlighted-code-line-bg: rgba(64, 128, 255, 0.18); +.button--primary { + color: #ffffff; } -/* 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; + border-bottom: 1px solid var(--bx-border); +} + +.navbar__brand { + gap: 8px; + margin-right: 1.5rem; } -[data-theme='dark'] .navbar { - border-bottom-color: rgba(255, 255, 255, 0.06); +.navbar__logo { + height: 28px; } .navbar__title { - font-weight: 700; + font-size: 1rem; + font-weight: 500; letter-spacing: 0; } .navbar__link { - font-weight: 500; font-size: 14px; + font-weight: 400; } -.navbar__link, -.button, -a { - transition: color 0.2s ease, background 0.2s ease, border-color 0.2s ease, box-shadow 0.2s ease; +.navbar__item.dropdown .navbar__link::after { + border-width: 0 1px 1px 0; + height: 6px; + margin-left: 7px; + transform: rotate(45deg) translateY(-2px); + width: 6px; } -.button { - border-radius: 8px; - font-weight: 650; +.dropdown__menu { + background: var(--ifm-background-surface-color); + border: 1px solid var(--bx-border); + border-radius: 4px; + padding: 4px; } -:focus-visible { - outline: 2px solid var(--ifm-color-primary); - outline-offset: 2px; +.dropdown__link { + border-radius: 2px; + font-size: 14px; +} + +.navbar-sidebar { + background: var(--ifm-background-color); +} + +.navbar-sidebar__brand { + border-bottom: 1px solid var(--bx-border); +} + +.theme-doc-sidebar-container { + border-right: 1px solid var(--bx-border) !important; +} + +.theme-doc-sidebar-menu { + padding: 1rem 0.75rem 2rem; +} + +.menu__list-item-collapsible { + border-radius: 4px; } -/* Sidebar tweaks */ .menu__link { + border-radius: 3px; font-size: 14px; - border-radius: 8px; - padding: 6px 10px; - line-height: 1.4; + font-weight: 400; + line-height: 1.45; + padding: 7px 10px; } .menu__link--active, .menu__link--active:hover { - font-weight: 600; + color: var(--ifm-color-primary); + font-weight: 500; } -.theme-doc-sidebar-container { - border-right: 1px solid var(--ifm-color-emphasis-200) !important; +.menu__caret::before, +.menu__link--sublist-caret::after { + background-size: 1.15rem 1.15rem; } -[data-theme='dark'] .theme-doc-sidebar-container { - border-right-color: rgba(255, 255, 255, 0.06) !important; +.breadcrumbs { + align-items: center; + font-size: 13px; + margin-bottom: 1.5rem; } -/* Article: better heading rhythm */ -.markdown h2 { - margin-top: 2.5rem; - padding-top: 0.5rem; - border-top: 1px solid var(--ifm-color-emphasis-200); +.breadcrumbs__link { + background: transparent; + border-radius: 2px; + color: var(--ifm-color-content-secondary); + padding: 3px 5px; +} + +.breadcrumbs__item--active .breadcrumbs__link { + color: var(--ifm-color-content); +} + +.theme-doc-markdown { + max-width: 860px; +} + +.theme-doc-markdown header + h1, +.theme-doc-markdown > h1:first-child { + font-size: clamp(1.9rem, 4vw, 2.35rem); + line-height: 1.2; + margin-bottom: 1rem; +} + +.markdown > p:first-of-type { + color: var(--ifm-color-content-secondary); + font-size: 1.04rem; } -[data-theme='dark'] .markdown h2 { - border-top-color: rgba(255, 255, 255, 0.06); +.markdown h2 { + border-top: 1px solid var(--bx-border); + margin-top: 2.75rem; + padding-top: 1.1rem; } .markdown h3 { margin-top: 2rem; } -/* Tables */ +.markdown h2, +.markdown h3, +.markdown h4 { + font-weight: 500; + letter-spacing: 0; +} + +.markdown a:not(.hash-link) { + text-decoration-color: color-mix(in srgb, var(--ifm-color-primary) 45%, transparent); + text-underline-offset: 3px; +} + +.markdown ul, +.markdown ol { + padding-left: 1.35rem; +} + +.markdown li + li { + margin-top: 0.35rem; +} + +.markdown blockquote { + background: var(--bx-surface-subtle); + border: 1px solid var(--bx-border); + border-radius: 4px; + color: var(--ifm-color-content-secondary); + margin: 1.5rem 0; + padding: 0.9rem 1rem; +} + +.markdown blockquote > :last-child { + margin-bottom: 0; +} + .markdown table { - border-radius: 8px; - overflow: hidden; - box-shadow: 0 0 0 1px var(--ifm-color-emphasis-200); + border: 1px solid var(--bx-border); border-collapse: separate; + border-radius: 4px; border-spacing: 0; + display: table; + overflow: hidden; + width: 100%; } .markdown table thead tr { - background: var(--ifm-color-emphasis-100); + background: var(--bx-surface-subtle); } .markdown table th, .markdown table td { - border: none; - border-bottom: 1px solid var(--ifm-color-emphasis-200); - padding: 10px 14px; + border: 0; + border-bottom: 1px solid var(--bx-border); + border-right: 1px solid var(--bx-border); + padding: 9px 12px; + text-align: left; +} + +.markdown table th { + font-weight: 500; +} + +.markdown table th:last-child, +.markdown table td:last-child { + border-right: 0; } .markdown table tr:last-child td { - border-bottom: none; + border-bottom: 0; } -/* Inline code */ code { background: var(--ifm-code-background); - border: none; - padding: 2px 6px; - border-radius: 4px; + border: 1px solid var(--bx-border); + border-radius: 3px; font-size: 0.92em; + padding: 1px 5px; +} + +pre code { + border: 0; + padding: 0; +} + +.theme-code-block { + border: 1px solid var(--bx-border); + border-radius: 4px; + overflow: hidden; +} + +.theme-code-block pre { + border-radius: 0; } -/* Admonitions: softer */ .theme-admonition { - border-radius: 8px; - border-width: 1px; - border-left-width: 4px; + border: 1px solid var(--bx-border-strong) !important; + border-left-width: 1px !important; + border-radius: 4px; +} + +.theme-admonition-heading h5 { + font-weight: 500; +} + +details { + background: var(--bx-surface-subtle); + border: 1px solid var(--bx-border) !important; + border-radius: 4px; +} + +details > summary { + font-weight: 500; +} + +.table-of-contents { + border-left: 0; + border-top: 1px solid var(--bx-border); + font-size: 13px; + padding: 0.8rem 0 0; +} + +.table-of-contents__link { + color: var(--ifm-color-content-secondary); +} + +.table-of-contents__link--active { + color: var(--ifm-color-primary); + font-weight: 500; +} + +.pagination-nav { + gap: 12px; +} + +.pagination-nav__link { + border: 1px solid var(--bx-border); + border-radius: 4px; + padding: 0.9rem 1rem; +} + +.pagination-nav__link:hover { + background: var(--bx-surface-subtle); + border-color: var(--ifm-color-primary); +} + +.pagination-nav__sublabel { + color: var(--ifm-color-content-secondary); + font-size: 12px; + font-weight: 400; +} + +.pagination-nav__label { + font-size: 14px; + font-weight: 500; +} + +.theme-edit-this-page, +.theme-last-updated { + font-size: 13px; } -/* Footer */ .footer { - --ifm-footer-background-color: #141720; - --ifm-footer-color: #9aa3b2; - --ifm-footer-link-color: #c9d1db; + --ifm-footer-background-color: #101828; + --ifm-footer-color: #98a2b3; + --ifm-footer-link-color: #d0d5dd; --ifm-footer-link-hover-color: #ffffff; - --ifm-footer-title-color: #f0f2f5; - padding: 3.5rem 0 2.5rem; + --ifm-footer-title-color: #f2f4f7; + border-top: 1px solid #273240; + padding: 3rem 0 2rem; } .footer__title { font-size: 13px; - letter-spacing: 0.08em; - text-transform: uppercase; - font-weight: 600; + font-weight: 500; + letter-spacing: 0; } .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; + border-top: 1px solid #273240; + margin-top: 2rem; + padding-top: 1.5rem; } .footer__copyright { + color: #98a2b3; font-size: 13px; - color: #6b7280; } -/* Scrollbar */ ::-webkit-scrollbar { - width: 10px; height: 10px; + width: 10px; } ::-webkit-scrollbar-thumb { background: var(--ifm-color-emphasis-300); - border-radius: 10px; + border: 2px solid var(--ifm-background-color); + border-radius: 4px; } ::-webkit-scrollbar-thumb:hover { background: var(--ifm-color-emphasis-400); } -[data-theme='dark'] ::-webkit-scrollbar-thumb { - background: rgba(255, 255, 255, 0.15); +@media (max-width: 996px) { + :root { + --ifm-navbar-height: 54px; + } + + .theme-doc-markdown { + max-width: none; + } + + .markdown table { + display: block; + overflow-x: auto; + } +} + +@media (max-width: 576px) { + .pagination-nav { + display: grid; + grid-template-columns: 1fr; + } + + .footer { + padding-top: 2.25rem; + } } @media (prefers-reduced-motion: reduce) { diff --git a/docs-site/src/pages/index.module.css b/docs-site/src/pages/index.module.css deleted file mode 100644 index 80d6410..0000000 --- a/docs-site/src/pages/index.module.css +++ /dev/null @@ -1,470 +0,0 @@ -/* Hero */ -.hero { - position: relative; - overflow: hidden; - padding: 7rem 0 5.5rem; - background: - linear-gradient(180deg, rgba(22, 93, 255, 0.08) 0%, rgba(255, 255, 255, 0) 72%), - linear-gradient(90deg, rgba(20, 201, 201, 0.08) 0%, rgba(250, 173, 20, 0.08) 100%), - var(--ifm-background-color); -} - -.hero::before { - position: absolute; - inset: 0; - content: ""; - pointer-events: none; - background-image: - linear-gradient(rgba(22, 93, 255, 0.06) 1px, transparent 1px), - linear-gradient(90deg, rgba(22, 93, 255, 0.06) 1px, transparent 1px); - background-size: 44px 44px; - mask-image: linear-gradient(180deg, rgba(0, 0, 0, 0.75), transparent 82%); -} - -[data-theme='dark'] .hero { - background: - linear-gradient(180deg, rgba(64, 128, 255, 0.16) 0%, rgba(15, 17, 21, 0) 72%), - linear-gradient(90deg, rgba(20, 201, 201, 0.1) 0%, rgba(250, 173, 20, 0.08) 100%), - var(--ifm-background-color); -} - -.heroInner { - position: relative; - z-index: 1; - display: grid; - grid-template-columns: minmax(0, 1fr) minmax(420px, 0.9fr); - gap: 4rem; - align-items: center; -} - -.heroContent { - display: flex; - flex-direction: column; - align-items: flex-start; - gap: 1.25rem; -} - -.badge { - display: inline-flex; - align-items: center; - gap: 8px; - min-height: 32px; - padding: 5px 12px; - color: var(--ifm-color-primary); - background: rgba(22, 93, 255, 0.09); - border: 1px solid rgba(22, 93, 255, 0.2); - border-radius: 8px; - font-size: 13px; - font-weight: 600; -} - -[data-theme='dark'] .badge { - background: rgba(64, 128, 255, 0.16); - border-color: rgba(64, 128, 255, 0.3); - color: var(--ifm-color-primary-lighter); -} - -.badgeDot { - width: 7px; - height: 7px; - background: #00b42a; - border-radius: 50%; - box-shadow: 0 0 0 4px rgba(0, 180, 42, 0.12); -} - -.heroTitle { - margin: 0; - color: var(--ifm-heading-color); - font-size: 3.45rem; - font-weight: 750; - letter-spacing: 0; - line-height: 1.08; -} - -.heroTitleAccent { - display: block; - margin-top: 8px; - color: var(--ifm-color-primary); -} - -.heroSubtitle { - max-width: 640px; - margin: 0; - color: var(--ifm-color-content-secondary); - font-size: 1.15rem; - line-height: 1.72; -} - -.actions { - display: flex; - flex-wrap: wrap; - gap: 12px; - margin-top: 8px; -} - -.primaryBtn, -.secondaryBtn { - min-height: 46px; - border-radius: 8px; - transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease, background 0.2s ease; -} - -.primaryBtn { - display: inline-flex; - align-items: center; - gap: 8px; - color: #fff; - background: #165dff; - border: 1px solid #165dff; - box-shadow: 0 10px 24px rgba(22, 93, 255, 0.24); - font-weight: 650; -} - -.primaryBtn:hover, -.primaryBtn:focus-visible { - color: #fff; - background: #0e4fe6; - border-color: #0e4fe6; - box-shadow: 0 14px 30px rgba(22, 93, 255, 0.3); - transform: translateY(-1px); -} - -.btnArrow { - transition: transform 0.2s ease; -} - -.primaryBtn:hover .btnArrow, -.primaryBtn:focus-visible .btnArrow { - transform: translateX(3px); -} - -.secondaryBtn { - display: inline-flex; - align-items: center; - color: var(--ifm-font-color-base); - background: var(--ifm-background-color); - border: 1px solid var(--ifm-color-emphasis-300); - font-weight: 600; -} - -.secondaryBtn:hover, -.secondaryBtn:focus-visible { - color: var(--ifm-color-primary); - border-color: var(--ifm-color-primary); - background: var(--ifm-background-color); - transform: translateY(-1px); -} - -.metrics { - display: flex; - align-items: center; - gap: 1.5rem; - margin-top: 0.5rem; - padding-top: 1.25rem; -} - -.metric { - display: flex; - min-width: 0; - flex-direction: column; - gap: 4px; -} - -.metricValue { - color: var(--ifm-heading-color); - font-size: 1.35rem; - font-weight: 750; - letter-spacing: 0; - line-height: 1.1; - white-space: nowrap; -} - -.metricLabel { - color: var(--ifm-color-content-secondary); - font-size: 12px; - font-weight: 600; - letter-spacing: 0; - line-height: 1.35; - text-transform: uppercase; -} - -.metricDivider { - width: 1px; - height: 30px; - background: var(--ifm-color-emphasis-300); -} - -/* Product visual */ -.heroVisual { - display: grid; - gap: 1rem; -} - -.consolePanel { - overflow: hidden; - background: rgba(255, 255, 255, 0.92); - border: 1px solid rgba(22, 93, 255, 0.16); - border-radius: 8px; - box-shadow: 0 24px 60px rgba(29, 33, 41, 0.12); -} - -[data-theme='dark'] .consolePanel { - background: rgba(22, 24, 29, 0.9); - border-color: rgba(255, 255, 255, 0.08); - box-shadow: 0 24px 60px rgba(0, 0, 0, 0.34); -} - -.consoleHeader { - display: flex; - align-items: flex-start; - justify-content: space-between; - gap: 1rem; - padding: 1.25rem; - border-bottom: 1px solid var(--ifm-color-emphasis-200); -} - -[data-theme='dark'] .consoleHeader { - border-bottom-color: rgba(255, 255, 255, 0.08); -} - -.consoleHeader strong { - display: block; - margin-top: 4px; - color: var(--ifm-heading-color); - font-size: 1.2rem; -} - -.consoleEyebrow { - color: var(--ifm-color-content-secondary); - font-size: 12px; - font-weight: 650; - letter-spacing: 0; - text-transform: uppercase; -} - -.consoleStatus { - display: inline-flex; - align-items: center; - min-height: 28px; - padding: 4px 10px; - color: #00a870; - background: rgba(0, 180, 42, 0.1); - border: 1px solid rgba(0, 180, 42, 0.2); - border-radius: 8px; - font-size: 12px; - font-weight: 700; -} - -.consoleGrid { - display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); - border-bottom: 1px solid var(--ifm-color-emphasis-200); -} - -[data-theme='dark'] .consoleGrid { - border-bottom-color: rgba(255, 255, 255, 0.08); -} - -.consoleGrid > div { - min-width: 0; - padding: 1.1rem 1.25rem; - border-right: 1px solid var(--ifm-color-emphasis-200); -} - -[data-theme='dark'] .consoleGrid > div { - border-right-color: rgba(255, 255, 255, 0.08); -} - -.consoleGrid > div:last-child { - border-right: 0; -} - -.consoleGrid strong { - display: block; - margin-top: 6px; - color: var(--ifm-heading-color); - font-size: 1.45rem; - line-height: 1.1; -} - -.consoleLabel { - display: block; - color: var(--ifm-color-content-secondary); - font-size: 12px; - font-weight: 650; -} - -.timeline { - display: grid; -} - -.timelineRow { - display: grid; - grid-template-columns: auto minmax(0, 1fr) auto; - gap: 12px; - align-items: center; - padding: 1rem 1.25rem; - border-bottom: 1px solid var(--ifm-color-emphasis-200); -} - -[data-theme='dark'] .timelineRow { - border-bottom-color: rgba(255, 255, 255, 0.08); -} - -.timelineRow:last-child { - border-bottom: 0; -} - -.timelineRow strong, -.timelineRow span { - display: block; -} - -.timelineRow strong { - color: var(--ifm-heading-color); - font-size: 0.95rem; - font-weight: 700; -} - -.timelineRow span { - color: var(--ifm-color-content-secondary); - font-size: 0.85rem; - line-height: 1.5; -} - -.timelineRow em { - color: var(--ifm-color-content-secondary); - font-size: 0.8rem; - font-style: normal; - font-weight: 650; - white-space: nowrap; -} - -.timelineDotOk, -.timelineDotInfo, -.timelineDotWarn { - width: 10px; - height: 10px; - border-radius: 50%; -} - -.timelineDotOk { - background: #00b42a; -} - -.timelineDotInfo { - background: #165dff; -} - -.timelineDotWarn { - background: #ff7d00; -} - -.commandCard { - display: grid; - gap: 8px; - padding: 1rem 1.1rem; - background: #111827; - border: 1px solid rgba(255, 255, 255, 0.08); - border-radius: 8px; - box-shadow: 0 16px 34px rgba(17, 24, 39, 0.18); -} - -.commandTitle { - color: #9ca3af; - font-size: 12px; - font-weight: 650; - letter-spacing: 0; - text-transform: uppercase; -} - -.commandCard code { - overflow-x: auto; - color: #e5e7eb; - background: transparent; - border: 0; - padding: 0; - font-size: 13px; - white-space: nowrap; -} - -@media (max-width: 996px) { - .hero { - padding: 4.5rem 0 3.5rem; - } - - .heroInner { - grid-template-columns: 1fr; - gap: 2.25rem; - } - - .heroTitle { - font-size: 2.45rem; - } -} - -@media (max-width: 640px) { - .hero { - padding: 3.75rem 0 2.75rem; - } - - .heroTitle { - font-size: 2.05rem; - } - - .heroSubtitle { - font-size: 1rem; - } - - .actions { - width: 100%; - } - - .primaryBtn, - .secondaryBtn { - width: 100%; - justify-content: center; - } - - .metrics { - width: 100%; - align-items: stretch; - gap: 0.85rem; - flex-direction: column; - } - - .metricDivider { - width: 100%; - height: 1px; - } - - .consoleHeader, - .timelineRow { - padding: 1rem; - } - - .consoleGrid { - grid-template-columns: 1fr; - } - - .consoleGrid > div { - border-right: 0; - border-bottom: 1px solid var(--ifm-color-emphasis-200); - } - - .consoleGrid > div:last-child { - border-bottom: 0; - } - - [data-theme='dark'] .consoleGrid > div { - border-bottom-color: rgba(255, 255, 255, 0.08); - } -} - -@media (prefers-reduced-motion: reduce) { - .primaryBtn, - .secondaryBtn, - .btnArrow { - transition: none; - } -} diff --git a/docs-site/src/pages/index.tsx b/docs-site/src/pages/index.tsx index 4a8971a..58d3c71 100644 --- a/docs-site/src/pages/index.tsx +++ b/docs-site/src/pages/index.tsx @@ -1,164 +1,19 @@ import type {ReactNode} from 'react'; -import clsx from 'clsx'; -import Link from '@docusaurus/Link'; -import Translate, {translate} from '@docusaurus/Translate'; +import {translate} from '@docusaurus/Translate'; import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; import Layout from '@theme/Layout'; -import Heading from '@theme/Heading'; +import HomepageHero from '@site/src/components/HomepageHero'; import HomepageFeatures from '@site/src/components/HomepageFeatures'; import HomepageShowcase from '@site/src/components/HomepageShowcase'; import HomepageCommunity from '@site/src/components/HomepageCommunity'; -import styles from './index.module.css'; - -function HomepageHeader() { - return ( - <header className={styles.hero}> - <div className={clsx('container', styles.heroInner)}> - <div className={styles.heroContent}> - <div className={styles.badge}> - <span className={styles.badgeDot} /> - <Translate id="home.badge">Open-source backup control plane · v2.2.1</Translate> - </div> - <Heading as="h1" className={styles.heroTitle}> - <Translate id="home.title.part1">Backup orchestration</Translate> - <span className={styles.heroTitleAccent}> - <Translate id="home.title.part2">for self-hosted servers.</Translate> - </span> - </Heading> - <p className={styles.heroSubtitle}> - <Translate id="home.tagline"> - Run file, database, SAP HANA and remote-node backups from one clean console. Keep the control plane yours, keep the storage flexible. - </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}> - <div className={styles.metric}> - <div className={styles.metricValue}>70+</div> - <div className={styles.metricLabel}> - <Translate id="home.metric.backends">Storage backends</Translate> - </div> - </div> - <div className={styles.metricDivider} /> - <div className={styles.metric}> - <div className={styles.metricValue}>Agent</div> - <div className={styles.metricLabel}> - <Translate id="home.metric.backupTypes">Remote execution</Translate> - </div> - </div> - <div className={styles.metricDivider} /> - <div className={styles.metric}> - <div className={styles.metricValue}>Apache 2.0</div> - <div className={styles.metricLabel}> - <Translate id="home.metric.license">License</Translate> - </div> - </div> - </div> - </div> - <div className={styles.heroVisual}> - <div className={styles.consolePanel}> - <div className={styles.consoleHeader}> - <div> - <span className={styles.consoleEyebrow}> - <Translate id="home.visual.eyebrow">BackupX Console</Translate> - </span> - <strong> - <Translate id="home.visual.title">Operations overview</Translate> - </strong> - </div> - <span className={styles.consoleStatus}> - <Translate id="home.visual.status">Healthy</Translate> - </span> - </div> - <div className={styles.consoleGrid}> - <div> - <span className={styles.consoleLabel}> - <Translate id="home.visual.success">Success rate</Translate> - </span> - <strong>99.4%</strong> - </div> - <div> - <span className={styles.consoleLabel}> - <Translate id="home.visual.nodes">Active nodes</Translate> - </span> - <strong>12</strong> - </div> - <div> - <span className={styles.consoleLabel}> - <Translate id="home.visual.targets">Storage targets</Translate> - </span> - <strong>8</strong> - </div> - </div> - <div className={styles.timeline}> - <div className={styles.timelineRow}> - <span className={styles.timelineDotOk} /> - <div> - <strong> - <Translate id="home.visual.row1.title">PostgreSQL nightly</Translate> - </strong> - <span> - <Translate id="home.visual.row1.desc">Encrypted archive uploaded to S3</Translate> - </span> - </div> - <em>02:10</em> - </div> - <div className={styles.timelineRow}> - <span className={styles.timelineDotInfo} /> - <div> - <strong> - <Translate id="home.visual.row2.title">SAP HANA snapshot</Translate> - </strong> - <span> - <Translate id="home.visual.row2.desc">Running on agent-shanghai-02</Translate> - </span> - </div> - <em>68%</em> - </div> - <div className={styles.timelineRow}> - <span className={styles.timelineDotWarn} /> - <div> - <strong> - <Translate id="home.visual.row3.title">Retention cleanup</Translate> - </strong> - <span> - <Translate id="home.visual.row3.desc">Next run in 4 hours</Translate> - </span> - </div> - <em>queued</em> - </div> - </div> - </div> - <div className={styles.commandCard}> - <div className={styles.commandTitle}> - <Translate id="home.command.title">Start with Docker</Translate> - </div> - <code>docker run -d -p 8340:8340 awuqing/backupx:v2.2.1</code> - </div> - </div> - </div> - </header> - ); -} - export default function Home(): ReactNode { const {siteConfig} = useDocusaurusContext(); return ( <Layout title={translate({id: 'home.pageTitle', message: 'Backup orchestration for self-hosted servers'})} description={siteConfig.tagline}> - <HomepageHeader /> + <HomepageHero /> <main> <HomepageFeatures /> <HomepageShowcase /> diff --git a/docs-site/src/pages/sponsors.tsx b/docs-site/src/pages/sponsors.tsx index 5626991..03d079f 100644 --- a/docs-site/src/pages/sponsors.tsx +++ b/docs-site/src/pages/sponsors.tsx @@ -18,15 +18,17 @@ export default function Sponsors(): ReactNode { <section className={styles.section}> <div className="container"> <div className={styles.sectionHead}> - <div className={styles.sectionTag}> - <Translate id="sponsors.tag">SPONSORS</Translate> + <div> + <div className={styles.sectionTag}> + <Translate id="sponsors.tag">Sponsorship</Translate> + </div> + <Heading as="h1" className={styles.sectionTitle}> + <Translate id="sponsors.title">Keep critical maintenance moving</Translate> + </Heading> </div> - <Heading as="h1" className={styles.sectionTitle}> - <Translate id="sponsors.title">Sponsor the BackupX ecosystem</Translate> - </Heading> <p className={styles.sectionSubtitle}> <Translate id="sponsors.subtitle"> - Sponsorship helps keep BackupX practical for real operators: tested storage providers, reliable releases, restore confidence and better documentation. + Sponsorship funds real provider validation, reliable releases, recovery drills, and better operational documentation. </Translate> </p> </div>