Skip to content

Commit fc8ed83

Browse files
committed
feat(website): redesign homepage hero
1 parent def8ecb commit fc8ed83

6 files changed

Lines changed: 332 additions & 42 deletions

File tree

‎website/i18n.json‎

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,29 @@
33
"en": "Quick start",
44
"zh": "快速上手"
55
},
6+
"viewSource": {
7+
"en": "View the code",
8+
"zh": "查看源码"
9+
},
10+
"copyCommand": {
11+
"en": "Copy command",
12+
"zh": "复制命令"
13+
},
14+
"copiedCommand": {
15+
"en": "Command copied",
16+
"zh": "命令已复制"
17+
},
18+
"title": {
19+
"en": "Unified Toolchain for",
20+
"zh": "统一工具链"
21+
},
622
"subtitle": {
7-
"en": "The Unified JavaScript Toolchain",
8-
"zh": "统一的 JavaScript 工具链"
23+
"en": "Shipping JavaScript Faster",
24+
"zh": "加速 JavaScript 开发"
925
},
1026
"slogan": {
11-
"en": "One CLI, one configuration, one consistent workflow",
12-
"zh": "一个命令行、一份配置、一致的工作流"
27+
"en": "One CLI unifies development, builds, testing, linting, and formatting across all your JavaScript projects. Powered by the Rspack ecosystem.",
28+
"zh": "只需一个 CLI,即可统一所有 JavaScript 项目的开发、构建、测试、代码检查与格式化。由 Rspack 生态驱动。"
1329
},
1430
"unifiedCli": {
1531
"en": "One CLI",
Lines changed: 239 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,241 @@
1-
:global {
2-
.rs-oval {
3-
width: 70% !important;
4-
height: 70% !important;
5-
top: calc(50% + 20px) !important;
6-
left: calc(50% + 5px) !important;
1+
.hero {
2+
--hero-title: #111214;
3+
--hero-title-muted: #747474;
4+
--hero-text: #707174;
5+
--hero-border: #d9dbde;
6+
--hero-command-bg: rgba(255, 255, 255, 0.72);
7+
--hero-link: #686a6d;
8+
--hero-link-hover: #111214;
9+
10+
position: relative;
11+
display: flex;
12+
align-items: center;
13+
justify-content: center;
14+
box-sizing: border-box;
15+
min-height: calc(100svh - var(--rp-nav-height));
16+
padding: clamp(4.5rem, 9vh, 7rem) 2rem;
17+
overflow: hidden;
18+
background: var(--rp-c-bg);
19+
}
20+
21+
:global(.dark) .hero {
22+
--hero-title: #f5f5f5;
23+
--hero-title-muted: #9a9a9a;
24+
--hero-text: #a1a3a6;
25+
--hero-border: #3a3c40;
26+
--hero-command-bg: rgba(255, 255, 255, 0.025);
27+
--hero-link: #a6a8ab;
28+
--hero-link-hover: #f5f5f5;
29+
}
30+
31+
.inner {
32+
display: flex;
33+
flex-direction: column;
34+
align-items: center;
35+
width: 100%;
36+
max-width: 64rem;
37+
text-align: center;
38+
transform: translateY(-1.5rem);
39+
}
40+
41+
.title {
42+
margin: 0;
43+
color: var(--hero-title);
44+
font-size: clamp(3rem, 4.5vw, 4rem);
45+
font-weight: 600;
46+
line-height: 1.1;
47+
letter-spacing: -0.055em;
48+
text-wrap: balance;
49+
50+
span {
51+
display: block;
52+
}
53+
}
54+
55+
.subtitle {
56+
color: var(--hero-title-muted);
57+
}
58+
59+
.description {
60+
max-width: 42rem;
61+
margin: clamp(2.5rem, 5vh, 3.75rem) 0 0;
62+
color: var(--hero-text);
63+
font-size: clamp(1rem, 1.25vw, 1.25rem);
64+
font-weight: 400;
65+
line-height: 1.5;
66+
letter-spacing: -0.02em;
67+
text-wrap: balance;
68+
}
69+
70+
.command {
71+
display: grid;
72+
grid-template-columns: auto minmax(0, 1fr) auto;
73+
align-items: center;
74+
box-sizing: border-box;
75+
width: min(100%, 20rem);
76+
min-height: 3.125rem;
77+
margin-top: clamp(2.75rem, 6vh, 4rem);
78+
padding: 0 0.5rem 0 1rem;
79+
color: var(--hero-title);
80+
text-align: left;
81+
border: 1px solid var(--hero-border);
82+
border-radius: 0.5rem;
83+
background: var(--hero-command-bg);
84+
85+
code,
86+
.prompt {
87+
font-family:
88+
ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', monospace;
89+
font-size: clamp(0.8125rem, 1vw, 0.9375rem);
90+
line-height: 1.4;
91+
}
92+
93+
code {
94+
padding: 0 0.625rem;
95+
overflow: hidden;
96+
color: inherit;
97+
text-overflow: ellipsis;
98+
white-space: nowrap;
99+
background: transparent;
100+
}
101+
}
102+
103+
.prompt {
104+
color: var(--hero-text);
105+
}
106+
107+
.copyButton {
108+
display: inline-flex;
109+
align-items: center;
110+
justify-content: center;
111+
width: 2rem;
112+
height: 2rem;
113+
padding: 0;
114+
color: var(--hero-text);
115+
cursor: pointer;
116+
border: 0;
117+
border-radius: 0.5rem;
118+
background: transparent;
119+
transition:
120+
color 0.2s ease,
121+
background-color 0.2s ease;
122+
123+
svg {
124+
width: 1.125rem;
125+
height: 1.125rem;
126+
}
127+
128+
&:hover {
129+
color: var(--hero-link-hover);
130+
background: color-mix(in srgb, var(--hero-title) 7%, transparent);
131+
}
132+
133+
&:focus-visible {
134+
outline: 2px solid var(--rp-c-brand);
135+
outline-offset: 2px;
136+
}
137+
}
138+
139+
.links {
140+
display: flex;
141+
flex-wrap: wrap;
142+
align-items: center;
143+
justify-content: center;
144+
gap: 1rem 2.5rem;
145+
margin-top: clamp(2rem, 4vh, 2.5rem);
146+
}
147+
148+
.link {
149+
display: inline-flex;
150+
align-items: center;
151+
gap: 0.5rem;
152+
color: var(--hero-link);
153+
font-size: 0.9375rem;
154+
font-weight: 500;
155+
line-height: 1.5;
156+
text-decoration: none;
157+
transition: color 0.2s ease;
158+
159+
svg {
160+
width: 0.75rem;
161+
height: 0.75rem;
162+
transition: transform 0.2s ease;
163+
}
164+
165+
&:hover {
166+
color: var(--hero-link-hover);
167+
168+
svg {
169+
transform: translateX(0.2rem);
170+
}
171+
}
172+
173+
&:focus-visible {
174+
border-radius: 0.25rem;
175+
outline: 2px solid var(--rp-c-brand);
176+
outline-offset: 4px;
177+
}
178+
}
179+
180+
@media (max-width: 640px) {
181+
.hero {
182+
min-height: calc(100svh - var(--rp-nav-height));
183+
padding: 4rem 1.25rem;
184+
}
185+
186+
.title {
187+
font-size: clamp(2.25rem, 10vw, 2.75rem);
188+
line-height: 1.04;
189+
letter-spacing: -0.055em;
190+
}
191+
192+
.description {
193+
max-width: 28rem;
194+
margin-top: 2rem;
195+
font-size: 0.9375rem;
196+
line-height: 1.55;
197+
}
198+
199+
.command {
200+
min-height: 3.25rem;
201+
margin-top: 2.25rem;
202+
padding: 0 0.5rem 0 0.875rem;
203+
border-radius: 0.625rem;
204+
205+
code,
206+
.prompt {
207+
font-size: 0.8125rem;
208+
}
209+
210+
code {
211+
padding: 0 0.625rem;
212+
}
213+
}
214+
215+
.copyButton {
216+
width: 2rem;
217+
height: 2rem;
218+
219+
svg {
220+
width: 1.125rem;
221+
height: 1.125rem;
222+
}
223+
}
224+
225+
.links {
226+
gap: 1rem 1.75rem;
227+
margin-top: 2rem;
228+
}
229+
230+
.link {
231+
font-size: 0.875rem;
232+
}
233+
}
234+
235+
@media (prefers-reduced-motion: reduce) {
236+
.copyButton,
237+
.link,
238+
.link svg {
239+
transition: none;
7240
}
8241
}

‎website/theme/components/Hero.tsx‎

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,76 @@
1-
import { useI18n, useNavigate } from '@rspress/core/runtime';
2-
import { Hero as BaseHero } from '@rstack-dev/doc-ui/hero';
1+
import { useI18n } from '@rspress/core/runtime';
2+
import {
3+
IconArrowRight,
4+
IconCopy,
5+
IconSuccess,
6+
Link,
7+
SvgWrapper,
8+
copyToClipboard,
9+
} from '@rspress/core/theme-original';
10+
import { useCallback, useEffect, useRef, useState } from 'react';
311
import { useI18nUrl } from './utils';
4-
import './Hero.module.scss';
12+
import styles from './Hero.module.scss';
13+
14+
const createCommand = 'pnpm create rstack';
15+
const githubUrl = 'https://github.com/rstackjs/rstack-cli';
516

617
export function Hero() {
7-
const navigate = useNavigate();
818
const tUrl = useI18nUrl();
919
const t = useI18n<typeof import('i18n')>();
20+
const [copied, setCopied] = useState(false);
21+
const resetTimer = useRef<number | undefined>(undefined);
22+
23+
const handleCopy = useCallback(async () => {
24+
const copiedSuccessfully = await copyToClipboard(createCommand);
25+
26+
if (!copiedSuccessfully) {
27+
return;
28+
}
29+
30+
setCopied(true);
31+
window.clearTimeout(resetTimer.current);
32+
resetTimer.current = window.setTimeout(() => setCopied(false), 1600);
33+
}, []);
34+
35+
useEffect(() => () => window.clearTimeout(resetTimer.current), []);
1036

1137
return (
12-
<BaseHero
13-
showStars
14-
onClickGetStarted={() => navigate(tUrl('/guide/quick-start'))}
15-
title="Rstack CLI"
16-
subTitle={t('subtitle')}
17-
description={t('slogan')}
18-
getStartedButtonText={t('quickStart')}
19-
githubURL="https://github.com/rstackjs/rstack-cli"
20-
/>
38+
<section className={styles.hero} aria-labelledby="home-hero-title">
39+
<div className={styles.inner}>
40+
<h1 id="home-hero-title" className={styles.title}>
41+
<span>{t('title')}</span>
42+
<span className={styles.subtitle}>{t('subtitle')}</span>
43+
</h1>
44+
45+
<p className={styles.description}>{t('slogan')}</p>
46+
47+
<div className={styles.command}>
48+
<span className={styles.prompt} aria-hidden="true">
49+
$
50+
</span>
51+
<code>{createCommand}</code>
52+
<button
53+
type="button"
54+
className={styles.copyButton}
55+
aria-label={copied ? t('copiedCommand') : t('copyCommand')}
56+
title={copied ? t('copiedCommand') : t('copyCommand')}
57+
onClick={() => void handleCopy()}
58+
>
59+
<SvgWrapper icon={copied ? IconSuccess : IconCopy} />
60+
</button>
61+
</div>
62+
63+
<div className={styles.links}>
64+
<Link className={styles.link} href={tUrl('/guide/quick-start')}>
65+
<span>{t('quickStart')}</span>
66+
<SvgWrapper icon={IconArrowRight} />
67+
</Link>
68+
<a className={styles.link} href={githubUrl} target="_blank" rel="noopener noreferrer">
69+
<span>{t('viewSource')}</span>
70+
<SvgWrapper icon={IconArrowRight} />
71+
</a>
72+
</div>
73+
</div>
74+
</section>
2175
);
2276
}

‎website/theme/components/ToolStack.tsx‎

Lines changed: 0 additions & 13 deletions
This file was deleted.

‎website/theme/index.scss‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
}
1414
}
1515

16+
body:has(#home-hero-title) .rp-nav {
17+
border-bottom: none;
18+
}
19+
1620
.rspress-logo {
1721
height: 1.8rem;
1822
}

0 commit comments

Comments
 (0)