diff --git a/.gitignore b/.gitignore index b83b61a..40348e4 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,9 @@ vite.config.ts.timestamp-*.mjs # ===== Large source image archives (use individual optimized files instead) ===== *.zip public/*.zip + +# ===== Local-only media masters (never deployed) ===== +# 视频/大图母版留档目录:不提交、也不放进 public/, +# 否则 Vite 会把整个目录拷进 dist/,让每次部署多传上百 MB。 +# 线上播放走 OSS 上压好的网页版,见 src/components/CompanyVideo.tsx。 +media/ diff --git a/index.html b/index.html index c048c29..771f7a1 100644 --- a/index.html +++ b/index.html @@ -7,27 +7,27 @@ - - + + - - + + + + 443 and +# serve both names from 443, keeping the apex -> www redirect there as well: +# server { +# listen 443 ssl http2; +# server_name baskbuddy.com; +# include /path/to/ssl.conf; +# return 301 https://www.baskbuddy.com$request_uri; +# } # server { # listen 443 ssl http2; -# server_name baskbuddy.com www.baskbuddy.com; +# server_name www.baskbuddy.com; # root /var/www/baskbuddy/dist; # include /path/to/ssl.conf; # location / { try_files $uri $uri/ /index.html; } diff --git a/package-lock.json b/package-lock.json index 3874301..68e84ee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "tailwind-merge": "^3.0.0" }, "devDependencies": { + "@types/node": "^24.13.3", "@types/react": "^18.3.31", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^4.3.4", @@ -1014,9 +1015,8 @@ "version": "24.13.3", "resolved": "https://registry.npmmirror.com/@types/node/-/node-24.13.3.tgz", "integrity": "sha512-Dh8vAsV36ig5wa9OX4pXvMc9D3Veibfw2wix0CUwYODLD8nkj9UsLjASr49nPg+2eKzxhBV+v7L8pXvT4e639Q==", + "devOptional": true, "license": "MIT", - "optional": true, - "peer": true, "dependencies": { "undici-types": "~7.18.0" } @@ -1840,9 +1840,8 @@ "version": "7.18.2", "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz", "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==", - "license": "MIT", - "optional": true, - "peer": true + "devOptional": true, + "license": "MIT" }, "node_modules/update-browserslist-db": { "version": "1.2.3", diff --git a/package.json b/package.json index 7f2b27c..a993544 100644 --- a/package.json +++ b/package.json @@ -6,17 +6,20 @@ "scripts": { "dev": "vite", "build": "vite build", + "typecheck": "tsc -b", "preview": "vite preview" }, "dependencies": { "@tailwindcss/vite": "^4.1.0", "clsx": "^2.1.1", + "lucide-react": "^1.25.0", "react": "^18.3.1", "react-dom": "^18.3.1", "react-router-dom": "^6.28.0", "tailwind-merge": "^3.0.0" }, "devDependencies": { + "@types/node": "^24.13.3", "@types/react": "^18.3.31", "@types/react-dom": "^18.3.7", "@vitejs/plugin-react": "^4.3.4", diff --git a/public/robots.txt b/public/robots.txt index 5851e5e..1127c63 100644 --- a/public/robots.txt +++ b/public/robots.txt @@ -1,5 +1,4 @@ User-agent: * Allow: / -Disallow: /api/ -Sitemap: https://baskbuddy.com/sitemap.xml +Sitemap: https://www.baskbuddy.com/sitemap.xml diff --git a/public/sitemap.xml b/public/sitemap.xml new file mode 100644 index 0000000..4ab3a54 --- /dev/null +++ b/public/sitemap.xml @@ -0,0 +1,244 @@ + + + + https://www.baskbuddy.com/ + 2026-09-20 + weekly + 1.0 + + + + + + + https://www.baskbuddy.com/en + 2026-09-20 + weekly + 1.0 + + + + + + + https://www.baskbuddy.com/hk + 2026-09-20 + weekly + 1.0 + + + + + + + https://www.baskbuddy.com/about + 2026-09-20 + monthly + 0.8 + + + + + + + https://www.baskbuddy.com/en/about + 2026-09-20 + monthly + 0.8 + + + + + + + https://www.baskbuddy.com/hk/about + 2026-09-20 + monthly + 0.8 + + + + + + + https://www.baskbuddy.com/business + 2026-09-20 + monthly + 0.9 + + + + + + + https://www.baskbuddy.com/en/business + 2026-09-20 + monthly + 0.9 + + + + + + + https://www.baskbuddy.com/hk/business + 2026-09-20 + monthly + 0.9 + + + + + + + https://www.baskbuddy.com/news + 2026-09-20 + weekly + 0.8 + + + + + + + https://www.baskbuddy.com/en/news + 2026-09-20 + weekly + 0.8 + + + + + + + https://www.baskbuddy.com/hk/news + 2026-09-20 + weekly + 0.8 + + + + + + + https://www.baskbuddy.com/sustainability + 2026-09-20 + monthly + 0.7 + + + + + + + https://www.baskbuddy.com/en/sustainability + 2026-09-20 + monthly + 0.7 + + + + + + + https://www.baskbuddy.com/hk/sustainability + 2026-09-20 + monthly + 0.7 + + + + + + + https://www.baskbuddy.com/investors + 2026-09-20 + monthly + 0.7 + + + + + + + https://www.baskbuddy.com/en/investors + 2026-09-20 + monthly + 0.7 + + + + + + + https://www.baskbuddy.com/hk/investors + 2026-09-20 + monthly + 0.7 + + + + + + + https://www.baskbuddy.com/contact + 2026-09-20 + monthly + 0.8 + + + + + + + https://www.baskbuddy.com/en/contact + 2026-09-20 + monthly + 0.8 + + + + + + + https://www.baskbuddy.com/hk/contact + 2026-09-20 + monthly + 0.8 + + + + + + + https://www.baskbuddy.com/privacy + 2026-09-20 + yearly + 0.3 + + + + + + + https://www.baskbuddy.com/en/privacy + 2026-09-20 + yearly + 0.3 + + + + + + + https://www.baskbuddy.com/hk/privacy + 2026-09-20 + yearly + 0.3 + + + + + + diff --git a/src/components/About.tsx b/src/components/About.tsx index 3eccb44..f5caf72 100644 --- a/src/components/About.tsx +++ b/src/components/About.tsx @@ -1,13 +1,17 @@ +import { useEffect, useRef, useState } from 'react'; import { getLocalizedPath, getTranslation, type Locale } from '../i18n/utils'; import { cssVars } from './css'; import { Icon, icidpIcons, visionMissionIcons, trackIcons } from '../icons'; import OrgNetwork from './OrgNetwork'; +import MemberProfileModal, { type DetailState, type MemberProfile } from './MemberProfileModal'; +import { loadTeamDetails } from '../i18n/teamDetails'; export default function About({ locale }: { locale: Locale }) { const t = getTranslation(locale); const a = t.about; const icidp = a.icidp; - const zh = locale !== 'en'; + // 三语辅助:繁体页面必须走 hk 文案,不能并进简体分支(与 Business/Sustainability 同一写法) + const tr = (zh: string, hk: string, en: string) => (locale === 'en' ? en : locale === 'hk' ? hk : zh); const business = t.business; const icidpItems = [ @@ -26,9 +30,109 @@ export default function About({ locale }: { locale: Locale }) { { key: 'vision', s: a.subnav.vision, href: a.subnav.vision.href }, ]; - // 管理团队头像:英文取姓名首字母,中文/繁体显示全名 + // 管理团队人像位:英文取姓名首字母缩写,中文/繁体取首字(无照片时的品牌渐变占位) const memberInitial = (name: string) => - locale === 'en' ? name.split(' ').filter(Boolean).map((w) => w[0]).join('') : name; + locale === 'en' ? name.split(' ').filter(Boolean).map((w) => w[0]).join('') : name[0]; + + // ===== 管理团队轮播(参照数字 Nexus「06 | Team」) ===== + const teamMembers = a.subnav.team.groups.flatMap((g) => + g.members.map((m) => ({ ...m, group: g.title })), + ); + const n = teamMembers.length; + const [teamIdx, setTeamIdx] = useState(0); + const [teamPaused, setTeamPaused] = useState(false); + const current = teamMembers[teamIdx]; + // 成员详情弹窗:存「成员对象快照」而不是索引——轮播会自动换人,索引会跟着漂 + const [profile, setProfile] = useState(null); + // 长简介是异步来的(单独 chunk,见 i18n/teamDetails/),头部照常立刻渲染 + const [profileDetail, setProfileDetail] = useState({ status: 'loading' }); + const profileTriggerRef = useRef(null); + // 同一次弹窗对应一个 token:加载期间关掉、或转头点开别人时,迟到的结果直接丢弃 + const profileTokenRef = useRef(0); + + // 预取失败不必声张:失败不会进缓存,点开时还会再拉一次并走弹窗里的错误文案 + const prefetchDetails = () => { + loadTeamDetails(locale).catch(() => {}); + }; + + const openProfile = (m: (typeof teamMembers)[number]) => { + if (!m.detailId) return; + const token = ++profileTokenRef.current; + setProfile({ + name: m.name, + role: m.role, + group: m.group, + photo: m.photo, + initials: memberInitial(m.name), + nationality: m.nationality, + detailId: m.detailId, + }); + setProfileDetail({ status: 'loading' }); + loadTeamDetails(locale) + .then((map) => { + if (profileTokenRef.current !== token) return; + setProfileDetail({ status: 'ready', detail: map[m.detailId!] }); + }) + .catch(() => { + if (profileTokenRef.current !== token) return; + // 失败不写缓存(见 teamDetails/index.ts),关掉重开会重新拉一次 + setProfileDetail({ status: 'error' }); + }); + }; + + // 自动轮播:4s 递进;prefers-reduced-motion 用户不自动播;悬停/键盘聚焦时暂停; + // 弹窗打开时也暂停(否则 4s 一到,弹窗里的成员和还焦点的按钮会一起被换掉) + useEffect(() => { + if (teamPaused || profile) return; + if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; + const timer = window.setInterval(() => setTeamIdx((i) => (i + 1) % n), 4000); + return () => window.clearInterval(timer); + }, [teamPaused, profile, n]); + + const closeProfile = () => { + profileTokenRef.current++; // 作废在途的简介加载,避免它回来时把状态写回已关闭的弹窗 + setProfile(null); + // 焦点还给触发按钮。html{scroll-behavior:smooth} 会把默认的聚焦滚动做成动画, + // 故 preventScroll;按钮若已随路由卸载则跳过。 + window.requestAnimationFrame(() => { + const el = profileTriggerRef.current; + if (el?.isConnected) el.focus({ preventScroll: true }); + }); + }; + + // 人像卡:有 photo 用照片,否则回落为品牌渐变 + 姓名首字;showInfo 控制底部姓名条(仅当前卡展示) + const renderPortrait = (m: (typeof teamMembers)[number], showInfo = false) => ( + <> + {m.photo ? ( + {showInfo + ) : ( + + )} + {showInfo && ( + + {m.name} + {m.role} + + )} + + ); // 业务版图:复用 business.tracks(五大赛道),描述取自 home.tracks,顺序一致 const trackKeys = Object.keys(t.home.tracks); // ['social','career','payment','ai','lifeService'] @@ -57,7 +161,7 @@ export default function About({ locale }: { locale: Locale }) {
{it.s.title}

{it.s.desc}

