Skip to content

Commit 9b3207b

Browse files
committed
feat(ui): redesign homepage, Team and Users pages with bilingual polish
- New homepage: animated graph-network hero, quick-start terminal, feature and capability cards, layered ecosystem, and CTA (shared EN/CN component) - Restyle Team and Users pages (stylized hero, metrics, member/case cards) - Global design system in custom.css (tokens, dark mode, responsive, motion) - Locale-aware footer; refine EN/CN copy and localize CN data labels - Fix Kapa Ask AI launcher logo to a root-relative path
1 parent 7662b8a commit 9b3207b

12 files changed

Lines changed: 2579 additions & 586 deletions

File tree

docusaurus.config.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,10 @@ const docsCnVersionOptions = {
5959
// repository variable after the HugeGraph Kapa setup is finalized.
6060
const defaultKapaWebsiteId = '0b277570-4740-451e-96fa-1e4ac1ac5e88';
6161
const kapaWebsiteId = (process.env.KAPA_WEBSITE_ID || defaultKapaWebsiteId).trim();
62+
// Root-relative so the launcher logo resolves against whichever origin serves
63+
// the page (e.g. hugegraph.staged.apache.org or hugegraph.apache.org), rather
64+
// than a hardcoded production URL that 404s before the site is promoted.
65+
const kapaProjectLogo = (process.env.KAPA_PROJECT_LOGO || '/img/hugegraph-logo.svg').trim();
6266
const kapaWidgetScripts = kapaWebsiteId
6367
? [
6468
{
@@ -68,7 +72,7 @@ const kapaWidgetScripts = kapaWebsiteId
6872
'data-project-name': 'Apache HugeGraph',
6973
'data-project-color': '#b32025',
7074
'data-project-color-dark': '#ff7a7e',
71-
'data-project-logo': 'https://hugegraph.apache.org/img/hugegraph-logo.svg',
75+
'data-project-logo': kapaProjectLogo,
7276
'data-modal-title': 'Apache HugeGraph AI Assistant',
7377
'data-launcher-button-text': 'Ask AI',
7478
'data-modal-open-on-command-k': 'true',
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import React from 'react';
2+
import clsx from 'clsx';
3+
4+
// Fixed (deterministic) node/edge layout so server and client markup match
5+
// and there is no hydration mismatch. Coordinates are in a 0..100 viewBox.
6+
const NODES = [
7+
[10, 22], [24, 12], [20, 46], [38, 30], [33, 64],
8+
[50, 18], [52, 48], [48, 78], [66, 34], [63, 66],
9+
[78, 20], [80, 52], [76, 80], [90, 38], [92, 70],
10+
[14, 78], [30, 88], [62, 12], [88, 12], [44, 50],
11+
];
12+
13+
const EDGES = [
14+
[0, 1], [0, 2], [1, 3], [2, 3], [2, 4], [3, 5], [3, 6],
15+
[4, 7], [5, 6], [5, 17], [6, 8], [6, 9], [7, 9], [8, 10],
16+
[8, 11], [9, 11], [9, 12], [10, 13], [11, 13], [11, 14],
17+
[12, 14], [4, 15], [15, 16], [16, 7], [17, 18], [8, 19],
18+
[6, 19], [3, 19],
19+
];
20+
21+
const SPARSE_NODE_COUNT = 12;
22+
23+
export function GraphField({className, sparse = false}) {
24+
const nodes = sparse ? NODES.slice(0, SPARSE_NODE_COUNT) : NODES;
25+
const edges = sparse
26+
? EDGES.filter(([a, b]) => a < SPARSE_NODE_COUNT && b < SPARSE_NODE_COUNT)
27+
: EDGES;
28+
29+
return (
30+
<div className={clsx('hgGraphField', className)} aria-hidden="true">
31+
<svg
32+
className="hgGraphField__svg"
33+
viewBox="0 0 100 100"
34+
preserveAspectRatio="xMidYMid slice"
35+
xmlns="http://www.w3.org/2000/svg">
36+
<g className="hgGraphField__edges">
37+
{edges.map(([a, b], i) => (
38+
<line
39+
key={`e${i}`}
40+
x1={nodes[a][0]}
41+
y1={nodes[a][1]}
42+
x2={nodes[b][0]}
43+
y2={nodes[b][1]}
44+
className="hgGraphField__edge"
45+
style={{animationDelay: `${(i % 8) * 0.45}s`}}
46+
/>
47+
))}
48+
</g>
49+
<g className="hgGraphField__nodes">
50+
{nodes.map(([x, y], i) => (
51+
<circle
52+
key={`n${i}`}
53+
cx={x}
54+
cy={y}
55+
r={i % 5 === 0 ? 1.5 : 1}
56+
className="hgGraphField__node"
57+
style={{animationDelay: `${(i % 6) * 0.6}s`}}
58+
/>
59+
))}
60+
</g>
61+
</svg>
62+
</div>
63+
);
64+
}
65+
66+
export default GraphField;
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
import React from 'react';
2+
3+
function CopyIcon(props) {
4+
return (
5+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}>
6+
<rect x="9" y="9" width="11" height="11" rx="2" stroke="currentColor" strokeWidth="1.7" />
7+
<path
8+
d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"
9+
stroke="currentColor"
10+
strokeWidth="1.7"
11+
strokeLinecap="round"
12+
/>
13+
</svg>
14+
);
15+
}
16+
17+
function CheckIcon(props) {
18+
return (
19+
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" aria-hidden="true" {...props}>
20+
<path d="m5 12.5 4.5 4.5L19 7" stroke="currentColor" strokeWidth="1.9" strokeLinecap="round" strokeLinejoin="round" />
21+
</svg>
22+
);
23+
}
24+
25+
export function TerminalBlock({title, lines, plain, copyLabel = 'Copy', copiedLabel = 'Copied'}) {
26+
const [copied, setCopied] = React.useState(false);
27+
const timerRef = React.useRef(null);
28+
29+
React.useEffect(() => () => timerRef.current && clearTimeout(timerRef.current), []);
30+
31+
function onCopy() {
32+
const text = plain || lines.map((line) => line.text).join('\n');
33+
const done = () => {
34+
setCopied(true);
35+
timerRef.current && clearTimeout(timerRef.current);
36+
timerRef.current = setTimeout(() => setCopied(false), 2000);
37+
};
38+
if (navigator.clipboard && navigator.clipboard.writeText) {
39+
navigator.clipboard.writeText(text).then(done).catch(() => {});
40+
} else {
41+
done();
42+
}
43+
}
44+
45+
return (
46+
<div className="hgTerminal">
47+
<div className="hgTerminal__bar">
48+
<span className="hgTerminal__dots" aria-hidden="true">
49+
<span className="hgTerminal__dot" />
50+
<span className="hgTerminal__dot" />
51+
<span className="hgTerminal__dot" />
52+
</span>
53+
<span className="hgTerminal__title">{title}</span>
54+
<button
55+
type="button"
56+
className="hgTerminal__copy"
57+
onClick={onCopy}
58+
aria-label={copied ? copiedLabel : copyLabel}>
59+
{copied ? <CheckIcon /> : <CopyIcon />}
60+
<span>{copied ? copiedLabel : copyLabel}</span>
61+
</button>
62+
</div>
63+
<pre className="hgTerminal__body">
64+
<code>
65+
{lines.map((line, i) => {
66+
if (line.type === 'blank') {
67+
return <span className="hgTerminal__line" key={i}>{'\u00a0'}</span>;
68+
}
69+
return (
70+
<span className={`hgTerminal__line hgTerminal__line--${line.type}`} key={i}>
71+
{line.type === 'cmd' && <span className="hgTerminal__prompt">$ </span>}
72+
{line.text}
73+
</span>
74+
);
75+
})}
76+
</code>
77+
</pre>
78+
</div>
79+
);
80+
}
81+
82+
export default TerminalBlock;

src/components/HomePage/icons.js

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
import React from 'react';
2+
3+
const base = {
4+
width: 24,
5+
height: 24,
6+
viewBox: '0 0 24 24',
7+
fill: 'none',
8+
xmlns: 'http://www.w3.org/2000/svg',
9+
'aria-hidden': true,
10+
focusable: false,
11+
};
12+
13+
const stroke = {
14+
stroke: 'currentColor',
15+
strokeWidth: 1.7,
16+
strokeLinecap: 'round',
17+
strokeLinejoin: 'round',
18+
};
19+
20+
export function IconDatabase(props) {
21+
return (
22+
<svg {...base} {...props}>
23+
<ellipse cx="12" cy="5.5" rx="7" ry="2.8" {...stroke} />
24+
<path d="M5 5.5v6c0 1.55 3.13 2.8 7 2.8s7-1.25 7-2.8v-6" {...stroke} />
25+
<path d="M5 11.5v6c0 1.55 3.13 2.8 7 2.8s7-1.25 7-2.8v-6" {...stroke} />
26+
</svg>
27+
);
28+
}
29+
30+
export function IconCompute(props) {
31+
return (
32+
<svg {...base} {...props}>
33+
<circle cx="6" cy="6" r="2.2" {...stroke} />
34+
<circle cx="18" cy="6" r="2.2" {...stroke} />
35+
<circle cx="12" cy="18" r="2.2" {...stroke} />
36+
<path d="M7.6 7.6 11 15.8M16.4 7.6 13 15.8M8.2 6h7.6" {...stroke} />
37+
</svg>
38+
);
39+
}
40+
41+
export function IconAI(props) {
42+
return (
43+
<svg {...base} {...props}>
44+
<path d="M12 3v3M12 18v3M3 12h3M18 12h3" {...stroke} />
45+
<rect x="7.5" y="7.5" width="9" height="9" rx="2.4" {...stroke} />
46+
<circle cx="12" cy="12" r="1.8" {...stroke} />
47+
</svg>
48+
);
49+
}
50+
51+
export function IconApi(props) {
52+
return (
53+
<svg {...base} {...props}>
54+
<path d="M8 7 4 12l4 5M16 7l4 5-4 5" {...stroke} />
55+
<path d="M13.5 5.5 10.5 18.5" {...stroke} />
56+
</svg>
57+
);
58+
}
59+
60+
export function IconQuery(props) {
61+
return (
62+
<svg {...base} {...props}>
63+
<circle cx="10.5" cy="10.5" r="6.2" {...stroke} />
64+
<path d="m15.3 15.3 4 4" {...stroke} />
65+
<path d="M8 10.5h5M10.5 8v5" {...stroke} />
66+
</svg>
67+
);
68+
}
69+
70+
export function IconDistributed(props) {
71+
return (
72+
<svg {...base} {...props}>
73+
<rect x="3.5" y="3.5" width="7" height="7" rx="1.6" {...stroke} />
74+
<rect x="13.5" y="3.5" width="7" height="7" rx="1.6" {...stroke} />
75+
<rect x="8.5" y="13.5" width="7" height="7" rx="1.6" {...stroke} />
76+
</svg>
77+
);
78+
}
79+
80+
export function IconVisualize(props) {
81+
return (
82+
<svg {...base} {...props}>
83+
<circle cx="6" cy="8" r="2.1" {...stroke} />
84+
<circle cx="17" cy="6" r="2.1" {...stroke} />
85+
<circle cx="15" cy="17" r="2.1" {...stroke} />
86+
<circle cx="7" cy="16" r="1.6" {...stroke} />
87+
<path d="m7.8 8.8 7.3-1.4M7.4 14.6l6.2 1.2M7.7 9.6l6.4 6" {...stroke} />
88+
</svg>
89+
);
90+
}
91+
92+
export function IconScale(props) {
93+
return (
94+
<svg {...base} {...props}>
95+
<path d="M12 3v18" {...stroke} />
96+
<path d="M5 8h14M7 8l-3 5h6zM17 8l-3 5h6z" {...stroke} />
97+
<path d="M8 20h8" {...stroke} />
98+
</svg>
99+
);
100+
}
101+
102+
export function IconArrowRight(props) {
103+
return (
104+
<svg {...base} {...props}>
105+
<path d="M5 12h13M13 6l6 6-6 6" {...stroke} />
106+
</svg>
107+
);
108+
}
109+
110+
export function IconGithub(props) {
111+
return (
112+
<svg {...base} {...props} fill="currentColor">
113+
<path d="M12 2C6.48 2 2 6.58 2 12.25c0 4.53 2.87 8.37 6.84 9.73.5.1.68-.22.68-.49l-.01-1.9c-2.78.62-3.37-1.2-3.37-1.2-.46-1.18-1.11-1.5-1.11-1.5-.91-.64.07-.62.07-.62 1 .07 1.53 1.06 1.53 1.06.9 1.56 2.36 1.11 2.93.85.09-.66.35-1.11.63-1.37-2.22-.26-4.55-1.14-4.55-5.06 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 5 0c1.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.93-2.34 4.79-4.57 5.05.36.32.68.94.68 1.9l-.01 2.82c0 .27.18.59.69.49A10.04 10.04 0 0 0 22 12.25C22 6.58 17.52 2 12 2Z" />
114+
</svg>
115+
);
116+
}
117+
118+
export function IconSpark(props) {
119+
return (
120+
<svg {...base} {...props}>
121+
<path d="M12 3.5 13.6 9 19 10.5 13.6 12 12 17.5 10.4 12 5 10.5 10.4 9z" {...stroke} />
122+
</svg>
123+
);
124+
}
125+
126+
export function IconBook(props) {
127+
return (
128+
<svg {...base} {...props}>
129+
<path d="M4 5.5C4 4.7 4.7 4 5.5 4H11v15.5H5.5c-.8 0-1.5-.7-1.5-1.5z" {...stroke} />
130+
<path d="M20 5.5c0-.8-.7-1.5-1.5-1.5H13v15.5h5.5c.8 0 1.5-.7 1.5-1.5z" {...stroke} />
131+
</svg>
132+
);
133+
}
134+
135+
export function IconUsers(props) {
136+
return (
137+
<svg {...base} {...props}>
138+
<circle cx="9" cy="8" r="3" {...stroke} />
139+
<path d="M3.5 19a5.5 5.5 0 0 1 11 0" {...stroke} />
140+
<path d="M16 5.2a3 3 0 0 1 0 5.6M17 13.5a5.5 5.5 0 0 1 3.5 5.1" {...stroke} />
141+
</svg>
142+
);
143+
}
144+
145+
export function IconCommit(props) {
146+
return (
147+
<svg {...base} {...props}>
148+
<circle cx="12" cy="12" r="3.2" {...stroke} />
149+
<path d="M3 12h5.8M15.2 12H21" {...stroke} />
150+
</svg>
151+
);
152+
}
153+
154+
export function IconBuilding(props) {
155+
return (
156+
<svg {...base} {...props}>
157+
<rect x="5" y="3.5" width="14" height="17" rx="1.6" {...stroke} />
158+
<path d="M9 7.5h2M13 7.5h2M9 11h2M13 11h2M9 14.5h2M13 14.5h2M10 20.5v-3h4v3" {...stroke} />
159+
</svg>
160+
);
161+
}
162+
163+
export function IconGlobe(props) {
164+
return (
165+
<svg {...base} {...props}>
166+
<circle cx="12" cy="12" r="8.5" {...stroke} />
167+
<path d="M3.5 12h17M12 3.5c2.5 2.4 2.5 14.6 0 17M12 3.5c-2.5 2.4-2.5 14.6 0 17" {...stroke} />
168+
</svg>
169+
);
170+
}
171+
172+
export function IconExternal(props) {
173+
return (
174+
<svg {...base} {...props}>
175+
<path d="M14 5h5v5M19 5l-8 8M17 13.5V18a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 5 18V9a1.5 1.5 0 0 1 1.5-1.5H11" {...stroke} />
176+
</svg>
177+
);
178+
}

0 commit comments

Comments
 (0)