feat(theme): add a capabilities section band

- Replace the techStack band with project-type capabilities
- Delete the TechStack component and its section type
- Give grid cards an optional status badge
- Pad --big buttons, which Rspress ships with none
- Revise the home, services and products copy
This commit is contained in:
zihluwang committed 2026-09-29 12:21:25 +08:00
1 parent f51407b98b
commit e149401346
14 files changed
+328 -289

No files matched your search

+1 -1
View File
@@ -133,7 +133,7 @@ sidebar and discards all the per-section ones.
The homepage, `/products/` and `/services/` render optional bands declared in
frontmatter under a `sections` key. Each band renders only when it has content,
so an unfilled page degrades to its heading and intro rather than looking
broken. Supported types are `advantages`, `contentGrid`, `techStack`, `cases`
broken. Supported types are `capabilities`, `advantages`, `contentGrid`, `cases`
and `cta`; see `theme/components/sections/types.ts` for their shapes.
`theme/components/sections/placeholder.ts` exposes a `SHOW_COPY_PLACEHOLDERS`
+44 -74
View File
@@ -3,105 +3,75 @@ pageType: home
hero:
name: OnixByte
tagline: We design, build and run the systems your business depends on.
tagline: Designing, building, and sustaining your core business systems for the long term.
actions:
- theme: brand
text: Explore Services
text: Explore Custom Services
link: /services/
- theme: alt
text: View Products
link: /products/
features:
- title: OnixByte Toolbox
details: A versatile Java library providing common utilities for modern development with seamless Spring integration. Published on Maven Central.
icon: ☕
link: https://github.com/orgs/onixbyte-opensource/repositories?q=toolbox
- title: Dev Lab
details: A collection of practical web tools built with Vite and React. Features a JSON Path viewer, BMI calculator, and JSON-to-table converter.
icon: 🛠️
link: https://dev-lab.onixbyte.com
- title: Helix
details: An evolving full-stack template combining Spring Boot with React, designed to help you ship applications faster.
icon: 🌀
link: https://github.com/onixbyte-opensource/helix
- title: Delta Force Guide
details: A community-oriented guide for Delta Force, delivering game news, tips, and strategic insights for fellow operators.
icon: 🎯
link: https://github.com/onixbyte-opensource/ahsarah-guide
# Marketing sections, rendered below the features grid. Each block renders only
# when it has content, so commenting one out removes it cleanly.
# Reorder freely; unknown `type` values are ignored.
sections:
- type: capabilities
id: capabilities
title: What We Do
subtitle: From initial business requirements to dependable deployment, we deliver end-to-end bespoke software engineering.
items:
- icon: "🏢"
title: Bespoke Business Systems
details: Tailored precisely to your operational workflow, establishing a resilient digital foundation that drives efficiency.
subItems:
- Custom Management Systems
- Operational Automation Tools
- Legacy System Modernisation
- icon: "🌐"
title: Official Portals & Marketing Sites
details: Crafting modern, high-converting web experiences that elevate your brand and clearly communicate value.
subItems:
- Corporate Web Portals
- Product Landing Pages
- Multilingual Global Sites
- icon: "📱"
title: Mobile & Cross-Platform Solutions
details: From lightweight mini-programmes to high-performance native applications, delivering fluid cross-device experiences.
subItems:
- WeChat Mini-Programmes
- Native Android & HarmonyOS Engineering
- Cross-Platform Applications
- type: advantages
id: advantages
title: Why OnixByte
subtitle: What shapes how we build, ship and keep software running.
subtitle: Rejecting piecemeal subcontracting and quick fixes — engineering reliable software with rigour.
items:
- icon: "🧩"
title: One Team, Start to Finish
details: Everything from the user interface and the server to the database and going live is handled by the same team, so you are not left coordinating several suppliers.
title: Single-Team Accountability
details: Handling interface design, backend architecture, databases, and infrastructure deployment in-house. A single point of ownership eliminates hand-off friction and ensures seamless delivery.
span: 6
- icon: "🔧"
title: We Stay After Launch
details: Handover is not the end of the engagement. We keep improving and supporting what we deliver, because software nobody maintains soon stops being an asset.
title: Lifecycle Stewardship
details: Deployment is merely day one. We provide ongoing maintenance, performance optimisation, and agile iterations so your software remains an appreciating digital asset.
span: 6
- type: techStack
id: tech-stack
title: Technology We Use
subtitle: What we build with, and what we keep running in production.
groups:
- name: Backend & Data
items:
- { name: "Java" }
- { name: "Kotlin" }
- { name: "Go" }
- { name: "Rust" }
- { name: "Spring Boot" }
- { name: "Spring Security" }
- { name: "MyBatis" }
- { name: "PostgreSQL" }
- { name: "Redis" }
- name: Frontend
items:
- { name: "React" }
- { name: "TypeScript" }
- { name: "Vue" }
- { name: "Tailwind CSS" }
- { name: "Ant Design" }
- { name: "Vite" }
- name: Infrastructure & Delivery
items:
- { name: "Docker" }
- { name: "Docker Compose" }
- { name: "GitHub Actions" }
- { name: "GitLab CI" }
- { name: "MinIO" }
- { name: "LDAP" }
- name: Cloud & Identity
items:
- { name: "AWS S3" }
- { name: "Microsoft Entra ID" }
- { name: "JWT" }
- type: cases
id: work
title: Selected Work
subtitle: A look at some of the work we have delivered.
subtitle: Rooted in real-world scenarios, demonstrating how engineering standards deliver consistent results.
items:
# TODO(copy): no approved client testimonial yet, so this describes the
# engagement instead. Swap in a `quote`/`author`/`role` once a client
# agrees to be quoted. Add more entries and the card becomes a carousel.
- company: A garment manufacturer in Guangdong
details: A corporate marketing site built with Vue and delivered as a static site.
- company: Apparel Manufacturer in Guangdong
industry: Traditional Manufacturing / Global Export
details: Overhauled their responsive brand portal and digital product showcase with modern front-end architecture and automated static site generation, substantially reducing initial load times and boosting global availability.
tags:
- Corporate Portal
- Static Site Generation (SSG)
- Responsive Design
- type: cta
id: contact
title: Let's Talk About What You Need Built
subtitle: Tell us about your project or your idea — we are around whenever you are ready.
title: Let's Discuss Your Next Initiative
subtitle: Tell us about your project or concept — we are always ready to collaborate.
actions:
- theme: brand
text: business@onixbyte.com
link: mailto:business@onixbyte.com
code:
src: /wechat-qr.png
caption: Or scan to reach us on WeCom
caption: Or scan to connect via WeCom
---
+30 -14
View File
@@ -1,8 +1,8 @@
---
title: Products
title: Product Portfolio
description: >-
Dev Lab — a set of free developer utilities from OnixByte that run entirely
in your browser.
Explore OnixByte's suite of proprietary products and SaaS platforms, delivering highly reliable
digital services underpinned by solid engineering.
pageType: doc-wide
sidebar: false
outline: false
@@ -10,33 +10,49 @@ footer: false
head:
- - meta
- name: keywords
content: Dev Lab, JSON viewer, JSONPath, developer tools, browser-based, privacy, OnixByte
content: >-
OnixByte, Dev Lab, Ahsarah Guide, Delta Force community, SaaS products, developer tools,
JSONPath, data privacy
sections:
- type: contentGrid
id: products
title: What We Run
subtitle: Small in number, and used by us daily.
title: In-House Products & Tools
subtitle: >-
Alongside bespoke client engineering, we continuously incubate proprietary SaaS solutions and
vertical products. Each tool originates from genuine demand and benefits from sustained
technical stewardship.
items:
- icon: "🧰"
title: Dev Lab
details: A collection of developer utilities, led by a JSON viewer with JSONPath queries. Everything runs locally in your browser, so nothing you paste in is ever uploaded.
details: >-
A dedicated utility suite engineered for developers. Anchored by a structured data viewer
with robust JSONPath query support, all computations execute entirely within the local
browser sandbox — zero data transmission, guaranteeing absolute security for sensitive
code and operational credentials.
link: https://dev-lab.onixbyte.com
span: 6
- icon: "🧭"
title: Ahsarah Guide
badge: Coming Soon
details: >-
An unofficial player intelligence hub dedicated to Delta Force. Delivering real-time
dispatches, weapon registries, and practical tactical guides via a dependable architecture
tailored for a fluid, distraction-free reading experience.
span: 6
- type: cta
id: contact
title: Need Something Like This Built for You?
subtitle: Tell us about your project or your idea — we are around whenever you are ready.
title: Looking to Build a Dedicated Business System or SaaS Platform?
subtitle: >-
From internal productivity utilities to public-facing commercial platforms, share your vision
with us — we are always ready to collaborate.
actions:
- theme: brand
text: See What We Do
text: Explore Our Engineering Services
link: /services/
- theme: alt
text: business@onixbyte.com
link: mailto:business@onixbyte.com
code:
src: /wechat-qr.png
caption: Or scan to reach us on WeCom
caption: Or scan to connect via WeCom
---
Alongside client work we build tools of our own. We keep the number small: each
one exists because we needed it ourselves and suspected others did too.
+25 -22
View File
@@ -1,8 +1,8 @@
---
title: Services
title: Bespoke Services
description: >-
Custom web applications, system integration and long-term maintenance —
built by OnixByte to fit how your organisation already works.
Custom web applications, system integration, and dedicated long-term maintenance — engineered
around your operational workflow, never forcing you to adapt to rigid software.
pageType: doc-wide
sidebar: false
outline: false
@@ -10,35 +10,38 @@ footer: false
head:
- - meta
- name: keywords
content: custom software development, bespoke web application, system integration, single sign-on, LDAP, legacy system maintenance, OnixByte
content: >-
Bespoke software engineering, custom web applications, system integration, single sign-on,
LDAP, legacy software maintenance, OnixByte
sections:
- type: cta
id: contact
title: Tell Us What You Need Built
subtitle: Tell us about your project or your idea — we are around whenever you are ready.
title: Let's Discuss Your Next Initiative
subtitle: >-
Whether scoping a specific system requirement or exploring technical feasibility, we are
always ready to collaborate.
actions:
- theme: brand
text: business@onixbyte.com
link: mailto:business@onixbyte.com
code:
src: /wechat-qr.png
caption: Or scan to reach us on WeCom
caption: Or scan to connect via WeCom
---
We build business systems, internal tools and integrations to fit the way your
organisation already works — and then we keep them running. Most of our
engagements turn into long relationships rather than one-off deliveries.
We engineer bespoke business systems, internal productivity utilities, and robust system
integrations shaped precisely around your organisation's current workflows — continuing to support
and refine them long after go-live. Our engagements consistently evolve into long-term technology
partnerships rather than transactional, one-off deliveries.
## Where We Fit
## Who We Are Best Suited For
We are not the right choice for every project, and it saves everybody time to
say so before we start.
### A good fit
- **The business logic is the hard part.** You need something shaped around how
you actually operate, not a template with your logo dropped into it.
- **You have no in-house engineering team**, or the one you have is committed
elsewhere.
- **You care what happens after launch.** We would rather maintain a system for
years than hand it over and disappear.
- **Complex business logic is the primary challenge**: You require a dedicated system tailored
around distinct, non-standard workflows, rather than generic templates that force your
organisation to compromise.
- **Requiring dependable full-stack technical capacity**: You have not yet established an in-house
engineering team, or existing technical resources are fully committed and require autonomous,
end-to-end delivery.
- **Valuing long-term stability and continuous evolution**: Deployment is merely day one. You prefer
an experienced technical team that understands your systems deeply and remains available for
sustained maintenance and agile iterations.
+42 -70
View File
@@ -11,87 +11,59 @@ hero:
- theme: alt
text: 查看产品
link: /products/
features:
- title: OnixByte Toolbox
details: 一个多功能 Java 类库,为现代 Java 开发提供通用工具,无缝集成 Spring 生态。已上架 Maven Central。
icon: ☕
link: https://github.com/orgs/onixbyte-opensource/repositories?q=toolbox
- title: Dev Lab
details: 基于 Vite 和 React 构建的实用在线工具集。包含 JSON Path 查看器、BMI 计算器以及 JSON 转表格工具。
icon: 🛠️
link: https://dev-lab.onixbyte.com
- title: Helix
details: 一个不断演进的前后端模版项目,结合 Spring Boot 与 React,助你快速构建应用。
icon: 🌀
link: https://github.com/onixbyte-opensource/helix
- title: Delta Force Guide
details: 社区向的《三角洲行动》游戏指南,提供最新游戏资讯、实用技巧和战术策略。
icon: 🎯
link: https://github.com/onixbyte-opensource/ahsarah-guide
# 营销区块,渲染在项目网格下方。每个区块仅在有内容时渲染,
# 注释掉即整块消失,不会留下空位。
# 区块顺序可自由调整;无法识别的 `type` 会被忽略。
sections:
- type: capabilities
id: capabilities
title: 我们能为您做什么
subtitle: 从业务诉求到稳定投产,提供全周期、定制化的软件研发交付。
items:
- icon: "🏢"
title: 定制化业务系统
details: 围绕实际运作流程构建,让系统成为推动效率提升的数字基座。
subItems:
- 定制业务系统
- 自动化运营工具
- 现有系统升级改造
- icon: "🌐"
title: 官方门户与营销站点
details: 打造现代、专业且兼顾转化率的数字门面,全面提升品牌认知。
subItems:
- 企业门户
- 产品落地页 (Landing Page)
- 国际化多语言站点
- icon: "📱"
title: 移动端应用全案
details: 从轻量化的小程序到全平台原生 App,打造顺畅无阻的移动体验。
subItems:
- 微信小程序
- Android / HarmonyOS 原生开发
- 多端跨平台解决方案
- type: advantages
id: advantages
title: 为什么选择曜珀科技
subtitle: 塑造我们交付与维护方式的原则。
title: 核心优势
subtitle: 摒弃外包拼凑与短期交付,用工程化思维构建可靠软件。
items:
- icon: "🧩"
title: 一支团队负责到底
details: 从用户界面、服务端、数据库到上线部署,全部由同一个团队承接,您不必在多个供应商之间来回协调。
title: 端到端自主研发
details: 涵盖前端、服务端、数据库与基础设施运维,单一责任主体贯穿全生命周期,沟通无缝隙,交付高效率。
span: 6
- icon: "🔧"
title: 上线之后我们还在
details: 交付不是合作的终点。我们对上线的系统持续改进与支持 —— 没人维护的软件,很快就不能再算作资产。
title: 全生命周期守护
details: 交付上线只是起点。我们提供长期可靠的系统运维与敏捷迭代支持,保障系统平稳演进与持续创造价值。
span: 6
- type: techStack
id: tech-stack
title: 技术栈
subtitle: 我们用来构建、以及在生产环境长期运行的技术。
groups:
- name: 后端与数据
items:
- { name: "Java" }
- { name: "Kotlin" }
- { name: "Go" }
- { name: "Rust" }
- { name: "Spring Boot" }
- { name: "Spring Security" }
- { name: "MyBatis" }
- { name: "PostgreSQL" }
- { name: "Redis" }
- name: 前端
items:
- { name: "React" }
- { name: "TypeScript" }
- { name: "Vue" }
- { name: "Tailwind CSS" }
- { name: "Ant Design" }
- { name: "Vite" }
- name: 基础设施与交付
items:
- { name: "Docker" }
- { name: "Docker Compose" }
- { name: "GitHub Actions" }
- { name: "GitLab CI" }
- { name: "MinIO" }
- { name: "LDAP" }
- name: 云服务与身份
items:
- { name: "AWS S3" }
- { name: "Microsoft Entra ID" }
- { name: "JWT" }
- type: cases
id: work
title: 项目实践
subtitle: 我们交付过的一些项目。
title: 典型案例
subtitle: 深入真实业务场景,看我们如何用工程标准完成稳定交付。
items:
# TODO(copy): 目前没有客户授权引语,因此改为描述项目本身。等客户同意具名
# 评价后,把 quote / author / role 换成引语。再加条目就会变成轮播。
- company: 广东省一家服饰制造企业
details: 基于 Vue 的企业宣传站点,以静态站点形式交付。
# 待客户授权后,可在此补充 quote, author, role 等评价字段
- company: 广东某服饰制造企业
industry: 传统制造 / 外贸出海
details: 为其重塑多端适配的品牌门户与产品数字展厅,采用现代前端架构与自动化静态构建,大幅缩短首屏加载时间并提升海外访问稳定性。
tags:
- 品牌官网
- 静态化构建 (SSG)
- 响应式设计
- type: cta
id: contact
title: 聊聊您要做的事
+13 -10
View File
@@ -1,6 +1,6 @@
---
title: 产品矩阵
description: Dev Lab —— 曜珀科技自研的开发者工具集,全部功能都在您的浏览器本地运行。
description: 探索曜珀科技(OnixByte)的自研产品与 SaaS 服务矩阵,以扎实工程提供高可用数字化服务。
pageType: doc-wide
sidebar: false
outline: false
@@ -8,25 +8,30 @@ footer: false
head:
- - meta
- name: keywords
content: Dev Lab, JSON 查看器, JSONPath, 开发者工具, 浏览器本地运行, 数据隐私, 曜珀科技
content: 曜珀科技, OnixByte, Dev Lab, 阿萨拉向导, 三角洲行动社区, SaaS产品, 开发者工具, JSONPath, 数据隐私
sections:
- type: contentGrid
id: products
title: 我们在运营的产品
subtitle: 数量不多,但我们每天都在用。
title: 自研产品与工具
subtitle: 除了为客户提供定制研发,我们也持续孵化自研 SaaS 与垂直产品。每一款都始于真实刚需,并坚持长期的工程投入与精细打磨。
items:
- icon: "🧰"
title: Dev Lab
details: 一组开发者实用工具,核心是一个支持 JSONPath 查询的 JSON 查看器。全部功能在您的浏览器本地运行,粘贴进去的内容不会被上传到任何地方。
details: 专为开发者打造的实用工具箱。核心提供深度支持 JSONPath 查询的结构化数据查看器;所有计算均在浏览器本地沙箱完成,绝不上传任何数据,保障敏感代码与业务凭据绝对安全。
link: https://dev-lab.onixbyte.com
span: 6
- icon: "🧭"
title: 阿萨拉向导
badge: 敬请期待
details: 专注于《三角洲行动》的非官方玩家资讯与数据社区。提供即时游戏资讯、武器装备数据库与实用战术指南,依托稳定的架构为玩家提供流畅纯净的查阅体验。
span: 6
- type: cta
id: contact
title: 需要为您做一套类似的东西?
subtitle: 聊聊您的项目或想法,我们随时在线。
title: 需要构建专属的业务系统或 SaaS 产品?
subtitle: 从内部提效工具到面向公众的商业化平台,聊聊您的构想,我们随时在线。
actions:
- theme: brand
text: 看看我们做什么
text: 了解我们的研发服务
link: /services/
- theme: alt
text: business@onixbyte.com
@@ -35,5 +40,3 @@ sections:
src: /wechat-qr.png
caption: 或者扫码加企业微信
---
除了客户项目,我们也做自己的工具。数量刻意保持得很少:每一件都是因为我们自己需要,并且认为别人可能也需要。
+9 -13
View File
@@ -1,6 +1,6 @@
---
title: 定制服务
description: 定制 Web 应用、系统集成与长期维护 —— 按您组织现有的运作方式来做,而不是让您去迁就软件。
description: 面向实际业务流程的定制 Web 应用、系统集成与长期技术支持。贴合您的组织运作,而非让您去迁就软件。
pageType: doc-wide
sidebar: false
outline: false
@@ -8,12 +8,12 @@ footer: false
head:
- - meta
- name: keywords
content: 定制软件开发, 定制 Web 应用, 系统集成, 单点登录, LDAP, 存量系统维护, 曜珀科技
content: 定制软件开发, 定制 Web 应用, 企业系统集成, 单点登录, LDAP, 存量系统维护, 软件长期维保, 曜珀科技
sections:
- type: cta
id: contact
title: 说说您要做什么
subtitle: 聊聊您的项目或想法,我们随时在线。
title: 聊聊您要做的事
subtitle: 无论是一个明确的系统需求,还是初期的技术选型探讨,我们随时在线。
actions:
- theme: brand
text: business@onixbyte.com
@@ -23,14 +23,10 @@ sections:
caption: 或者扫码加企业微信
---
我们按您组织现有的运作方式,构建业务系统、内部工具与系统集成 —— 并在上线之后继续维护它们。我们的合作大多会变成长期关系,而不是一次性交付。
我们围绕您组织的实际运作方式,构建专属业务系统、内部效能工具与系统集成方案,并在上线后持续提供维护与改进支持。我们的合作大多会发展为长期的技术伙伴关系,而非一次性的交付了事。
## 什么样的项目适合我们
## 什么样的需求最适合找我们
我们并不适合所有项目。在开始之前把这一点说清楚,对双方都省时间。
### 适合找我们
- **业务逻辑本身才是难点。** 您需要的是按实际运作方式做出来的系统,而不是换个 logo 的模板。
- **您没有自己的技术团队**,或者团队已经排满了。
- **您在意上线之后的事。** 我们更愿意把一个系统维护好几年,而不是交付完就消失。
- **业务逻辑本身才是核心难点**:您面对的是非标、特殊的业务流,需要一套量身定制的专属系统,而不是套用市面上死板的通用模板。
- **需要一支能打硬仗的外部技术力量**:您尚未组建内部研发团队,或现有团队精力已饱和,需要一支懂业务、能闭环推进落地的工程师团队。
- **重视系统投产后的长期稳健与持续演进**:交付上线不是终点。您希望拥有一支熟悉您业务架构的技术力量,随时为系统提供长期的运行保障与敏捷迭代。
+6 -6
View File
@@ -1,7 +1,7 @@
import { useFrontmatter } from "@rspress/core/runtime"
import { SectionBand } from "./sections/SectionBand"
import { FeatureGrid } from "./sections/FeatureGrid"
import { TechStack } from "./sections/TechStack"
import { Capabilities } from "./sections/Capabilities"
import { Cases } from "./sections/Cases"
import { CallToAction } from "./sections/CallToAction"
import { hasContent, isHoisted, type Section } from "./sections/types"
@@ -13,7 +13,7 @@ function isSection(value: unknown): value is Section {
return (
type === "advantages" ||
type === "contentGrid" ||
type === "techStack" ||
type === "capabilities" ||
type === "cases" ||
type === "cta"
)
@@ -24,8 +24,8 @@ function renderBody(section: Section) {
case "advantages":
case "contentGrid":
return <FeatureGrid items={section.items} />
case "techStack":
return <TechStack groups={section.groups} />
case "capabilities":
return <Capabilities items={section.items} />
case "cases":
return <Cases items={section.items} />
case "cta":
@@ -47,8 +47,8 @@ interface HomeSectionsProps {
*
* Attached to the `Layout` `afterFeatures` (home) and `afterDoc` (products,
* services) slots, and to the `DocContent` override for hoisted bands. Returns
* `null` for any page without a `sections` array, so it is inert on blog posts,
* project pages and every other doc page.
* `null` for any page without a `sections` array, so it is inert on every doc
* page that does not opt in — the legal pages included.
*/
export function HomeSections({ placement }: HomeSectionsProps = {}) {
const { frontmatter } = useFrontmatter()
+1 -5
View File
@@ -32,11 +32,7 @@ export function CallToAction({
key={`${str(action.text)}-${index}`}
type="a"
theme={action.theme === "alt" ? "alt" : "brand"}
href={str(action.link) as string}
// `.rp-button--big` sets a min-width but no horizontal padding, so
// a long label such as an email address runs into the rounded
// edges. Supply the padding ourselves.
className="px-10">
href={str(action.link) as string}>
{str(action.text)}
</Button>
))}
@@ -0,0 +1,57 @@
import { arr, str, type Capability } from "./types"
/**
* Direction cards: the kinds of project we take on.
*
* The buying question is "what can you build for me", not "what do you build
* with", so each card names a project category and the concrete deliverables
* under it. The fill comes from `rp-capability-card` in `index.css` rather than
* a `bg-` utility, because the band alternates between the page background and
* `--rp-c-bg-soft` and the card has to invert against whichever it lands on.
*/
export function Capabilities({ items }: { items?: Capability[] }) {
const cards = (arr<Capability>(items) ?? [])
.map((item) => ({
title: str(item?.title) ?? "",
details: str(item?.details),
icon: str(item?.icon),
// `arr<string>` is an assertion, not a guarantee — frontmatter is parsed by
// gray-matter at runtime and takes no part in MDX type checking, so each
// entry still goes through `str` before it reaches the DOM.
entries: (arr<string>(item?.subItems) ?? []).map((entry) => str(entry)),
}))
.filter((card) => card.title !== "")
if (cards.length === 0) return null
return (
<ul className="m-0 grid list-none grid-cols-1 gap-6 p-0 md:grid-cols-3">
{cards.map(({ title, details, icon, entries }, index) => (
<li
key={`${title}-${index}`}
className="rp-capability-card flex flex-col rounded-xl border border-(--rp-c-divider-light) p-6">
{icon && (
<span aria-hidden="true" className="text-2xl">
{icon}
</span>
)}
<h3 className="mt-3 mb-0 text-lg font-semibold text-(--rp-c-text-1)">{title}</h3>
{details && (
<p className="mt-2 mb-0 text-sm leading-relaxed text-(--rp-c-text-2)">{details}</p>
)}
{entries.length > 0 && (
<ul className="mt-4 mb-0 list-disc pl-5 text-sm leading-relaxed text-(--rp-c-text-2)">
{entries.map((entry, entryIndex) =>
entry ? (
<li key={`${entry}-${entryIndex}`} className="m-0">
{entry}
</li>
) : null
)}
</ul>
)}
</li>
))}
</ul>
)
}
+26 -3
View File
@@ -1,15 +1,38 @@
import type { Feature } from "@rspress/core"
import { HomeFeature } from "@rspress/core/theme-original"
import { arr } from "./types"
import { arr, str, type FeatureCard } from "./types"
/** The badge is interpolated into markup, so it cannot carry its own markup. */
function escapeHtml(value: string): string {
return value
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
}
/**
* Card grid used for both the homepage advantages and the products/services
* grids. Delegates to the stock `HomeFeature`, which already handles `span`,
* icons and the reveal animation — it accepts a `features` prop precisely so it
* can be driven from somewhere other than page frontmatter.
*
* A card's `badge` is folded into the front of `details` as a span, because
* `HomeFeature` renders `details` through `renderHtmlOrText` (which passes HTML
* through) and exposes no other slot. Note this makes the whole `details` string
* render as HTML for badged cards — frontmatter is first-party, and
* `renderHtmlOrText` already does this for any string containing a tag.
*/
export function FeatureGrid({ items }: { items?: Feature[] }) {
const features = arr<Feature>(items)
export function FeatureGrid({ items }: { items?: FeatureCard[] }) {
const features = arr<FeatureCard>(items)?.map(({ badge, ...feature }) => {
const label = str(badge)
if (!label) return feature as Feature
const details = str(feature.details) ?? ""
return {
...feature,
details: `<span class="rp-card-badge">${escapeHtml(label)}</span>${details}`,
} as Feature
})
if (!features) return null
return <HomeFeature features={features} />
}
-54
View File
@@ -1,54 +0,0 @@
import { arr, str, type TechStackGroup, type TechStackItem } from "./types"
/**
* Technology matrix: named groups of technologies, each with an optional
* emoji/icon and link.
*/
export function TechStack({ groups }: { groups?: TechStackGroup[] }) {
const visible = (arr<TechStackGroup>(groups) ?? [])
.map((group) => ({ group, items: arr<TechStackItem>(group?.items) ?? [] }))
.filter(({ items }) => items.length > 0)
if (visible.length === 0) return null
return (
<div className="flex flex-col gap-10">
{visible.map(({ group, items }, groupIndex) => (
<div key={str(group.name) ?? `group-${groupIndex}`}>
<h3 className="mb-4 text-sm font-semibold uppercase tracking-wide text-(--rp-c-text-3)">
{str(group.name) ?? ""}
</h3>
<ul className="m-0 grid list-none grid-cols-2 gap-3 p-0 sm:grid-cols-3 md:grid-cols-4">
{items.map((item, itemIndex) => {
const name = str(item?.name)
if (!name) return null
const url = str(item?.url)
const icon = str(item?.icon)
const body = (
<span className="flex items-center gap-2">
{icon && <span aria-hidden="true">{icon}</span>}
<span>{name}</span>
</span>
)
return (
<li key={`${name}-${itemIndex}`} className="m-0">
{url ? (
<a
className="rp-link flex items-center rounded-md border border-(--rp-c-divider-light) px-3 py-2 no-underline transition-colors hover:bg-(--rp-c-bg-mute)"
href={url}>
{body}
</a>
) : (
<span className="flex items-center rounded-md border border-(--rp-c-divider-light) px-3 py-2">
{body}
</span>
)}
</li>
)
})}
</ul>
</div>
))}
</div>
)
}
+37 -17
View File
@@ -8,15 +8,37 @@ import type { Feature } from "@rspress/core"
* checking — every field must therefore be defended at the point of use.
*/
export interface TechStackItem {
name: string
icon?: string
url?: string
/**
* A `Feature` card, plus an optional status pill shown above its description —
* used to mark something as not yet released.
*
* The stock `Feature` shape has no field for this, and the `HomeFeature`
* component that renders the card (with its hover animation) is not exported in
* a form that can be extended without reimplementing it. `FeatureGrid` therefore
* folds the badge into `details`, which `HomeFeature` already renders as HTML.
*/
export interface FeatureCard extends Feature {
/** Short status label, e.g. "敬请期待". Rendered as a pill above `details`. */
badge?: string
}
export interface TechStackGroup {
name: string
items?: TechStackItem[]
/**
* One card in the `capabilities` band: a kind of project we take on.
*
* This band exists because buyers shop for "can you build me a corporate site",
* not for "do you know Docker" — so it names project categories and the
* deliverables under them, never technologies.
*/
export interface Capability {
title: string
/** One line on what the card covers. */
details?: string
icon?: string
/**
* The concrete things delivered in this direction. Named `subItems` rather
* than `items` because the card list itself is `items`.
*/
subItems?: string[]
}
export interface CaseStudy {
@@ -63,9 +85,9 @@ interface SectionBase {
}
export type Section =
| (SectionBase & { type: "advantages"; items?: Feature[] })
| (SectionBase & { type: "contentGrid"; items?: Feature[] })
| (SectionBase & { type: "techStack"; groups?: TechStackGroup[] })
| (SectionBase & { type: "advantages"; items?: FeatureCard[] })
| (SectionBase & { type: "contentGrid"; items?: FeatureCard[] })
| (SectionBase & { type: "capabilities"; items?: Capability[] })
| (SectionBase & { type: "cases"; items?: CaseStudy[] })
| (SectionBase & {
type: "cta"
@@ -95,13 +117,11 @@ export function hasContent(section: Section): boolean {
switch (section.type) {
case "advantages":
case "contentGrid":
return arr<Feature>(section.items) !== undefined
case "techStack":
return (
arr<TechStackGroup>(section.groups)?.some(
(group) => arr<TechStackItem>(group?.items) !== undefined
) ?? false
)
return arr<FeatureCard>(section.items) !== undefined
case "capabilities":
// `.some` rather than a bare existence check: a card with no title renders
// nothing, so a list of blank cards must not count as content.
return arr<Capability>(section.items)?.some((item) => str(item?.title)) ?? false
case "cases":
return arr<CaseStudy>(section.items) !== undefined
case "cta":
+37
View File
@@ -10,6 +10,14 @@
--rp-site-section-gap: 4rem;
}
/* Rspress ships the `--big` button variant with no horizontal padding, relying
on its `min-width` alone to leave room around the label. A label wider than
that `min-width` — "Explore Custom Services", or a longer translation — fills
the pill edge to edge. Give it the padding `--medium` already has. */
.rp-button--big {
padding-inline: 1.5rem;
}
/* `HomeFeature` always renders its wrapper, even when the page declares no
`features`. Hide the empty shell so a hero-only page does not show a
stray empty band. */
@@ -32,3 +40,32 @@
.rp-site-section--tinted {
background: var(--rp-c-bg-soft);
}
/* Status pill on a grid card, driven by the section item's `badge` field.
Matches the tag pills in `components/Tags.tsx`. Lives here rather than as
utilities on the element because the element is built in `FeatureGrid` as an
HTML string for `HomeFeature` to render. */
.rp-card-badge {
display: block;
width: fit-content;
margin-bottom: 0.5rem;
padding: 0.125rem 0.625rem;
border-radius: 0.375rem;
font-size: 0.75rem;
line-height: 1.5;
white-space: nowrap;
color: var(--rp-c-brand);
background: var(--rp-c-brand-tint);
}
/* Card surface for the `capabilities` band. A band is either the page
background (`--rp-c-bg`) or `--rp-c-bg-soft`, so a card filled with either
one alone would disappear on the other. Invert against the band instead, so
the cards read whichever stripe the band happens to land on. */
.rp-capability-card {
background: var(--rp-c-bg-soft);
}
.rp-site-section--tinted .rp-capability-card {
background: var(--rp-c-bg);
}