- {zh ? '了解更多' : 'Learn more'} + {tr('了解更多', '了解更多', 'Learn more')} @@ -82,10 +186,10 @@ export default function About({ locale }: { locale: Locale }) {
{a.subnav.company.title} -

{zh ? '公司概览' : 'Company Overview'}

+

{tr('公司概览', '公司概覽', 'Company Overview')}

- {zh ? '点击节点,展开了解巴适伙伴' : 'Tap a node to explore BaskBuddy'} + {tr('点击节点,展开了解巴适伙伴', '點擊節點,展開了解巴適夥伴', 'Tap a node to explore BaskBuddy')}

@@ -102,7 +206,7 @@ export default function About({ locale }: { locale: Locale }) {
- {zh ? 'BUSINESS LANDSCAPE' : 'BUSINESS LANDSCAPE'} + BUSINESS LANDSCAPE

{business.title}

{business.subtitle}

@@ -122,9 +226,15 @@ export default function About({ locale }: { locale: Locale }) {

{track.name}

{desc}

- {track.products.length} 款产品 + + {tr( + `${track.products.length} 款产品`, + `${track.products.length} 款產品`, + `${track.products.length} ${track.products.length === 1 ? 'product' : 'products'}`, + )} + - {zh ? '查看' : 'View'} + {tr('查看', '查看', 'View')}
@@ -139,29 +249,29 @@ export default function About({ locale }: { locale: Locale }) {
{a.subnav.vision.title} -

{zh ? '愿景及使命' : 'Vision & Mission'}

+

{tr('愿景及使命', '願景及使命', 'Vision & Mission')}

-
VISION · 愿景
+
{tr('VISION · 愿景', 'VISION · 願景', 'VISION')}

{a.visionMission.vision}

-
MISSION · 使命
+
{tr('MISSION · 使命', 'MISSION · 使命', 'MISSION')}

{a.visionMission.mission}

-
PURPOSE · 初心
+
{tr('PURPOSE · 初心', 'PURPOSE · 初心', 'PURPOSE')}

{a.visionMission.purpose}

-
CULTURE · 文化
+
{tr('CULTURE · 文化', 'CULTURE · 文化', 'CULTURE')}

{a.visionMission.culture}

@@ -183,7 +293,7 @@ export default function About({ locale }: { locale: Locale }) {

{item.c.label}

{item.c.position} - {zh ? '点击查看' : 'Tap to flip'} + {tr('点击查看', '點擊查看', 'Tap to flip')}
- {/* ===== 管理团队 #about-team ===== */} -
-
- {a.subnav.team.title} -

{a.subnav.team.title}

-

{a.subnav.team.desc}

-
-
- {a.subnav.team.members.map((m, i) => { - const defaultAvatar = m.gender === 'female' - ? 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg' - : m.gender === 'male' - ? 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' - : null; - const avatar = m.photo || defaultAvatar; - return ( -
-
+ + {/* 成员详情弹窗(原生 + showModal,渲染在 top layer) */} + {profile && ( + + )} ); } diff --git a/src/components/Business.tsx b/src/components/Business.tsx index 2eab66a..71d5baa 100644 --- a/src/components/Business.tsx +++ b/src/components/Business.tsx @@ -16,7 +16,6 @@ export default function Business({ locale }: { locale: Locale }) { const t = getTranslation(locale); const b = t.business; const inv = t.innovation; - const isZh = locale !== 'en'; const tr = (zh: string, hk: string, en: string) => (locale === 'en' ? en : locale === 'hk' ? hk : zh); return ( @@ -55,11 +54,13 @@ export default function Business({ locale }: { locale: Locale }) {
ECOSYSTEM -

{isZh ? '数字生活生态全景图' : 'Digital Life Ecosystem Map'}

+

{tr('数字生活生态全景图', '數字生活生態全景圖', 'Digital Life Ecosystem Map')}

- {isZh - ? '同一组产品,两种视角——按赛道看我们造什么,按受众看我们为谁。' - : 'One product family, two lenses — by track for what we build, by audience for who we serve.'} + {tr( + '同一组产品,两种视角——按赛道看我们造什么,按受众看我们为谁。', + '同一組產品,兩種視角——按賽道看我們造什麼,按受眾看我們為誰。', + 'One product family, two lenses — by track for what we build, by audience for who we serve.', + )}

@@ -77,11 +78,11 @@ export default function Business({ locale }: { locale: Locale }) { -

{track.name}

+

{track.name}

{String(idx + 1).padStart(2, '0')} / {String(b.tracks.length).padStart(2, '0')}
-

+

{trackTaglines[idx][locale]}

@@ -185,15 +186,15 @@ export default function Business({ locale }: { locale: Locale }) { {/* ===== CTA Band ===== */}
-

- {locale === 'en' ? 'Build the digital life ecosystem with BaskBuddy' : '与巴适伙伴,共建数字生活生态'} +

+ {tr('与巴适伙伴,共建数字生活生态', '與巴適夥伴,共建數字生活生態', 'Build the digital life ecosystem with BaskBuddy')}

diff --git a/src/components/CompanyVideo.tsx b/src/components/CompanyVideo.tsx new file mode 100644 index 0000000..10b02d0 --- /dev/null +++ b/src/components/CompanyVideo.tsx @@ -0,0 +1,113 @@ +import { getTranslation, type Locale } from '../i18n/utils'; + +/* ===================================================================== + * 公司宣传视频展示模块(首页第二屏) + * + * 版式:左栏通栏品牌色面板承载文案,右栏为「INTRODUCTION」+ 视频画面。 + * 播放策略:直接展示浏览器原生控制条(
- {/* ===== 最新资讯(腾讯式三列卡片) ===== */} -
- -
+ {/* ===== 公司宣传视频(紧跟首屏,进入即第二屏) ===== */} + - {/* ===== 大文案 + 向下滚动指示(腾讯式) ===== */} -
-
-

+ {/* ===== 大文案 + 向下滚动指示(腾讯式) + 浅灰 band 承接蓝色 INTRODUCTION 与黑底支柱区:蓝 → 浅灰 → 黑,节奏过渡更顺 ===== */} +

+
+

{tr( 'BaskBuddy improves the quality of life for people around the world through innovative technology, products and services.', '巴適夥伴透過創新的科技、產品和服務,提升全球各地人們的生活品質。', '巴适伙伴通过创新的科技、产品和服务,提升全球各地人们的生活品质。', )}

-
-
- SCROLL - - +
+ SCROLL + + +
@@ -336,7 +107,7 @@ export default function Home({ locale }: { locale: Locale }) { />
0{i + 1} -

+

{p.name}

{p.desc}

@@ -349,11 +120,14 @@ export default function Home({ locale }: { locale: Locale }) { ))}
- {/* ===== 愿景带 + 巴适速度 大数字 ===== */} + {/* ===== 愿景带 + 愿景性指标 ===== + 只留「全球 / 长期」两个愿景指标:5 大核心赛道 / 8+ 自研产品 已由上方 + CompanyVideo 左栏(home.video.intro)讲述,此处不再重复。 + 两格用 .stat-band--pair 在桌面端保持 2 列(默认 .stat-band 会排 4 列,空两格)。 */}
OUR VISION -

+

{tr('Value for Users, Tech for Good', '用戶為本 · 科技向善', '用户为本 · 科技向善')}

@@ -365,12 +139,12 @@ export default function Home({ locale }: { locale: Locale }) {

-
+
{stats.map((sItem, idx) => (
- {sItem.value} + {sItem.value} {sItem.suffix && {sItem.suffix}}
{sItem.label}
@@ -384,7 +158,7 @@ export default function Home({ locale }: { locale: Locale }) { {/* ===== CTA Band ===== */}
-

{t.contact.subtitle}

+

{t.contact.subtitle}

{tr( 'Whether for a job inquiry or business cooperation, we look forward to meeting you with warmth.', diff --git a/src/components/Investors.tsx b/src/components/Investors.tsx index b30d30d..3ac9cae 100644 --- a/src/components/Investors.tsx +++ b/src/components/Investors.tsx @@ -31,7 +31,7 @@ export default function Investors({ locale }: { locale: Locale }) {

- {sItem.value} + {sItem.value} {sItem.suffix && {sItem.suffix}}
{sItem.label}
@@ -129,7 +129,7 @@ export default function Investors({ locale }: { locale: Locale }) { {ir.contact.eyebrow} -

{ir.contact.title}

+

{ir.contact.title}

{ir.contact.desc}

diff --git a/src/components/Mascot.tsx b/src/components/Mascot.tsx index e07df8e..76a0937 100644 --- a/src/components/Mascot.tsx +++ b/src/components/Mascot.tsx @@ -31,54 +31,54 @@ const MASCOT: Record = { ], }, scroll: [ - { zh: '五大赛道都在这里,想深入了解的话我可以带你跳过去~', hk: '五大赛道都在這裡,想深入了解的話我可以帶你跳過去~', en: 'All five tracks are here — let me jump you straight to whichever you like!' }, - { zh: '慢慢看,不急,好东西值得多停留~', hk: '慢慢看,不急,好东西值得多停留~', en: 'Take your time — good things deserve a second glance.' }, + { zh: '五大赛道都在这里,想深入了解的话我可以带你跳过去~', hk: '五大賽道都在這裡,想深入了解的話我可以帶你跳過去~', en: 'All five tracks are here — let me jump you straight to whichever you like!' }, + { zh: '慢慢看,不急,好东西值得多停留~', hk: '慢慢看,不急,好東西值得多停留~', en: 'Take your time — good things deserve a second glance.' }, ], idle: [ - { zh: '在想什么呢?要不要我给你讲讲巴适伙伴?', hk: '在想什麼呢?要不要我给你讲讲巴适伙伴?', en: 'Deep in thought? Want me to walk you through BaskBuddy?' }, - { zh: '别累着自己哦,喝口水歇会儿~', hk: '别累着自己哦,喝口水歇会儿~', en: 'Take a breather — grab a sip of water!' }, + { zh: '在想什么呢?要不要我给你讲讲巴适伙伴?', hk: '在想什麼呢?要不要我給你講講巴適夥伴?', en: 'Deep in thought? Want me to walk you through BaskBuddy?' }, + { zh: '别累着自己哦,喝口水歇会儿~', hk: '別累著自己哦,喝口水歇會兒~', en: 'Take a breather — grab a sip of water!' }, ], }, '/about': { enter: { mood: 'think', msgs: [ - { zh: '想了解我们是怎么来的吗?我慢慢讲给你听~', hk: '想了解我们是怎么来的吗?我慢慢讲给你听~', en: "Curious how we started? Let me tell you the story." }, - { zh: '我们叫「巴适伙伴」,就是想让你的生活更巴适。', hk: '我们叫「巴适伙伴」,就是想让你的生活更巴适。', en: "Our name means 'comfy companion' — we want every day to feel easy." }, - { zh: '往下翻,看看我们背后的几个巴适小伙伴~', hk: '往下翻,看看我们背后的几个巴适小伙伴~', en: "Scroll down to meet the comfy buddies behind everything." }, + { zh: '想了解我们是怎么来的吗?我慢慢讲给你听~', hk: '想了解我們是怎麼來的嗎?我慢慢講給你聽~', en: "Curious how we started? Let me tell you the story." }, + { zh: '我们叫「巴适伙伴」,就是想让你的生活更巴适。', hk: '我們叫「巴適夥伴」,就是想讓你的生活更巴適。', en: "Our name means 'comfy companion' — we want every day to feel easy." }, + { zh: '往下翻,看看我们背后的几个巴适小伙伴~', hk: '往下翻,看看我們背後的幾個巴適小夥伴~', en: "Scroll down to meet the comfy buddies behind everything." }, ], }, scroll: [ - { zh: 'ICIDP 五个字是我们的灵魂——点每张小卡都可以翻面看哦~', hk: 'ICIDP 五个字是我们的灵魂——点每张小卡都可以翻面看哦~', en: "ICIDP is our soul — tap each little card to flip it!" }, + { zh: 'ICIDP 五个字是我们的灵魂——点每张小卡都可以翻面看哦~', hk: 'ICIDP 五個字是我們的靈魂——點每張小卡都可以翻面看哦~', en: "ICIDP is our soul — tap each little card to flip it!" }, ], idle: [ - { zh: '对哪块感兴趣?点卡片,它们会"翻身"告诉你更多~', hk: '对哪块感兴趣?点卡片,它们会「翻身」告诉你更多~', en: "Curious about any part? The cards will 'flip' and tell you more!" }, + { zh: '对哪块感兴趣?点卡片,它们会"翻身"告诉你更多~', hk: '對哪塊感興趣?點卡片,它們會「翻身」告訴你更多~', en: "Curious about any part? The cards will 'flip' and tell you more!" }, ], }, '/business': { enter: { mood: 'cheer', msgs: [ - { zh: '业务大盘点!先看看我们的数字生活生态全景图~', hk: '业务大盘点!先看看我们的数字生活生态全景图~', en: 'Business time! Check the Digital Life Ecosystem Map first.' }, - { zh: '产品卡可以翻面哦,点一下就能看到完整画像~', hk: '产品卡可以翻面哦,点一下就能看到完整画像~', en: "Product cards can flip — tap one to see the full profile!" }, + { zh: '业务大盘点!先看看我们的数字生活生态全景图~', hk: '業務大盤點!先看看我們的數字生活生態全景圖~', en: 'Business time! Check the Digital Life Ecosystem Map first.' }, + { zh: '产品卡可以翻面哦,点一下就能看到完整画像~', hk: '產品卡可以翻面哦,點一下就能看到完整畫像~', en: "Product cards can flip — tap one to see the full profile!" }, ], }, flip: [ - { zh: '哇!这张卡翻面了——每个产品都有点意思对吧?', hk: '哇!这张卡翻面了——每个产品都有点意思对吧?', en: "Whoa, flipped! Every product has its own little story, right?" }, - { zh: '我超喜欢这一面的!定位、人群、生态都清清楚楚~', hk: '我超喜欢这一面的!定位、人群、生态都清清楚楚~', en: 'I love this side — role, users, ecosystem, all laid out!' }, + { zh: '哇!这张卡翻面了——每个产品都有点意思对吧?', hk: '哇!這張卡翻面了——每個產品都有點意思對吧?', en: "Whoa, flipped! Every product has its own little story, right?" }, + { zh: '我超喜欢这一面的!定位、人群、生态都清清楚楚~', hk: '我超喜歡這一面的!定位、人群、生態都清清楚楚~', en: 'I love this side — role, users, ecosystem, all laid out!' }, ], eco: [ - { zh: '全景图可以两种视角切换——按赛道或按受众,试试另一种!', hk: '全景图可以两种视角切换——按赛道或按受众,试试另一种!', en: 'Two views on the map — by track and by audience. Try switching!' }, + { zh: '全景图可以两种视角切换——按赛道或按受众,试试另一种!', hk: '全景圖可以兩種視角切換——按賽道或按受眾,試試另一種!', en: 'Two views on the map — by track and by audience. Try switching!' }, ], idle: [ - { zh: '五大赛道,选一个你感兴趣的深入看看?', hk: '五大赛道,選一個你感興趣的深入看看?', en: 'Five tracks — pick one that sparks your interest!' }, + { zh: '五大赛道,选一个你感兴趣的深入看看?', hk: '五大賽道,選一個你感興趣的深入看看?', en: 'Five tracks — pick one that sparks your interest!' }, ], }, '/news': { enter: { mood: 'think', msgs: [ - { zh: '新闻中心还在铺地基,很快就有好消息啦~', hk: '新闻中心还在铺地基,很快就有好消息啦~', en: 'The newsroom is under construction — good news coming soon!' }, + { zh: '新闻中心还在铺地基,很快就有好消息啦~', hk: '新聞中心還在鋪地基,很快就有好消息啦~', en: 'The newsroom is under construction — good news coming soon!' }, ], }, }, @@ -95,8 +95,8 @@ const MASCOT: Record = { enter: { mood: 'cheer', msgs: [ - { zh: '长期主义投资者的小天地!顺便看看我们的业务版图~', hk: '长期主义投资者的小天地!顺便看看我们的业务版图~', en: 'Long-termist\'s corner — while you’re here, check the business map too!' }, - { zh: '我们不是讲故事,是在造生态——点那个蓝色链接看看~', hk: '我们不是讲故事,是在造生态——点那个蓝色链接看看~', en: 'We’re not just telling a story — we’re building an ecosystem.' }, + { zh: '长期主义投资者的小天地!顺便看看我们的业务版图~', hk: '長期主義投資者的小天地!順便看看我們的業務版圖~', en: 'Long-termist\'s corner — while you’re here, check the business map too!' }, + { zh: '我们不是讲故事,是在造生态——点那个蓝色链接看看~', hk: '我們不是講故事,是在造生態——點那個藍色連結看看~', en: 'We’re not just telling a story — we’re building an ecosystem.' }, ], }, }, @@ -104,8 +104,8 @@ const MASCOT: Record = { enter: { mood: 'wave', msgs: [ - { zh: '想投递简历或谈合作?邮箱在这儿,复制就能发!', hk: '想投递简历或谈合作?邮箱在这儿,复制就能发!', en: 'Apply or pitch? Copy the email and you’re set!' }, - { zh: '找不到路?别急,我帮你指路~', hk: '找不到路?别急,我帮你指路~', en: "Lost? No worries, let me point you the way." }, + { zh: '想投递简历或谈合作?邮箱在这儿,复制就能发!', hk: '想投遞簡歷或談合作?郵箱在這兒,複製就能發!', en: 'Apply or pitch? Copy the email and you’re set!' }, + { zh: '找不到路?别急,我帮你指路~', hk: '找不到路?別急,我幫你指路~', en: "Lost? No worries, let me point you the way." }, ], }, }, @@ -113,7 +113,7 @@ const MASCOT: Record = { enter: { mood: 'think', msgs: [ - { zh: '放心,你的信息我替你守得严严实实。', hk: '放心,你的信息我替你守得严严实实。', en: "Relax — your data is locked up safe with me." }, + { zh: '放心,你的信息我替你守得严严实实。', hk: '放心,你的信息我替你守得嚴嚴實實。', en: "Relax — your data is locked up safe with me." }, ], }, }, @@ -121,7 +121,7 @@ const MASCOT: Record = { enter: { mood: 'wave', msgs: [ - { zh: '哎呀,这条小路好像没铺好~我带你回主页?', hk: '哎呀,这条小路好像没铺好~我带你回主页?', en: "Oops, this path isn't built yet — shall I walk you home?" }, + { zh: '哎呀,这条小路好像没铺好~我带你回主页?', hk: '哎呀,這條小路好像沒鋪好~我帶你回主頁?', en: "Oops, this path isn't built yet — shall I walk you home?" }, ], }, }, @@ -387,17 +387,24 @@ export default function Mascot() { {/* 车头灯(暖琥珀黄,像车灯发光) */} - {/* 风挡(车窗,圆角更柔) */} - - {/* 眼白(稍大,更水灵) */} - - - {/* 眼珠(增大+下偏,看着下方用户,更灵动) */} - - + {/* 风挡(车窗,圆角更柔)——浅色玻璃 + 细描边。 + 原为 #1A1A1A 实心黑窗,横在眼睛上会被看成"戴黑眼罩",故改浅玻璃。 */} + + {/* 眼珠(深色,直接落在浅玻璃上;不再需要眼白托底) */} + + {/* 高光(左上小白点,让眼睛有神) */} - - + + {/* 微笑 */} {/* 车轮 */} diff --git a/src/components/MemberProfileModal.tsx b/src/components/MemberProfileModal.tsx new file mode 100644 index 0000000..98b6561 --- /dev/null +++ b/src/components/MemberProfileModal.tsx @@ -0,0 +1,178 @@ +import { useEffect, useRef } from 'react'; +import { Icon } from '@/icons'; +import type { MemberDetail, TeamDetailId } from '@/i18n/zh'; + +/** 弹窗展示的成员快照。刻意做成「对象快照」而不是索引: + 轮播的 4s 自动切换会让 teamMembers[teamIdx] 换人,弹窗内容必须钉住点击那一刻的人。 + 不含长简介——那是异步加载的,走下面的 DetailState。 */ +export type MemberProfile = { + name: string; + role: string; + group: string; + photo?: string; + /** 无照片时的占位字(About 的 memberInitial 算好传进来) */ + initials: string; + nationality?: string; + detailId: TeamDetailId; +}; + +/** 长简介的加载状态。弹窗头部(姓名/岗位/国籍)不依赖它,所以点开即有内容, + 正文区先占位、加载完再填——长简介在单独的 chunk 里,见 i18n/teamDetails/。 */ +export type DetailState = + | { status: 'loading' } + | { status: 'ready'; detail: MemberDetail } + | { status: 'error' }; + +type Labels = { + close: string; + nationalityLabel: string; + introLabel: string; + strengthsLabel: string; + traitsLabel: string; + loadingLabel: string; + errorLabel: string; +}; + +/** 成员详情弹窗。 + * 用原生 + showModal() 而不是 portal + 手写遮罩: + * · top layer 渲染天然逃出 .page-enter / .scroll-reveal 的 transform 包含块 + * (否则 fixed 定位会以那个祖先为基准、跟着页面滚); + * · 自带 Esc 关闭、焦点陷阱、背景 inert,省掉 portal / z-index 军备竞赛; + * · 仍是 DOM 子树里的节点,字体与字色自动继承 RootLayout(portal 到 body 会丢)。 */ +export default function MemberProfileModal({ + member, + detail, + labels, + onClose, +}: { + member: MemberProfile; + detail: DetailState; + labels: Labels; + onClose: () => void; +}) { + const dialogRef = useRef(null); + const titleId = 'member-profile-title'; + + useEffect(() => { + const el = dialogRef.current; + if (!el) return; + // StrictMode 下 effect 会跑两遍,对已 open 的 dialog 再调 showModal() 会抛 InvalidStateError + if (!el.open) el.showModal(); + // 焦点落在弹窗容器而非关闭按钮上,屏读先念标题 + el.focus(); + + // 锁背景滚动。body 上已有 overflow-x:hidden 与安全区内边距,所以用 class 而不是内联 + // style,避免把 padding 覆盖掉;--sbw 补偿滚动条消失的宽度,防止锁定瞬间横向抖动。 + document.body.style.setProperty( + '--sbw', + `${Math.max(0, window.innerWidth - document.documentElement.clientWidth)}px`, + ); + document.body.classList.add('is-modal-open'); + + return () => { + document.body.classList.remove('is-modal-open'); + document.body.style.removeProperty('--sbw'); + if (el.open) el.close(); + }; + }, []); + + return ( + { + e.preventDefault(); + onClose(); + }} + // 点遮罩关闭:::backdrop 不是 DOM 节点,点到它时 target 就是 dialog 本身; + // 弹窗内容由 head + body 两段铺满,所以只有遮罩会命中这个判断。 + onMouseDown={(e) => { + if (e.target === dialogRef.current) onClose(); + }} + > +
+
+ {member.photo ? ( + + ) : ( + + )} +
+ +
+
+ {member.group} +
+

+ {member.name} +

+
{member.role}
+ {/* 国籍标签:英文国名比中文长得多,nowrap 防止在窄屏被拆成两行。 + 国籍不随长简介一起异步加载,所以这个标签点开即在。 */} + {member.nationality && ( + + {labels.nationalityLabel} · {member.nationality} + + )} +
+ + +
+ +
+ {detail.status === 'ready' ? ( + <> +
+

{labels.introLabel}

+

{detail.detail.intro}

+
+
+

{labels.strengthsLabel}

+

{detail.detail.strengths}

+
+
+

{labels.traitsLabel}

+

{detail.detail.traits}

+
+ + ) : ( + // aria-live:加载完成/失败时读屏会念出变化,而不是静默换掉正文。 + // min-h 只是给占位一个下限,免得弹窗先矮后高跳一下——不能写进 + // .member-modal-body 的 min-height,那条被 flex 收缩用着(见 global.css)。 +

+ {detail.status === 'loading' ? labels.loadingLabel : labels.errorLabel} +

+ )} +
+
+ ); +} diff --git a/src/components/News.tsx b/src/components/News.tsx index 716b9be..0c280db 100644 --- a/src/components/News.tsx +++ b/src/components/News.tsx @@ -1,5 +1,6 @@ import { getLocalizedPath, getTranslation, type Locale } from '../i18n/utils'; -import { PLACEHOLDER_IMG } from '../placeholder'; +import { cssVars } from './css'; +import { Icon } from '../icons'; export default function News({ locale }: { locale: Locale }) { const t = getTranslation(locale); @@ -30,8 +31,21 @@ export default function News({ locale }: { locale: Locale }) { href={getLocalizedPath('/news', locale)} className="track-card group overflow-hidden flex flex-col scroll-reveal" > -
- {item.title} + {/* 图片位:OSS 无对应素材,改用品牌渐变 + 图标占位(写法同 Contact 的团队图集) */} +
+ + + {item.category} diff --git a/src/components/NotFound.tsx b/src/components/NotFound.tsx index 157f634..077a8b8 100644 --- a/src/components/NotFound.tsx +++ b/src/components/NotFound.tsx @@ -3,7 +3,7 @@ import { Icon, navIcons } from '../icons'; export default function NotFound({ locale }: { locale: Locale }) { const t = getTranslation(locale); - const isZh = locale !== 'en'; + const tr = (zh: string, hk: string, en: string) => (locale === 'en' ? en : locale === 'hk' ? hk : zh); return (
@@ -15,14 +15,16 @@ export default function NotFound({ locale }: { locale: Locale }) {
-

- {isZh ? '走丢了' : 'Lost in Space'} +

+ {tr('走丢了', '走丟了', 'Lost in Space')}

- {isZh - ? '抱歉,您访问的页面不存在或已被移动。别担心,让我们带您回到温暖的主路。' - : 'Sorry, the page you are looking for does not exist or has been moved. Let us take you back home.'} + {tr( + '抱歉,您访问的页面不存在或已被移动。别担心,让我们带您回到温暖的主路。', + '抱歉,您訪問的頁面不存在或已被移動。別擔心,讓我們帶您回到溫暖的主路。', + 'Sorry, the page you are looking for does not exist or has been moved. Let us take you back home.', + )}

diff --git a/src/components/Privacy.tsx b/src/components/Privacy.tsx index 18307ae..58d4bec 100644 --- a/src/components/Privacy.tsx +++ b/src/components/Privacy.tsx @@ -1,55 +1,45 @@ import type { Locale } from '../i18n/utils'; -export default function Privacy({ locale }: { locale: Locale }) { - const isZh = locale !== 'en'; - - return ( -
-
-
-

- {isZh ? '隐私政策' : 'Privacy Policy'} -

-

- {isZh ? '巴适伙伴(上海)科技有限公司 · 最后更新:2026年7月' : 'BaskBuddy (Shanghai) Technology Co., Ltd. · Last updated: July 2026'} -

-
-
- -
-
-
- {isZh ? ( -
-
-

一、信息收集范围

-

{`我们仅在您主动提供的情况下收集以下个人信息: +/* ===================================================================== + * 隐私政策页(三语) + * + * 正文按语言拆成三份常量再取用,而不是在 JSX 里写 `isZh ? A : B`: + * 繁体分支必须逐字给出,否则 hk 会落到简体分支上(本项目 hk 的既有约定是 + * 「简体正文的繁体字形转换」,用词维持大陆写法:隱私/郵箱/聯繫/視頻, + * 见 src/i18n/hk.ts 的既有译法),不是港式本地化。 + * ===================================================================== */ + +const ZH_BODY = ( + <> +

+

一、信息收集范围

+

{`我们仅在您主动提供的情况下收集以下个人信息: · 求职咨询:姓名、邮箱、联系电话、意向职位、留言内容 · 商务合作:公司名称、联系人、邮箱、合作类型、留言内容 我们不会通过任何隐蔽手段收集您的个人信息。`}

-
-
-

二、信息使用目的

-

{`收集的信息仅用于以下目的: +

+
+

二、信息使用目的

+

{`收集的信息仅用于以下目的: · 处理您的求职申请或商务合作咨询 · 在必要时与您取得联系 · 改善我们的服务质量 我们不会将您的个人信息用于未经您同意的其他目的。`}

-
-
-

三、信息存储与保护

-

{`· 您的个人信息存储于中国境内的安全服务器 +

+
+

三、信息存储与保护

+

{`· 您的个人信息存储于中国境内的安全服务器 · 我们采用业界标准的安全措施保护您的数据 · 我们不会将您的个人信息传输至境外 · 数据保留期限:咨询信息保留不超过2年,期满后自动删除`}

-
-
-

四、您的权利

-

{`根据《个人信息保护法》,您享有以下权利: +

+
+

四、您的权利

+

{`根据《个人信息保护法》,您享有以下权利: · 查询权:随时查询我们持有的您的个人信息 · 更正权:要求更正不准确的信息 @@ -57,53 +47,111 @@ export default function Privacy({ locale }: { locale: Locale }) { · 撤回同意权:撤回此前给予的授权同意 行使以上权利,请发送邮件至:baskbuddy@163.com`}

-
-
-

五、未成年人保护

-

{`我们高度重视未成年人个人信息的保护。根据《中华人民共和国个人信息保护法》第三十一条,不满十八周岁未成年人的个人信息属于敏感个人信息。我们不会专门以未成年人为服务对象,也不主动收集不满十八周岁未成年人的个人信息。 +

+
+

五、未成年人保护

+

{`我们高度重视未成年人个人信息的保护。根据《中华人民共和国个人信息保护法》第三十一条,不满十八周岁未成年人的个人信息属于敏感个人信息。我们不会专门以未成年人为服务对象,也不主动收集不满十八周岁未成年人的个人信息。 如我们发现在未经其监护人同意的情况下收集了此类信息,将立即删除相关记录,并通知其监护人。`}

-
-
-

六、联系方式

-

{`如有任何隐私相关问题,请联系: +

+
+

六、联系方式

+

{`如有任何隐私相关问题,请联系: 巴适伙伴(上海)科技有限公司 邮箱:baskbuddy@163.com 地址:上海市静安区康宁路288弄2号410室`}

-
-
- ) : ( -
-
-

1. Information Collection Scope

-

{`We only collect the following personal information when you proactively provide it: +

+ +); + +const HK_BODY = ( + <> +
+

一、信息收集範圍

+

{`我們僅在您主動提供的情況下收集以下個人信息: + +· 求職咨詢:姓名、郵箱、聯繫電話、意向職位、留言內容 +· 商務合作:公司名稱、聯繫人、郵箱、合作類型、留言內容 + +我們不會通過任何隱蔽手段收集您的個人信息。`}

+
+
+

二、信息使用目的

+

{`收集的信息僅用於以下目的: + +· 處理您的求職申請或商務合作咨詢 +· 在必要時與您取得聯繫 +· 改善我們的服務質量 + +我們不會將您的個人信息用於未經您同意的其他目的。`}

+
+
+

三、信息存儲與保護

+

{`· 您的個人信息存儲於中國境內的安全服務器 +· 我們採用業界標準的安全措施保護您的數據 +· 我們不會將您的個人信息傳輸至境外 +· 數據保留期限:咨詢信息保留不超過2年,期滿後自動刪除`}

+
+
+

四、您的權利

+

{`根據《個人信息保護法》,您享有以下權利: + +· 查詢權:隨時查詢我們持有的您的個人信息 +· 更正權:要求更正不準確的信息 +· 刪除權:要求我們刪除您的個人信息 +· 撤回同意權:撤回此前給予的授權同意 + +行使以上權利,請發送郵件至:baskbuddy@163.com`}

+
+
+

五、未成年人保護

+

{`我們高度重視未成年人個人信息的保護。根據《中華人民共和國個人信息保護法》第三十一條,不滿十八周歲未成年人的個人信息屬於敏感個人信息。我們不會專門以未成年人為服務對象,也不主動收集不滿十八周歲未成年人的個人信息。 + +如我們發現在未經其監護人同意的情況下收集了此類信息,將立即刪除相關記錄,並通知其監護人。`}

+
+
+

六、聯繫方式

+

{`如有任何隱私相關問題,請聯繫: + +巴適夥伴(上海)科技有限公司 +郵箱:baskbuddy@163.com +地址:上海市靜安區康寧路288弄2號410室`}

+
+ +); + +const EN_BODY = ( + <> +
+

1. Information Collection Scope

+

{`We only collect the following personal information when you proactively provide it: · Job inquiries: name, email, phone, desired position, message · Business cooperation: company name, contact person, email, cooperation type, message We do not collect your personal information through any hidden means.`}

-
-
-

2. Purpose of Information Use

-

{`The collected information is used only for the following purposes: +

+
+

2. Purpose of Information Use

+

{`The collected information is used only for the following purposes: · Processing your job application or business inquiry · Contacting you when necessary · Improving our service quality We will not use your personal information for purposes beyond your consent.`}

-
-
-

3. Information Storage & Protection

-

{`· Your personal information is stored on secure servers within China +

+
+

3. Information Storage & Protection

+

{`· Your personal information is stored on secure servers within China · We use industry-standard security measures to protect your data · We do not transfer your personal information overseas · Data retention: inquiry information is retained for up to 2 years, then automatically deleted`}

-
-
-

4. Your Rights

-

{`Under the Personal Information Protection Law, you have the following rights: +

+
+

4. Your Rights

+

{`Under the Personal Information Protection Law, you have the following rights: · Right to query: request information we hold about you at any time · Right to correct: request correction of inaccurate information @@ -111,23 +159,52 @@ We will not use your personal information for purposes beyond your consent.`}

-
-
-

5. Protection of Minors

-

{`We attach great importance to the protection of minors' personal information. Under Article 31 of the Personal Information Protection Law of the People's Republic of China, the personal information of minors under the age of 18 is classified as sensitive personal information. We do not specifically target minors, nor do we actively collect personal information from minors under 18. +

+
+

5. Protection of Minors

+

{`We attach great importance to the protection of minors' personal information. Under Article 31 of the Personal Information Protection Law of the People's Republic of China, the personal information of minors under the age of 18 is classified as sensitive personal information. We do not specifically target minors, nor do we actively collect personal information from minors under 18. If we discover that such information has been collected without the guardian's consent, we will promptly delete the relevant records and notify the guardian.`}

-
-
-

6. Contact Information

-

{`For any privacy-related questions, please contact: +

+
+

6. Contact Information

+

{`For any privacy-related questions, please contact: BaskBuddy (Shanghai) Technology Co., Ltd. Email: baskbuddy@163.com Address: Room 410, No.2, Lane 288, Kangning Road, Jing'an District, Shanghai, China`}

-
-
- )} +
+ +); + +const TITLE: Record = { zh: '隐私政策', hk: '隱私政策', en: 'Privacy Policy' }; + +const BYLINE: Record = { + zh: '巴适伙伴(上海)科技有限公司 · 最后更新:2026年7月', + hk: '巴適夥伴(上海)科技有限公司 · 最後更新:2026年7月', + en: 'BaskBuddy (Shanghai) Technology Co., Ltd. · Last updated: July 2026', +}; + +export default function Privacy({ locale }: { locale: Locale }) { + return ( +
+
+
+

+ {TITLE[locale]} +

+

+ {BYLINE[locale]} +

+
+
+ +
+
+
+
+ {locale === 'en' ? EN_BODY : locale === 'hk' ? HK_BODY : ZH_BODY} +
diff --git a/src/components/Sustainability.tsx b/src/components/Sustainability.tsx index 712ad03..af56a4f 100644 --- a/src/components/Sustainability.tsx +++ b/src/components/Sustainability.tsx @@ -79,12 +79,12 @@ export default function Sustainability({ locale }: { locale: Locale }) {
-

{p.title}

+

{p.title}

{p.enName}
{p.metric} @@ -187,8 +187,12 @@ export default function Sustainability({ locale }: { locale: Locale }) { {/* ===== CTA ===== */}
-

- {locale === 'en' ? 'Build a sustainable future with BaskBuddy' : '与巴适伙伴,共创可持续未来'} +

+ {locale === 'en' + ? 'Build a sustainable future with BaskBuddy' + : locale === 'hk' + ? '與巴適夥伴,共創可持續未來' + : '与巴适伙伴,共创可持续未来'}

diff --git a/src/hooks/useClientEffects.ts b/src/hooks/useClientEffects.ts index 71c078b..20c5726 100644 --- a/src/hooks/useClientEffects.ts +++ b/src/hooks/useClientEffects.ts @@ -7,10 +7,12 @@ import { useEffect } from 'react'; // Behaviors: // - .flip-card (About.tsx ICIDP cards): click / keyboard toggle + a11y // - .scroll-reveal[-left|-right]: staggered IntersectionObserver reveal -// - .stat-value[data-target]: animated count-up -// - .copy-btn[data-copy]: clipboard copy with label swap -// The old `#star-field` generation is intentionally dropped — no element -// uses it in the React components (dead code in the Astro layout). +// +// The Astro-era `.stat-value[data-target]` count-up and `.copy-btn[data-copy]` +// branches were removed: no element ever emits `data-target` / `.copy-btn` +// (React renders stat values as static text), so both observers watched an +// always-empty NodeList. Same reasoning as the `#star-field` generator that +// was already dropped — the matching CSS came out of global.css in the same pass. export function useClientEffects(pathname: string) { useEffect(() => { // ----- Flip cards ----- @@ -59,73 +61,9 @@ export function useClientEffects(pathname: string) { .querySelectorAll('.scroll-reveal, .scroll-reveal-left, .scroll-reveal-right') .forEach((el) => revealObserver.observe(el)); - // ----- Animated stat counters ----- - const statValues = document.querySelectorAll('.stat-value[data-target]'); - const statObserver = new IntersectionObserver( - (entries) => { - entries.forEach((entry) => { - if (!entry.isIntersecting) return; - const el = entry.target as HTMLElement; - const target = parseFloat(el.getAttribute('data-target') || '0') || 0; - const decimals = parseInt(el.getAttribute('data-decimals') || '0', 10); - const suffix = el.getAttribute('data-suffix') || ''; - const prefix = el.getAttribute('data-prefix') || ''; - const duration = 1600; - const start = performance.now(); - const easeOut = (tt: number) => 1 - Math.pow(1 - tt, 3); - const fmt = (v: number) => - prefix + (decimals ? v.toFixed(decimals) : Math.round(v).toLocaleString('en-US')) + suffix; - const tick = (now: number) => { - const p = Math.min((now - start) / duration, 1); - el.textContent = fmt(target * easeOut(p)); - if (p < 1) requestAnimationFrame(tick); - else el.textContent = fmt(target); - }; - requestAnimationFrame(tick); - statObserver.unobserve(el); - }); - }, - { threshold: 0.4 }, - ); - statValues.forEach((el) => statObserver.observe(el)); - - // ----- Copy-to-clipboard ----- - const copyCleanups: Array<() => void> = []; - document.querySelectorAll('.copy-btn').forEach((btnEl) => { - const onClick = async () => { - const text = btnEl.getAttribute('data-copy') || ''; - const label = btnEl.getAttribute('data-label') || btnEl.textContent || ''; - const copied = btnEl.getAttribute('data-copied') || 'Copied'; - try { - await navigator.clipboard.writeText(text); - } catch (_e) { - const ta = document.createElement('textarea'); - ta.value = text; - ta.style.position = 'fixed'; - ta.style.opacity = '0'; - document.body.appendChild(ta); - ta.select(); - try { - document.execCommand('copy'); - } catch { - /* ignore */ - } - document.body.removeChild(ta); - } - btnEl.textContent = copied; - window.setTimeout(() => { - btnEl.textContent = label; - }, 1500); - }; - btnEl.addEventListener('click', onClick); - copyCleanups.push(() => btnEl.removeEventListener('click', onClick)); - }); - return () => { revealObserver.disconnect(); - statObserver.disconnect(); flipCleanups.forEach((fn) => fn()); - copyCleanups.forEach((fn) => fn()); }; }, [pathname]); } diff --git a/src/i18n/en.ts b/src/i18n/en.ts index f423cac..9593997 100644 --- a/src/i18n/en.ts +++ b/src/i18n/en.ts @@ -1,6 +1,8 @@ +import type { TeamGroup } from './zh'; + export const en = { brand: 'BaskBuddy', - brandSub: 'Bashi Partners', + brandSub: 'BaskBuddy (Shanghai)', nav: { home: 'Home', about: 'About Us', @@ -16,6 +18,28 @@ export const en = { sloganIntro: 'Digital Life Service Provider · Content Social, Career Services, Digital Payment, AI, Life Services', heroTitle: 'Opening the Digital Future through Intelligent Innovation', heroCta: 'Learn More', + video: { + eyebrow: 'INTRODUCTION', + title: 'Inside BaskBuddy', + subtitle: 'A short film on how we connect beautiful life with warm technology.', + intro: [ + { + value: '5', + unit: 'Core Tracks', + desc: 'As the pillars of our digital-life ecosystem, we dive deep into content & social, career services, digital payment, AI and life services — answering real user needs through a multi-brand matrix.', + }, + { + value: '8', + unit: 'In-house Products', + desc: 'As a product network connecting users and life — from youth social and career growth to secure payment and wish fulfillment — covering every digital-life scenario, keeping warmth within reach.', + }, + { + value: '2026', + unit: 'Year of Launch', + desc: 'A new starting point for our promise of "lifelong companionship, lifelong trust" — connecting everyone with warm technology toward a globally leading digital-life ecosystem.', + }, + ], + }, trackOverview: 'Five Core Tracks', enterLab: 'Explore Innovation Lab', enterBusiness: 'View Business Map', @@ -32,8 +56,8 @@ export const en = { title: 'BaskBuddy Velocity', subtitle: 'The solid foundation connecting beautiful life with warm tech', items: [ - { value: '5', suffix: '', label: 'Core Tracks', sub: 'Social · Career · Payment · AI · Life Services' }, - { value: '8', suffix: '+', label: 'Self-built Products', sub: 'Covering the full digital life scene' }, + // Vision-only metrics. The 5 core tracks / 8+ self-built products figures are + // already told by CompanyVideo's left column (home.video.intro) — not repeated here. { value: 'Global', suffix: '', label: 'Service Vision', sub: 'Deliver quality products to users worldwide' }, { value: 'Long-term', suffix: '', label: 'Value Principle', sub: 'Win-win with users, partners, society' }, ], @@ -41,7 +65,7 @@ export const en = { about: { eyebrow: 'ABOUT BASKBUDDY', title: 'Inside BaskBuddy', - text: 'Bashi Partners (Shanghai) Technology Co., Ltd. is a high-tech enterprise focused on the digital life services field. Through a multi-brand, multi-vertical-track product layout covering Content & Social, Career Services, Digital Payment, Artificial Intelligence and Life Services, we connect everyone’s beautiful life with warm technology.', + text: 'BaskBuddy (Shanghai) Technology Co., Ltd. is a high-tech enterprise focused on the digital life services field. Through a multi-brand, multi-vertical-track product layout covering Content & Social, Career Services, Digital Payment, Artificial Intelligence and Life Services, we connect everyone’s beautiful life with warm technology.', cta: 'Learn More', }, pillars: { @@ -61,7 +85,7 @@ export const en = { cta: 'View Business Map', }, footer: { - copyright: `© ${new Date().getFullYear()} Bashi Partners (Shanghai) Technology Co., Ltd.`, + copyright: `© ${new Date().getFullYear()} BaskBuddy (Shanghai) Technology Co., Ltd.`, icidp: 'ICIDP: Integrity · Courage · Innovation · Devotion · Passion', }, }, @@ -92,7 +116,7 @@ export const en = { about: { title: 'About Us', subtitle: 'BaskBuddy — Together for Life', - intro: `Bashi Partners (Shanghai) Technology Co., Ltd. is a high-tech enterprise focused on the digital life services field. Through a multi-brand, multi-vertical-track product layout that delivers quality products and services to diverse user groups, we operate multiple independent subsidiaries specializing in Content & Social (Youthome, CrazyCool, MeLove), Career Services (mydreamjob, Peaksight), Digital Payment (Trustdeal), Artificial Intelligence (AbuAI), and Life Services (HearWish). We always put user needs first, prioritize quality and technological innovation, create growth space for employees, create a better life for users, and create long-term value for society.`, + intro: `BaskBuddy (Shanghai) Technology Co., Ltd. is a high-tech enterprise focused on the digital life services field. Through a multi-brand, multi-vertical-track product layout that delivers quality products and services to diverse user groups, we operate multiple independent subsidiaries specializing in Content & Social (Youthome, CrazyCool, MeLove), Career Services (mydreamjob, Peaksight), Digital Payment (Trustdeal), Artificial Intelligence (AbuAI), and Life Services (HearWish). We always put user needs first, prioritize quality and technological innovation, create growth space for employees, create a better life for users, and create long-term value for society.`, visionMission: { vision: 'Build a world-leading digital life service ecosystem where users of different needs receive quality-adapted product experiences', mission: 'Connect people and services through innovative technology, leveraging diverse vertical products to create convenient, efficient, and valuable beautiful lives for different user groups', @@ -102,7 +126,7 @@ export const en = { icidp: { title: 'ICIDP Core Values', integrity: { label: 'Integrity', position: 'Foundation', dialect: 'Being reliable is the real deal', layers: ['To users: trustworthy product commitments and service quality', 'To partners: transparent win-win cooperation and mutual growth', 'To self: consistency between words and actions'] }, - courage: { label: 'Courage', position: 'Drive', dialect: 'Bold thinking, bold action — that\'s Bashi', layers: ['Brave enough to break technical and cognitive boundaries', 'Brave enough to challenge industry conventions', 'Brave enough to admit mistakes and correct course quickly'] }, + courage: { label: 'Courage', position: 'Drive', dialect: 'Bold thinking, bold action — that\'s the BaskBuddy way', layers: ['Brave enough to break technical and cognitive boundaries', 'Brave enough to challenge industry conventions', 'Brave enough to admit mistakes and correct course quickly'] }, innovation: { label: 'Innovation', position: 'Soul', dialect: 'Being different makes it interesting', layers: ['Tech innovation: beyond code, transforming mindsets', 'Product innovation: defining value from user pain points', 'Org innovation: every member becomes an innovation source'] }, devotion: { label: 'Devotion', position: 'Warmth', dialect: 'Generosity shows true vision', layers: ['Infusing love into every product detail', 'Investing time into every team collaboration', 'Returning value through every social action'] }, passion: { label: 'Passion', position: 'Flame', dialect: 'Passion outlasts the longest days', layers: ['For career: waking up with light in your eyes still', 'For team: everyone is a spark that can ignite others', 'For world: still loving life beyond work hours'] }, @@ -113,15 +137,74 @@ export const en = { team: { title: 'Management Team', desc: 'The core partners driving us forward', + intro: 'We are a specialised, tightly coordinated team led by our Legal Representative, covering full-stack, front-end, back-end and Android development and finance — supporting product R&D and business operations every day.', icon: 'users', href: '#about-team', - members: [ - { name: 'Zhang Shun', role: 'Software Engineer', gender: 'male' }, - { name: 'Zhang Buyun', role: 'Software Engineer', gender: 'female' }, - { name: 'Chen Xueni', role: 'Software Engineer', gender: 'female' }, - { name: 'Chen Manli', role: 'Finance Specialist', gender: 'female' }, - { name: 'Yang Xingxing', role: 'Software Engineer', gender: 'female' }, - ], + // UI copy for the profile dialog. The long member bios are NOT here — they + // live in src/i18n/teamDetails/ so they load as a separate lazy chunk. + ui: { + viewProfile: 'View profile', + close: 'Close', + nationalityLabel: 'Nationality', + introLabel: 'Role', + strengthsLabel: 'Technical strengths', + traitsLabel: 'Personal qualities', + loadingLabel: 'Loading profile…', + errorLabel: 'This profile could not be loaded — close and reopen to try again.', + }, + groups: [ + { + key: 'management', + title: 'Management', + members: [ + { name: 'Chen Pengtao', role: 'Legal Representative', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + ], + }, + { + key: 'engineering', + title: 'Engineering', + members: [ + { name: 'Wu Qiming', role: 'Android Engineer', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + { + name: 'Yang Xingxing', + role: 'Full-Stack Engineer', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: 'China', + detailId: 'yang-xingxing', + }, + { + name: 'Zhang Shun', + role: 'Backend Engineer & Product Manager', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg', + nationality: 'China', + detailId: 'zhang-shun', + }, + { + name: 'Chen Jie', + role: 'Front-End Engineer', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: 'China', + detailId: 'chen-jie', + }, + { + name: 'Zhang Buyun', + role: 'Front-End Engineer & UI Designer', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: 'China', + detailId: 'zhang-buyun', + }, + { name: 'Liu Xiaoqiang', role: 'Backend Engineer', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + { name: 'Li Shibo', role: 'Backend Engineer', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + ], + }, + { + key: 'finance', + title: 'Finance', + members: [ + { name: 'Zhang Mengxiang', role: 'Finance Specialist', photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg' }, + ], + }, + ] satisfies TeamGroup[], }, vision: { title: 'Vision & Mission', desc: 'Where we are going and why we started', icon: 'telescope', href: '#about-vision' }, }, @@ -134,7 +217,10 @@ export const en = { { value: '5', label: 'Core tracks' }, { value: '8', label: 'Products' }, { value: 'Global', label: 'Ecosystem' }, - { value: '2026', label: 'Founded' }, + // 与 zh 的「起航之年」/ home.video.intro 的 'Year of Launch' 对齐。 + // 不要写 'Founded':投资人路线图把 2024—2025 记为「搭骨架」阶段, + // 说 2026 才成立与自家路线图自相矛盾。 + { value: '2026', label: 'Year of Launch' }, ], network: { center: { @@ -145,7 +231,7 @@ export const en = { nodes: [ { id: 'position', title: 'Positioning', desc: 'BaskBuddy (Shanghai) Technology Co., Ltd. is a high-tech enterprise focused on digital lifestyle services, delivering quality products and services to diverse user groups through a multi-brand, multi-vertical product portfolio.' }, { id: 'tracks', title: 'Five Core Tracks', desc: 'Built on five pillars — Content & Social, Career Services, Digital Payment, AI, and Life Services — with 8 self-developed products covering every scenario of users digital life.', link: '/business' }, - { id: 'team', title: 'Management', desc: 'A core team of five partners — Zhang Shun, Zhang Buyun, Chen Xueni, Chen Manli, and Yang Xingxing — focused on software R&D and product innovation.', link: '#about-team' }, + { id: 'team', title: 'Management', desc: 'A core team of nine covering engineering and finance — focused on product R&D and business operations.', link: '#about-team' }, { id: 'vision', title: 'Vision & Mission', desc: 'Vision: build the world-leading digital lifestyle service ecosystem. Mission: connect people and services through innovative technology, creating convenient, efficient, and valuable lives for diverse users.' }, { id: 'data', title: 'Key Facts', desc: '5 core tracks · 8 products · global ecosystem · founded in 2026.' }, { id: 'life', title: 'Connecting Life', desc: 'Treating every ordinary day with care — technology fades into the background, warmth remains.' }, @@ -237,9 +323,9 @@ Let's go — let's journey toward a beautiful future together with BaskBuddy!`, title: 'A group of young people, making BaskBuddy real', subtitle: 'Brainstorms, pair programming, celebrating every small win — striving here has warmth.', items: [ - { img: 'https://oss.baskbuddy.com/baskbuddy/team-brainstorm.jpg', caption: 'Ideas colliding at the whiteboard' }, - { img: 'https://oss.baskbuddy.com/baskbuddy/team-pairing.jpg', caption: 'Pair programming — ideas into code' }, - { img: 'https://oss.baskbuddy.com/baskbuddy/team-celebrate.jpg', caption: 'Every small win deserves a toast' }, + { caption: 'Ideas colliding at the whiteboard' }, + { caption: 'Pair programming — ideas into code' }, + { caption: 'Every small win deserves a toast' }, ], }, growthIncentive: { @@ -404,7 +490,7 @@ Let's go — let's journey toward a beautiful future together with BaskBuddy!`, legal: { privacy: 'Privacy Policy', }, - copyright: `© ${new Date().getFullYear()} Bashi Partners (Shanghai) Technology Co., Ltd.`, + copyright: `© ${new Date().getFullYear()} BaskBuddy (Shanghai) Technology Co., Ltd.`, icidpFull: 'Integrity · Courage · Innovation · Devotion · Passion', }, }; diff --git a/src/i18n/hk.ts b/src/i18n/hk.ts index 8b4e0af..7a147cc 100644 --- a/src/i18n/hk.ts +++ b/src/i18n/hk.ts @@ -1,3 +1,5 @@ +import type { TeamGroup } from './zh'; + export const hk = { brand: 'BaskBuddy', brandSub: '巴適夥伴', @@ -16,6 +18,28 @@ export const hk = { sloganIntro: '覆蓋內容社交 · 職場服務 · 數字支付 · AI · 生活服務的數字生活服務商', heroTitle: '以智能創新開創數字未來', heroCta: '了解我們', + video: { + eyebrow: 'INTRODUCTION', + title: '走進巴適夥伴', + subtitle: '用一段影像,看見我們如何以溫暖科技連接美好生活。', + intro: [ + { + value: '5', + unit: '大核心賽道', + desc: '作為數字生活服務生態的支柱佈局,深耕內容社交、職場服務、數字支付、人工智能與生活服務,以多品牌矩陣回應不同用戶群體的真實需求。', + }, + { + value: '8', + unit: '自研產品', + desc: '作為連接用戶與生活的產品網絡,從青年社交、求職升學,到安全支付與心願達成,覆蓋數字生活全場景,讓溫暖觸手可及。', + }, + { + value: '2026', + unit: '起航之年', + desc: '作為「一生相伴·一生信賴」承諾的新起點,以溫暖科技連接每一個人,穩步邁向全球領先的數字生活服務生態。', + }, + ], + }, trackOverview: '五大核心賽道', enterLab: '探索創新實驗室', enterBusiness: '查看業務版圖', @@ -32,8 +56,8 @@ export const hk = { title: '巴適速度', subtitle: '以溫暖科技,連接美好生活的堅實底座', items: [ - { value: '5', suffix: '大', label: '核心賽道', sub: '內容社交 · 職場服務 · 數字支付 · AI · 生活服務' }, - { value: '8', suffix: '+', label: '自研產品', sub: '覆蓋數字生活全場景' }, + // 只保留願景性指標。5 大核心賽道 / 8+ 自研產品已由上方 CompanyVideo 左欄 + // (home.video.intro)負責講,這裡不再重複第二遍。 { value: '全球', suffix: '', label: '服務願景', sub: '讓優質產品觸達全球用戶' }, { value: '長期', suffix: '', label: '價值主義', sub: '與用戶、夥伴、社會共贏' }, ], @@ -113,15 +137,73 @@ export const hk = { team: { title: '管理團隊', desc: '推動公司前行的核心夥伴', + intro: '我們是一支專業分工、協同高效的團隊,由法人代表領銜,成員覆蓋全棧、前端、後端、安卓開發及財務等方向,持續為產品研發與業務運營提供支持。', icon: 'users', href: '#about-team', - members: [ - { name: '張順', role: '軟件開發工程師', gender: 'male' }, - { name: '張卜允', role: '軟件開發工程師', gender: 'female' }, - { name: '陳雪妮', role: '軟件開發工程師', gender: 'female' }, - { name: '陳曼麗', role: '財務專員', gender: 'female' }, - { name: '楊幸幸', role: '軟件開發工程師', gender: 'female' }, - ], + // 詳情彈窗的 UI 文案(成員長簡介在 src/i18n/teamDetails/,按需載入) + ui: { + viewProfile: '查看詳情', + close: '關閉', + nationalityLabel: '國籍', + introLabel: '崗位介紹', + strengthsLabel: '技術優勢', + traitsLabel: '個人特點', + loadingLabel: '正在載入簡介…', + errorLabel: '簡介暫時載入失敗,關閉後重開可再試一次。', + }, + groups: [ + { + key: 'management', + title: '管理層', + members: [ + { name: '陳蓬濤', role: '法人代表', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + ], + }, + { + key: 'engineering', + title: '技術研發', + members: [ + { name: '吳啟明', role: '安卓開發工程師', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + { + name: '楊幸幸', + role: '全棧開發工程師', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: '中國', + detailId: 'yang-xingxing', + }, + { + name: '張順', + role: '後端開發工程師兼產品經理', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg', + nationality: '中國', + detailId: 'zhang-shun', + }, + { + name: '陳潔', + role: '前端開發工程師', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: '中國', + detailId: 'chen-jie', + }, + { + name: '張卜允', + role: '前端開發工程師兼 UI 設計師', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: '中國', + detailId: 'zhang-buyun', + }, + { name: '劉瀟強', role: '後端開發工程師', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + { name: '李世博', role: '後端開發工程師', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + ], + }, + { + key: 'finance', + title: '財務', + members: [ + { name: '張夢想', role: '財務專員', photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg' }, + ], + }, + ] satisfies TeamGroup[], }, vision: { title: '願景及使命', desc: '我們要去的地方,與為何出發', icon: 'telescope', href: '#about-vision' }, }, @@ -145,7 +227,7 @@ export const hk = { nodes: [ { id: 'position', title: '公司定位', desc: '巴適夥伴(上海)科技有限公司是一家聚焦數字生活服務領域的高新技術企業,依託多品牌、多垂直賽道的產品佈局,面向不同用戶群體提供優質產品與服務。' }, { id: 'tracks', title: '五大核心賽道', desc: '以內容社交、職場服務、數字支付、人工智能、生活服務五大核心賽道為支柱,孵化 8 自研產品,覆蓋用戶數字生活全場景。', link: '/business' }, - { id: 'team', title: '管理團隊', desc: '由張順、張卜允、陳雪妮、陳曼麗、楊幸幸五位夥伴組成的核心團隊,專注軟件研發與產品創新。', link: '#about-team' }, + { id: 'team', title: '管理團隊', desc: '由 9 位夥伴組成的核心團隊,覆蓋技術研發與財務,專注產品研發與業務運營。', link: '#about-team' }, { id: 'vision', title: '願景使命', desc: '願景:打造全球領先的數字生活服務生態。使命:以創新技術連接人與服務,為不同用戶群體打造便捷高效、富有價值的美好生活。' }, { id: 'data', title: '關鍵數據', desc: '5 大核心賽道 · 8 自研產品 · 全球生態佈局 · 2026 啟航之年。' }, { id: 'life', title: '連接用戶與生活', desc: '讓每一個平凡的日常,都被用心對待——技術終將隱於無形,溫暖始終相伴。' }, @@ -237,9 +319,9 @@ export const hk = { title: '一群年輕人,正把巴適做成現實', subtitle: '腦力激盪、結對編程、慶祝每一次小小的勝利——奮鬥在這裡是有溫度的。', items: [ - { img: 'https://oss.baskbuddy.com/baskbuddy/team-brainstorm.jpg', caption: '白板前的靈感碰撞' }, - { img: 'https://oss.baskbuddy.com/baskbuddy/team-pairing.jpg', caption: '結對編程,把想法落到程式碼' }, - { img: 'https://oss.baskbuddy.com/baskbuddy/team-celebrate.jpg', caption: '每一個小勝利,都值得舉杯' }, + { caption: '白板前的靈感碰撞' }, + { caption: '結對編程,把想法落到程式碼' }, + { caption: '每一個小勝利,都值得舉杯' }, ], }, growthIncentive: { @@ -404,7 +486,7 @@ export const hk = { legal: { privacy: '隱私政策', }, - copyright: `© ${new Date().getFullYear()} 巴適夥伴(上海)科技有限公司`, + copyright: `© ${new Date().getFullYear()} 巴適夥伴(上海)科技有限公司 BaskBuddy (Shanghai) Technology Co., Ltd.`, icidpFull: 'Integrity · Courage · Innovation · Devotion · Passion', }, }; diff --git a/src/i18n/teamDetails/en.ts b/src/i18n/teamDetails/en.ts new file mode 100644 index 0000000..6f4365b --- /dev/null +++ b/src/i18n/teamDetails/en.ts @@ -0,0 +1,39 @@ +import type { MemberDetail, TeamDetailId } from '../zh'; + +/** Team member profiles (English). See ./zh.ts for why these live outside en.ts. */ +export const enTeamDetails = { + 'yang-xingxing': { + intro: + 'Yang Xingxing is a full-stack engineer and the front-end lead for our core job-and-recruitment intelligence product. They own front-end and back-end architecture, core system development, API governance and feature iteration, and take part in requirement reviews and technical decision-making. Day to day they build with Vibe Coding, and have shipped RAG retrieval-augmented systems and Agent modules that bring large-model capability into our business systems.', + strengths: + 'Fluent across mainstream front-end frameworks such as React and Vue, with Node.js, Java and Python on the back end, and solid grounding in microservice architecture, high-concurrency optimisation and cloud-native deployment. Able to build RAG knowledge bases, vector search and document-processing pipelines, orchestrate Agent tasks, and carry a project independently from requirement analysis and system design through to delivery — using AI tooling where it genuinely speeds development up.', + traits: + 'Always tracking frontier work on Agents and RAG, and putting it into production. Rigorous in code, with a focus on stability, maintainability and extensibility. Communicates well across teams, reads business requirements accurately, and delivers solutions that are both technically sound and commercially useful.', + }, + 'zhang-shun': { + intro: + 'Zhang Shun is a back-end engineer and product manager, and leads our core job-and-recruitment intelligence product. Their work covers back-end system design, feature development, database design, API development, performance tuning and day-to-day system maintenance.', + strengths: + 'Experienced with Java, Spring Boot, MySQL and Redis, with a strong programming foundation and solid system-design ability. They can independently handle requirement analysis, API design, feature development and troubleshooting, with a firm focus on security, stability and code quality, backed by deep experience in data processing, service architecture and business logic.', + traits: + 'Careful and rigorous, with a strong sense of responsibility, good logical thinking and clear communication. They hold to coding standards, data security and risk control, are skilled at tracking down complex technical problems, and work closely with every team to land projects on stable, dependable technical foundations.', + }, + 'chen-jie': { + intro: + 'Chen Jie is a front-end engineer and the front-end lead for our core content-and-social intelligence product. They are responsible for building and maintaining project pages — layout, interactions, API integration and multi-device adaptation.', + strengths: + 'Familiar with the front-end development workflow and equipped with a solid technical foundation, they deliver page development, performance optimisation and troubleshooting efficiently, and have a deep understanding of mobile adaptation and user experience.', + traits: + 'Meticulous and responsible, with close attention to coding standards and detail, plus patience and good communication skills. They work steadily alongside product, design, back-end and QA teams to make sure projects ship on schedule and to a high standard.', + }, + 'zhang-buyun': { + intro: + 'Zhang Buyun is a front-end engineer and UI designer, and leads our core content-and-social intelligence product. They build and maintain our H5, WeChat Mini Program and PC pages, integrate APIs with back-end engineers, implement interactions and motion, and keep improving front-end performance and cross-platform compatibility — alongside requirement reviews, technical design and production troubleshooting.', + strengths: + 'Comfortable with the mainstream front-end stack (Vue, React, Taro and more), with a solid grounding in HTML, CSS and JavaScript. They turn UI designs into faithful code, have hands-on experience with front-end engineering, componentisation and performance work, and can take a page from first build all the way through to API integration.', + traits: + 'Clear-headed and detail-oriented, with high standards for code quality and user experience. They communicate well and work as part of a team, turning business requirements into technical implementation quickly. Steady and quick to learn, they work patiently through problems and coordinate closely with product, design and back-end colleagues.', + }, +} satisfies Record; + +export default enTeamDetails; diff --git a/src/i18n/teamDetails/hk.ts b/src/i18n/teamDetails/hk.ts new file mode 100644 index 0000000..80fe968 --- /dev/null +++ b/src/i18n/teamDetails/hk.ts @@ -0,0 +1,39 @@ +import type { MemberDetail, TeamDetailId } from '../zh'; + +/** 團隊成員長簡介(繁體)。拆分理由見 ./zh.ts。 */ +export const hkTeamDetails = { + 'yang-xingxing': { + intro: + '楊幸幸,全棧開發工程師,目前擔任公司核心業務——求職招聘智能服務產品的前端負責人,主要負責前後端架構設計、核心業務系統研發、接口治理與產品功能迭代,參與需求評審與技術方案決策;日常以 Vibe Coding 方式編碼開發,同時基於業務場景落地 RAG 檢索增強系統與 Agent 智能體模組,推動大模型能力與業務系統整合。', + strengths: + '精通 React/Vue 等主流前端框架與 Node.js/Java/Python 後端技術,熟悉微服務架構、高併發場景優化與雲原生部署;具備 RAG 知識庫建構、向量檢索、文件處理及 Agent 任務編排能力,擁有從需求分析、系統設計到業務開發與 AI 應用落地的全鏈路獨立開發能力,能合理運用 AI 開發工具提升編碼效率。', + traits: + '持續關注 Agent、RAG 等前沿技術並落地業務,編碼嚴謹,重視系統穩定性、可維護性與可擴展性;擅長跨團隊溝通協作,能準確理解業務需求,輸出兼具技術合理性與業務價值的解決方案,善用 AI 工具賦能研發,為團隊產品迭代與技術建設提供支撐。', + }, + 'zhang-shun': { + intro: + '張順,現任公司後端開發工程師兼產品經理,目前擔任公司核心業務——求職招聘智能服務產品的負責人,主要負責後端系統設計、業務功能開發、數據庫設計、接口開發、效能優化以及系統日常維護工作。', + strengths: + '熟悉 Java、Spring Boot、MySQL、Redis 等後端技術,具備紮實的編程基礎與良好的系統設計能力,能獨立完成業務需求分析、接口設計、功能開發與問題排查,重視系統安全性、穩定性與程式碼品質,在數據處理、服務架構及業務邏輯實現上經驗豐富。', + traits: + '做事認真嚴謹,責任心強,具備良好的邏輯思維、問題分析與溝通協作能力。工作中注重程式碼規範、數據安全與風險控制,善於定位並解決複雜技術問題,能積極配合各部門推進項目落地,以穩定可靠的技術方案為公司業務發展提供後端支持。', + }, + 'chen-jie': { + intro: + '陳潔,現任前端開發工程師,目前擔任公司核心業務——內容社交智能服務產品的前端負責人,主要負責公司項目頁面的開發與維護,包括頁面搭建、互動實現、接口聯調以及多機型適配等工作。', + strengths: + '熟悉前端開發流程,具備紮實的前端技術基礎,能高效完成頁面開發、效能優化和問題排查,對移動端適配與用戶體驗有很深的體會和了解。', + traits: + '做事認真細緻,責任心強,注重程式碼規範與細節打磨,具備良好的耐心與溝通能力。工作中踏實穩重,善於配合產品、設計、後端及測試團隊完成協作,以專業嚴謹的工作態度保障項目按期高品質上線。', + }, + 'zhang-buyun': { + intro: + '張卜允,現任前端開發工程師兼 UI 設計師,目前擔任公司核心業務——內容社交智能服務產品的負責人,主要負責公司 H5、微信小程序及 PC 端頁面的開發與維護,與後端工程師對接接口聯調,實現頁面互動與動態效果,持續優化前端效能與多端相容性,並參與需求評審、技術方案設計及線上問題排查等工作。', + strengths: + '熟悉主流前端技術棧(Vue/React/Taro 等),具備紮實的 HTML、CSS、JavaScript 基礎,能高效還原 UI 設計稿,對前端工程化、組件化開發及效能優化有一定實踐經驗,能獨立完成從頁面搭建到接口對接的完整開發流程。', + traits: + '邏輯清晰,注重細節,對程式碼規範與用戶體驗有較高要求;具備良好的溝通能力與團隊協作意識,能快速理解業務需求並轉化為技術實現。工作中踏實穩重,學習能力強,面對問題耐心排查,善於配合產品、設計及後端同事推進項目,以嚴謹負責的技術態度為公司產品研發與業務落地提供可靠保障。', + }, +} satisfies Record; + +export default hkTeamDetails; diff --git a/src/i18n/teamDetails/index.ts b/src/i18n/teamDetails/index.ts new file mode 100644 index 0000000..39b9e98 --- /dev/null +++ b/src/i18n/teamDetails/index.ts @@ -0,0 +1,27 @@ +import type { MemberDetail, TeamDetailId } from '../zh'; +import type { Locale } from '../utils'; + +export type TeamDetailMap = Record; + +/** 每个语种一个 chunk。Vite 看到的是静态字面量的 import(),能静态分析出这三个分包, + * 所以它们不会被打进主包——这正是把长简介从 i18n/*.ts 里搬出来的目的。 */ +const loaders: Record Promise<{ default: TeamDetailMap }>> = { + zh: () => import('./zh'), + hk: () => import('./hk'), + en: () => import('./en'), +}; + +/** 缓存的是 Promise 而不是结果:弹窗开着时若因故重入,也不会并发打两次请求。 */ +const cache = new Map>(); + +/** 按需拉取某个语种的成员长简介。同一语种只发一次请求。 */ +export function loadTeamDetails(locale: Locale): Promise { + let pending = cache.get(locale); + if (!pending) { + pending = loaders[locale]().then((m) => m.default); + // 失败时把 promise 从缓存里摘掉,否则后续每次重试都只会拿到同一个 rejected promise + pending.catch(() => cache.delete(locale)); + cache.set(locale, pending); + } + return pending; +} diff --git a/src/i18n/teamDetails/zh.ts b/src/i18n/teamDetails/zh.ts new file mode 100644 index 0000000..05184a3 --- /dev/null +++ b/src/i18n/teamDetails/zh.ts @@ -0,0 +1,47 @@ +import type { MemberDetail, TeamDetailId } from '../zh'; + +/** 团队成员长简介(简体)。 + * + * 刻意从 zh.ts 里拆出来单独放,原因只有一个:这三段正文每人 300–450 字,而 + * 9 位成员里只有 4 位有——它却跟着 locale 一起被打进主包,导致首页、业务、 + * 新闻这些根本用不到的页面每次首屏都在下载它。挪到这里之后,由 About 在用户 + * 真要开弹窗时才 `import()`,见 ./index.ts。 + * + * 用 Record 收口:少写一位成员、或写了 id 联合类型里没有的 + * 键,都会在 tsc 阶段报错。三个语种各一份,缺一即挂。 */ +export const zhTeamDetails = { + 'yang-xingxing': { + intro: + '杨幸幸,全栈开发工程师,目前担任公司核心业务——求职招聘智能服务产品的前端负责人,主要负责前后端架构设计、核心业务系统研发、接口治理与产品功能迭代,参与需求评审与技术方案决策;日常以 Vibe Coding 方式编码开发,同时基于业务场景落地 RAG 检索增强系统与 Agent 智能体模块,推动大模型能力与业务系统集成。', + strengths: + '精通 React/Vue 等主流前端框架与 Node.js/Java/Python 后端技术,熟悉微服务架构、高并发场景优化与云原生部署;具备 RAG 知识库构建、向量检索、文档处理及 Agent 任务编排能力,拥有从需求分析、系统设计到业务开发与 AI 应用落地的全链路独立开发能力,能够合理运用 AI 开发工具提升编码效率。', + traits: + '持续关注 Agent、RAG 等前沿技术并落地业务,编码严谨,重视系统稳定性、可维护性与可扩展性;擅长跨团队沟通协作,能够准确理解业务需求,输出兼具技术合理性与业务价值的解决方案,善用 AI 工具赋能研发,为团队产品迭代与技术建设提供支撑。', + }, + 'zhang-shun': { + intro: + '张顺,现任公司后端开发工程师兼产品经理,目前担任公司核心业务——求职招聘智能服务产品的负责人,主要负责后端系统设计、业务功能开发、数据库设计、接口开发、性能优化以及系统日常维护工作。', + strengths: + '熟悉 Java、Spring Boot、MySQL、Redis 等后端技术,具备扎实的编程基础和良好的系统设计能力,能够独立完成业务需求分析、接口设计、功能开发与问题排查,重视系统安全性、稳定性和代码质量,在数据处理、服务架构及业务逻辑实现上经验丰富。', + traits: + '做事认真严谨,责任心强,具备良好的逻辑思维、问题分析与沟通协作能力。工作中注重代码规范、数据安全和风险控制,善于定位并解决复杂技术问题,能够积极配合各部门推进项目落地,以稳定可靠的技术方案为公司业务发展提供后端支持。', + }, + 'chen-jie': { + intro: + '陈洁,现任前端开发工程师,目前担任公司核心业务——内容社交智能服务产品的前端负责人,主要负责公司项目页面的开发与维护,包括页面搭建、交互实现、接口联调以及多机型适配等工作。', + strengths: + '熟悉前端开发流程,具备扎实的前端技术基础,能够高效完成页面开发、性能优化和问题排查,对移动端适配与用户体验有很深的体会和了解。', + traits: + '做事认真细致,责任心强,注重代码规范与细节打磨,具备良好的耐心与沟通能力。工作中踏实稳重,善于配合产品、设计、后端及测试团队完成协作,以专业严谨的工作态度保障项目按期高质量上线。', + }, + 'zhang-buyun': { + intro: + '张卜允,现任前端开发工程师兼 UI 设计师,目前担任公司核心业务——内容社交智能服务产品的负责人,主要负责公司 H5、微信小程序及 PC 端页面的开发与维护,与后端工程师对接接口联调,实现页面交互与动态效果,持续优化前端性能与多端兼容性,并参与需求评审、技术方案设计及线上问题排查等工作。', + strengths: + '熟悉主流前端技术栈(Vue/React/Taro 等),具备扎实的 HTML、CSS、JavaScript 基础,能够高效还原 UI 设计稿,对前端工程化、组件化开发及性能优化有一定实践经验,能够独立完成从页面搭建到接口对接的完整开发流程。', + traits: + '逻辑清晰,注重细节,对代码规范和用户体验有较高要求;具备良好的沟通能力与团队协作意识,能够快速理解业务需求并转化为技术实现。工作中踏实稳重,学习能力强,面对问题耐心排查,善于配合产品、设计及后端同事推进项目,以严谨负责的技术态度为公司产品研发与业务落地提供可靠保障。', + }, +} satisfies Record; + +export default zhTeamDetails; diff --git a/src/i18n/utils.ts b/src/i18n/utils.ts index 7582968..96f3611 100644 --- a/src/i18n/utils.ts +++ b/src/i18n/utils.ts @@ -30,5 +30,3 @@ export function getLocalizedPath(path: string, locale: Locale): string { } return path; } - -// 赛道配图已统一使用 src/placeholder.ts 的 PLACEHOLDER_IMG,不再维护按赛道映射的静态图。 diff --git a/src/i18n/zh.ts b/src/i18n/zh.ts index 52d62f3..9c11aac 100644 --- a/src/i18n/zh.ts +++ b/src/i18n/zh.ts @@ -1,3 +1,45 @@ +// 简体中文是 `Record` 里的基准形状,en/hk 都按它做结构校验, +// 所以下面这些「当前为空 / 字段可选」的集合必须在这里显式标注类型—— +// 否则空数组会被推断成 never[],消费方(Home / News / About)一读字段就报 TS2339。 +export type NewsItem = { + title: string; + category: string; + date: string; +}; + +/** 成员长简介(弹窗正文)。三段标题「岗位介绍 / 技术优势 / 个人特点」是 UI 文案, + 放在 about.subnav.team.ui 里,不随成员重复。 + 正文本身不存在这儿——见 teamDetails/,那里按需加载。 */ +export type MemberDetail = { + intro: string; + strengths: string; + traits: string; +}; + +/** 有长简介的成员 id。正文在 teamDetails/.ts,用它做键; + 这个联合类型是「三语必须齐全」的编译期闸门(见 teamDetails/zh.ts 的 satisfies)。 */ +export type TeamDetailId = 'yang-xingxing' | 'zhang-shun' | 'chen-jie' | 'zhang-buyun'; + +export type TeamMember = { + name: string; + role: string; + /** 头像图 URL;留空则回落为姓名首字占位圆,见 About.tsx 的 memberInitial() */ + photo?: string; + /** 国籍。刻意与长简介分开存:弹窗头部的国籍标签要立刻渲染, + 不能等 teamDetails 那个 chunk 加载完再冒出来。 */ + nationality?: string; + /** 有值即表示这位成员有长简介、出现「查看详情」入口(团队 9 人中目前 4 位有); + 正文在同 id 的 teamDetails/.ts 里,点开时按需加载。 */ + detailId?: TeamDetailId; +}; + +/** 管理团队按职能分组展示(技术研发 / 设计 / 财务) */ +export type TeamGroup = { + key: string; + title: string; + members: TeamMember[]; +}; + export const zh = { brand: 'BaskBuddy', brandSub: '巴适伙伴', @@ -16,6 +58,28 @@ export const zh = { sloganIntro: '覆盖内容社交 · 职场服务 · 数字支付 · AI · 生活服务的数字生活服务商', heroTitle: '以智能创新开创数字未来', heroCta: '了解我们', + video: { + eyebrow: 'INTRODUCTION', + title: '走进巴适伙伴', + subtitle: '用一段影像,看见我们如何以温暖科技连接美好生活。', + intro: [ + { + value: '5', + unit: '大核心赛道', + desc: '作为数字生活服务生态的支柱布局,深耕内容社交、职场服务、数字支付、人工智能与生活服务,以多品牌矩阵回应不同用户群体的真实需求。', + }, + { + value: '8', + unit: '自研产品', + desc: '作为连接用户与生活的产品网络,从青年社交、求职升学,到安全支付与心愿达成,覆盖数字生活全场景,让温暖触手可及。', + }, + { + value: '2026', + unit: '起航之年', + desc: '作为"一生相伴·一生信赖"承诺的新起点,以温暖科技连接每一个人,稳步迈向全球领先的数字生活服务生态。', + }, + ], + }, trackOverview: '五大核心赛道', enterLab: '探索创新实验室', enterBusiness: '查看业务版图', @@ -32,8 +96,8 @@ export const zh = { title: '巴适速度', subtitle: '以温暖科技,连接美好生活的坚实底座', items: [ - { value: '5', suffix: '大', label: '核心赛道', sub: '内容社交 · 职场服务 · 数字支付 · AI · 生活服务' }, - { value: '8', suffix: '+', label: '自研产品', sub: '覆盖数字生活全场景' }, + // 只保留愿景性指标。5 大核心赛道 / 8+ 自研产品已由上方 CompanyVideo 左栏 + // (home.video.intro)负责讲,这里不再重复第二遍。 { value: '全球', suffix: '', label: '服务愿景', sub: '让优质产品触达全球用户' }, { value: '长期', suffix: '', label: '价值主义', sub: '与用户、伙伴、社会共赢' }, ], @@ -113,15 +177,74 @@ export const zh = { team: { title: '管理团队', desc: '推动公司前行的核心伙伴', + // 区块简介:用于 #about-team 的 section-lead(desc 太短,撑不起区块导语) + intro: '我们是一支专业分工、协同高效的团队,由法人代表领衔,成员覆盖全栈、前端、后端、安卓开发及财务等方向,持续为产品研发与业务运营提供支持。', icon: 'users', href: '#about-team', - members: [ - { name: '张顺', role: '软件开发工程师', gender: 'male' }, - { name: '张卜允', role: '软件开发工程师', gender: 'female' }, - { name: '陈雪妮', role: '软件开发工程师', gender: 'female' }, - { name: '陈曼丽', role: '财务专员', gender: 'female' }, - { name: '杨幸幸', role: '软件开发工程师', gender: 'female' }, - ], + // 详情弹窗的 UI 文案(成员长简介在 src/i18n/teamDetails/,按需加载) + ui: { + viewProfile: '查看详情', + close: '关闭', + nationalityLabel: '国籍', + introLabel: '岗位介绍', + strengthsLabel: '技术优势', + traitsLabel: '个人特点', + loadingLabel: '正在加载简介…', + errorLabel: '简介暂时加载失败,关闭后重开可再试一次。', + }, + groups: [ + { + key: 'management', + title: '管理层', + members: [ + { name: '陈蓬涛', role: '法人代表', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + ], + }, + { + key: 'engineering', + title: '技术研发', + members: [ + { name: '吴启明', role: '安卓开发工程师', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + { + name: '杨幸幸', + role: '全栈开发工程师', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: '中国', + detailId: 'yang-xingxing', + }, + { + name: '张顺', + role: '后端开发工程师兼产品经理', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg', + nationality: '中国', + detailId: 'zhang-shun', + }, + { + name: '陈洁', + role: '前端开发工程师', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: '中国', + detailId: 'chen-jie', + }, + { + name: '张卜允', + role: '前端开发工程师兼UI设计师', + photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg', + nationality: '中国', + detailId: 'zhang-buyun', + }, + { name: '刘潇强', role: '后端开发工程师', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + { name: '李世博', role: '后端开发工程师', photo: 'https://oss.baskbuddy.com/baskbuddy/team-male.jpg' }, + ], + }, + { + key: 'finance', + title: '财务', + members: [ + { name: '张梦想', role: '财务专员', photo: 'https://oss.baskbuddy.com/baskbuddy/team-female.jpg' }, + ], + }, + ] satisfies TeamGroup[], }, vision: { title: '愿景及使命', desc: '我们要去的地方,与为何出发', icon: 'telescope', href: '#about-vision' }, }, @@ -145,7 +268,7 @@ export const zh = { nodes: [ { id: 'position', title: '公司定位', desc: '巴适伙伴(上海)科技有限公司是一家聚焦数字生活服务领域的高科技企业,依托多品牌、多垂直赛道的产品布局,面向不同用户群体提供优质产品与服务。' }, { id: 'tracks', title: '五大核心赛道', desc: '以内容社交、职场服务、数字支付、人工智能、生活服务五大核心赛道为支柱,孵化 8 自研产品,覆盖用户数字生活全场景。', link: '/business' }, - { id: 'team', title: '管理团队', desc: '由张顺、张卜允、陈雪妮、陈曼丽、杨幸幸五位伙伴组成的核心团队,专注软件研发与产品创新。', link: '#about-team' }, + { id: 'team', title: '管理团队', desc: '由 9 位伙伴组成的核心团队,覆盖技术研发与财务,专注产品研发与业务运营。', link: '#about-team' }, { id: 'vision', title: '愿景使命', desc: '愿景:打造全球领先的数字生活服务生态。使命:以创新技术连接人与服务,为不同用户群体打造便捷高效、富有价值的美好生活。' }, { id: 'data', title: '关键数据', desc: '5 大核心赛道 · 8 自研产品 · 全球生态布局 · 2026 起航之年。' }, { id: 'life', title: '连接用户与生活', desc: '让每一个平凡的日常,都被用心对待——技术终将隐于无形,温暖始终相伴。' }, @@ -237,9 +360,9 @@ export const zh = { title: '一群年轻人,正把巴适做成现实', subtitle: '头脑风暴、结对编程、庆祝每一次小小的胜利——奋斗在这里是有温度的。', items: [ - { img: 'https://oss.baskbuddy.com/baskbuddy/team-brainstorm.jpg', caption: '白板前的灵感碰撞' }, - { img: 'https://oss.baskbuddy.com/baskbuddy/team-pairing.jpg', caption: '结对编程,把想法落到代码' }, - { img: 'https://oss.baskbuddy.com/baskbuddy/team-celebrate.jpg', caption: '每一个小胜利,都值得举杯' }, + { caption: '白板前的灵感碰撞' }, + { caption: '结对编程,把想法落到代码' }, + { caption: '每一个小胜利,都值得举杯' }, ], }, growthIncentive: { @@ -342,7 +465,7 @@ export const zh = { subtitle: '每一步,都值得被记录', intro: '这里将汇集巴适伙伴的公司动态、品牌观点与行业洞察。', empty: '暂无新闻动态,敬请期待。', - items: [], + items: [] as NewsItem[], }, investors: { title: '投资者关系', @@ -404,7 +527,7 @@ export const zh = { legal: { privacy: '隐私政策', }, - copyright: `© ${new Date().getFullYear()} 巴适伙伴(上海)科技有限公司`, + copyright: `© ${new Date().getFullYear()} 巴适伙伴(上海)科技有限公司 BaskBuddy (Shanghai) Technology Co., Ltd.`, icidpFull: 'Integrity · Courage · Innovation · Devotion · Passion', }, }; diff --git a/src/icons/index.tsx b/src/icons/index.tsx index 7edc2d5..23cb436 100644 --- a/src/icons/index.tsx +++ b/src/icons/index.tsx @@ -1,7 +1,7 @@ // Central icon system for BaskBuddy. // Single source of truth: all icons come from lucide-react (a set indexed on yesicon.app). // Components render icons through or the semantic maps below, -// so the whole site stays consistent and emoji-free. +// so the whole site stays consistent and emoji-free. import type { SVGProps } from 'react'; import { Home, @@ -57,7 +57,9 @@ import { Leaf, FileText, Copy, + Play, Check, + X, } from 'lucide-react'; export const iconMap = { @@ -114,7 +116,9 @@ export const iconMap = { leaf: Leaf, fileText: FileText, copy: Copy, + play: Play, check: Check, + x: X, } as const; export type IconName = keyof typeof iconMap; @@ -192,12 +196,7 @@ export const longTermIncentiveIcons: IconName[] = ['pieChart', 'sprout', 'handsh export const benefitIcons: IconName[] = ['clock', 'home', 'heartPulse', 'users', 'bookOpen']; -export const statsIcons: IconName[] = ['layoutGrid', 'package', 'telescope', 'globe']; - -// Home「巴适速度」四宫格:每格承一种赛道色,强化全站色彩体系 -export const statsColors: string[] = [ - 'var(--track-social)', - 'var(--track-career)', - 'var(--track-payment)', - 'var(--track-ai)', -]; +// 首页「愿景带」两格(按 home.stats.items 顺序:服务愿景 / 价值主义)。 +// 原为四格 ['layoutGrid','package','telescope','globe'] 对应 5大/8+/全球/长期; +// 5大 与 8+ 已删(与 CompanyVideo 左栏重复),故此数组同步缩为两项,否则图标整体错位。 +export const statsIcons: IconName[] = ['telescope', 'globe']; diff --git a/src/layout/RootLayout.tsx b/src/layout/RootLayout.tsx index a23874a..90994ef 100644 --- a/src/layout/RootLayout.tsx +++ b/src/layout/RootLayout.tsx @@ -16,7 +16,7 @@ export default function RootLayout() { useClientEffects(location.pathname); return ( -
+
}>
diff --git a/src/pages/AboutPage.tsx b/src/pages/AboutPage.tsx index 9d54ded..24f517c 100644 --- a/src/pages/AboutPage.tsx +++ b/src/pages/AboutPage.tsx @@ -7,7 +7,7 @@ export default function AboutPage() { const t = getTranslation(locale); return ( <> - + ); diff --git a/src/pages/BusinessPage.tsx b/src/pages/BusinessPage.tsx index ee18814..e0b1c37 100644 --- a/src/pages/BusinessPage.tsx +++ b/src/pages/BusinessPage.tsx @@ -7,7 +7,7 @@ export default function BusinessPage() { const t = getTranslation(locale); return ( <> - + ); diff --git a/src/pages/ContactPage.tsx b/src/pages/ContactPage.tsx index 7d08623..42dc8d5 100644 --- a/src/pages/ContactPage.tsx +++ b/src/pages/ContactPage.tsx @@ -7,7 +7,7 @@ export default function ContactPage() { const t = getTranslation(locale); return ( <> - + ); diff --git a/src/pages/InvestorsPage.tsx b/src/pages/InvestorsPage.tsx index 3b36107..247501f 100644 --- a/src/pages/InvestorsPage.tsx +++ b/src/pages/InvestorsPage.tsx @@ -7,7 +7,7 @@ export default function InvestorsPage() { const t = getTranslation(locale); return ( <> - + ); diff --git a/src/pages/NewsPage.tsx b/src/pages/NewsPage.tsx index fbdb88c..588327e 100644 --- a/src/pages/NewsPage.tsx +++ b/src/pages/NewsPage.tsx @@ -7,7 +7,7 @@ export default function NewsPage() { const t = getTranslation(locale); return ( <> - + ); diff --git a/src/pages/SustainabilityPage.tsx b/src/pages/SustainabilityPage.tsx index 36ac122..8787bf2 100644 --- a/src/pages/SustainabilityPage.tsx +++ b/src/pages/SustainabilityPage.tsx @@ -7,7 +7,7 @@ export default function SustainabilityPage() { const t = getTranslation(locale); return ( <> - + ); diff --git a/src/placeholder.ts b/src/placeholder.ts deleted file mode 100644 index 3f6d9bc..0000000 --- a/src/placeholder.ts +++ /dev/null @@ -1,5 +0,0 @@ -// 统一占位图:全站内容图(办公/团队/业务/新闻/横幅等)共用此图。 -// 图片统一托管于 OSS:https://oss.baskbuddy.com/baskbuddy/ -// 若要替换占位图,把对应文件上传到 OSS 后改本常量一处即可全局生效。 -export const PLACEHOLDER_IMG = - 'https://oss.baskbuddy.com/baskbuddy/placeholder.jpg'; diff --git a/src/seo/Seo.tsx b/src/seo/Seo.tsx index 55a99bb..f6a8f4c 100644 --- a/src/seo/Seo.tsx +++ b/src/seo/Seo.tsx @@ -6,23 +6,32 @@ interface SeoProps { description?: string; keywords?: string; locale: Locale; - /** Per-page og:image override (default: OSS og-default.png) */ - ogImageOverride?: string; } const DEFAULT_DESC_ZH = 'BaskBuddy 巴适伙伴(上海)科技有限公司 — 以温暖科技连接美好生活,五大赛道覆盖内容社交、职场服务、数字支付、人工智能、生活服务'; const DEFAULT_DESC_EN = - 'BaskBuddy (Bashi Partners Shanghai) — Connecting beautiful life with warm technology. Five tracks: Content & Social, Career Services, Digital Payment, Artificial Intelligence, Life Services'; + 'BaskBuddy (Shanghai) Technology Co., Ltd. — Connecting beautiful life with warm technology. Five tracks: Content & Social, Career Services, Digital Payment, Artificial Intelligence, Life Services'; const DEFAULT_DESC_HK = 'BaskBuddy 巴適夥伴(上海)科技有限公司 — 以溫暖科技連接美好生活,五大賽道覆蓋內容社交、職場服務、數字支付、人工智能、生活服務'; const DEFAULT_KW_ZH = '巴适伙伴,BaskBuddy,数字生活服务,内容社交,职场服务,数字支付,人工智能,ICIDP,企业官网,上海科技公司'; const DEFAULT_KW_EN = - 'BaskBuddy,Bashi Partners,Digital Life Services,Content Social,Career Services,Digital Payment,Artificial Intelligence,ICIDP,Shanghai Tech Company'; + 'BaskBuddy,BaskBuddy Shanghai,Digital Life Services,Content Social,Career Services,Digital Payment,Artificial Intelligence,ICIDP,Shanghai Tech Company'; const DEFAULT_KW_HK = '巴適夥伴,BaskBuddy,數字生活服務,內容社交,職場服務,數字支付,人工智能,ICIDP,企業官網,上海科技公司'; +// ===== Canonical origin ===== +// 线上只有 www.baskbuddy.com 有 DNS 解析(裸域 baskbuddy.com 在 Google / AliDNS / +// Cloudflare 上均无 A 记录),故 canonical / og:url / hreflang 一律锚定 www。 +// 这里刻意不用 window.location.origin:否则从裸域、IP 或测试域名访问时会把非规范 +// 地址写进 canonical,反而误导搜索引擎。 +const SITE_ORIGIN = 'https://www.baskbuddy.com'; + +// 全站图片/视频托管于 OSS(国内直连快),OG 分享图同源。 +const OSS_BASE = 'https://oss.baskbuddy.com/baskbuddy'; +const OG_IMAGE = `${OSS_BASE}/hero-bg.jpg`; + function setMeta(uniqueAttr: string, attrs: Record, content: string) { const selector = `meta[${uniqueAttr}]`; let el = document.head.querySelector(selector) as HTMLMetaElement | null; @@ -47,16 +56,16 @@ function setLink(uniqueAttr: string, attrs: Record, href: string // Recreates the per-page metadata that BaseLayout.astro used to emit. // Runs on every route change so the document title / meta stay in sync in the SPA. -export default function Seo({ title, description, keywords, locale, ogImageOverride }: SeoProps) { +export default function Seo({ title, description, keywords, locale }: SeoProps) { useEffect(() => { const t = getTranslation(locale); const brandName = t.brand || 'BaskBuddy'; const finalDescription = description ?? (locale === 'en' ? DEFAULT_DESC_EN : locale === 'hk' ? DEFAULT_DESC_HK : DEFAULT_DESC_ZH); const finalKeywords = keywords ?? (locale === 'en' ? DEFAULT_KW_EN : locale === 'hk' ? DEFAULT_KW_HK : DEFAULT_KW_ZH); const orgName = - locale === 'en' ? 'Bashi Partners (Shanghai) Technology Co., Ltd.' : locale === 'hk' ? '巴適夥伴(上海)科技有限公司' : '巴适伙伴(上海)科技有限公司'; + locale === 'en' ? 'BaskBuddy (Shanghai) Technology Co., Ltd.' : locale === 'hk' ? '巴適夥伴(上海)科技有限公司' : '巴适伙伴(上海)科技有限公司'; - const origin = window.location.origin; + const origin = SITE_ORIGIN; const pathname = window.location.pathname; const canonicalUrl = origin + pathname; const segs = pathname.split('/'); @@ -66,9 +75,7 @@ export default function Seo({ title, description, keywords, locale, ogImageOverr const enAlternate = origin + (base === '/' ? '/en' : `/en${base}`); const hkAlternate = origin + (base === '/' ? '/hk' : `/hk${base}`); - const logoPath = 'https://oss.baskbuddy.com/baskbuddy/baskbuddy-logo.png'; - const ogImagePath = ogImageOverride || 'https://oss.baskbuddy.com/baskbuddy/og-default.png'; - const ogImage = ogImagePath.startsWith('http') ? ogImagePath : origin + ogImagePath; + const logoPath = `${OSS_BASE}/baskbuddy-logo.png`; // Document level document.documentElement.lang = locale === 'en' ? 'en' : locale === 'hk' ? 'zh-HK' : 'zh-CN'; @@ -91,13 +98,9 @@ export default function Seo({ title, description, keywords, locale, ogImageOverr setMeta('property="og:title"', { property: 'og:title' }, `${title} | ${brandName}`); setMeta('property="og:description"', { property: 'og:description' }, finalDescription); setMeta('property="og:url"', { property: 'og:url' }, canonicalUrl); - setMeta('property="og:image"', { property: 'og:image' }, ogImage); - setMeta( - 'property="og:image:alt"', - { property: 'og:image:alt' }, - `${brandName} — ${locale === 'en' ? 'Together for Life' : locale === 'hk' ? '巴適夥伴,一生相伴' : '巴适伙伴,一生相伴'}`, - ); const ogLocale = locale === 'en' ? 'en_US' : locale === 'hk' ? 'zh_HK' : 'zh_CN'; + setMeta('property="og:image"', { property: 'og:image' }, OG_IMAGE); + setMeta('property="og:image:alt"', { property: 'og:image:alt' }, `${brandName} ${t.brandSub || '巴适伙伴'}`); setMeta('property="og:locale"', { property: 'og:locale' }, ogLocale); setMeta( 'property="og:locale:alt"', @@ -108,11 +111,10 @@ export default function Seo({ title, description, keywords, locale, ogImageOverr // Twitter Card setMeta('name="twitter:card"', { name: 'twitter:card' }, 'summary_large_image'); + setMeta('name="twitter:image"', { name: 'twitter:image' }, OG_IMAGE); setMeta('name="twitter:site"', { name: 'twitter:site' }, '@baskbuddy'); setMeta('name="twitter:title"', { name: 'twitter:title' }, `${title} | ${brandName}`); setMeta('name="twitter:description"', { name: 'twitter:description' }, finalDescription); - setMeta('name="twitter:image"', { name: 'twitter:image' }, ogImage); - setMeta('name="twitter:image:alt"', { name: 'twitter:image:alt' }, `${brandName} Logo`); // Organization JSON-LD const jsonLdOrg = { @@ -140,7 +142,7 @@ export default function Seo({ title, description, keywords, locale, ogImageOverr document.head.appendChild(script); } script.textContent = JSON.stringify(jsonLdOrg); - }, [title, description, keywords, locale, ogImageOverride]); + }, [title, description, keywords, locale]); return null; } diff --git a/src/styles/global.css b/src/styles/global.css index a55c754..9eb6834 100644 --- a/src/styles/global.css +++ b/src/styles/global.css @@ -11,18 +11,18 @@ Key: Clarity over decoration, warmth through subtlety */ :root { - /* Core Brand Colors — 主题色 #EBEAF6(浅薰衣草),作为全站冷调基调 */ + /* Core Brand Colors — 主题色 #E8EAF6(浅薰衣草),作为全站冷调基调 */ --brand-white: #FFFFFF; --brand-black: #1A1A1A; - --brand: #EBEAF6; /* 主题色(浅薰衣草)—— 背景/色块/图标底/浅描边 */ - --brand-cream: #EBEAF6; /* 沿用名 = 主题色 */ - --brand-cream-light: #EBEAF6; /* 主题色更浅一档(hover 底)—— 统一为 #EBEAF6 */ - --brand-cream-deep: #EBEAF6; /* 主题色更深一档(描边/边框)—— 统一为 #EBEAF6 */ + --brand: #E8EAF6; /* 主题色(浅薰衣草)—— 背景/色块/图标底/浅描边 */ + --brand-cream: #E8EAF6; /* 沿用名 = 主题色 */ + --brand-cream-light: #E8EAF6; /* 主题色更浅一档(hover 底)—— 统一为 #E8EAF6 */ + --brand-cream-deep: #E8EAF6; /* 主题色更深一档(描边/边框)—— 统一为 #E8EAF6 */ /* ===== 赛道 Identity — 统一为深色强调(#1A1A1A 深暖黑,与底部黑呼应)===== 原四赛道色(红/蓝/绿/紫)已统一到 --accent(深黑),全站单一强调锚点。 强色用于:图标、卡片顶条、产品标签描边、强调点缀;浅色由 color-mix 派生。 - 主题色 #EBEAF6(浅薰衣草)承担全站冷调基调:底色、图标底、大色块、导航底。 */ + 主题色 #E8EAF6(浅薰衣草)承担全站冷调基调:底色、图标底、大色块、导航底。 */ --track-social: var(--accent); /* 社交赛道 — 统一深色强调 */ --track-career: var(--accent); /* 职场赛道 — 统一深色强调 */ --track-payment: var(--accent); /* 支付赛道 — 统一深色强调 */ @@ -41,9 +41,9 @@ · --brand-accent-deep #2E40D8 小字正文 / 按钮底色(白字 7.4:1,过 AA)★ 导航/CTA 用此档 · --brand-accent-darker #1B2A9E hover/press 更深一档(白字 11:1,高对比反馈) · --brand-accent-ink #1B2A9E 浅底上的蓝字(超高对比,文字选中 / 浅色块内文字) - · --brand-accent-soft #EBEAF6 浅洗 hover / 选中高亮底 / 点缀底 */ + · --brand-accent-soft #E8EAF6 浅洗 hover / 选中高亮底 / 点缀底 */ --brand-accent: #3d5afe; - --brand-accent-soft: #EBEAF6; + --brand-accent-soft: #E8EAF6; --brand-accent-deep: #2E40D8; --brand-accent-darker: #1B2A9E; --brand-accent-ink: #1B2A9E; @@ -80,8 +80,8 @@ /* Semantic Colors */ --bg-primary: #FAFAFE; /* 冷调近白底:与浅薰衣草主题色呼应 */ - --bg-secondary: var(--brand-cream-light); /* 现为 #EBEAF6 */ - --bg-subtle: #EBEAF6; /* 浅薰衣草交替底 */ + --bg-secondary: var(--brand-cream-light); /* 现为 #E8EAF6 */ + --bg-subtle: #E8EAF6; /* 浅薰衣草交替底 */ --bg-dark: var(--brand-black); --text-primary: var(--brand-black); --text-secondary: #4A4A4A; @@ -134,20 +134,15 @@ /* Shadows — Warm but subtle */ --shadow-xs: 0 1px 3px rgba(26,26,26,0.04); - --shadow-sm: 0 2px 8px rgba(26,26,26,0.06), 0 1px 2px rgba(235,234,246,0.06); - --shadow-md: 0 6px 20px rgba(26,26,26,0.07), 0 2px 6px rgba(235,234,246,0.08); - --shadow-lg: 0 12px 40px rgba(26,26,26,0.08), 0 4px 12px rgba(235,234,246,0.10); - --shadow-xl: 0 20px 60px rgba(26,26,26,0.10), 0 8px 24px rgba(235,234,246,0.12); + --shadow-sm: 0 2px 8px rgba(26,26,26,0.06), 0 1px 2px rgba(232,234,246,0.06); + --shadow-md: 0 6px 20px rgba(26,26,26,0.07), 0 2px 6px rgba(232,234,246,0.08); + --shadow-lg: 0 12px 40px rgba(26,26,26,0.08), 0 4px 12px rgba(232,234,246,0.10); + --shadow-xl: 0 20px 60px rgba(26,26,26,0.10), 0 8px 24px rgba(232,234,246,0.12); --shadow-dark-sm: 0 2px 8px color-mix(in oklch, var(--track-ai) 10%, transparent); --shadow-dark-md: 0 6px 24px color-mix(in oklch, var(--track-ai) 12%, transparent); --shadow-dark-lg: 0 12px 40px color-mix(in oklch, var(--track-ai) 14%, transparent); } -/* 文字选中高亮:品牌暖橙淡调(premium 细节,替代默认蓝) */ -::selection { - background: var(--brand-accent-soft); - color: var(--brand-black); -} /* ===== Smooth Scroll + Mobile Safe-Area ===== */ html { @@ -156,6 +151,14 @@ html { scroll-padding-top: 80px; -webkit-text-size-adjust: 100%; -webkit-tap-highlight-color: transparent; + /* 全站默认字体 = 思源黑体(--font-body)。必须在这里落一条真实的 + font-family:Tailwind v4 的 preflight 把 html 的 font-family 绑到 + --default-font-family → --font-sans(系统字体栈),而我们自己的 + --font-* 是定义在 :root 里、没进 @theme,所以 Tailwind 不认识它们。 + 另注意:Tailwind v4 会把裸的 font-[...] 任意值推断成 font-weight,所以 + 组件里必须写成 font-[family-name:var(--font-display)] 才会编译成 font-family。 + 全站兜底的字体声明就是这一条。 */ + font-family: var(--font-body); } body { background-color: var(--bg-primary); @@ -179,7 +182,7 @@ body { } ::selection { - background: rgba(235,234,246,0.35); + background: rgba(232,234,246,0.35); color: var(--brand-black); } @@ -194,9 +197,9 @@ body { position: absolute; inset: 0; background: - radial-gradient(ellipse 60% 40% at 15% 20%, rgba(235,234,246,0.34), transparent 60%), - radial-gradient(ellipse 40% 30% at 85% 75%, rgba(235,234,246,0.20), transparent 50%), - radial-gradient(ellipse 35% 25% at 60% 10%, rgba(235,234,246,0.16), transparent 50%); + radial-gradient(ellipse 60% 40% at 15% 20%, rgba(232,234,246,0.34), transparent 60%), + radial-gradient(ellipse 40% 30% at 85% 75%, rgba(232,234,246,0.20), transparent 50%), + radial-gradient(ellipse 35% 25% at 60% 10%, rgba(232,234,246,0.16), transparent 50%); animation: meshDrift 40s ease-in-out infinite; /* 25s→40s 降低动画强度 */ pointer-events: none; z-index: 0; @@ -208,80 +211,10 @@ body { 66% { opacity: 0.55; transform: translate(0.6%, -0.4%) scale(1.003); } } -/* ===== Dark Gradient Mesh (Innovation Lab) ===== */ -.gradient-mesh-dark { - position: relative; - background: var(--brand-black); -} - -.gradient-mesh-dark::before { - content: ''; - position: absolute; - inset: 0; - background: - radial-gradient(ellipse 50% 40% at 20% 30%, color-mix(in oklch, var(--track-ai) 10%, transparent), transparent 50%), - radial-gradient(ellipse 35% 25% at 80% 70%, color-mix(in oklch, var(--track-career) 6%, transparent), transparent 40%); - animation: meshDriftDark 50s ease-in-out infinite; /* 30s→50s 降低动画强度 */ - pointer-events: none; - z-index: 0; -} - -@keyframes meshDriftDark { - 0%, 100% { opacity: 0.45; transform: translate(0, 0); } - 50% { opacity: 0.55; transform: translate(-1%, 0.6%); } /* 位移大幅降低 */ -} - -/* ===== Gradient Text — Elegant & Subtle ===== */ -.text-gradient-animated { - background: linear-gradient( - 90deg, - var(--brand-black) 0%, - var(--brand-black) 30%, - var(--brand-cream-deep) 55%, - var(--track-social) 75%, - var(--brand-black) 100% - ); - background-size: 200% auto; - -webkit-background-clip: text; - -webkit-text-fill-color: transparent; - background-clip: text; - animation: textGradientSlide 14s ease-in-out infinite; /* 8s→14s 降低动画强度,避免视觉疲劳 */ -} - -@keyframes textGradientSlide { - 0% { background-position: 0% center; } - 50% { background-position: 100% center; } - 100% { background-position: 0% center; } -} - /* ===== 统一按钮/卡片交互层 ===== */ a, button { touch-action: manipulation; } -/* 卡片悬浮微交互 */ -.card-hover { - transition: transform 400ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 400ms cubic-bezier(0.16, 1, 0.3, 1), border-color 200ms ease; -} -.card-hover:hover { - transform: translateY(-3px); - box-shadow: var(--shadow-xl); -} - - -/* ===== Professional Glassmorphism ===== */ -.glass-premium { - background: rgba(255,255,255,0.80); - backdrop-filter: blur(16px) saturate(120%); - border: 1px solid var(--border-light); - box-shadow: var(--shadow-sm); -} - -.glass-premium-dark { - background: rgba(26,26,26,0.85); - backdrop-filter: blur(16px) saturate(120%); - border: 1px solid rgba(235,234,246,0.06); - box-shadow: var(--shadow-dark-sm); -} /* ===== 暖科技玻璃卡 — 全站卡片统一套用(核心升级) ===== 玻璃底色 + 细边 + 柔影;hover 微浮交给元素自带的 Tailwind hover 类, @@ -293,21 +226,6 @@ a, button { border: 1px solid var(--border-light); box-shadow: var(--shadow-sm); } -.card-surface-soft { - background: rgba(255,255,255,0.55); - -webkit-backdrop-filter: blur(10px) saturate(120%); - backdrop-filter: blur(10px) saturate(120%); - border: 1px solid var(--border-light); -} - -/* 科技感网格底纹(极淡,premium 纹理不抢戏) */ -.tech-grid-bg { - background-image: - linear-gradient(rgba(26,26,26,0.035) 1px, transparent 1px), - linear-gradient(90deg, rgba(26,26,26,0.035) 1px, transparent 1px); - background-size: 48px 48px; - background-position: center; -} /* ===== Scroll Reveal — Smooth & Professional ===== */ .scroll-reveal { @@ -384,129 +302,6 @@ a, button { } } -/* ===== Hover Effects — Subtle & Elegant ===== */ -.hover-lift { - transition: transform var(--duration-normal) var(--ease-out), - box-shadow var(--duration-normal) var(--ease-out); -} - -.hover-lift:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-lg); -} - -.hover-lift-dark:hover { - box-shadow: var(--shadow-dark-lg); -} - -/* ===== Section Divider — Clean ===== */ -.section-divider { - height: 1px; - max-width: 80%; - margin: var(--space-8) auto; - background: linear-gradient(90deg, transparent, var(--border-medium), transparent); -} - -.dark-divider { - background: linear-gradient(90deg, transparent, rgba(235,234,246,0.10), transparent); -} - -/* ===== Timeline Style — 响应式移动端单列布局 ===== */ -.timeline-line { - position: relative; - padding-left: 48px; -} - -@media (max-width: 640px) { - .timeline-line { - padding-left: 36px; - } -} - -.timeline-line::before { - content: ''; - position: absolute; - left: 20px; - top: 0; - bottom: 0; - width: 2px; - background: linear-gradient(180deg, transparent, rgba(235,234,246,0.4), color-mix(in oklch, var(--track-ai) 22%, transparent), transparent); -} - -@media (max-width: 640px) { - .timeline-line::before { - left: 14px; - } -} - -.timeline-dot { - width: 10px; - height: 10px; - border-radius: 50%; - position: absolute; - left: 15px; - border: 2px solid; - display: flex; - align-items: center; - justify-content: center; -} - -@media (max-width: 640px) { - .timeline-dot { - left: 9px; - width: 8px; - height: 8px; - } -} - -/* ===== Star Field (Innovation Lab) — 降低密度和亮度,保持科技克制感 ===== */ -.star-field { - position: absolute; - inset: 0; - overflow: hidden; - pointer-events: none; -} - -.star { - position: absolute; - width: 1px; - height: 1px; - background: var(--brand-cream); - border-radius: 50%; - animation: starTwinkle var(--twinkle-duration) ease-in-out infinite; - opacity: 0; -} - -@keyframes starTwinkle { - 0%, 100% { opacity: 0; } - 50% { opacity: 0.35; } -} - -/* ===== Neon Elements — Dark pages only ===== */ -.neon-dot { - width: 6px; - height: 6px; - border-radius: 50%; - animation: neonDotPulse 3s ease-in-out infinite; -} - -@keyframes neonDotPulse { - 0%, 100% { opacity: 0.5; box-shadow: 0 0 3px currentColor; } - 50% { opacity: 1; box-shadow: 0 0 6px currentColor, 0 0 12px currentColor; } -} - -.neon-line { - height: 1px; - background: linear-gradient(90deg, transparent, var(--brand-cream), var(--track-ai), transparent); - animation: neonPulse 4s ease-in-out infinite; -} - -@keyframes neonPulse { - 0%, 100% { opacity: 0.3; } - 50% { opacity: 0.8; } -} - -/* ===== Flip Card (WCAG 2.1 AA 无障碍:支持键盘/触摸/点击) ===== */ /* ===== Flip Card (WCAG 2.1 AA 无障碍:支持键盘/触摸/点击) ===== */ /* Grid Stack 方案:两面叠在同一 grid-cell → 容器高度 = max(正面高, 背面高) —— 彻底解决原 absolute inset-0 + 固定 h-[200px] 导致长内容截断的问题 @@ -661,266 +456,6 @@ footer a { } } -/* ===== Innovation Lab — 科技感样式系统 ===== */ -.innovation-lab-container { - background: linear-gradient(180deg, #0a0a0f 0%, #12121a 30%, #0e0e16 70%, #0a0a0f 100%); - position: relative; - overflow: hidden; -} - -/* 动态背景网格 */ -.grid-background { - position: fixed; - inset: 0; - background-image: - linear-gradient(rgba(201,177,255,0.03) 1px, transparent 1px), - linear-gradient(90deg, rgba(201,177,255,0.03) 1px, transparent 1px); - background-size: 60px 60px; - opacity: 0; - animation: gridFadeIn 2s ease-out 0.5s forwards; - pointer-events: none; - z-index: 0; -} - -@keyframes gridFadeIn { - to { opacity: 1; } -} - -/* 光晕装饰层 */ -.glow-orbs { - position: fixed; - inset: 0; - pointer-events: none; - z-index: 0; -} - -.glow-orbs::before, -.glow-orbs::after { - content: ''; - position: absolute; - border-radius: 50%; - filter: blur(100px); - opacity: 0.3; -} - -.glow-orbs::before { - width: 600px; - height: 600px; - background: radial-gradient(circle, var(--track-ai)40 0%, transparent 70%); - top: -200px; - right: -100px; - animation: orbFloat1 20s ease-in-out infinite; -} - -.glow-orbs::after { - width: 500px; - height: 500px; - background: radial-gradient(circle, var(--track-career)30 0%, transparent 70%); - bottom: -150px; - left: -80px; - animation: orbFloat2 25s ease-in-out infinite; -} - -@keyframes orbFloat1 { - 0%, 100% { transform: translate(0, 0) scale(1); } - 33% { transform: translate(-30px, 20px) scale(1.1); } - 66% { transform: translate(20px, -15px) scale(0.95); } -} - -@keyframes orbFloat2 { - 0%, 100% { transform: translate(0, 0) scale(1); } - 50% { transform: translate(40px, 30px) scale(1.15); } -} - - -/* 扫描线 */ -.scan-line { - position: absolute; - top: 0; - left: 50%; - transform: translateX(-50%); - width: 100%; - height: 2px; - background: linear-gradient(90deg, transparent, var(--track-ai), transparent); - animation: scanLineMove 3s ease-in-out infinite; -} - -@keyframes scanLineMove { - 0% { top: 0; opacity: 0; } - 20% { opacity: 1; } - 80% { opacity: 1; } - 100% { top: 100%; opacity: 0; } -} - -/* 动态进度条 */ -.progress-bar { - width: 0%; - height: 100%; - background: linear-gradient(90deg, var(--track-ai), var(--track-career)); - border-radius: inherit; - animation: progressFill 1.5s ease-out 0.5s forwards; - box-shadow: 0 0 10px var(--track-ai); -} - -@keyframes progressFill { - to { width: 100%; } -} - -/* 发光圆点 */ -.neon-dot-glow { - width: 8px; - height: 8px; - border-radius: 50%; - animation: neonGlow 2s ease-in-out infinite; -} - -.neon-dot-glow-lg { - width: 12px; - height: 12px; - border-radius: 50%; - animation: neonGlow 2s ease-in-out infinite; -} - -@keyframes neonGlow { - 0%, 100% { opacity: 0.6; box-shadow: 0 0 5px currentColor, 0 0 10px currentColor; } - 50% { opacity: 1; box-shadow: 0 0 10px currentColor, 0 0 20px currentColor, 0 0 30px currentColor; } -} - -/* 科技感时间线 */ -.timeline-tech { - position: relative; - padding-left: 52px; -} - -@media (max-width: 640px) { - .timeline-tech { - padding-left: 40px; - } -} - -.timeline-item { - position: relative; -} - -.timeline-connector { - position: absolute; - left: 9px; - top: 16px; - width: 2px; - height: calc(100% - 16px); -} - -@media (max-width: 640px) { - .timeline-connector { - left: 5px; - } -} - -.timeline-node { - width: 16px; - height: 16px; - border-radius: 50%; - position: absolute; - left: -44px; - border: 2px solid var(--node-color); - background: #0a0a0f; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.5s ease; -} - -@media (max-width: 640px) { - .timeline-node { - left: -32px; - width: 12px; - height: 12px; - } -} - -.timeline-node.active { - background: var(--node-color); -} - -.timeline-node.upcoming { - opacity: 0.5; - border-color: rgba(255,255,255,0.3); -} - -.timeline-node.upcoming .node-inner { - opacity: 0.3; -} - -.timeline-node.upcoming .node-glow { - opacity: 0; -} - -.timeline-node:hover { - transform: scale(1.3); -} - -.node-inner { - width: 8px; - height: 8px; - border-radius: 50%; - background: var(--node-color); - opacity: 0; - transition: opacity 0.3s ease; -} - -.timeline-node.active .node-inner { - opacity: 1; - box-shadow: 0 0 10px var(--node-color); -} - -.node-glow { - position: absolute; - inset: -4px; - border-radius: 50%; - border: 1px solid var(--node-color); - opacity: 0; - animation: nodePulse 2s ease-in-out infinite; -} - -.timeline-node.active .node-glow { - opacity: 0.5; -} - -@keyframes nodePulse { - 0%, 100% { transform: scale(1); opacity: 0.5; } - 50% { transform: scale(1.5); opacity: 0; } -} - -.node-check { - color: #0a0a0f; - font-size: 8px; - font-weight: bold; - opacity: 0; - transition: opacity 0.3s ease; -} - -.timeline-node.active .node-check { - opacity: 1; -} - -.timeline-card { - position: relative; -} - -.timeline-card::before { - content: ''; - position: absolute; - inset: 0; - border-radius: inherit; - background: linear-gradient(135deg, rgba(255,255,255,0.03) 0%, transparent 50%); - opacity: 0; - transition: opacity 0.3s ease; -} - -.timeline-card:hover::before { - opacity: 1; -} - /* ===== Responsive ===== */ @media (max-width: 768px) { :root { @@ -993,7 +528,7 @@ footer a { /* 细线分隔的版式(已并入 .section-eyebrow 统一引导线,保留兼容) */ -/* ===== Stat band — "翱捷速度" 风格的数据滚动 ===== */ +/* ===== Stat band — 细线分隔的数据格 ===== */ .stat-band { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); @@ -1006,6 +541,22 @@ footer a { @media (min-width: 768px) { .stat-band { grid-template-columns: repeat(4, minmax(0, 1fr)); } } +/* 两项版(首页愿景带「全球 / 长期」):桌面端维持 2 列,否则四列栅格里空两格。 + 收窄到 720px 居中,使两格宽度接近 Investors 四格版的观感。 + 双类名提高优先级,避免与被复用的 .stat-band 桌面四列规则打架。 */ +.stat-band.stat-band--pair { + max-width: 720px; + margin-inline: auto; +} +@media (min-width: 768px) { + .stat-band.stat-band--pair { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +/* 两项版的值是词不是数字。中文「全球 / 长期」两字,沿用 .stat-num 原字号即可; + 英文最长词 "Long-term"(约 4.77em 宽)在原 38px 下限下会在 390px 屏折成 + "Long-" / "term" 两行,故只对英文收一档:26px→124px < 移动端内容宽 138px, + 54px→258px < 桌面 319px,两端都单行放得下。中英文分开取值,避免为迁就英文 + 而把中文那格也缩小。Investors 的四格版不受影响,仍是原字号。 */ +html:lang(en) .stat-band--pair .stat-num { font-size: clamp(26px, 5.2vw, 54px); } .stat-cell { background: var(--bg-primary); padding: 32px 20px; @@ -1022,7 +573,6 @@ footer a { color: var(--brand-black); } .stat-suffix { font-size: 0.52em; font-weight: 600; color: var(--track-color, var(--brand-accent-deep)); } -.stat-value--text { /* 兼容保留:统一字号后无需差异化,空实现避免引用报错 */ } .stat-label { margin-top: 10px; font-size: var(--fs-micro); @@ -1036,18 +586,7 @@ footer a { opacity: 0.7; } -/* ===== Hero video wrap (首页) ===== */ -.hero-video { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - object-fit: cover; - object-position: center 65%; - /* 不要任何底垫色,只保留视频本身(视频未加载时透出底层页面背景) */ - background: transparent; - z-index: 0; -} +/* ===== Hero slogan(首页 / 各页首屏大标题)===== */ .hero-slogan { font-family: var(--font-display); font-weight: 700; @@ -1058,56 +597,6 @@ footer a { white-space: pre-line; } -/* ===== News / 动态 card (asrmicro 新闻中心风格) ===== */ -.news-card { - display: flex; - flex-direction: column; - background: var(--bg-primary); - border: 1px solid var(--border-light); - border-radius: var(--radius-md); - padding: 22px 22px 24px; - transition: transform var(--duration-normal) var(--ease-out), - box-shadow var(--duration-normal) var(--ease-out), - border-color var(--duration-normal) var(--ease-out); -} -.news-card:hover { - transform: translateY(-4px); - box-shadow: var(--shadow-md); - border-color: var(--brand-cream-deep); -} -.news-tag { - align-self: flex-start; - font-family: var(--font-mono); - font-size: var(--fs-micro); - letter-spacing: 0.10em; - text-transform: uppercase; - padding: 3px 9px; - border-radius: 999px; - background: rgba(235,234,246,0.22); - color: var(--text-muted); - border: 1px solid rgba(235,234,246,0.30); -} -.news-year { - font-family: var(--font-mono); - font-size: var(--fs-micro); - color: var(--track-career); - margin-top: 14px; -} -.news-title { - margin-top: 6px; - font-size: var(--fs-body); - font-weight: 600; - line-height: 1.6; - color: var(--text-primary); -} -.news-desc { - margin-top: 8px; - font-size: var(--fs-body); - line-height: 1.7; - color: var(--text-secondary); - flex: 1; -} - /* ===== Track / product matrix cards (主营业务风格) ===== */ .track-card { position: relative; @@ -1129,15 +618,15 @@ footer a { background: color-mix(in oklch, var(--track-color, var(--track-career)) 4%, var(--bg-primary)); } -/* ===== 统一图标块(品牌双色体系:#3d5afe ↔ #EBEAF6)===== - 全站图标 tile 共用一套配方:#EBEAF6 浅薰衣草底 + #3d5afe 品牌蓝图标。 +/* ===== 统一图标块(品牌双色体系:#3d5afe ↔ #E8EAF6)===== + 全站图标 tile 共用一套配方:#E8EAF6 浅薰衣草底 + #3d5afe 品牌蓝图标。 不再依赖 --track-color,统一为品牌双色,保证全站图标视觉一致。 */ .icon-tile { display: inline-flex; align-items: center; justify-content: center; border-radius: var(--radius-md); - background: var(--brand); /* #EBEAF6 浅薰衣草底 */ + background: var(--brand); /* #E8EAF6 浅薰衣草底 */ border: none; color: var(--brand-accent); /* #3d5afe 品牌蓝图标(lucide 用 currentColor) */ flex-shrink: 0; @@ -1151,16 +640,17 @@ footer a { background: color-mix(in oklch, var(--brand-accent) 8%, var(--brand)); box-shadow: 0 6px 18px rgba(61, 90, 254, 0.18); } -.icon-tile--lg { width: 48px; height: 48px; } -.icon-tile--md { width: 40px; height: 40px; } -.icon-tile--sm { width: 36px; height: 36px; } /* ===== CTA band ===== */ .cta-band { position: relative; border-radius: var(--radius-xl); overflow: hidden; - background: linear-gradient(135deg, #1A1A1A 0%, #2a2118 55%, #1A1A1A 100%); + /* 中段提亮由 brand-black + brand-accent 派生:冷调深靛,替代早期暖棕 #2a2118 */ + background: linear-gradient(135deg, + var(--brand-black) 0%, + color-mix(in oklch, var(--brand-black) 76%, var(--brand-accent)) 55%, + var(--brand-black) 100%); color: var(--brand-white); } .cta-band::before { @@ -1168,7 +658,7 @@ footer a { position: absolute; inset: 0; background: - radial-gradient(ellipse 40% 60% at 20% 20%, rgba(235,234,246,0.16), transparent 60%), + radial-gradient(ellipse 40% 60% at 20% 20%, rgba(232,234,246,0.16), transparent 60%), radial-gradient(ellipse 40% 60% at 85% 80%, color-mix(in oklch, var(--track-career) 12%, transparent), transparent 55%); pointer-events: none; } @@ -1255,9 +745,9 @@ footer a { .btn-outline-light { background: transparent; color: var(--brand-cream); - border-color: rgba(235,234,246,0.35); + border-color: rgba(232,234,246,0.35); } -.btn-outline-light:hover { background: rgba(235,234,246,0.10); } +.btn-outline-light:hover { background: rgba(232,234,246,0.10); } .btn-lg { padding: 15px 32px; font-size: var(--fs-body); } .btn-sm { padding: 10px 18px; font-size: var(--fs-micro); } @@ -1284,137 +774,6 @@ footer a { } .feature-media:hover img { transform: scale(1.05); } -/* ===== 图文卡(asrmicro 式:相关图进卡,图在上、文在下)===== - 用于 Home 四大赛道、Innovation 技术路径:每张卡带一张与赛道强相关的图, - 顶部 4px 赛道色条保留识别度,hover 轻微上浮 + 图片缓放。 */ -.track-media { - position: relative; - display: flex; - flex-direction: column; - background: var(--bg-primary); - border: 1px solid var(--border-light); - border-radius: var(--radius-lg); - overflow: hidden; - transition: transform var(--duration-normal) var(--ease-out), - box-shadow var(--duration-normal) var(--ease-out), - border-color var(--duration-normal) var(--ease-out), - background-color var(--duration-normal) var(--ease-out); -} -.track-media:hover { - transform: translateY(-5px); - box-shadow: var(--shadow-lg); - border-color: color-mix(in oklch, var(--track-color, var(--track-career)) 45%, transparent); - background: color-mix(in oklch, var(--track-color, var(--track-career)) 4%, var(--bg-primary)); -} -.track-media__media { - position: relative; - aspect-ratio: 16 / 10; - overflow: hidden; -} -.track-media__media img { - width: 100%; - height: 100%; - object-fit: cover; - display: block; - transition: transform var(--duration-slow) var(--ease-out); -} -.track-media:hover .track-media__media img { transform: scale(1.06); } -.track-media__body { - display: flex; - flex-direction: column; - flex: 1; - padding: 18px 20px 22px; -} - -/* ===== 图文并排卡(图在侧、内容在另一侧;赛道大段用)===== - 用于 Business 四大赛道:左侧相关图、右侧标题+产品矩阵;奇偶交替左右。 */ -.track-split { - position: relative; - display: grid; - grid-template-columns: 1fr; - background: var(--bg-primary); - border: 1px solid var(--border-light); - border-radius: 0; - overflow: hidden; - transition: box-shadow var(--duration-normal) var(--ease-out), - border-color var(--duration-normal) var(--ease-out); -} -.track-split:hover { - box-shadow: var(--shadow-lg); - border-color: color-mix(in oklch, var(--track-color, var(--track-career)) 35%, transparent); -} -.track-split__media { - position: relative; - min-height: 240px; - overflow: hidden; -} -.track-split__media img { - position: absolute; - inset: 0; - width: 100%; - height: 100%; - object-fit: cover; - transition: transform var(--duration-slow) var(--ease-out); -} -.track-split:hover .track-split__media img { transform: scale(1.04); } -.track-split__body { padding: 24px 26px 28px; } -@media (min-width: 768px) { - .track-split { grid-template-columns: 0.95fr 1.05fr; } - .track-split__media { min-height: 100%; } - .track-split--rev .track-split__media { order: 2; } -} - -/* ===== Tech Hero — asrmicro「科技公司」暗色脉搏段(暖米/琥珀点缀,不返红)===== - 用于 Innovation 页首屏:近黑底 + 细网格 + 暖米光晕,给全站中段注入能量与对比。 - 网格/光晕用 absolute 限定在本区块内(避免旧 .grid-background 的 fixed 溢出问题)。 */ -.tech-hero { - position: relative; - background: linear-gradient(180deg, #0c0c12 0%, #15151f 42%, #0e0e16 100%); - overflow: hidden; - isolation: isolate; -} -.tech-hero-grid { - position: absolute; - inset: 0; - z-index: 0; - pointer-events: none; - background-image: - linear-gradient(rgba(235,234,246,0.05) 1px, transparent 1px), - linear-gradient(90deg, rgba(235,234,246,0.05) 1px, transparent 1px); - background-size: 56px 56px; - -webkit-mask-image: radial-gradient(ellipse 85% 70% at 50% 38%, #000 25%, transparent 82%); - mask-image: radial-gradient(ellipse 85% 70% at 50% 38%, #000 25%, transparent 82%); - animation: gridFadeIn 1.6s ease-out forwards; -} -.tech-hero-glow { position: absolute; inset: 0; z-index: 0; pointer-events: none; } -.tech-hero-glow::before, -.tech-hero-glow::after { - content: ''; - position: absolute; - border-radius: 50%; - filter: blur(110px); - opacity: 0.35; -} -.tech-hero-glow::before { - width: 520px; height: 520px; - background: radial-gradient(circle, rgba(235,234,246,0.18) 0%, transparent 70%); - top: -160px; right: -80px; - animation: orbFloat1 22s ease-in-out infinite; -} -.tech-hero-glow::after { - width: 460px; height: 460px; - background: radial-gradient(circle, rgba(200,132,26,0.22) 0%, transparent 70%); - bottom: -140px; left: -60px; - animation: orbFloat2 26s ease-in-out infinite; -} -.tech-hero .section-eyebrow { color: rgba(235,234,246,0.78); } -.tech-hero .section-eyebrow::before { background: var(--brand-accent); } -.tech-hero .section-title { color: #fff; } -.tech-hero .section-lead { color: rgba(255,255,255,0.72); } - -/* 图片悬停轻微放大(已在 .feature-media 内),此处补一处暗场图片呼吸感 */ -@keyframes gridFadeIn { from { opacity: 0; } to { opacity: 1; } } - /* ===== Typography Utilities — 全站字号统一走 CSS 变量阶梯 ===== */ @utility text-display { font-size: var(--fs-display); } @utility text-h1 { font-size: var(--fs-h1); } @@ -1437,10 +796,13 @@ footer a { flex-direction: column; align-items: flex-end; gap: 12px; - /* 主题色:主体用浅薰衣草;点缀用品牌蓝;车灯与星星用暖琥珀黄(光感闪耀) */ + /* 主题色:主体用浅薰衣草;点缀/车灯/星星一律品牌蓝(原暖琥珀 #F5A623 已废弃) */ --mascot-body: var(--brand); --mascot-accent: var(--brand-accent); - --mascot-star: #F5A623; + --mascot-star: var(--brand-accent); + /* 车窗玻璃:浅色(原为 #1A1A1A 实心黑,横在眼睛上像"黑眼罩")。 + 由主题色掺一点品牌蓝得来,比车身略深一档,才看得出是"窗"而不是个洞。 */ + --mascot-glass: color-mix(in srgb, var(--brand-accent) 16%, var(--brand)); transition: transform 0.88s cubic-bezier(0.16, 1, 0.3, 1), opacity 0.45s ease; transform: translate3d(180px, 60px, 0) rotate(-10deg); @@ -1466,6 +828,7 @@ footer a { .mascot-svg .mascot-body { fill: var(--mascot-body); } .mascot-svg .mascot-accent { fill: var(--mascot-accent); } .mascot-svg .mascot-light { fill: var(--mascot-star); } +.mascot-svg .mascot-glass { fill: var(--mascot-glass); } .mascot-svg .mascot-star { fill: var(--mascot-star); } .mascot-float:hover { transform: scale(1.06); } .mascot-float:focus-visible { outline: 2px solid var(--accent); outline-offset: 3px; } @@ -1561,7 +924,7 @@ footer a { pointer-events: auto; } .back-to-top:hover { - background: var(--brand-accent-deep, #2a3fd6); + background: var(--brand-accent-deep); transform: translateY(-2px); box-shadow: 0 12px 26px rgba(61, 90, 254, 0.45); } @@ -1693,319 +1056,6 @@ footer a { } } -/* ================================================================ - Hero 背景:环绕中央玻璃球的精致轨道粒子动画 - - 7 圈轨道(半径 110~275px),明确围绕玻璃球外侧公转 - - 16 颗精致小点(3~6px),白核 + 淡蓝边缘,光晕柔和不抢戏 - - 每点带轻微脉冲(3s,stagger),整体节奏从容 - - 锚点对齐 hero-bg.jpg 中央玻璃球(约 70% / 38%) - - 仅动 transform / opacity(GPU 友好) - ================================================================ */ -.hero-orbit { - position: absolute; - left: 70%; - top: 46%; - width: 0; - height: 0; - pointer-events: none; -} -@media (max-width: 640px) { - /* 窄屏下图片横向裁切更多,球偏右;轨道中心随之下移以贴近主体 */ - .hero-orbit { left: 77%; top: 48%; } -} -.hero-ring { - position: absolute; - top: 0; - left: 0; - width: 0; - height: 0; - transform-origin: 0 0; - animation: heroOrbitSpin 12s linear infinite; -} -.hero-orbit-slot { - position: absolute; - top: 0; - left: 0; - width: 0; - height: 0; - transform-origin: 0 0; - /* 预旋转到角度 a,再向外推 r —— 父级 .hero-ring 旋转时整圈绕中心公转 */ - transform: rotate(var(--a, 0deg)) translateY(calc(-1 * var(--r, 50px))); -} -.hero-orbit-dot { - position: absolute; - top: 0; - left: 0; - border-radius: 50%; - /* 柔和径向渐变:白核 → 淡蓝雾化 → 透明,告别"硬光球"观感 */ - background: radial-gradient( - circle, - rgba(255, 255, 255, 0.95) 0%, - rgba(255, 255, 255, 0.55) 30%, - rgba(186, 204, 255, 0.32) 60%, - rgba(186, 204, 255, 0) 80% - ); - box-shadow: - 0 0 6px rgba(255, 255, 255, 0.55), - 0 0 14px rgba(186, 204, 255, 0.32); - transform: translate(-50%, -50%); - will-change: transform, opacity; - animation: heroOrbitPulse 3.2s ease-in-out infinite; -} - -/* 彗星拖尾:沿轨道切线方向(slot 局部 ±X 轴),公转时自然拖在运动后方 - - normal 公转(顺时针)→ 拖尾在 -X(dot 左侧) - - reverse 公转(逆时针)→ 拖尾镜像到 +X(dot 右侧),见 .is-rev 覆盖 - - 跟随 dot 的 pulse 一起呼吸/缩放,无需单独动画 */ -.hero-orbit-dot::before { - content: ""; - position: absolute; - top: 50%; - right: 50%; - transform: translateY(-50%); - width: 22px; - height: 100%; - border-radius: 999px; - background: linear-gradient( - to left, - rgba(255, 255, 255, 0.9) 0%, - rgba(186, 204, 255, 0.55) 35%, - rgba(186, 204, 255, 0) 100% - ); - pointer-events: none; -} -.hero-ring.is-rev .hero-orbit-dot::before { - right: auto; - left: 50%; - background: linear-gradient( - to right, - rgba(255, 255, 255, 0.9) 0%, - rgba(186, 204, 255, 0.55) 35%, - rgba(186, 204, 255, 0) 100% - ); -} - -@keyframes heroOrbitSpin { - to { transform: rotate(360deg); } -} - -/* 轻微呼吸:相位由 inline animationDelay 注入(每颗错开 0.35s),避免齐刷刷 */ -@keyframes heroOrbitPulse { - 0%, 100% { opacity: 0.6; transform: translate(-50%, -50%) scale(0.85); } - 50% { opacity: 1; transform: translate(-50%, -50%) scale(1.25); } -} - -/* ===== 轨道粒子 4 色变体(白/蓝/紫/粉)+ 加长彗星拖尾 ===== */ -.hero-orbit-dot.dot-white { - background: radial-gradient( - circle, - rgba(255, 255, 255, 0.98) 0%, - rgba(255, 255, 255, 0.6) 30%, - rgba(220, 230, 255, 0.35) 60%, - rgba(220, 230, 255, 0) 82% - ); - box-shadow: 0 0 8px rgba(255, 255, 255, 0.55), 0 0 18px rgba(210, 220, 255, 0.28); -} -.hero-orbit-dot.dot-white::before { - width: 32px; - background: linear-gradient( - to left, - rgba(255, 255, 255, 0.88) 0%, - rgba(200, 215, 255, 0.45) 40%, - rgba(200, 215, 255, 0) 100% - ); -} -.hero-ring.is-rev .hero-orbit-dot.dot-white::before { - background: linear-gradient( - to right, - rgba(255, 255, 255, 0.88) 0%, - rgba(200, 215, 255, 0.45) 40%, - rgba(200, 215, 255, 0) 100% - ); -} - -.hero-orbit-dot.dot-blue { - background: radial-gradient( - circle, - rgba(210, 225, 255, 0.98) 0%, - rgba(120, 160, 255, 0.62) 32%, - rgba(70, 110, 255, 0.30) 62%, - rgba(70, 110, 255, 0) 84% - ); - box-shadow: 0 0 8px rgba(120, 160, 255, 0.55), 0 0 20px rgba(61, 90, 254, 0.32); -} -.hero-orbit-dot.dot-blue::before { - width: 36px; - background: linear-gradient( - to left, - rgba(170, 198, 255, 0.9) 0%, - rgba(85, 125, 255, 0.45) 42%, - rgba(85, 125, 255, 0) 100% - ); -} -.hero-ring.is-rev .hero-orbit-dot.dot-blue::before { - background: linear-gradient( - to right, - rgba(170, 198, 255, 0.9) 0%, - rgba(85, 125, 255, 0.45) 42%, - rgba(85, 125, 255, 0) 100% - ); -} - -.hero-orbit-dot.dot-violet { - background: radial-gradient( - circle, - rgba(232, 220, 255, 0.98) 0%, - rgba(185, 155, 255, 0.60) 32%, - rgba(150, 110, 255, 0.28) 62%, - rgba(150, 110, 255, 0) 84% - ); - box-shadow: 0 0 8px rgba(185, 155, 255, 0.55), 0 0 20px rgba(150, 110, 255, 0.30); -} -.hero-orbit-dot.dot-violet::before { - width: 34px; - background: linear-gradient( - to left, - rgba(220, 205, 255, 0.9) 0%, - rgba(160, 130, 255, 0.44) 42%, - rgba(160, 130, 255, 0) 100% - ); -} -.hero-ring.is-rev .hero-orbit-dot.dot-violet::before { - background: linear-gradient( - to right, - rgba(220, 205, 255, 0.9) 0%, - rgba(160, 130, 255, 0.44) 42%, - rgba(160, 130, 255, 0) 100% - ); -} - -.hero-orbit-dot.dot-pink { - background: radial-gradient( - circle, - rgba(255, 230, 240, 0.98) 0%, - rgba(255, 170, 205, 0.58) 32%, - rgba(255, 125, 180, 0.26) 62%, - rgba(255, 125, 180, 0) 84% - ); - box-shadow: 0 0 8px rgba(255, 170, 205, 0.52), 0 0 20px rgba(255, 125, 180, 0.28); -} -.hero-orbit-dot.dot-pink::before { - width: 32px; - background: linear-gradient( - to left, - rgba(255, 215, 230, 0.9) 0%, - rgba(255, 145, 193, 0.42) 42%, - rgba(255, 145, 193, 0) 100% - ); -} -.hero-ring.is-rev .hero-orbit-dot.dot-pink::before { - background: linear-gradient( - to right, - rgba(255, 215, 230, 0.9) 0%, - rgba(255, 145, 193, 0.42) 42%, - rgba(255, 145, 193, 0) 100% - ); -} - -/* ===== 雾化光晕 orb:超大 blur 圆,静止不漂(浅底可见版:透明度×2.2) ===== */ -.hero-glow-orb { - position: absolute; - left: var(--orb-x); - top: var(--orb-y); - width: calc(var(--orb-sz) * 1.15); /* 尺寸 ×1.15,浅底上更有存在感 */ - height: calc(var(--orb-sz) * 1.15); - border-radius: 9999px; - background: radial-gradient(circle, var(--orb-color) 0%, transparent 80%); - filter: blur(32px); /* 原 28px → 略增大,扩散感更强 */ - opacity: 1.0; /* 原 0.85 → ×1.18,完全可见 */ - mix-blend-mode: multiply;/* 浅底叠加更柔和,不突兀 */ - animation-duration: 18s; - animation-timing-function: ease-in-out; - animation-iteration-count: infinite; - will-change: transform; - pointer-events: none; - transform: translate(-50%, -50%); -} - -@keyframes hero-orb-float-1 { - 0%, 100% { transform: translate(-50%, -50%) translate(0, 0) scale(1); } - 33% { transform: translate(-50%, -50%) translate(40px, -30px) scale(1.08); } - 66% { transform: translate(-50%, -50%) translate(-30px, 25px) scale(0.94); } -} -@keyframes hero-orb-float-2 { - 0%, 100% { transform: translate(-50%, -50%) translate(0, 0) scale(1); } - 40% { transform: translate(-50%, -50%) translate(-36px, 30px) scale(1.05); } - 75% { transform: translate(-50%, -50%) translate(28px, -28px) scale(0.97); } -} - -@media (prefers-reduced-motion: reduce) { - .hero-ring { animation: none; } - .hero-orbit-dot { animation: none; opacity: 0.82; } - .hero-glow-orb { animation: none; } -} - -/* ===== 方案B:用户不喜欢"游来游去" → 全部停移动,保留呼吸闪烁 ===== */ -.hero-ring { - animation: none !important; -} -.hero-glow-orb { - animation: none !important; -} - -/* 轨道粒子:去掉彗星游动拖尾 → 改成静态"星芒放射"(4 向渐变光线) */ -.hero-orbit-dot::before, -.hero-ring.is-rev .hero-orbit-dot::before { - content: ""; - position: absolute; - top: 50%; - left: 50%; - transform: translate(-50%, -50%); - width: 220%; - height: 220%; - border-radius: 9999px; - background: - radial-gradient( - ellipse at 50% 50%, - rgba(255, 255, 255, 0.0) 0%, - rgba(255, 255, 255, 0.0) 28%, - rgba(255, 255, 255, 0.0) 100% - ); - pointer-events: none; -} -.hero-orbit-dot.dot-white::before { - background: - radial-gradient(circle at 50% 50%, rgba(255,255,255,0.0) 30%, rgba(255,255,255,0.60) 42%, rgba(255,255,255,0.0) 54%), - linear-gradient(to right, transparent 48%, rgba(255,255,255,0.48) 50%, transparent 52%), - linear-gradient(to bottom, transparent 48%, rgba(255,255,255,0.48) 50%, transparent 52%), - linear-gradient(45deg, transparent 48%, rgba(255,255,255,0.30) 50%, transparent 52%), - linear-gradient(-45deg, transparent 48%, rgba(255,255,255,0.30) 50%, transparent 52%); -} -.hero-orbit-dot.dot-blue::before { - background: - radial-gradient(circle at 50% 50%, rgba(80,125,255,0.0) 30%, rgba(80,125,255,0.60) 42%, rgba(80,125,255,0.0) 54%), - linear-gradient(to right, transparent 48%, rgba(80,125,255,0.48) 50%, transparent 52%), - linear-gradient(to bottom, transparent 48%, rgba(80,125,255,0.48) 50%, transparent 52%), - linear-gradient(45deg, transparent 48%, rgba(80,125,255,0.30) 50%, transparent 52%), - linear-gradient(-45deg, transparent 48%, rgba(80,125,255,0.30) 50%, transparent 52%); -} -.hero-orbit-dot.dot-violet::before { - background: - radial-gradient(circle at 50% 50%, rgba(135,100,255,0.0) 30%, rgba(135,100,255,0.60) 42%, rgba(135,100,255,0.0) 54%), - linear-gradient(to right, transparent 48%, rgba(135,100,255,0.48) 50%, transparent 52%), - linear-gradient(to bottom, transparent 48%, rgba(135,100,255,0.48) 50%, transparent 52%), - linear-gradient(45deg, transparent 48%, rgba(135,100,255,0.30) 50%, transparent 52%), - linear-gradient(-45deg, transparent 48%, rgba(135,100,255,0.30) 50%, transparent 52%); -} -.hero-orbit-dot.dot-pink::before { - background: - radial-gradient(circle at 50% 50%, rgba(255,105,175,0.0) 30%, rgba(255,105,175,0.60) 42%, rgba(255,105,175,0.0) 54%), - linear-gradient(to right, transparent 48%, rgba(255,105,175,0.48) 50%, transparent 52%), - linear-gradient(to bottom, transparent 48%, rgba(255,105,175,0.48) 50%, transparent 52%), - linear-gradient(45deg, transparent 48%, rgba(255,105,175,0.30) 50%, transparent 52%), - linear-gradient(-45deg, transparent 48%, rgba(255,105,175,0.30) 50%, transparent 52%); -} - /* HERO 左下文字(h1 / p / a)在浅底 hero-bg.jpg 上的可读阴影,保证不蒙层时也清晰 */ .hero-text-visible h1.text-white, .hero-text-visible p.text-white, @@ -2022,10 +1072,6 @@ footer a { border-bottom-color: rgba(255, 255, 255, 0.85); } -@media (max-width: 640px) { - .hero-glow-orb { filter: blur(18px); opacity: 0.7; } -} - /* ================================================================ 公司概览:中心节点辐射网络图(深色科技感) - 中心「巴适伙伴」+ 6 个可点击子节点,SVG 发光线连接 @@ -2125,20 +1171,9 @@ footer a { } /* ===== 无障碍工具类(WCAG 2.4.1 Bypass Blocks + 2.5.8 Target Size)===== - sr-only:视觉隐藏但屏幕阅读器可读。 + sr-only:曾在此自建,现已删除——Tailwind 自带同名工具类,用到时按需生成即可。 skip-link:默认隐藏,键盘 Tab 聚焦时显现,跳到主内容(绕过重复导航)。 触控目标:桌面端导航 ≈24px(达 AA 24px 最小值),移动端导航 py-2.5≈44px(达 AAA)。 */ -.sr-only { - position: absolute; - width: 1px; - height: 1px; - padding: 0; - margin: -1px; - overflow: hidden; - clip: rect(0, 0, 0, 0); - white-space: nowrap; - border: 0; -} .skip-link { position: absolute; left: 1rem; @@ -2260,7 +1295,7 @@ footer a { max-height: 560px; border-radius: var(--radius-xl); background: - radial-gradient(ellipse 70% 60% at 50% 50%, rgba(235,234,246,0.55), transparent 70%), + radial-gradient(ellipse 70% 60% at 50% 50%, rgba(232,234,246,0.55), transparent 70%), var(--bg-primary); border: 1px solid var(--border-light); box-shadow: var(--shadow-sm); @@ -2329,7 +1364,7 @@ footer a { position: absolute; inset: -10px; border-radius: inherit; - background: radial-gradient(circle, rgba(235,234,246,0.55) 0%, transparent 70%); + background: radial-gradient(circle, rgba(232,234,246,0.55) 0%, transparent 70%); z-index: -1; animation: ecoPulse 3.6s ease-in-out infinite; } @@ -2563,3 +1598,100 @@ footer a { transform: none !important; } } + +/* ================================================================ + 管理团队 · 成员详情弹窗(.member-modal) + 用原生 + showModal():元素进入 top layer,天然逃出 .page-enter / + .scroll-reveal 的 transform 包含块(否则 fixed 定位会跟着页面滚),并自带 + Esc 关闭、焦点陷阱与背景 inert——不需要 portal、z-index 和手写 focus trap。 + ================================================================ */ +.member-modal { + /* 必须显式写:模态 dialog 靠 UA 的 margin:auto + inset:0 居中,而 Tailwind v4 的 + preflight 在 * 上写了 margin:0,把这条 UA 规则覆盖掉了 —— 不补回来弹窗会贴在 + 左上角。inset/top-layer 都正常,只有 margin 这一处被 preflight 打掉。 */ + margin: auto; + width: min(880px, calc(100vw - 32px)); + max-width: none; + /* dvh 而不是 vh:移动端浏览器地址栏会算进 vh,用 dvh 才不会把底部顶出屏幕 */ + max-height: min(85dvh, 860px); + padding: 0; + border: 1px solid var(--border-light); + border-radius: var(--radius-xl); + background: var(--bg-primary); + color: var(--text-primary); + box-shadow: var(--shadow-xl); + overflow: hidden; +} +/* 只在 [open] 时改 display:UA 用 dialog:not([open]) { display: none } 收起弹窗, + 而作者样式优先级高于 UA —— 无条件写 display 会让关闭态的弹窗一直挂在页面上 */ +.member-modal[open] { + display: flex; + flex-direction: column; + animation: memberModalIn 0.24s var(--ease-out-expo); +} +.member-modal::backdrop { + background: color-mix(in oklch, var(--brand-black) 55%, transparent); +} +.member-modal[open]::backdrop { + animation: memberModalFade 0.24s ease-out; +} +@keyframes memberModalIn { + from { opacity: 0; transform: translateY(14px) scale(0.985); } + to { opacity: 1; transform: none; } +} +@keyframes memberModalFade { + from { opacity: 0; } + to { opacity: 1; } +} + +/* 头部常驻不滚动(身份信息始终可见),只有正文区滚动 */ +.member-modal-head { + flex: none; + display: flex; + align-items: flex-start; + gap: 16px; + padding: 20px 22px 16px; + border-bottom: 1px solid var(--border-light); +} +.member-modal-portrait { + position: relative; + flex: none; + width: 84px; + height: 108px; + overflow: hidden; + border-radius: var(--radius-md); + background: var(--bg-subtle); +} +.member-modal-body { + flex: 1 1 auto; + min-height: 0; /* flex 子项默认 min-height:auto,不归零则 overflow-y 不生效 */ + overflow-y: auto; + overscroll-behavior: contain; + padding: 18px 22px calc(24px + env(safe-area-inset-bottom, 0px)); +} +.member-modal-section + .member-modal-section { margin-top: 20px; } +.member-modal-section h3 { + margin: 0 0 6px; + font-size: var(--fs-body-sm); + font-weight: 700; + color: var(--brand-accent-deep); +} +.member-modal-section p { + margin: 0; + font-size: var(--fs-body); + line-height: 1.85; + color: var(--text-secondary); +} +@media (min-width: 640px) { + .member-modal-head { padding: 24px 28px 18px; gap: 20px; } + .member-modal-portrait { width: 120px; height: 156px; } + .member-modal-body { padding: 22px 28px calc(28px + env(safe-area-inset-bottom, 0px)); } +} + +/* 弹窗打开时锁背景滚动。body 上已有 overflow-x:hidden 与安全区内边距, + 所以用 class 而非内联 style,避免覆盖 padding;--sbw 由弹窗组件写入, + 补偿滚动条消失的宽度。 */ +body.is-modal-open { + overflow: hidden; + padding-right: calc(var(--sbw, 0px) + env(safe-area-inset-right, 0)); +} diff --git a/tsconfig.json b/tsconfig.json index 295f661..e34345b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,8 @@ "isolatedModules": true, "moduleDetection": "force", "noEmit": true, + // 增量编译缓存也放进 node_modules,别在项目根留下 tsconfig.tsbuildinfo + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.tsbuildinfo", "jsx": "react-jsx", "jsxImportSource": "react", "strict": true, diff --git a/tsconfig.node.json b/tsconfig.node.json index 02cc893..86f7853 100644 --- a/tsconfig.node.json +++ b/tsconfig.node.json @@ -6,6 +6,12 @@ "moduleResolution": "bundler", "allowSyntheticDefaultImports": true, "strict": true, + // 本工程只做类型检查,不产出可运行 JS。 + // 注意:被 tsconfig.json 引用的 composite 工程不允许设 "noEmit"(TS6310), + // 所以退而求其次 —— 只 emit 声明文件,并把产物关进 node_modules/.tmp, + // 避免 `tsc -b` 把 vite.config.js / vite.config.d.ts 吐在项目根目录。 + "emitDeclarationOnly": true, + "outDir": "./node_modules/.tmp/node-types", "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" }, "include": ["vite.config.ts"] diff --git a/vercel.json b/vercel.json deleted file mode 100644 index cad12a2..0000000 --- a/vercel.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "rewrites": [ - { "source": "/robots.txt", "destination": "/robots.txt" }, - { "source": "/sitemap.xml", "destination": "/sitemap.xml" }, - { "source": "/og-default.png", "destination": "/og-default.png" }, - { "source": "/og-about.png", "destination": "/og-about.png" }, - { "source": "/og-business.png", "destination": "/og-business.png" }, - { "source": "/og-innovation.png", "destination": "/og-innovation.png" }, - { "source": "/og-join.png", "destination": "/og-join.png" }, - { "source": "/og-contact.png", "destination": "/og-contact.png" }, - { "source": "/(.*)", "destination": "/index.html" } - ] -}