commit
b7490ec1b7
49 changed files with 6039 additions and 0 deletions
@ -0,0 +1,46 @@ |
|||||
|
# ===== Dependencies ===== |
||||
|
node_modules/ |
||||
|
.pnpm-store/ |
||||
|
|
||||
|
# ===== Build Outputs ===== |
||||
|
dist/ |
||||
|
.astro/ |
||||
|
.output/ |
||||
|
|
||||
|
# ===== Environment ===== |
||||
|
.env |
||||
|
.env.local |
||||
|
.env.*.local |
||||
|
|
||||
|
# ===== macOS ===== |
||||
|
.DS_Store |
||||
|
.AppleDouble |
||||
|
.LSOverride |
||||
|
|
||||
|
# ===== Windows ===== |
||||
|
Thumbs.db |
||||
|
ehthumbs.db |
||||
|
Desktop.ini |
||||
|
|
||||
|
# ===== Editors ===== |
||||
|
.vscode/* |
||||
|
!.vscode/settings.json |
||||
|
!.vscode/extensions.json |
||||
|
.idea/ |
||||
|
*.swp |
||||
|
*.swo |
||||
|
*~ |
||||
|
|
||||
|
# ===== Logs ===== |
||||
|
*.log |
||||
|
npm-debug.log* |
||||
|
yarn-debug.log* |
||||
|
yarn-error.log* |
||||
|
pnpm-debug.log* |
||||
|
|
||||
|
# ===== Misc ===== |
||||
|
*.local |
||||
|
.cache/ |
||||
|
|
||||
|
# ===== Vite build temp artifacts ===== |
||||
|
vite.config.ts.timestamp-*.mjs |
||||
@ -0,0 +1,43 @@ |
|||||
|
<!doctype html> |
||||
|
<html lang="zh"> |
||||
|
<head> |
||||
|
<meta charset="UTF-8" /> |
||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
||||
|
<meta name="robots" content="index,follow,max-image-preview:large" /> |
||||
|
<meta name="referrer" content="strict-origin-when-cross-origin" /> |
||||
|
<link rel="icon" type="image/webp" href="/baskbuddy-logo.webp" /> |
||||
|
<link rel="apple-touch-icon" href="/baskbuddy-logo.webp" /> |
||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" /> |
||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin /> |
||||
|
<link |
||||
|
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Inter:wght@300;400;500;700&family=Quicksand:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap" |
||||
|
rel="stylesheet" |
||||
|
media="print" |
||||
|
onload="this.media='all'" |
||||
|
/> |
||||
|
<noscript> |
||||
|
<link |
||||
|
href="https://fonts.googleapis.com/css2?family=Noto+Sans+SC:wght@300;400;500;700&family=Inter:wght@300;400;500;700&family=Quicksand:wght@400;500;700&family=JetBrains+Mono:wght@400;500&display=swap" |
||||
|
rel="stylesheet" |
||||
|
/> |
||||
|
</noscript> |
||||
|
<title>BaskBuddy</title> |
||||
|
<script> |
||||
|
(function () { |
||||
|
try { |
||||
|
var t = localStorage.getItem('baskbuddy-theme') || 'system'; |
||||
|
var d = t === 'system' |
||||
|
? (window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light') |
||||
|
: t; |
||||
|
var r = document.documentElement; |
||||
|
r.setAttribute('data-theme', d); |
||||
|
r.style.colorScheme = d; |
||||
|
} catch (e) {} |
||||
|
})(); |
||||
|
</script> |
||||
|
</head> |
||||
|
<body> |
||||
|
<div id="root"></div> |
||||
|
<script type="module" src="/src/main.tsx"></script> |
||||
|
</body> |
||||
|
</html> |
||||
@ -0,0 +1,45 @@ |
|||||
|
# bashi-website — 色彩 & 图标 最终决策落地(像素君 / UI Designer) |
||||
|
|
||||
|
用户拍板「听你的」,以下决策已直接执行并验证通过。 |
||||
|
|
||||
|
## 1. 赛道色统一(解决双套定义) |
||||
|
之前:赛道卡片用粉彩 `#E8A0BF/#7EC8E3/#98D8AA/#C9B1FF`,但全局 `--track-*` token 是饱和默认色 `#EF4444/#2563EB/#10B981/#8B5CF6` —— **色相一致,唯独「社交」一边粉、一边红**最扎眼。 |
||||
|
|
||||
|
改为:token 与卡片粉彩**同色相**,并加深以保证 WCAG AA(白底文字 ≥4.5:1;实心按钮白字 ≥4.5:1): |
||||
|
|
||||
|
| 赛道 | 旧 token | 新 token | 对齐的卡片粉彩 | |
||||
|
|------|----------|----------|----------------| |
||||
|
| 社交 Social | `#EF4444` 红 | `#C2416B` 深玫瑰 | `#E8A0BF` 粉 | |
||||
|
| 职场 Career | `#2563EB` | `#1C7AB6` 深天蓝 | `#7EC8E3` 蓝 | |
||||
|
| 支付 Payment | `#10B981` | `#15803D` 深绿 | `#98D8AA` 绿 | |
||||
|
| AI | `#8B5CF6` | `#7C3AED` 深紫 | `#C9B1FF` 紫 | |
||||
|
|
||||
|
- 顺手**删掉 4 个从未引用的 `--track-*-bright` 死 token**,设计系统更干净。 |
||||
|
|
||||
|
## 2. 图标全部统一为 lucide(单一图标源) |
||||
|
设计系统第一原则就是一致性。把所有手写内联 SVG 换成 lucide: |
||||
|
- **Header**:汉堡 ↔ 关闭 → `Menu` / `X` |
||||
|
- **Contact**:`💼`→`Briefcase`、`🤝`→`Handshake`、CTA 箭头→`ArrowRight`、邮件→`Mail` |
||||
|
- **NotFound**:返回箭头→`ArrowLeft`、前往箭头→`ArrowRight` |
||||
|
- **Join**:投递区残留的邮件 SVG → `Mail` |
||||
|
|
||||
|
全站**已无任何 emoji**(上次 Join 福利区已改,本次清掉 Contact 两处 + Join 邮件图标)。 |
||||
|
|
||||
|
## 3. SPA fallback(生产环境深链接不 404) |
||||
|
新增 `public/_redirects`:`/* /index.html 200`(Netlify 及多数静态托管通用,对其它平台无害),已随构建进入 `dist/`。 |
||||
|
|
||||
|
> 若用其它托管,规则如下: |
||||
|
> - **nginx**:`try_files $uri $uri/ /index.html;` |
||||
|
> - **Vercel**:`vercel.json` → `{ "rewrites": [{ "source": "/(.*)", "destination": "/index.html" }] }` |
||||
|
> - **GitHub Pages**:需用 `gh-pages` + 404.html 拷贝,或 hash router |
||||
|
|
||||
|
## 验证 |
||||
|
- `npx tsc --noEmit` → 0 错误 |
||||
|
- `npm run build` → 1819 模块,新色已进入 `dist` CSS,`_redirects` 已进 `dist` |
||||
|
- `src/` 内 **0 emoji** |
||||
|
- 开发服务器在线 **http://localhost:4321**:`/` `/business` `/contact` `/join` `/en/contact` `/404` 全部 200,改动模块编译干净,lucide 依赖解析 200 |
||||
|
|
||||
|
## 仍可继续打磨(可选,未动) |
||||
|
- 字体层级/行高 token 化(目前散落在 class 里) |
||||
|
- 暗色主题目前由 `page-enter`/CSS 变量驱动,可补一套更完整的深色 token 校验 |
||||
|
- 赛道卡片粉彩与全局 token 仍有「深浅两档」的语义区分,若想彻底单源,可把卡片色也改从 `--track-*` 派生(需动 i18n 数据) |
||||
@ -0,0 +1,38 @@ |
|||||
|
# bashi-website — 设计系统完善 + 暗色模式落地 |
||||
|
|
||||
|
> 像素君(UI Designer)· 第二轮打磨:把"死"的暗色 token 真正激活,并补全设计系统刻度尺 + 赛道色单源。 |
||||
|
|
||||
|
## 1. 设计令牌补全(`global.css`) |
||||
|
- **字体刻度尺**:在 `:root` 新增模块化 token —— `--fs-xs…4xl`、`--lh-tight/snug/normal/relaxed`、`--fw-regular…bold`、`--ls-tight…widest`。设计系统从此有统一"刻度尺",组件可逐步迁移到令牌而非散值。 |
||||
|
- **主题感知 surface 令牌**:`--surface-card` / `--text-strong` / `--header-bg` / `--accent`,浅色下指向既有值,暗色下自动切换。 |
||||
|
|
||||
|
## 2. 暗色模式(从摆设变成真功能) |
||||
|
之前 `--bg-dark` / `--text-on-dark` / `--shadow-dark-*` 都定义了,却**没有 `data-theme="dark"` 覆盖块、也没有切换开关**——等于死代码。本次: |
||||
|
- 新增 `[data-theme="dark"]` 覆盖块:暖调近黑表面(`--bg-primary:#16110E`、`--surface-card:#1E1813`)、暖白文字(`--text-primary:#F5EFE6`、`--text-strong:#FBF6EC`)、低透明度奶油描边、暗色阴影、`color-scheme: dark`。 |
||||
|
- `body` 加 `background-color/color` 依赖令牌,整页随主题切换。 |
||||
|
- **三态主题开关**:`src/hooks/useTheme.ts`(浅 / 深 / 跟随系统,localStorage 持久化 + 系统偏好实时监听)+ Header 顶栏按钮循环切换 `Sun / Moon / Monitor`(lucide 图标)。 |
||||
|
- `index.html` 内联防闪烁脚本:首屏绘制前就设好 `data-theme` 与 `color-scheme`,**无 FOUC**。 |
||||
|
|
||||
|
## 3. 组件迁移到主题令牌 |
||||
|
- 中性/深色底上的 `text-[var(--brand-black)]` → `text-[var(--text-strong)]`(覆盖 Home/Business/Innovation/Join/Contact/About/Footer/Privacy/NotFound/Header 的全部标题与导航文字)。 |
||||
|
- `bg-white` 卡片 → `bg-[var(--surface-card)]`(Contact/Join/NotFound/Privacy)。 |
||||
|
- 悬停底色 `hover:bg-[var(--brand-cream-light)]` → `hover:bg-[var(--accent)]`;静态奶油色高亮块(About/Join/Innovation 序号徽章、Contact Passion 卡)→ `bg-[var(--accent)]`。 |
||||
|
- 英雄区/区块渐变遮罩:白底遮罩改为 `color-mix(in srgb, var(--bg-primary) NN%, transparent)`,奶油渐变改为 `var(--bg-secondary)`,均随主题淡入背景。 |
||||
|
- Header 吸顶与移动菜单底色 → `var(--header-bg)`。 |
||||
|
- **关键边界**:奶油实心按钮(Home 联系 CTA、Business CTA)保留 `text-[var(--brand-black)]`——浅奶油底配深字在两种主题都可读,若误改成浅字会在暗色下"浅字叠浅底"翻车(已修复一处该回归)。 |
||||
|
|
||||
|
## 4. 赛道色单源(`--track-*` 成为唯一来源) |
||||
|
- 新增 `src/lib/tracks.ts`:`TRACK_TOKEN` 把 social/career/payment/ai 映射到 `var(--track-*)`。 |
||||
|
- `i18n` 的 `business.tracks[]` 加 `key` 字段、**删除**原先写死的粉彩 `color` 字段(即那套双源粉彩)。Home/Business 改用 `TRACK_TOKEN[track.key]`,卡片底色(`color-mix` 深色调)与圆点 accent 现在共享同一 token,永不漂移。 |
||||
|
|
||||
|
## 5. Footer 清理 |
||||
|
- 残留 `📍` emoji → lucide `MapPin`;硬编码 `rgba(26,26,26,0.40)` 的标语文字 → `var(--text-muted)`(暗色下原本会不可见)。 |
||||
|
|
||||
|
## 验证 |
||||
|
- `npx tsc --noEmit` → 0 错误;`npm run build` → 1821 模块;`[data-theme=dark]` 块与 `#16110e` 已进入 `dist` CSS;**全站 0 emoji**;所有路由 200;改动模块编译干净;主题接线(`baskbuddy-theme`、`data-theme`)在产物 JS 中可查。 |
||||
|
- 说明:暗色**视觉**正确性经逐项推演确认(无 headless 浏览器截图);请点击顶栏 Sun/Moon/Monitor 按钮实际体验切换。 |
||||
|
|
||||
|
## 仍可继续打磨的方向(可选) |
||||
|
- 把组件里的 `text-[15px]` 等散值逐步替换成本轮新增的 `--fs-*` 刻度(目前令牌已就位,属渐进式重构)。 |
||||
|
- 补一套更完整的暗色对比度走查清单(针对 `.gradient-mesh` / `.text-gradient-warm` 等渐变类在暗色下的表现)。 |
||||
|
- 部署级:确认托管平台的 SPA fallback(已有 `public/_redirects`,nginx/Vercel 规则可补)。 |
||||
@ -0,0 +1,47 @@ |
|||||
|
# 色彩 & 图标审计与修复 — bashi-website |
||||
|
|
||||
|
> 角色:像素君(UI Designer)|审计依据:Impeccable + UI/UX Pro Max 设计规范 |
||||
|
|
||||
|
## 已修复(明确违规 / 无障碍硬伤) |
||||
|
|
||||
|
### 1. 图标:Emoji → 真实 SVG |
||||
|
- **Join.tsx 福利区** 原用 emoji `⏰🏠💪🎉📚`,违反「禁止 emoji 作图标」规范。 |
||||
|
- 安装 **lucide-react 1.25.0**,替换为 `Clock / Home / Dumbbell / PartyPopper / BookOpen`,置于 `#brand-cream-light` 圆角方块中,与全站图标语言一致。 |
||||
|
|
||||
|
### 2. 图标:唤醒沉睡的 `icon` 数据 |
||||
|
- `i18n/{zh,en}.ts` 中每个产品都带 `icon: 'heart'|'music'|…` 字段,但**从未渲染**。 |
||||
|
- `Business.tsx` 新增 `productIcon` 映射,给每个产品芯片渲染真实 lucide 图标 |
||||
|
(heart/music/sparkles/briefcase/mountain/footprints/rocket/check-circle→CircleCheck/bot/ear)。 |
||||
|
|
||||
|
### 3. 色彩:状态徽章对比度硬伤(WCAG AA 失败) |
||||
|
- `.status-developing` 原为 **奶油色文字 `#F5DEB3` 叠在近透明奶油底** → 白底上约 **1.3:1**,文字几乎不可见。 |
||||
|
- 修复:`color-mix(... cream-deep 20% + bg-primary)` 底 + `#8A6A2F` 文字(≈ **4.9:1**,通过)。 |
||||
|
- `.status-planned` 文字加深到 `#1D4ED8`,留足 AA 余量。 |
||||
|
|
||||
|
### 4. 色彩:暖白微调 |
||||
|
- `--brand-white` 由 `#FFFFFF` → `#FCFBF8`,与奶油暖调主题更协调(纯白在「温暖未来主义」里偏冷)。 |
||||
|
|
||||
|
## 验证 |
||||
|
- `npx tsc --noEmit` → 0 错误 |
||||
|
- `npm run build` → 1819 模块(lucide 已打包) |
||||
|
- 开发服务器 `/business`、`/join` 模块编译通过,全站页面 200 |
||||
|
|
||||
|
## 待你拍板(未改,属品牌级决策) |
||||
|
|
||||
|
### A. 赛道色存在「双套定义」不一致 |
||||
|
| 位置 | 社交 | 职场 | 支付 | AI | |
||||
|
|------|------|------|------|-----| |
||||
|
| 赛道卡片(Home/Business/Innovation) | `#E8A0BF` 粉 | `#7EC8E3` 青 | `#98D8AA` 薄荷 | `#C9B1FF` 薰衣草 | |
||||
|
| 全局强调 token `--track-*` | `#EF4444` 红 | `#2563EB` 蓝 | `#10B981` 翠 | `#8B5CF6` 紫 | |
||||
|
|
||||
|
- 卡片用的是柔和粉彩,强调 token 是 Tailwind 默认饱和色,**色相不一致**(尤其「社交」= 粉 vs 红)。 |
||||
|
- **建议**:统一为一套色相(例如让全局强调 token 与粉彩共享色相,仅提高饱和度用于小面积强调)。 |
||||
|
- 需要你确认方向后我再改——这是品牌视觉基调,不想擅自重绘。 |
||||
|
|
||||
|
### B. 手写内联 SVG |
||||
|
- `Header / Contact / NotFound` 用的是手写 24×24 描边 SVG,风格一致、无问题;可选统一换成 lucide 做单一图标源。 |
||||
|
|
||||
|
## 你需要告诉我的 |
||||
|
1. 赛道色要不要统一?统一到「粉彩色相」还是维持饱和色相? |
||||
|
2. 是否要把手写 SVG 也换成 lucide(单一图标源)? |
||||
|
3. 是否需要我补充一个 **SPA fallback** 部署配置(nginx / vercel / netlify),否则生产环境深链接 404。 |
||||
@ -0,0 +1,25 @@ |
|||||
|
# bashi-website — Migrated to Pure React (Vite + React Router) |
||||
|
|
||||
|
The site was migrated from **Astro 5** to a **pure React SPA** (Vite + React Router + Tailwind v4), so every page route is now a real `.tsx` file as requested. |
||||
|
|
||||
|
## What changed |
||||
|
- **Removed**: all `.astro` files (`src/pages/*.astro`, `src/pages/en/*.astro`, `src/layouts/BaseLayout.astro`), `astro.config.mjs`, and `scripts/`. Zero Astro dependencies remain in `package.json`. |
||||
|
- **Added**: Vite + React Router toolchain, `src/main.tsx`, `src/App.tsx` (routes), `src/layout/RootLayout.tsx`, `src/hooks/useClientEffects.ts`, `src/seo/Seo.tsx`, and 8 `src/pages/*.tsx` route wrappers. |
||||
|
- **Reused unchanged**: `src/styles/global.css` (full BaskBuddy design system), all existing React components (`Home/About/Business/Contact/Innovation/Join/Privacy/NotFound/Header/Footer/SiteShell`), and the i18n catalogs (`zh.ts`/`en.ts`). |
||||
|
|
||||
|
## Architecture |
||||
|
- `zh` at `/` & `/{page}`, `en` at `/en/{page}`, unknown paths → 404. |
||||
|
- `RootLayout` keeps `Header`/`Footer` mounted across navigations and replays the `page-enter` animation on routed content. |
||||
|
- `useClientEffects(pathname)` ports the interaction logic that previously lived in Astro's `<script>`: **flip-cards** (About ICIDP), **scroll-reveal** (staggered), **stat counters** (`.stat-value[data-target]`), and **copy-to-clipboard** (Contact). The dead `#star-field` generation was dropped. |
||||
|
- `Seo` recreates the per-page `<head>` (title, description, canonical, hreflang alternates, OG/Twitter cards, Organization JSON-LD) via direct DOM updates — faithful to the original Astro output. |
||||
|
- `Header` mobile menu now uses React `useState` instead of the old id-based global script toggle. |
||||
|
|
||||
|
## Verification |
||||
|
- `npm run build` → 60 modules transformed, clean `dist/`. |
||||
|
- `npx tsc --noEmit` → 0 type errors. |
||||
|
- Dev server live at **http://localhost:4321** — `/`, `/about`, `/en/innovation`, `/en`, `/privacy` and all assets return HTTP 200 (SPA fallback works). |
||||
|
|
||||
|
## Notes / tradeoffs |
||||
|
- **SPA SEO**: meta is set client-side (JS-executing crawlers are fine). For production, configure the host with an SPA fallback (Vite dev/preview already do this). |
||||
|
- A sandbox safe-delete guard blocks Vite from bulk-clearing `node_modules/.vite/deps`; resolved by redirecting Vite's `cacheDir` to the OS temp dir in `vite.config.ts`. |
||||
|
- Run: `npm run dev` (4321) · `npm run build` · `npm run preview`. |
||||
File diff suppressed because it is too large
@ -0,0 +1,27 @@ |
|||||
|
{ |
||||
|
"name": "bashi-website", |
||||
|
"version": "1.0.0", |
||||
|
"private": true, |
||||
|
"type": "module", |
||||
|
"scripts": { |
||||
|
"dev": "vite", |
||||
|
"build": "vite build", |
||||
|
"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/react": "^18.3.31", |
||||
|
"@types/react-dom": "^18.3.7", |
||||
|
"@vitejs/plugin-react": "^4.3.4", |
||||
|
"typescript": "^5.8.0", |
||||
|
"vite": "^5.4.11" |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1 @@ |
|||||
|
/* /index.html 200 |
||||
|
After Width: | Height: | Size: 22 KiB |
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
@ -0,0 +1,5 @@ |
|||||
|
User-agent: * |
||||
|
Allow: / |
||||
|
Disallow: /api/ |
||||
|
|
||||
|
Sitemap: https://baskbuddy.com/sitemap-index.xml |
||||
@ -0,0 +1,39 @@ |
|||||
|
import { Routes, Route } from 'react-router-dom'; |
||||
|
import RootLayout from '@/layout/RootLayout'; |
||||
|
import HomePage from '@/pages/HomePage'; |
||||
|
import AboutPage from '@/pages/AboutPage'; |
||||
|
import BusinessPage from '@/pages/BusinessPage'; |
||||
|
import ContactPage from '@/pages/ContactPage'; |
||||
|
import InnovationPage from '@/pages/InnovationPage'; |
||||
|
import JoinPage from '@/pages/JoinPage'; |
||||
|
import PrivacyPage from '@/pages/PrivacyPage'; |
||||
|
import NotFoundPage from '@/pages/NotFoundPage'; |
||||
|
|
||||
|
// Pure client-side routing.
|
||||
|
// zh at `/` and `/{page}`, en at `/en/{page}`, catch-all → 404.
|
||||
|
export default function App() { |
||||
|
return ( |
||||
|
<Routes> |
||||
|
<Route element={<RootLayout />}> |
||||
|
{/* zh */} |
||||
|
<Route path="/" element={<HomePage />} /> |
||||
|
<Route path="/about" element={<AboutPage />} /> |
||||
|
<Route path="/business" element={<BusinessPage />} /> |
||||
|
<Route path="/contact" element={<ContactPage />} /> |
||||
|
<Route path="/innovation" element={<InnovationPage />} /> |
||||
|
<Route path="/join" element={<JoinPage />} /> |
||||
|
<Route path="/privacy" element={<PrivacyPage />} /> |
||||
|
{/* en */} |
||||
|
<Route path="/en" element={<HomePage />} /> |
||||
|
<Route path="/en/about" element={<AboutPage />} /> |
||||
|
<Route path="/en/business" element={<BusinessPage />} /> |
||||
|
<Route path="/en/contact" element={<ContactPage />} /> |
||||
|
<Route path="/en/innovation" element={<InnovationPage />} /> |
||||
|
<Route path="/en/join" element={<JoinPage />} /> |
||||
|
<Route path="/en/privacy" element={<PrivacyPage />} /> |
||||
|
{/* 404 */} |
||||
|
<Route path="*" element={<NotFoundPage />} /> |
||||
|
</Route> |
||||
|
</Routes> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,153 @@ |
|||||
|
import { getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { cssVars } from './css'; |
||||
|
|
||||
|
export default function About({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const a = t.about; |
||||
|
const icidp = a.icidp; |
||||
|
|
||||
|
const icidpItems = [ |
||||
|
{ key: 'integrity', c: icidp.integrity, color: 'var(--icidp-integrity)' }, |
||||
|
{ key: 'courage', c: icidp.courage, color: 'var(--icidp-courage)' }, |
||||
|
{ key: 'innovation', c: icidp.innovation, color: 'var(--icidp-innovation)' }, |
||||
|
{ key: 'devotion', c: icidp.devotion, color: 'var(--icidp-devotion)' }, |
||||
|
{ key: 'passion', c: icidp.passion, color: 'var(--icidp-passion)' }, |
||||
|
]; |
||||
|
|
||||
|
const goalItems = [ |
||||
|
{ key: 'near', label: locale === 'zh' ? '近期目标' : 'Near-term', text: a.goals.near }, |
||||
|
{ key: 'mid', label: locale === 'zh' ? '中期目标' : 'Mid-term', text: a.goals.mid }, |
||||
|
{ key: 'far', label: locale === 'zh' ? '远期目标' : 'Long-term', text: a.goals.far }, |
||||
|
{ key: 'long', label: locale === 'zh' ? '长远目标' : 'Aspiration', text: a.goals.long }, |
||||
|
]; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
{/* ===== Header ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-[var(--space-10)] pb-[var(--space-6)] text-center"> |
||||
|
<span className="section-eyebrow">ABOUT BASKBUDDY</span> |
||||
|
<h1 className="section-title mt-3">{a.title}</h1> |
||||
|
<p className="section-lead">{a.subtitle}</p> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 公司介绍 ===== */} |
||||
|
<section className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-8)]"> |
||||
|
<p className="scroll-reveal text-[16px] md:text-[17px] leading-[2] text-[var(--text-secondary)] text-center">{a.intro}</p> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 使命愿景 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-8)]"> |
||||
|
<div className="grid md:grid-cols-2 gap-4"> |
||||
|
<div className="scroll-reveal rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-7"> |
||||
|
<div className="text-[11px] font-mono tracking-[0.15em] text-[var(--track-career)] mb-2">VISION · 愿景</div> |
||||
|
<p className="text-[16px] leading-[1.8] text-[var(--text-strong)] font-medium">{a.visionMission.vision}</p> |
||||
|
</div> |
||||
|
<div className="scroll-reveal rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-7"> |
||||
|
<div className="text-[11px] font-mono tracking-[0.15em] text-[var(--track-social)] mb-2">MISSION · 使命</div> |
||||
|
<p className="text-[16px] leading-[1.8] text-[var(--text-strong)] font-medium">{a.visionMission.mission}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 gap-4 mt-4"> |
||||
|
<div className="scroll-reveal rounded-2xl bg-[var(--accent)] border border-[rgba(255,239,213,0.4)] p-6"> |
||||
|
<div className="text-[11px] font-mono tracking-[0.15em] text-[var(--text-muted)] mb-2">PURPOSE · 初心</div> |
||||
|
<p className="text-[14px] leading-[1.7] text-[var(--text-strong)]">{a.visionMission.purpose}</p> |
||||
|
</div> |
||||
|
<div className="scroll-reveal rounded-2xl bg-[var(--bg-primary)] border border-[var(--border-light)] p-6"> |
||||
|
<div className="text-[11px] font-mono tracking-[0.15em] text-[var(--text-muted)] mb-2">CULTURE · 文化</div> |
||||
|
<p className="text-[14px] leading-[1.7] text-[var(--text-secondary)]">{a.visionMission.culture}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== ICIDP 核心价值观 (flip cards) ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-9)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">ICIDP</span> |
||||
|
<h2 className="section-title">{a.icidp.title}</h2> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-5 gap-4"> |
||||
|
{icidpItems.map((item) => ( |
||||
|
<div key={item.key} className="flip-card scroll-reveal h-[230px]" tabIndex={0} role="button" aria-expanded="false" aria-label={item.c.label}> |
||||
|
<div className="flip-card-inner relative w-full h-full"> |
||||
|
<div className="flip-card-front absolute inset-0 rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-5 flex flex-col items-center justify-center text-center" style={cssVars({ '--c': item.color })}> |
||||
|
<span className="w-10 h-10 rounded-full flex items-center justify-center mb-3" style={cssVars({ background: `color-mix(in srgb, ${item.color} 16%, transparent)` })}> |
||||
|
<span className="w-3 h-3 rounded-full" style={cssVars({ background: item.color })}></span> |
||||
|
</span> |
||||
|
<h3 className="text-[16px] font-semibold text-[var(--text-strong)]">{item.c.label}</h3> |
||||
|
<span className="text-[10px] font-mono mt-1" style={cssVars({ color: item.color })}>{item.c.position}</span> |
||||
|
<span className="mt-3 text-[10px] text-[var(--text-muted)]">{locale === 'zh' ? '点击查看' : 'Tap to flip'}</span> |
||||
|
</div> |
||||
|
<div |
||||
|
className="flip-card-back absolute inset-0 rounded-2xl border p-5 flex flex-col justify-center text-center" |
||||
|
style={cssVars({ |
||||
|
borderColor: `color-mix(in srgb, ${item.color} 35%, transparent)`, |
||||
|
background: `color-mix(in srgb, ${item.color} 7%, var(--bg-primary))`, |
||||
|
})} |
||||
|
> |
||||
|
<p className="text-[14px] font-semibold mb-2" style={cssVars({ color: item.color })}>{item.c.dialect}</p> |
||||
|
<ul className="space-y-1 text-left"> |
||||
|
{item.c.layers.map((layer: string, i: number) => ( |
||||
|
<li key={i} className="text-[11px] leading-snug text-[var(--text-secondary)] flex gap-1.5"> |
||||
|
<span style={cssVars({ color: item.color })}>·</span> |
||||
|
<span>{layer}</span> |
||||
|
</li> |
||||
|
))} |
||||
|
</ul> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 生存法宝 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-9)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">{locale === 'zh' ? 'SURVIVAL TOOLS' : 'SURVIVAL TOOLS'}</span> |
||||
|
<h2 className="section-title">{a.survivalTools.title}</h2> |
||||
|
<p className="section-lead">{a.survivalTools.desc}</p> |
||||
|
</div> |
||||
|
<div className="grid md:grid-cols-3 gap-4"> |
||||
|
{a.survivalTools.items.map((item: string, i: number) => ( |
||||
|
<div key={i} className="scroll-reveal rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-6"> |
||||
|
<span className="font-mono text-[13px] font-semibold text-[var(--track-career)]">{String(i + 1).padStart(2, '0')}</span> |
||||
|
<p className="mt-2 text-[14px] leading-[1.7] text-[var(--text-secondary)]">{item}</p> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 五个主动 ===== */} |
||||
|
<section className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-9)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">{locale === 'zh' ? 'FIVE PROACTIVE' : 'FIVE PROACTIVE'}</span> |
||||
|
<h2 className="section-title">{a.fiveProactive.title}</h2> |
||||
|
</div> |
||||
|
<div className="space-y-3"> |
||||
|
{a.fiveProactive.items.map((item: string, i: number) => ( |
||||
|
<div key={i} className="scroll-reveal flex gap-4 rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-5"> |
||||
|
<span className="shrink-0 w-8 h-8 rounded-lg bg-[var(--accent)] text-[var(--track-career)] font-mono text-[12px] font-semibold flex items-center justify-center">{i + 1}</span> |
||||
|
<p className="text-[14px] leading-[1.8] text-[var(--text-secondary)]">{item}</p> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 发展目标 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-9)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">{locale === 'zh' ? 'GOALS' : 'GOALS'}</span> |
||||
|
<h2 className="section-title">{locale === 'zh' ? '发展目标' : 'Development Goals'}</h2> |
||||
|
</div> |
||||
|
<div className="grid md:grid-cols-2 gap-4"> |
||||
|
{goalItems.map((g) => ( |
||||
|
<div key={g.key} className="scroll-reveal rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-7 hover:border-[var(--brand-cream-deep)] transition-colors duration-300"> |
||||
|
<div className="text-[11px] font-mono tracking-[0.15em] text-[var(--track-career)] mb-2">{g.label}</div> |
||||
|
<p className="text-[14px] leading-[1.85] text-[var(--text-secondary)]">{g.text}</p> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,80 @@ |
|||||
|
import { getLocalizedPath, getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { cssVars } from './css'; |
||||
|
import { TRACK_TOKEN } from '../lib/tracks'; |
||||
|
import { Heart, Music, Sparkles, Briefcase, Mountain, Footprints, Rocket, CircleCheck, Bot, Ear, type LucideIcon } from 'lucide-react'; |
||||
|
|
||||
|
export default function Business({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const b = t.business; |
||||
|
const isPlanned = (status: string) => /规划|Planned/i.test(status); |
||||
|
const productIcon: Record<string, LucideIcon> = { |
||||
|
heart: Heart, music: Music, sparkles: Sparkles, briefcase: Briefcase, mountain: Mountain, |
||||
|
footprints: Footprints, rocket: Rocket, 'check-circle': CircleCheck, bot: Bot, ear: Ear, |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
{/* ===== Page Header + 视觉 ===== */} |
||||
|
<section className="relative overflow-hidden"> |
||||
|
<div className="absolute inset-0"> |
||||
|
<img src="/img-business.webp" alt="" className="w-full h-full object-cover opacity-[0.16]" aria-hidden="true" loading="eager" decoding="async" /> |
||||
|
<div className="absolute inset-0 bg-gradient-to-b from-[color-mix(in srgb, var(--bg-primary) 92%, transparent)] via-[color-mix(in srgb, var(--bg-primary) 80%, transparent)] to-[var(--bg-primary)]"></div> |
||||
|
</div> |
||||
|
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-[var(--space-10)] pb-[var(--space-7)] text-center"> |
||||
|
<span className="section-eyebrow">BUSINESS MAP</span> |
||||
|
<h1 className="section-title mt-3">{b.title}</h1> |
||||
|
<p className="section-lead">{b.subtitle}</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 四大赛道 → 产品矩阵 ===== */} |
||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-10)] space-y-6"> |
||||
|
{b.tracks.map((track, idx) => ( |
||||
|
<section key={track.name} className="scroll-reveal track-card" style={cssVars({ '--track-color': TRACK_TOKEN[track.key] })}> |
||||
|
<div className="flex flex-col sm:flex-row sm:items-end justify-between gap-3 mb-6"> |
||||
|
<div className="flex items-center gap-3"> |
||||
|
<span className="w-3 h-3 rounded-full" style={cssVars({ background: TRACK_TOKEN[track.key] })}></span> |
||||
|
<h2 className="text-[22px] md:text-[26px] font-bold font-[var(--font-display)] text-[var(--text-strong)]">{track.name}</h2> |
||||
|
</div> |
||||
|
<span className="font-mono text-[11px] tracking-[0.15em] text-[var(--text-muted)]">{String(idx + 1).padStart(2, '0')} / {String(b.tracks.length).padStart(2, '0')}</span> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-3.5"> |
||||
|
{track.products.map((p) => { |
||||
|
const Ico = productIcon[p.icon]; |
||||
|
return ( |
||||
|
<div key={p.name} className="product-chip"> |
||||
|
<div className="min-w-0"> |
||||
|
<div className="flex items-center gap-2"> |
||||
|
{Ico && <Ico className="w-4 h-4 shrink-0 text-[var(--text-secondary)]" />} |
||||
|
<h3 className="text-[15px] font-semibold text-[var(--text-strong)] truncate">{p.name}</h3> |
||||
|
</div> |
||||
|
<p className="text-[12px] text-[var(--text-secondary)] mt-0.5 leading-snug">{p.desc}</p> |
||||
|
</div> |
||||
|
<span className={`status-badge shrink-0 ${isPlanned(p.status) ? 'status-planned' : 'status-developing'}`}>{p.status}</span> |
||||
|
</div> |
||||
|
); |
||||
|
})} |
||||
|
</div> |
||||
|
</section> |
||||
|
))} |
||||
|
</div> |
||||
|
|
||||
|
{/* ===== CTA Band ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-10)]"> |
||||
|
<div className="cta-band scroll-reveal px-8 py-14 text-center"> |
||||
|
<h2 className="relative z-10 text-[24px] md:text-[32px] font-bold font-[var(--font-display)] text-white"> |
||||
|
{locale === 'zh' ? '与巴适伙伴,共建数字生活生态' : 'Build the digital life ecosystem with BaskBuddy'} |
||||
|
</h2> |
||||
|
<div className="relative z-10 mt-7 flex flex-col sm:flex-row items-center justify-center gap-3.5"> |
||||
|
<a href={getLocalizedPath('/contact', locale)} className="inline-flex items-center justify-center px-7 py-3 rounded-full text-[14px] font-semibold bg-[var(--brand-cream)] text-[var(--brand-black)] hover:-translate-y-0.5 transition-all duration-300"> |
||||
|
{locale === 'zh' ? '商务合作' : 'Business Cooperation'} |
||||
|
</a> |
||||
|
<a href={getLocalizedPath('/join', locale)} className="inline-flex items-center justify-center px-7 py-3 rounded-full text-[14px] font-semibold border border-[rgba(255,239,213,0.3)] text-[var(--brand-cream)] hover:bg-[rgba(255,239,213,0.08)] transition-all duration-300"> |
||||
|
{t.nav.join} |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,135 @@ |
|||||
|
import { getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { cssVars } from './css'; |
||||
|
import { Briefcase, Handshake, ArrowRight, Mail } from 'lucide-react'; |
||||
|
|
||||
|
export default function Contact({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const c = t.contact; |
||||
|
const joinUrl = locale === 'zh' ? '/join' : '/en/join'; |
||||
|
const privacyUrl = locale === 'zh' ? '/privacy' : '/en/privacy'; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
{/* ===== HERO ===== */} |
||||
|
<section className="relative overflow-hidden"> |
||||
|
<div className="absolute inset-0 bg-gradient-to-b from-[var(--bg-secondary)] to-[var(--bg-primary)]"></div> |
||||
|
<div className="relative z-10 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 pt-[var(--space-10)] pb-[var(--space-6)] text-center"> |
||||
|
<span className="section-eyebrow">CONTACT US</span> |
||||
|
<h1 className="section-title mt-3">{c.title}</h1> |
||||
|
<p className="section-lead">{c.subtitle}</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== Contact Channels — no backend, direct channels ===== */} |
||||
|
<section className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="grid md:grid-cols-2 gap-5 md:gap-6"> |
||||
|
{/* 求职咨询 */} |
||||
|
<div className="scroll-reveal-left"> |
||||
|
<div className="rounded-2xl bg-[var(--surface-card)] border border-[var(--border-light)] h-full overflow-hidden transition-all duration-300 hover:shadow-md" style={cssVars({ boxShadow: 'var(--shadow-sm)' })}> |
||||
|
<div className="h-[2px]" style={cssVars({ background: 'var(--track-social)', opacity: '0.5' })}></div> |
||||
|
<div className="p-6 md:p-8 flex flex-col h-full"> |
||||
|
<div className="flex items-center gap-3 mb-4"> |
||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={cssVars({ background: 'var(--track-social)08', border: '1px solid var(--track-social)20' })}> |
||||
|
<Briefcase className="w-5 h-5" style={{ color: 'var(--track-social)' }} /> |
||||
|
</div> |
||||
|
<h2 className="text-[20px] md:text-[24px] font-[var(--font-display)] font-bold tracking-tight text-[var(--text-strong)]">{c.job.title}</h2> |
||||
|
</div> |
||||
|
<p className="text-[14px] leading-[1.7] text-[var(--text-secondary)] mb-6">{c.job.desc}</p> |
||||
|
<div className="mt-auto space-y-4"> |
||||
|
<a href={joinUrl} className="inline-flex items-center justify-center gap-2 w-full py-3.5 rounded-xl bg-[var(--brand-black)] text-white text-[13px] font-semibold transition-all duration-300 hover:bg-[#2a2a2a] hover:shadow-md" style={{ fontFamily: "'Quicksand', sans-serif", boxShadow: 'var(--shadow-sm)' }}> |
||||
|
{c.job.cta} |
||||
|
<ArrowRight className="w-4 h-4" /> |
||||
|
</a> |
||||
|
<div className="flex items-center gap-2 rounded-xl border border-[var(--border-light)] bg-[var(--bg-primary)] px-3 py-2.5"> |
||||
|
<span className="text-[11px] font-semibold tracking-[0.06em] uppercase text-[var(--text-muted)] mr-1 whitespace-nowrap" style={{ fontFamily: "'JetBrains Mono', monospace" }}>{c.job.emailLabel}</span> |
||||
|
<a href={'mailto:' + c.job.email} className="text-[13px] font-medium truncate" style={{ color: 'var(--track-social)', fontFamily: "'JetBrains Mono', monospace" }}>{c.job.email}</a> |
||||
|
<button |
||||
|
type="button" |
||||
|
className="copy-btn ml-auto shrink-0 text-[11px] font-semibold px-2 py-1 rounded-md transition-colors duration-200" |
||||
|
data-copy={c.job.email} |
||||
|
data-label={c.job.copy} |
||||
|
data-copied={c.job.copied} |
||||
|
style={cssVars({ color: 'var(--text-muted)', border: '1px solid var(--border-light)' })} |
||||
|
aria-label={c.job.copy} |
||||
|
> |
||||
|
{c.job.copy} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* 商务合作 */} |
||||
|
<div className="scroll-reveal-right"> |
||||
|
<div className="rounded-2xl bg-[var(--surface-card)] border border-[var(--border-light)] h-full overflow-hidden transition-all duration-300 hover:shadow-md" style={cssVars({ boxShadow: 'var(--shadow-sm)' })}> |
||||
|
<div className="h-[2px]" style={cssVars({ background: 'var(--track-career)', opacity: '0.5' })}></div> |
||||
|
<div className="p-6 md:p-8 flex flex-col h-full"> |
||||
|
<div className="flex items-center gap-3 mb-4"> |
||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center" style={cssVars({ background: 'var(--track-career)08', border: '1px solid var(--track-career)20' })}> |
||||
|
<Handshake className="w-5 h-5" style={{ color: 'var(--track-career)' }} /> |
||||
|
</div> |
||||
|
<h2 className="text-[20px] md:text-[24px] font-[var(--font-display)] font-bold tracking-tight text-[var(--text-strong)]">{c.biz.title}</h2> |
||||
|
</div> |
||||
|
<p className="text-[14px] leading-[1.7] text-[var(--text-secondary)] mb-6">{c.biz.desc}</p> |
||||
|
<div className="mt-auto space-y-4"> |
||||
|
<a href={'mailto:' + c.biz.email} className="inline-flex items-center justify-center gap-2 w-full py-3.5 rounded-xl text-white text-[13px] font-semibold transition-all duration-300 hover:shadow-md" style={{ background: 'var(--track-career)', fontFamily: "'Quicksand', sans-serif", boxShadow: 'var(--shadow-sm)' }}> |
||||
|
{c.biz.cta} |
||||
|
<Mail className="w-4 h-4" /> |
||||
|
</a> |
||||
|
<div className="flex items-center gap-2 rounded-xl border border-[var(--border-light)] bg-[var(--bg-primary)] px-3 py-2.5"> |
||||
|
<span className="text-[11px] font-semibold tracking-[0.06em] uppercase text-[var(--text-muted)] mr-1 whitespace-nowrap" style={{ fontFamily: "'JetBrains Mono', monospace" }}>{c.biz.emailLabel}</span> |
||||
|
<a href={'mailto:' + c.biz.email} className="text-[13px] font-medium truncate" style={{ color: 'var(--track-career)', fontFamily: "'JetBrains Mono', monospace" }}>{c.biz.email}</a> |
||||
|
<button |
||||
|
type="button" |
||||
|
className="copy-btn ml-auto shrink-0 text-[11px] font-semibold px-2 py-1 rounded-md transition-colors duration-200" |
||||
|
data-copy={c.biz.email} |
||||
|
data-label={c.biz.copy} |
||||
|
data-copied={c.biz.copied} |
||||
|
style={cssVars({ color: 'var(--text-muted)', border: '1px solid var(--border-light)' })} |
||||
|
aria-label={c.biz.copy} |
||||
|
> |
||||
|
{c.biz.copy} |
||||
|
</button> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== Contact Info Card ===== */} |
||||
|
<section className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-10)]"> |
||||
|
<div className="scroll-reveal rounded-2xl bg-[var(--accent)] border border-[rgba(255,239,213,0.20)] p-6 md:p-8" style={cssVars({ boxShadow: 'var(--shadow-sm)' })}> |
||||
|
<div className="flex flex-col sm:flex-row gap-6"> |
||||
|
<div className="flex-1"> |
||||
|
<h3 className="text-[16px] font-[var(--font-display)] font-bold mb-2 text-[var(--text-strong)]"> |
||||
|
{locale === 'zh' ? '巴适伙伴(上海)科技有限公司' : 'BaskBuddy (Shanghai) Technology Co., Ltd.'} |
||||
|
</h3> |
||||
|
<p className="text-[12px] text-[var(--text-muted)] leading-relaxed mb-4"> |
||||
|
{locale === 'zh' ? '上海市静安区康宁路288弄2号410室' : "Room 410, No.2, Lane 288, Kangning Rd, Jing'an District, Shanghai"} |
||||
|
</p> |
||||
|
<p className="text-[12px] text-[var(--text-muted)] leading-relaxed"> |
||||
|
{c.privacyNote} |
||||
|
<a href={privacyUrl} className="ml-1 hover:underline font-medium" style={{ color: 'var(--track-career)' }}>《{locale === 'zh' ? '隐私政策' : 'Privacy Policy'}》</a> |
||||
|
</p> |
||||
|
</div> |
||||
|
<div className="flex-1"> |
||||
|
<div className="rounded-xl bg-[var(--surface-card)] border border-[var(--border-light)] p-4"> |
||||
|
<h4 className="text-[11px] font-semibold tracking-[0.06em] uppercase text-[var(--text-muted)] mb-3" style={{ fontFamily: "'JetBrains Mono', monospace" }}>ICIDP</h4> |
||||
|
<div className="flex flex-wrap gap-1.5"> |
||||
|
<span className="text-[11px] font-medium px-2 py-0.5 rounded-md" style={cssVars({ background: 'var(--track-social)10', color: 'var(--track-social)' })}>Integrity</span> |
||||
|
<span className="text-[11px] font-medium px-2 py-0.5 rounded-md" style={cssVars({ background: 'var(--track-career)10', color: 'var(--track-career)' })}>Courage</span> |
||||
|
<span className="text-[11px] font-medium px-2 py-0.5 rounded-md" style={cssVars({ background: 'var(--track-payment)10', color: 'var(--track-payment)' })}>Innovation</span> |
||||
|
<span className="text-[11px] font-medium px-2 py-0.5 rounded-md" style={cssVars({ background: 'var(--track-ai)10', color: 'var(--track-ai)' })}>Devotion</span> |
||||
|
<span className="text-[11px] font-medium px-2 py-0.5 rounded-md" style={cssVars({ background: 'var(--brand-cream)30', color: 'var(--brand-cream-deep)' })}>Passion</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,125 @@ |
|||||
|
import { getLocalizedPath, type Locale } from '../i18n/utils'; |
||||
|
import { getTranslation } from '../i18n/utils'; |
||||
|
import { MapPin } from 'lucide-react'; |
||||
|
|
||||
|
export default function Footer({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const brandName = t.brand || 'BaskBuddy'; |
||||
|
const icpNumber = '沪ICP备XXXXXXXX号'; // TODO: 备案完成后替换为真实备案号(单点修改即可全局生效)
|
||||
|
|
||||
|
return ( |
||||
|
<footer className="bg-[var(--bg-secondary)] border-t border-[var(--border-light)]"> |
||||
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-12 md:py-16"> |
||||
|
{/* Main Footer Grid */} |
||||
|
<div className="grid grid-cols-1 md:grid-cols-4 gap-8 md:gap-12 mb-10 md:mb-14"> |
||||
|
{/* Column 1: Brand */} |
||||
|
<div className="md:col-span-1"> |
||||
|
<div className="flex items-center gap-2.5 mb-4"> |
||||
|
<img src="/baskbuddy-logo.webp" alt="BaskBuddy" className="w-8 h-8 object-contain rounded-lg" /> |
||||
|
<span className="font-semibold text-[15px] text-[var(--text-strong)]" style={{ fontFamily: "'Quicksand', 'Noto Sans SC', sans-serif" }}> |
||||
|
{brandName} |
||||
|
</span> |
||||
|
</div> |
||||
|
<p className="text-sm leading-relaxed mb-4 text-[var(--text-muted)]"> |
||||
|
{locale === 'zh' ? '以温暖科技,连接美好生活。四大赛道全面覆盖数字生活服务生态。' : 'Connecting beautiful life with warm tech. Four tracks covering the digital life service ecosystem.'} |
||||
|
</p> |
||||
|
<div className="inline-flex items-center gap-1.5 px-3 py-1.5 rounded-lg text-[10px] font-semibold tracking-[0.06em] uppercase bg-[rgba(255,239,213,0.15)] text-[var(--text-muted)] border border-[rgba(255,239,213,0.20)]" style={{ fontFamily: "'JetBrains Mono', monospace" }}> |
||||
|
I·C·I·D·P |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* Column 2: Quick Links */} |
||||
|
<div> |
||||
|
<h4 className="text-[11px] font-semibold tracking-[0.08em] uppercase mb-4 text-[var(--text-muted)]" style={{ fontFamily: "'JetBrains Mono', monospace" }}> |
||||
|
{locale === 'zh' ? '快速导航' : 'QUICK LINKS'} |
||||
|
</h4> |
||||
|
<div className="flex flex-col gap-3.5 leading-relaxed"> |
||||
|
{Object.entries(t.nav).map(([key, label]) => ( |
||||
|
<a |
||||
|
key={key} |
||||
|
href={getLocalizedPath(`/${key === 'home' ? '' : key}`, locale)} |
||||
|
className="text-sm transition-colors duration-200 text-[var(--text-secondary)] hover:text-[var(--text-strong)]" |
||||
|
> |
||||
|
{label} |
||||
|
</a> |
||||
|
))} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* Column 3: Tracks */} |
||||
|
<div> |
||||
|
<h4 className="text-[11px] font-semibold tracking-[0.08em] uppercase mb-4 text-[var(--text-muted)]" style={{ fontFamily: "'JetBrains Mono', monospace" }}> |
||||
|
{locale === 'zh' ? '业务赛道' : 'BUSINESS TRACKS'} |
||||
|
</h4> |
||||
|
<div className="flex flex-col gap-3.5 leading-relaxed"> |
||||
|
{Object.entries(t.home.tracks).map(([key, track]) => ( |
||||
|
<a key={key} href="/business" className="text-sm transition-colors duration-200 text-[var(--text-secondary)] hover:text-[var(--text-strong)]"> |
||||
|
{track.name} |
||||
|
</a> |
||||
|
))} |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* Column 4: Contact */} |
||||
|
<div> |
||||
|
<h4 className="text-[11px] font-semibold tracking-[0.08em] uppercase mb-4 text-[var(--text-muted)]" style={{ fontFamily: "'JetBrains Mono', monospace" }}> |
||||
|
{locale === 'zh' ? '联系我们' : 'CONTACT'} |
||||
|
</h4> |
||||
|
<div className="flex flex-col gap-3.5 leading-relaxed"> |
||||
|
<a href="/contact" className="text-sm transition-colors duration-200 text-[var(--text-secondary)] hover:text-[var(--text-strong)]"> |
||||
|
{locale === 'zh' ? '求职咨询' : 'Job Inquiry'} |
||||
|
</a> |
||||
|
<a href="/contact" className="text-sm transition-colors duration-200 text-[var(--text-secondary)] hover:text-[var(--text-strong)]"> |
||||
|
{locale === 'zh' ? '商务合作' : 'Business Cooperation'} |
||||
|
</a> |
||||
|
<a |
||||
|
href="mailto:baskbuddy@163.com" |
||||
|
className="text-sm font-medium transition-all duration-200 hover:underline underline-offset-2 text-[var(--track-career)] hover:text-[#4a6977]" |
||||
|
style={{ fontFamily: "'JetBrains Mono', monospace" }} |
||||
|
title={locale === 'zh' ? '点击发送邮件 (求职)' : 'Click to send email (Jobs)'} |
||||
|
> |
||||
|
baskbuddy@163.com |
||||
|
</a> |
||||
|
<a |
||||
|
href="mailto:baskbuddy@163.com" |
||||
|
className="text-xs transition-all duration-200 hover:underline underline-offset-2 text-[var(--text-muted)] hover:text-[var(--text-secondary)]" |
||||
|
style={{ fontFamily: "'JetBrains Mono', monospace" }} |
||||
|
title={locale === 'zh' ? '点击发送邮件 (商务)' : 'Click to send email (Business)'} |
||||
|
> |
||||
|
baskbuddy@163.com |
||||
|
</a> |
||||
|
<p className="text-xs text-[var(--text-muted)]" title={locale === 'zh' ? '巴适伙伴(上海)科技有限公司 办公地址' : 'BaskBuddy (Shanghai) Office'}> |
||||
|
<MapPin className="w-3.5 h-3.5 inline-block mr-1 -translate-y-px shrink-0" /> |
||||
|
{locale === 'zh' ? '中国 · 上海市静安区康宁路288弄2号410室' : "Room 410, No.2, Lane 288, Kangning Rd, Jing'an District, Shanghai, China"} |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
{/* Bottom Bar */} |
||||
|
<div className="border-t border-[var(--border-light)] pt-6 flex flex-col md:flex-row items-center justify-between gap-5"> |
||||
|
<div className="flex flex-col md:flex-row items-center gap-3 md:gap-4 text-center md:text-left"> |
||||
|
<p className="text-xs text-[var(--text-muted)]"> |
||||
|
© 2024–2026 {locale === 'zh' ? '巴适伙伴(上海)科技有限公司' : 'BaskBuddy (Shanghai) Technology Co., Ltd.'}. {locale === 'zh' ? '保留所有权利' : 'All rights reserved.'} |
||||
|
</p> |
||||
|
<a |
||||
|
href="https://beian.miit.gov.cn/" |
||||
|
target="_blank" |
||||
|
rel="noopener noreferrer" |
||||
|
className="text-xs transition-all duration-200 hover:underline underline-offset-2 text-[var(--text-muted)] hover:text-[var(--text-strong)]" |
||||
|
title={locale === 'zh' ? '工信部ICP/IP地址/域名信息备案管理系统' : 'MIIT ICP Filing Query (China)'} |
||||
|
> |
||||
|
{locale === 'zh' ? icpNumber : 'MIIT ICP Filing'} |
||||
|
</a> |
||||
|
<a href="/privacy" className="text-xs transition-all duration-200 hover:underline underline-offset-2 text-[var(--text-muted)] hover:text-[var(--text-strong)]"> |
||||
|
{locale === 'zh' ? '隐私政策' : 'Privacy'} |
||||
|
</a> |
||||
|
</div> |
||||
|
<p className="text-xs font-semibold tracking-wide text-[var(--text-muted)]" style={{ fontFamily: "'JetBrains Mono', monospace" }} aria-hidden="true"> |
||||
|
Integrity · Courage · Innovation · Devotion · Passion |
||||
|
</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
</footer> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,116 @@ |
|||||
|
import { useState } from 'react'; |
||||
|
import { getLocalizedPath, getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { Menu, X, Sun, Moon, Monitor } from 'lucide-react'; |
||||
|
import { useTheme, type Theme } from '../hooks/useTheme'; |
||||
|
|
||||
|
export default function Header({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const brandName = t.brand || 'BaskBuddy'; |
||||
|
const [menuOpen, setMenuOpen] = useState(false); |
||||
|
const { theme, setTheme } = useTheme(); |
||||
|
|
||||
|
const themeOrder: Theme[] = ['light', 'dark', 'system']; |
||||
|
const cycleTheme = () => setTheme(themeOrder[(themeOrder.indexOf(theme) + 1) % 3]); |
||||
|
const ThemeIcon = theme === 'light' ? Sun : theme === 'dark' ? Moon : Monitor; |
||||
|
const themeLabel = |
||||
|
theme === 'light' |
||||
|
? locale === 'zh' ? '当前:浅色,点击切换' : 'Light mode, click to switch' |
||||
|
: theme === 'dark' |
||||
|
? locale === 'zh' ? '当前:深色,点击切换' : 'Dark mode, click to switch' |
||||
|
: locale === 'zh' ? '当前:跟随系统,点击切换' : 'System mode, click to switch'; |
||||
|
|
||||
|
return ( |
||||
|
<header className="sticky top-0 z-50 transition-all duration-300 bg-[var(--header-bg)] backdrop-blur-md border-b border-[var(--border-light)]"> |
||||
|
<nav className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 h-[60px] flex items-center justify-between"> |
||||
|
{/* Brand Logo */} |
||||
|
<a |
||||
|
href={getLocalizedPath('/', locale)} |
||||
|
className="flex items-center gap-2.5 group" |
||||
|
onClick={() => setMenuOpen(false)} |
||||
|
> |
||||
|
<img |
||||
|
src="/baskbuddy-logo.webp" |
||||
|
alt={locale === 'zh' ? '巴适伙伴 BaskBuddy 品牌Logo — 数字生活服务商' : 'BaskBuddy Logo — Digital Life Service Provider'} |
||||
|
loading="eager" |
||||
|
decoding="async" |
||||
|
className="w-9 h-9 object-contain rounded-lg transition-transform duration-300 group-hover:scale-105" |
||||
|
/> |
||||
|
<div className="flex flex-col leading-tight"> |
||||
|
<span |
||||
|
className="font-semibold text-[15px] tracking-tight text-[var(--text-strong)] transition-colors duration-200" |
||||
|
style={{ fontFamily: "'Quicksand', 'Noto Sans SC', sans-serif" }} |
||||
|
> |
||||
|
{brandName} |
||||
|
</span> |
||||
|
<span className="text-[9px] font-medium tracking-[0.08em] uppercase text-[var(--text-muted)]"> |
||||
|
{t.brandSub || '巴适伙伴'} |
||||
|
</span> |
||||
|
</div> |
||||
|
</a> |
||||
|
|
||||
|
{/* Desktop Navigation */} |
||||
|
<div className="hidden md:flex items-center gap-0.5"> |
||||
|
{Object.entries(t.nav).map(([key, label]) => ( |
||||
|
<a |
||||
|
key={key} |
||||
|
href={getLocalizedPath(`/${key === 'home' ? '' : key}`, locale)} |
||||
|
className="px-3.5 py-[6px] rounded-[8px] text-[13px] font-medium transition-all duration-200 text-[var(--text-secondary)] hover:text-[var(--text-strong)] hover:bg-[var(--accent)]" |
||||
|
> |
||||
|
{label} |
||||
|
</a> |
||||
|
))} |
||||
|
</div> |
||||
|
|
||||
|
{/* Language Switch + Mobile Menu */} |
||||
|
<div className="flex items-center gap-2.5"> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={cycleTheme} |
||||
|
aria-label={themeLabel} |
||||
|
className="p-2 rounded-lg transition-colors duration-200 text-[var(--text-muted)] hover:bg-[var(--accent)] hover:text-[var(--text-strong)]" |
||||
|
> |
||||
|
<ThemeIcon className="w-5 h-5" /> |
||||
|
</button> |
||||
|
<a |
||||
|
href={locale === 'zh' ? '/en' : '/'} |
||||
|
className="text-[11px] px-2.5 py-1 rounded-[6px] border font-medium tracking-wide transition-all duration-200 border-[var(--border-light)] text-[var(--text-muted)] hover:bg-[var(--accent)] hover:text-[var(--text-strong)]" |
||||
|
style={{ fontFamily: "'JetBrains Mono', monospace" }} |
||||
|
> |
||||
|
{locale === 'zh' ? 'EN' : '中文'} |
||||
|
</a> |
||||
|
<button |
||||
|
type="button" |
||||
|
onClick={() => setMenuOpen((o) => !o)} |
||||
|
className="md:hidden p-2 rounded-lg transition-colors duration-200 text-[var(--text-muted)] hover:bg-[var(--accent)]" |
||||
|
aria-label={menuOpen ? (locale === 'zh' ? '关闭菜单' : 'Close menu') : (locale === 'zh' ? '打开菜单' : 'Open menu')} |
||||
|
aria-expanded={menuOpen} |
||||
|
> |
||||
|
{menuOpen ? ( |
||||
|
<X className="w-5 h-5" /> |
||||
|
) : ( |
||||
|
<Menu className="w-5 h-5" /> |
||||
|
)} |
||||
|
</button> |
||||
|
</div> |
||||
|
</nav> |
||||
|
|
||||
|
{/* Mobile Navigation */} |
||||
|
<div className={`md:hidden ${menuOpen ? 'block' : 'hidden'}`}> |
||||
|
<div className="px-4 pb-3 pt-1 bg-[var(--header-bg)]"> |
||||
|
<div className="flex flex-col gap-0.5"> |
||||
|
{Object.entries(t.nav).map(([key, label]) => ( |
||||
|
<a |
||||
|
key={key} |
||||
|
href={getLocalizedPath(`/${key === 'home' ? '' : key}`, locale)} |
||||
|
onClick={() => setMenuOpen(false)} |
||||
|
className="text-[14px] py-2.5 px-3 rounded-lg transition-all duration-200 text-[var(--text-secondary)] hover:text-[var(--text-strong)] hover:bg-[var(--accent)]" |
||||
|
> |
||||
|
{label} |
||||
|
</a> |
||||
|
))} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</header> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,134 @@ |
|||||
|
import { getLocalizedPath, getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { cssVars } from './css'; |
||||
|
import { TRACK_TOKEN } from '../lib/tracks'; |
||||
|
|
||||
|
export default function Home({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const h = t.home; |
||||
|
const stats = h.stats.items; |
||||
|
const bizTracks = t.business.tracks; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
{/* ===== HERO ===== */} |
||||
|
<section className="relative min-h-[88vh] flex items-center overflow-hidden"> |
||||
|
<video className="hero-video" autoPlay muted loop playsInline poster="/img-about.webp" aria-hidden="true"> |
||||
|
<source src="/hero-bg.mp4" type="video/mp4" /> |
||||
|
</video> |
||||
|
<div className="hero-overlay"></div> |
||||
|
<div className="relative z-10 w-full max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center"> |
||||
|
<span className="section-eyebrow">{h.sloganSub}</span> |
||||
|
<h1 className="hero-slogan">{h.slogan}</h1> |
||||
|
<p className="mt-5 text-[15px] md:text-[17px] text-[var(--text-secondary)] max-w-2xl mx-auto leading-relaxed px-2">{h.sloganIntro}</p> |
||||
|
<div className="mt-9 flex flex-col sm:flex-row items-center justify-center gap-3.5"> |
||||
|
<a |
||||
|
href={getLocalizedPath('/business', locale)} |
||||
|
className="inline-flex items-center justify-center px-7 py-3 rounded-full text-[14px] font-semibold bg-[var(--brand-black)] text-white shadow-sm hover:-translate-y-0.5 hover:shadow-lg transition-all duration-300" |
||||
|
> |
||||
|
{h.enterBusiness} |
||||
|
</a> |
||||
|
<a |
||||
|
href={getLocalizedPath('/about', locale)} |
||||
|
className="inline-flex items-center justify-center px-7 py-3 rounded-full text-[14px] font-semibold border border-[var(--border-medium)] text-[var(--text-strong)] hover:bg-[var(--accent)] transition-all duration-300" |
||||
|
> |
||||
|
{h.about.cta} |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
<div className="absolute bottom-7 left-1/2 -translate-x-1/2 z-10 flex flex-col items-center gap-2 text-[var(--text-muted)]"> |
||||
|
<span className="text-[10px] tracking-[0.2em] uppercase font-mono">SCROLL</span> |
||||
|
<span className="w-px h-8 bg-gradient-to-b from-[var(--text-muted)] to-transparent"></span> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 走进巴适伙伴 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-10)]"> |
||||
|
<div className="grid md:grid-cols-2 gap-10 md:gap-16 items-center"> |
||||
|
<div className="scroll-reveal"> |
||||
|
<span className="section-eyebrow eyebrow-line">{h.about.eyebrow}</span> |
||||
|
<h2 className="section-title mt-3">{h.about.title}</h2> |
||||
|
<p className="mt-5 text-[15px] leading-[1.95] text-[var(--text-secondary)]">{h.about.text}</p> |
||||
|
<a href={getLocalizedPath('/about', locale)} className="inline-flex items-center gap-1.5 mt-7 text-[14px] font-semibold text-[var(--track-career)] hover:gap-2.5 transition-all duration-300"> |
||||
|
{h.about.cta} |
||||
|
<span aria-hidden="true">→</span> |
||||
|
</a> |
||||
|
</div> |
||||
|
<div className="feature-media scroll-reveal-left h-[300px] md:h-[380px]"> |
||||
|
<img |
||||
|
src="/about-companion.webp" |
||||
|
alt={locale === 'zh' ? '成长之钥,开启巴适伙伴的数字化未来' : 'Key to growth, unlocking BaskBuddy digital future'} |
||||
|
loading="lazy" |
||||
|
decoding="async" |
||||
|
/> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 巴适速度 (stat counters) ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-9)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">{h.stats.eyebrow}</span> |
||||
|
<h2 className="section-title">{h.stats.title}</h2> |
||||
|
<p className="section-lead">{h.stats.subtitle}</p> |
||||
|
</div> |
||||
|
<div className="stat-band scroll-reveal"> |
||||
|
{stats.map((s) => ( |
||||
|
<div className="stat-cell" key={s.label}> |
||||
|
<div className="stat-num"> |
||||
|
<span className="stat-value" data-target={s.value}>0</span> |
||||
|
<span className="stat-suffix">{s.suffix}</span> |
||||
|
</div> |
||||
|
<div className="stat-label">{s.label}</div> |
||||
|
<div className="stat-sub">{s.sub}</div> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 四大核心赛道 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-10)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">{h.trackOverview}</span> |
||||
|
<h2 className="section-title">{t.business.subtitle}</h2> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-4 gap-5"> |
||||
|
{bizTracks.map((track) => ( |
||||
|
<a key={track.name} href={getLocalizedPath('/business', locale)} className="track-card scroll-reveal block" style={cssVars({ '--track-color': TRACK_TOKEN[track.key] })}> |
||||
|
<div className="flex items-center gap-2.5 mb-3"> |
||||
|
<span className="w-2.5 h-2.5 rounded-full" style={cssVars({ background: TRACK_TOKEN[track.key] })}></span> |
||||
|
<h3 className="text-[17px] font-semibold text-[var(--text-strong)]">{track.name}</h3> |
||||
|
</div> |
||||
|
<p className="text-[13px] leading-relaxed text-[var(--text-secondary)] mb-4 min-h-[58px]">{track.products[0]?.desc || ''}</p> |
||||
|
<div className="flex flex-wrap gap-1.5"> |
||||
|
{track.products.slice(0, 3).map((p) => ( |
||||
|
<span key={p.name} className="text-[11px] px-2 py-1 rounded-md bg-[var(--bg-secondary)] text-[var(--text-muted)]">{p.name}</span> |
||||
|
))} |
||||
|
{track.products.length > 3 && ( |
||||
|
<span className="text-[11px] px-2 py-1 rounded-md bg-[var(--bg-secondary)] text-[var(--text-muted)]">+{track.products.length - 3}</span> |
||||
|
)} |
||||
|
</div> |
||||
|
</a> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== CTA Band ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pb-[var(--space-10)]"> |
||||
|
<div className="cta-band scroll-reveal px-8 py-14 md:py-16 text-center"> |
||||
|
<h2 className="relative z-10 text-[26px] md:text-[34px] font-bold font-[var(--font-display)] text-white">{t.contact.subtitle}</h2> |
||||
|
<p className="relative z-10 mt-3 text-[14px] md:text-[15px] text-[rgba(255,255,255,0.7)] max-w-xl mx-auto"> |
||||
|
{locale === 'zh' ? '无论是求职咨询还是商务合作,我们都期待与你温暖相遇。' : 'Whether for a job inquiry or business cooperation, we look forward to meeting you with warmth.'} |
||||
|
</p> |
||||
|
<div className="relative z-10 mt-8 flex flex-col sm:flex-row items-center justify-center gap-3.5"> |
||||
|
<a href={getLocalizedPath('/contact', locale)} className="inline-flex items-center justify-center px-7 py-3 rounded-full text-[14px] font-semibold bg-[var(--brand-cream)] text-[var(--brand-black)] hover:-translate-y-0.5 transition-all duration-300"> |
||||
|
{locale === 'zh' ? '联系我们' : 'Contact Us'} |
||||
|
</a> |
||||
|
<a href={getLocalizedPath('/join', locale)} className="inline-flex items-center justify-center px-7 py-3 rounded-full text-[14px] font-semibold border border-[rgba(255,239,213,0.3)] text-[var(--brand-cream)] hover:bg-[rgba(255,239,213,0.08)] transition-all duration-300"> |
||||
|
{t.nav.join} |
||||
|
</a> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,127 @@ |
|||||
|
import { getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { cssVars } from './css'; |
||||
|
|
||||
|
export default function Innovation({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const inv = t.innovation; |
||||
|
|
||||
|
const trackColor: Record<string, string> = { |
||||
|
'内容社交': '#E8A0BF', 'Content & Social': '#E8A0BF', |
||||
|
'职场服务': '#7EC8E3', 'Career Services': '#7EC8E3', |
||||
|
'数字支付': '#98D8AA', 'Digital Payment': '#98D8AA', |
||||
|
'人工智能': '#C9B1FF', 'Artificial Intelligence': '#C9B1FF', |
||||
|
}; |
||||
|
const statusLabel: Record<string, string> = { |
||||
|
completed: locale === 'zh' ? '已完成' : 'Completed', |
||||
|
'in-progress': locale === 'zh' ? '推进中' : 'In Progress', |
||||
|
upcoming: locale === 'zh' ? '规划中' : 'Planned', |
||||
|
}; |
||||
|
const statusColor: Record<string, string> = { |
||||
|
completed: 'var(--track-payment)', |
||||
|
'in-progress': 'var(--track-career)', |
||||
|
upcoming: 'var(--text-muted)', |
||||
|
}; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
{/* ===== Page Header + 视觉 ===== */} |
||||
|
<section className="relative overflow-hidden"> |
||||
|
<div className="absolute inset-0"> |
||||
|
<img src="/img-innovation.webp" alt="" className="w-full h-full object-cover opacity-[0.18]" aria-hidden="true" loading="eager" decoding="async" /> |
||||
|
<div className="absolute inset-0 bg-gradient-to-b from-[color-mix(in srgb, var(--bg-primary) 90%, transparent)] via-[color-mix(in srgb, var(--bg-primary) 78%, transparent)] to-[var(--bg-primary)]"></div> |
||||
|
</div> |
||||
|
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 pt-[var(--space-10)] pb-[var(--space-7)] text-center"> |
||||
|
<span className="section-eyebrow">INNOVATION LAB</span> |
||||
|
<h1 className="section-title mt-3">{inv.title}</h1> |
||||
|
<p className="section-lead">{inv.subtitle}</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== ICIDP 研发哲学 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">R&D PHILOSOPHY</span> |
||||
|
<h2 className="section-title">{inv.philosophy.title}</h2> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-5 gap-4"> |
||||
|
{inv.philosophy.items.map((item) => ( |
||||
|
<div key={item.title} className="scroll-reveal rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-6 hover:-translate-y-1 hover:shadow-md transition-all duration-300"> |
||||
|
<div className="w-9 h-9 rounded-xl flex items-center justify-center mb-4" style={cssVars({ background: `color-mix(in srgb, ${item.color} 16%, transparent)` })}> |
||||
|
<span className="w-2.5 h-2.5 rounded-full" style={cssVars({ background: item.color })}></span> |
||||
|
</div> |
||||
|
<h3 className="text-[17px] font-semibold text-[var(--text-strong)]">{item.title}</h3> |
||||
|
<p className="text-[11px] font-mono tracking-wide mt-0.5" style={cssVars({ color: item.color })}>{item.subtitle}</p> |
||||
|
<p className="mt-3 text-[13px] leading-relaxed text-[var(--text-secondary)]">{item.desc}</p> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 技术差异化路径 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">TECH PATHS</span> |
||||
|
<h2 className="section-title">{inv.techPaths.title}</h2> |
||||
|
</div> |
||||
|
<div className="grid md:grid-cols-2 gap-5"> |
||||
|
{inv.techPaths.items.map((p) => ( |
||||
|
<div key={p.track} className="track-card scroll-reveal" style={cssVars({ '--track-color': trackColor[p.track] || 'var(--track-career)' })}> |
||||
|
<div className="flex items-center justify-between mb-3"> |
||||
|
<h3 className="text-[18px] font-semibold text-[var(--text-strong)]">{p.track}</h3> |
||||
|
<span className="text-[11px] font-mono px-2.5 py-1 rounded-full" style={cssVars({ background: `color-mix(in srgb, ${trackColor[p.track] || '#7EC8E3'} 14%, transparent)`, color: trackColor[p.track] || '#7EC8E3' })}>{p.products}</span> |
||||
|
</div> |
||||
|
<p className="text-[13px] leading-relaxed text-[var(--text-secondary)] mb-4">{p.logic}</p> |
||||
|
<div className="rounded-xl bg-[var(--bg-secondary)] border border-[var(--border-light)] p-4"> |
||||
|
<div className="text-[11px] font-semibold text-[var(--text-muted)] mb-1.5 tracking-wide">{locale === 'zh' ? '技术支点' : 'Tech Pillars'}</div> |
||||
|
<p className="text-[13px] font-medium text-[var(--text-strong)] leading-relaxed">{p.tech}</p> |
||||
|
</div> |
||||
|
<p className="mt-3 text-[13px] font-medium" style={cssVars({ color: trackColor[p.track] || 'var(--track-career)' })}>{p.desc}</p> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 自研技术布局 ===== */} |
||||
|
<section className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">SELF-DEVELOPED</span> |
||||
|
<h2 className="section-title">{inv.selfDeveloped.title}</h2> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4"> |
||||
|
{inv.selfDeveloped.items.map((item, i) => ( |
||||
|
<div key={item.name} className="scroll-reveal flex items-start gap-4 rounded-2xl border border-[var(--border-light)] bg-[var(--bg-primary)] p-5 hover:border-[var(--brand-cream-deep)] hover:shadow-sm transition-all duration-300"> |
||||
|
<span className="shrink-0 w-9 h-9 rounded-xl bg-[var(--accent)] text-[var(--track-career)] font-mono text-[13px] font-semibold flex items-center justify-center">{String(i + 1).padStart(2, '0')}</span> |
||||
|
<div> |
||||
|
<h3 className="text-[15px] font-semibold text-[var(--text-strong)]">{item.name}</h3> |
||||
|
<p className="text-[10px] font-mono tracking-wide text-[var(--text-muted)] mt-0.5">{item.en}</p> |
||||
|
<p className="mt-2 text-[13px] leading-relaxed text-[var(--text-secondary)]">{item.desc}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 发展时间线 (full) ===== */} |
||||
|
<section className="max-w-4xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">MILESTONES</span> |
||||
|
<h2 className="section-title">{inv.milestones.title}</h2> |
||||
|
</div> |
||||
|
<div className="timeline-line scroll-reveal"> |
||||
|
{inv.milestones.items.map((m) => ( |
||||
|
<div key={m.year} className="relative pb-10 last:pb-0"> |
||||
|
<span className="timeline-dot" style={cssVars({ left: '15px', color: statusColor[m.status], borderColor: statusColor[m.status] })}></span> |
||||
|
<div className="pl-3"> |
||||
|
<div className="flex items-center gap-2.5"> |
||||
|
<span className="font-mono text-[12px] font-semibold tracking-wide" style={cssVars({ color: statusColor[m.status] })}>{m.year}</span> |
||||
|
<span className="text-[10px] font-medium px-2 py-0.5 rounded-full" style={cssVars({ background: `color-mix(in srgb, ${statusColor[m.status]} 12%, transparent)`, color: statusColor[m.status] })}>{statusLabel[m.status]}</span> |
||||
|
</div> |
||||
|
<p className="mt-1.5 text-[15px] leading-relaxed text-[var(--text-primary)]">{m.event}</p> |
||||
|
</div> |
||||
|
</div> |
||||
|
))} |
||||
|
</div> |
||||
|
</section> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,103 @@ |
|||||
|
import { getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { cssVars } from './css'; |
||||
|
import { Clock, Home, Dumbbell, PartyPopper, BookOpen, Mail, type LucideIcon } from 'lucide-react'; |
||||
|
|
||||
|
export default function Join({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const j = t.join; |
||||
|
const benefitIcons: LucideIcon[] = [Clock, Home, Dumbbell, PartyPopper, BookOpen]; |
||||
|
|
||||
|
return ( |
||||
|
<> |
||||
|
{/* ===== HERO — Growth Manifesto ===== */} |
||||
|
<section className="relative overflow-hidden"> |
||||
|
<div className="absolute inset-0 bg-gradient-to-b from-[var(--bg-secondary)] via-[var(--bg-secondary)] to-[var(--bg-primary)]"></div> |
||||
|
<div className="relative z-10 max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 pt-[var(--space-10)] pb-[var(--space-8)] text-center"> |
||||
|
<span className="section-eyebrow">JOIN US</span> |
||||
|
<h1 className="section-title mt-3">{j.title}</h1> |
||||
|
<p className="section-lead">{j.subtitle}</p> |
||||
|
<div className="scroll-reveal mt-9 rounded-2xl bg-[var(--surface-card)] border border-[var(--border-light)] p-7 md:p-9 text-left" style={cssVars({ boxShadow: 'var(--shadow-lg)' })}> |
||||
|
<div className="text-[15px] md:text-[17px] leading-[2] font-light whitespace-pre-line text-[var(--text-strong)]" style={{ fontFamily: "'Quicksand', 'Noto Sans SC', serif" }}> |
||||
|
{j.manifesto} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 成长激励 ===== */} |
||||
|
<section className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">GROWTH INCENTIVE</span> |
||||
|
<h2 className="section-title">{j.growthIncentive.title}</h2> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 gap-4"> |
||||
|
{j.growthIncentive.items.map((item: string, index: number) => { |
||||
|
const colors = ['var(--track-social)', 'var(--track-career)', 'var(--track-payment)', 'var(--track-ai)']; |
||||
|
const color = colors[index % colors.length]; |
||||
|
return ( |
||||
|
<div key={index} className="scroll-reveal flex items-start gap-3 rounded-2xl bg-[var(--bg-primary)] border border-[var(--border-light)] p-5 hover:border-[rgba(255,239,213,0.25)] hover:shadow-sm transition-all duration-300"> |
||||
|
<span className="w-2 h-2 rounded-full mt-2 shrink-0" style={cssVars({ background: color })}></span> |
||||
|
<p className="text-[14px] leading-relaxed text-[var(--text-secondary)]">{item}</p> |
||||
|
</div> |
||||
|
); |
||||
|
})} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 长期激励 ===== */} |
||||
|
<section className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">LONG-TERM INCENTIVE</span> |
||||
|
<h2 className="section-title">{j.longTermIncentive.title}</h2> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-3 gap-4"> |
||||
|
{j.longTermIncentive.items.map((item: string, index: number) => { |
||||
|
const colors = ['var(--track-social)', 'var(--track-career)', 'var(--track-ai)']; |
||||
|
const color = colors[index]; |
||||
|
return ( |
||||
|
<div key={index} className="scroll-reveal rounded-2xl bg-[var(--bg-primary)] border border-[var(--border-light)] p-6 hover:-translate-y-1 hover:shadow-md transition-all duration-300"> |
||||
|
<div className="w-10 h-10 rounded-xl flex items-center justify-center mb-4" style={cssVars({ background: `color-mix(in srgb, ${color} 10%, transparent)`, border: `1px solid color-mix(in srgb, ${color} 22%, transparent)` })}> |
||||
|
<span className="w-2.5 h-2.5 rounded-full" style={cssVars({ background: color })}></span> |
||||
|
</div> |
||||
|
<p className="text-[14px] leading-relaxed text-[var(--text-secondary)]">{item}</p> |
||||
|
</div> |
||||
|
); |
||||
|
})} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 福利待遇 ===== */} |
||||
|
<section className="max-w-5xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="section-head text-center scroll-reveal"> |
||||
|
<span className="section-eyebrow">BENEFITS</span> |
||||
|
<h2 className="section-title">{j.benefits.title}</h2> |
||||
|
</div> |
||||
|
<div className="grid sm:grid-cols-2 lg:grid-cols-3 gap-4"> |
||||
|
{j.benefits.items.map((item: string, index: number) => { |
||||
|
const Icon = benefitIcons[index % benefitIcons.length]; |
||||
|
return ( |
||||
|
<div key={index} className="scroll-reveal flex items-start gap-3 rounded-2xl bg-[var(--bg-primary)] border border-[var(--border-light)] p-5 hover:shadow-sm transition-all duration-300"> |
||||
|
<span className="w-9 h-9 rounded-xl bg-[var(--accent)] flex items-center justify-center shrink-0"> |
||||
|
<Icon className="w-[18px] h-[18px] text-[var(--track-career)]" /> |
||||
|
</span> |
||||
|
<p className="text-[13px] leading-relaxed text-[var(--text-secondary)]">{item}</p> |
||||
|
</div> |
||||
|
); |
||||
|
})} |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
{/* ===== 投递方式 ===== */} |
||||
|
<section className="max-w-3xl mx-auto px-4 sm:px-6 lg:px-8 py-[var(--space-8)]"> |
||||
|
<div className="scroll-reveal rounded-2xl bg-[var(--brand-black)] px-8 py-12 text-center"> |
||||
|
<h3 className="text-[22px] md:text-[28px] font-bold font-[var(--font-display)] text-white mb-4">{j.delivery.title}</h3> |
||||
|
<a href={`mailto:${j.delivery.email}`} className="inline-flex items-center gap-2 text-[18px] md:text-[22px] font-semibold text-[var(--brand-cream)] transition-colors duration-200" style={{ fontFamily: "'JetBrains Mono', monospace" }}> |
||||
|
{j.delivery.email} |
||||
|
<Mail className="w-4 h-4 opacity-60" /> |
||||
|
</a> |
||||
|
<p className="mt-3 text-[12px] text-[rgba(255,255,255,0.55)]">{j.delivery.note}</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,57 @@ |
|||||
|
import { getLocalizedPath, getTranslation, type Locale } from '../i18n/utils'; |
||||
|
import { ArrowLeft, ArrowRight } from 'lucide-react'; |
||||
|
|
||||
|
export default function NotFound({ locale }: { locale: Locale }) { |
||||
|
const t = getTranslation(locale); |
||||
|
const isZh = locale === 'zh'; |
||||
|
|
||||
|
return ( |
||||
|
<div className="gradient-mesh min-h-screen"> |
||||
|
<section className="relative min-h-[85vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8 py-16"> |
||||
|
<div className="max-w-[520px] mx-auto text-center relative z-10"> |
||||
|
<div className="scroll-reveal mb-8"> |
||||
|
<div className="inline-flex items-center justify-center w-20 h-20 md:w-24 md:h-24 rounded-2xl bg-[var(--surface-card)] border border-[var(--border-light)]" style={{ boxShadow: 'var(--shadow-md)' }}> |
||||
|
<span className="text-3xl md:text-4xl font-bold" style={{ fontFamily: "'JetBrains Mono', monospace", color: 'var(--track-social)' }}>404</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
|
||||
|
<h1 className="scroll-reveal text-[36px] sm:text-[48px] md:text-[56px] font-[var(--font-display)] font-bold mb-4 tracking-tight"> |
||||
|
<span className="text-gradient-warm">{isZh ? '走丢了' : 'Lost in Space'}</span> |
||||
|
</h1> |
||||
|
|
||||
|
<p className="scroll-reveal text-[var(--text-muted)] text-[15px] md:text-[17px] font-light leading-relaxed mb-10 max-w-[420px] mx-auto"> |
||||
|
{isZh |
||||
|
? '抱歉,您访问的页面不存在或已被移动。别担心,让我们带您回到温暖的主路。' |
||||
|
: 'Sorry, the page you are looking for does not exist or has been moved. Let us take you back home.'} |
||||
|
</p> |
||||
|
|
||||
|
<div className="scroll-reveal flex flex-col sm:flex-row items-center justify-center gap-3"> |
||||
|
<a href={getLocalizedPath('/', locale)} className="group inline-flex items-center gap-2 px-6 py-3.5 rounded-xl bg-[var(--brand-black)] text-white text-[13px] font-semibold transition-all duration-300 hover:bg-[#2a2a2a] hover:shadow-md" style={{ fontFamily: "'Quicksand', sans-serif", boxShadow: 'var(--shadow-md)' }}> |
||||
|
<ArrowLeft className="w-4 h-4 transition-transform duration-200 group-hover:-translate-x-0.5" /> |
||||
|
{t.nav.home} |
||||
|
</a> |
||||
|
<a href={getLocalizedPath('/contact', locale)} className="group inline-flex items-center gap-2 px-6 py-3.5 rounded-xl bg-[var(--surface-card)] text-[var(--text-strong)] text-[13px] font-semibold border border-[var(--border-medium)] transition-all duration-300 hover:bg-[var(--accent)] hover:border-[rgba(255,239,213,0.3)] hover:shadow-md" style={{ fontFamily: "'Quicksand', sans-serif", boxShadow: 'var(--shadow-sm)' }}> |
||||
|
{t.nav.contact} |
||||
|
<ArrowRight className="w-4 h-4 transition-transform duration-200 group-hover:translate-x-0.5" /> |
||||
|
</a> |
||||
|
</div> |
||||
|
|
||||
|
<div className="scroll-reveal mt-14 grid grid-cols-2 sm:grid-cols-4 gap-3 max-w-[560px] mx-auto"> |
||||
|
{Object.entries(t.nav).map(([key, label]) => { |
||||
|
if (key === 'home') return null; |
||||
|
return ( |
||||
|
<a |
||||
|
key={key} |
||||
|
href={getLocalizedPath(`/${key}`, locale)} |
||||
|
className="group rounded-xl bg-[var(--surface-card)] border border-[var(--border-light)] px-4 py-3 text-[12px] font-medium text-[var(--text-secondary)] transition-all duration-200 hover:border-[rgba(255,239,213,0.2)] hover:bg-[var(--accent)] hover:text-[var(--text-strong)]" |
||||
|
> |
||||
|
{label} |
||||
|
</a> |
||||
|
); |
||||
|
})} |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,132 @@ |
|||||
|
import type { Locale } from '../i18n/utils'; |
||||
|
|
||||
|
export default function Privacy({ locale }: { locale: Locale }) { |
||||
|
const isZh = locale === 'zh'; |
||||
|
|
||||
|
return ( |
||||
|
<div className="gradient-mesh min-h-screen"> |
||||
|
<section className="relative min-h-[32vh] flex flex-col items-center justify-center px-4 sm:px-6 lg:px-8 pt-20 pb-6"> |
||||
|
<div className="max-w-[520px] mx-auto text-center relative z-10"> |
||||
|
<h1 className="scroll-reveal text-[36px] sm:text-[48px] md:text-[56px] font-[var(--font-display)] font-bold mb-3 tracking-tight"> |
||||
|
<span className="text-gradient-warm">{isZh ? '隐私政策' : 'Privacy Policy'}</span> |
||||
|
</h1> |
||||
|
<p className="scroll-reveal text-[var(--text-muted)] text-[14px] md:text-[15px] font-light"> |
||||
|
{isZh ? '巴适伙伴(上海)科技有限公司 · 最后更新:2026年7月' : 'BaskBuddy (Shanghai) Technology Co., Ltd. · Last updated: July 2026'} |
||||
|
</p> |
||||
|
</div> |
||||
|
</section> |
||||
|
|
||||
|
<section className="py-10 md:py-16 px-4 sm:px-6 lg:px-8"> |
||||
|
<div className="max-w-[640px] mx-auto relative z-10"> |
||||
|
<div className="scroll-reveal rounded-2xl bg-[var(--surface-card)] border border-[var(--border-light)] p-6 md:p-8" style={{ boxShadow: 'var(--shadow-md)' }}> |
||||
|
{isZh ? ( |
||||
|
<div className="text-[14px] md:text-[15px] leading-[1.8] text-[var(--text-secondary)] space-y-6"> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">一、信息收集范围</h2> |
||||
|
<p className="whitespace-pre-line">{`我们仅在您主动提供的情况下收集以下个人信息:
|
||||
|
|
||||
|
· 求职咨询:姓名、邮箱、联系电话、意向职位、留言内容 |
||||
|
· 商务合作:公司名称、联系人、邮箱、合作类型、留言内容 |
||||
|
|
||||
|
我们不会通过任何隐蔽手段收集您的个人信息。`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">二、信息使用目的</h2> |
||||
|
<p className="whitespace-pre-line">{`收集的信息仅用于以下目的:
|
||||
|
|
||||
|
· 处理您的求职申请或商务合作咨询 |
||||
|
· 在必要时与您取得联系 |
||||
|
· 改善我们的服务质量 |
||||
|
|
||||
|
我们不会将您的个人信息用于未经您同意的其他目的。`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">三、信息存储与保护</h2> |
||||
|
<p className="whitespace-pre-line">{`· 您的个人信息存储于中国境内的安全服务器
|
||||
|
· 我们采用业界标准的安全措施保护您的数据 |
||||
|
· 我们不会将您的个人信息传输至境外 |
||||
|
· 数据保留期限:咨询信息保留不超过2年,期满后自动删除`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">四、您的权利</h2> |
||||
|
<p className="whitespace-pre-line">{`根据《个人信息保护法》,您享有以下权利:
|
||||
|
|
||||
|
· 查询权:随时查询我们持有的您的个人信息 |
||||
|
· 更正权:要求更正不准确的信息 |
||||
|
· 删除权:要求我们删除您的个人信息 |
||||
|
· 撤回同意权:撤回此前给予的授权同意 |
||||
|
|
||||
|
行使以上权利,请发送邮件至:baskbuddy@163.com`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">五、未成年人保护</h2> |
||||
|
<p>我们不主动收集不满14周岁未成年人的个人信息。如发现误收集,将立即删除。</p> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">六、联系方式</h2> |
||||
|
<p className="whitespace-pre-line">{`如有任何隐私相关问题,请联系:
|
||||
|
|
||||
|
巴适伙伴(上海)科技有限公司 |
||||
|
邮箱:baskbuddy@163.com |
||||
|
地址:上海市静安区康宁路288弄2号410室`}</p>
|
||||
|
</div> |
||||
|
</div> |
||||
|
) : ( |
||||
|
<div className="text-[14px] md:text-[15px] leading-[1.8] text-[var(--text-secondary)] space-y-6"> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">1. Information Collection Scope</h2> |
||||
|
<p className="whitespace-pre-line">{`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.`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">2. Purpose of Information Use</h2> |
||||
|
<p className="whitespace-pre-line">{`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.`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">3. Information Storage & Protection</h2> |
||||
|
<p className="whitespace-pre-line">{`· 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`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">4. Your Rights</h2> |
||||
|
<p className="whitespace-pre-line">{`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 |
||||
|
· Right to delete: request deletion of your personal information |
||||
|
· Right to withdraw consent: revoke previously granted consent |
||||
|
|
||||
|
To exercise these rights, please email: baskbuddy@163.com`}</p>
|
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">5. Protection of Minors</h2> |
||||
|
<p>We do not actively collect personal information from minors under 14. If accidentally collected, it will be deleted immediately.</p> |
||||
|
</div> |
||||
|
<div> |
||||
|
<h2 className="text-[18px] font-bold text-[var(--text-strong)] mb-3">6. Contact Information</h2> |
||||
|
<p className="whitespace-pre-line">{`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`}</p>
|
||||
|
</div> |
||||
|
</div> |
||||
|
)} |
||||
|
</div> |
||||
|
</div> |
||||
|
</section> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import type { ReactNode } from 'react'; |
||||
|
import type { Locale } from '../i18n/utils'; |
||||
|
import Header from './Header'; |
||||
|
import Footer from './Footer'; |
||||
|
|
||||
|
export default function SiteShell({ locale, children }: { locale: Locale; children: ReactNode }) { |
||||
|
return ( |
||||
|
<> |
||||
|
<Header locale={locale} /> |
||||
|
<main className="min-h-screen">{children}</main> |
||||
|
<Footer locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,9 @@ |
|||||
|
import type { CSSProperties } from 'react'; |
||||
|
|
||||
|
/** |
||||
|
* Helper to pass CSS custom properties (e.g. `--track-color`) and arbitrary |
||||
|
* style records to React's `style` prop without TS friction. |
||||
|
*/ |
||||
|
export function cssVars(vars: Record<string, string | number>): CSSProperties { |
||||
|
return vars as unknown as CSSProperties; |
||||
|
} |
||||
@ -0,0 +1,133 @@ |
|||||
|
import { useEffect } from 'react'; |
||||
|
|
||||
|
// Ports the interaction logic that previously lived in BaseLayout.astro's
|
||||
|
// <script> into a client hook. Re-runs on every route change (pathname dep)
|
||||
|
// and cleans up observers / listeners to avoid leaks in the SPA.
|
||||
|
//
|
||||
|
// 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).
|
||||
|
export function useClientEffects(pathname: string) { |
||||
|
useEffect(() => { |
||||
|
// ----- Flip cards -----
|
||||
|
const flipCleanups: Array<() => void> = []; |
||||
|
document.querySelectorAll<HTMLElement>('.flip-card').forEach((card) => { |
||||
|
const inner = card.querySelector<HTMLElement>('.flip-card-inner'); |
||||
|
const toggleFlip = (e: Event) => { |
||||
|
e.stopPropagation(); |
||||
|
const flipped = card.classList.toggle('flipped'); |
||||
|
card.setAttribute('aria-expanded', flipped ? 'true' : 'false'); |
||||
|
if (inner) { |
||||
|
inner.style.transform = flipped ? 'rotateY(180deg)' : ''; |
||||
|
} |
||||
|
}; |
||||
|
const onKey = (e: KeyboardEvent) => { |
||||
|
if (e.key === 'Enter' || e.key === ' ') { |
||||
|
e.preventDefault(); |
||||
|
toggleFlip(e); |
||||
|
} |
||||
|
}; |
||||
|
card.addEventListener('click', toggleFlip); |
||||
|
card.addEventListener('keydown', onKey); |
||||
|
if ('ontouchstart' in window) card.classList.add('touch'); |
||||
|
flipCleanups.push(() => { |
||||
|
card.removeEventListener('click', toggleFlip); |
||||
|
card.removeEventListener('keydown', onKey); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
// ----- Scroll reveal -----
|
||||
|
const revealObserver = new IntersectionObserver( |
||||
|
(entries) => { |
||||
|
entries.forEach((entry) => { |
||||
|
if (!entry.isIntersecting) return; |
||||
|
const parent = entry.target.parentElement; |
||||
|
const siblings = parent |
||||
|
? parent.querySelectorAll('.scroll-reveal, .scroll-reveal-left, .scroll-reveal-right') |
||||
|
: []; |
||||
|
const staggerIndex = Array.from(siblings).indexOf(entry.target); |
||||
|
const delay = staggerIndex * 80; |
||||
|
window.setTimeout(() => entry.target.classList.add('visible'), delay); |
||||
|
revealObserver.unobserve(entry.target); |
||||
|
}); |
||||
|
}, |
||||
|
{ threshold: 0.08, rootMargin: '0px 0px -40px 0px' }, |
||||
|
); |
||||
|
document |
||||
|
.querySelectorAll('.scroll-reveal, .scroll-reveal-left, .scroll-reveal-right') |
||||
|
.forEach((el) => revealObserver.observe(el)); |
||||
|
|
||||
|
// ----- Animated stat counters -----
|
||||
|
const statValues = document.querySelectorAll<HTMLElement>('.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<HTMLButtonElement>('.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]); |
||||
|
} |
||||
@ -0,0 +1,50 @@ |
|||||
|
import { useEffect, useState } from 'react'; |
||||
|
|
||||
|
export type Theme = 'light' | 'dark' | 'system'; |
||||
|
const STORAGE_KEY = 'baskbuddy-theme'; |
||||
|
|
||||
|
function systemPrefersDark(): boolean { |
||||
|
return ( |
||||
|
typeof window !== 'undefined' && |
||||
|
window.matchMedia('(prefers-color-scheme: dark)').matches |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
export function resolveTheme(theme: Theme): 'light' | 'dark' { |
||||
|
if (theme === 'system') return systemPrefersDark() ? 'dark' : 'light'; |
||||
|
return theme; |
||||
|
} |
||||
|
|
||||
|
export function applyTheme(theme: Theme) { |
||||
|
if (typeof document === 'undefined') return; |
||||
|
const resolved = resolveTheme(theme); |
||||
|
const root = document.documentElement; |
||||
|
root.setAttribute('data-theme', resolved); |
||||
|
root.style.colorScheme = resolved; |
||||
|
} |
||||
|
|
||||
|
export function getStoredTheme(): Theme { |
||||
|
if (typeof window === 'undefined') return 'system'; |
||||
|
const stored = localStorage.getItem(STORAGE_KEY) as Theme | null; |
||||
|
return stored ?? 'system'; |
||||
|
} |
||||
|
|
||||
|
// Three-state theme: light | dark | system. Persists to localStorage and
|
||||
|
// live-reacts to OS changes while in `system` mode.
|
||||
|
export function useTheme() { |
||||
|
const [theme, setTheme] = useState<Theme>(() => getStoredTheme()); |
||||
|
|
||||
|
useEffect(() => { |
||||
|
applyTheme(theme); |
||||
|
localStorage.setItem(STORAGE_KEY, theme); |
||||
|
|
||||
|
if (theme === 'system') { |
||||
|
const mq = window.matchMedia('(prefers-color-scheme: dark)'); |
||||
|
const onChange = () => applyTheme('system'); |
||||
|
mq.addEventListener('change', onChange); |
||||
|
return () => mq.removeEventListener('change', onChange); |
||||
|
} |
||||
|
}, [theme]); |
||||
|
|
||||
|
return { theme, setTheme } as const; |
||||
|
} |
||||
@ -0,0 +1,272 @@ |
|||||
|
export const en = { |
||||
|
brand: 'BaskBuddy', |
||||
|
brandSub: 'Bashi Partners', |
||||
|
nav: { |
||||
|
home: 'Home', |
||||
|
innovation: 'Innovation Lab', |
||||
|
business: 'Business Map', |
||||
|
about: 'About Us', |
||||
|
join: 'Join Us', |
||||
|
contact: 'Contact Us', |
||||
|
}, |
||||
|
home: { |
||||
|
slogan: 'BaskBuddy — Together for Life\nComfort by Your Side, Trust for a Lifetime', |
||||
|
sloganSub: 'BaskBuddy · Warm Tech, Better Life', |
||||
|
sloganIntro: 'Digital Life Service Provider · Content Social, Career Services, Digital Payment, AI', |
||||
|
trackOverview: 'Four Core Tracks', |
||||
|
enterLab: 'Explore Innovation Lab', |
||||
|
enterBusiness: 'View Business Map', |
||||
|
icidpBadge: 'ICIDP Culture Driven', |
||||
|
tracks: { |
||||
|
social: { name: 'Content & Social', desc: 'Build genuine warm social connections with like-minded people', color: '#E8A0BF' }, |
||||
|
career: { name: 'Career Services', desc: 'Smart opportunity matching + knowledge empowerment, every step visible', color: '#7EC8E3' }, |
||||
|
payment: { name: 'Digital Payment', desc: 'Secure, convenient contextual payments with a human touch', color: '#98D8AA' }, |
||||
|
ai: { name: 'Artificial Intelligence', desc: 'Multi-modal understanding — AI as a warm life companion', color: '#C9B1FF' }, |
||||
|
}, |
||||
|
stats: { |
||||
|
eyebrow: 'BASKBUDDY IN NUMBERS', |
||||
|
title: 'BaskBuddy Velocity', |
||||
|
subtitle: 'The solid foundation connecting beautiful life with warm tech', |
||||
|
items: [ |
||||
|
{ value: '4', suffix: '', label: 'Core Tracks', sub: 'Social · Career · Payment · AI' }, |
||||
|
{ value: '10', suffix: '+', label: 'Planned Products', sub: 'Covering the full digital life scene' }, |
||||
|
{ value: '10', suffix: '', label: 'Year Vision', sub: 'Goal: a globally renowned tech enterprise' }, |
||||
|
{ value: '2', suffix: 'B+', label: 'Target Users', sub: 'Cross-scenario, full-lifecycle service' }, |
||||
|
], |
||||
|
}, |
||||
|
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 and Artificial Intelligence, we connect everyone’s beautiful life with warm technology.', |
||||
|
cta: 'Learn More', |
||||
|
}, |
||||
|
dynamics: { |
||||
|
eyebrow: 'NEWSROOM', |
||||
|
title: 'Company Updates', |
||||
|
subtitle: 'Every step moves toward the promise of "Together for Life, Trust for a Lifetime"', |
||||
|
cta: 'View Business Map', |
||||
|
}, |
||||
|
footer: { |
||||
|
copyright: `© ${new Date().getFullYear()} Bashi Partners (Shanghai) Technology Co., Ltd.`, |
||||
|
icidp: 'ICIDP: Integrity · Courage · Innovation · Devotion · Passion', |
||||
|
}, |
||||
|
}, |
||||
|
innovation: { |
||||
|
title: 'Innovation Lab', |
||||
|
subtitle: 'Exploring in depth, illuminating the future with warmth', |
||||
|
philosophy: { |
||||
|
title: 'ICIDP R&D Philosophy', |
||||
|
items: [ |
||||
|
{ title: 'Integrity', subtitle: '诚信', color: '#667EEA', desc: 'Never overstate capabilities before launch. Embed data security from design. Privacy protection is our conscious commitment beyond compliance.' }, |
||||
|
{ title: 'Courage', subtitle: '勇敢', color: '#EF4444', desc: 'Take the road less traveled. Make long-term investments in core technologies. Dare to start over without being bound by sunk costs.' }, |
||||
|
{ title: 'Innovation', subtitle: '创新', color: '#8B5CF6', desc: 'Don\'t do me-too. Be the only-one. Find differentiated solutions in every track.' }, |
||||
|
{ title: 'Devotion', subtitle: '奉献', color: '#10B981', desc: 'Take responsibility for every line of code. Polish user experience without limits. Devotion must be rewarded to sustain.' }, |
||||
|
{ title: 'Passion', subtitle: '激情', color: '#FFEFD5', desc: 'Today\'s best is tomorrow\'s minimum. Never settle for "good enough" or "just okay."' }, |
||||
|
], |
||||
|
}, |
||||
|
techPaths: { |
||||
|
title: 'Technology Differentiation Paths', |
||||
|
items: [ |
||||
|
{ |
||||
|
track: 'Content & Social', |
||||
|
logic: 'Z-generation social needs are fragmented and vertical. Existing platforms cannot meet the integrated needs of "authentic social + content creation + interest discovery".', |
||||
|
products: 'QingHe / YinCool / MiLian', |
||||
|
tech: 'AI Social Engine + AI Content Moderation + AI Matching Algorithm', |
||||
|
desc: 'Understanding emotions, building warm connections' |
||||
|
}, |
||||
|
{ |
||||
|
track: 'Career Services', |
||||
|
logic: 'Job-seeking online rate exceeds 82%, but information asymmetry remains severe. Users need end-to-end services from "finding a job" to "finding direction".', |
||||
|
products: 'ZhiLai / KanShan / BuTu / YueSheng', |
||||
|
tech: 'Company Evaluation System + AI College Application + Career Skill Graph', |
||||
|
desc: 'Making growth paths clearly visible' |
||||
|
}, |
||||
|
{ |
||||
|
track: 'Digital Payment', |
||||
|
logic: 'Digital payment market exceeds 12 trillion, but transaction trust and security guarantee remain core pain points.', |
||||
|
products: 'TuoDa', |
||||
|
tech: 'Platform-wide Guarantee + Payment Traceability + Performance Verification', |
||||
|
desc: 'Secure, convenient, warm payment experiences' |
||||
|
}, |
||||
|
{ |
||||
|
track: 'Artificial Intelligence', |
||||
|
logic: 'AI is evolving from general capabilities to vertical expertise. AI deeply integrated with business scenarios is truly useful.', |
||||
|
products: 'Abu AI / TingYuan', |
||||
|
tech: 'Social Icebreaking + Content Co-creation + Resume Optimization + College Application', |
||||
|
desc: 'AI as a warm life companion' |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
milestones: { |
||||
|
title: 'Development Milestones', |
||||
|
items: [ |
||||
|
{ year: '2026', event: 'Initiated development of five core products: QingHe, YinCool, MiLian, ZhiLai, KanShan (including BuTu & YueSheng)', status: 'completed' }, |
||||
|
{ year: '2027', event: 'Progressing TuoDa, Abu AI, and TingYuan product launches', status: 'in-progress' }, |
||||
|
{ year: '2028-2030', event: 'Deep collaboration and data integration across tracks, building localized digital life service ecosystem covering major global markets', status: 'upcoming' }, |
||||
|
], |
||||
|
}, |
||||
|
selfDeveloped: { |
||||
|
title: 'Self-Developed Technology Layout', |
||||
|
items: [ |
||||
|
{ name: 'Emotional Semantic Engine', en: 'ESE', desc: 'Understanding emotions, building warm social connections' }, |
||||
|
{ name: 'Intelligent Matching Engine', en: 'IME', desc: 'Precision matching opportunities, clear growth paths' }, |
||||
|
{ name: 'Knowledge-Capability Graph', en: 'KCG', desc: 'Breaking knowledge barriers, empowering career growth' }, |
||||
|
{ name: 'Multi-Modal Perception Framework', en: 'MMPF', desc: 'Understanding multi-dimensional inputs, AI that understands you' }, |
||||
|
{ name: 'Security Risk Control System', en: 'SRCS', desc: 'Protecting every transaction, secure payment experiences' }, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
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 (Qinghe, YinCool, MiLian), Career Services (ZhiLai, KanShan, BuTu, YueSheng), Digital Payment (TuoDa), and Artificial Intelligence (Abu AI). 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', |
||||
|
purpose: 'Dedicated to employee growth · Dedicated to company development · Dedicated to social progress · Dedicated to changing the world', |
||||
|
culture: 'Let those who dare to innovate break boundaries! Let those closest to users define value! Let those who hear the sounds of battle command the fight!', |
||||
|
}, |
||||
|
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'] }, |
||||
|
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'] }, |
||||
|
}, |
||||
|
survivalTools: { |
||||
|
title: 'Survival Tools', |
||||
|
desc: 'Efficiency · Effectiveness · Benefit — All three are essential', |
||||
|
items: ['Efficiency determines response speed — rapid iteration, agile market response', 'Effectiveness validates value creation — let results speak, let data prove', 'Benefit ensures sustainable development — creating lasting commercial and social value'], |
||||
|
}, |
||||
|
fiveProactive: { |
||||
|
title: 'Five Proactive Traits', |
||||
|
items: [ |
||||
|
'Proactive Communication — Report problems and risks promptly, accumulate experience through problem-solving, build capability through breakthrough moments. Take responsibility for yourself and for the company.', |
||||
|
'Proactive Growth — Dare to embrace challenges, maintain sharp insight into new technologies and new demands, stay competitive through self-iteration, constantly advance toward better UX and greater product value.', |
||||
|
'Proactive Responsibility — Embrace company values, don\'t dwell on short-term gains, willingly follow the company\'s pace, achieve rapid personal growth amid the company\'s rapid development.', |
||||
|
'Proactive Sharing — Willingly share work experience and insights with teammates, drive overall team capability improvement through sharing, support each other\'s growth, gather powerful momentum driving the company forward.', |
||||
|
'Proactive Innovation — Maintain curiosity for exploration, dare to try new methods and directions, infuse fresh ideas into products and services, create value through innovative technology, warm hearts with genuine service, pour care into every detail.', |
||||
|
], |
||||
|
}, |
||||
|
growthManifesto: { |
||||
|
title: 'Growth Manifesto', |
||||
|
content: `I Promise
|
||||
|
To cherish this precious time of youth doubly |
||||
|
To love the cause in our hands boundlessly |
||||
|
We will surely spread our wings and fly high, realizing all our wishes |
||||
|
From this moment, starting right here |
||||
|
Follow your heart, move toward the light |
||||
|
|
||||
|
May every effort bear fruit |
||||
|
May the original intention of every BaskBuddier |
||||
|
Illuminate the company's path forward, light up users' beautiful lives! |
||||
|
|
||||
|
Come on, let's honor this solemn commitment with down-to-earth action! |
||||
|
Come on, let's write this new chapter with our forward footsteps! |
||||
|
Come on, let's journey toward a beautiful future together with BaskBuddy!`,
|
||||
|
}, |
||||
|
goals: { |
||||
|
near: '2026: Launch the five core products (Qinghe, YinCool, MiLian, ZhiLai, KanShan/BuTu/YueSheng), forming a product matrix and achieving dual growth in user scale and engagement. 2027: Scale the initial five products and launch the three flagship products TuoDa, Abu AI, and TingYuan, completing the digital life service loop. 2028–2030: Deep cross-track synergy and data convergence, forming a self-driven, iterable intelligent service network and a localized digital life ecosystem covering major global markets (Asia, Europe, South America, Africa, etc.).', |
||||
|
mid: 'Within 10 years: cultivate 3-4 publicly listed companies, including 1-2 industry-leading first movers and 2-3 top-tier industry leaders.', |
||||
|
far: 'Build multiple globally recognized digital life products, and establish a globally interconnected digital service network covering diverse needs with symbiotic ecosystems. Serve over 2 billion users across scenarios and the full lifecycle, continuously lead the industry’s innovation direction, and become the benchmark enterprise that consistently drives industry transformation and development in digital life services. Driven by the deep integration of AI and industry, we achieve technological autonomy and controllability, open and win-win ecosystems, significant social value, shared development opportunities, and shared growth responsibilities.', |
||||
|
long: 'Within 10 years, grow into a globally renowned high-tech enterprise — where every user takes pride in using our products, every employee is proud to be part of Bashi Partners, and we continuously create irreplaceable long-term value for employees, users, and society, achieving multi-party value win-win for employees, users, the company, and society.', |
||||
|
}, |
||||
|
}, |
||||
|
business: { |
||||
|
title: 'Business Map', |
||||
|
subtitle: 'Four Core Tracks, Full Digital Life Coverage', |
||||
|
tracks: [ |
||||
|
{ |
||||
|
key: 'social', |
||||
|
name: 'Content & Social', |
||||
|
products: [ |
||||
|
{ name: 'Qinghe', desc: 'Content Social Engine — warm bonds connecting people', status: 'In Development', icon: 'heart' }, |
||||
|
{ name: 'YinCool', desc: 'AI Music Creation Platform — everyone can create music', status: 'In Development', icon: 'music' }, |
||||
|
{ name: 'MiLian', desc: 'Emotional Social Computing — understanding emotions, warming connections', status: 'In Development', icon: 'sparkles' }, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
key: 'career', |
||||
|
name: 'Career Services', |
||||
|
products: [ |
||||
|
{ name: 'ZhiLai', desc: 'Smart Career Matching System — clear growth paths', status: 'In Development', icon: 'briefcase' }, |
||||
|
{ name: 'KanShan', desc: 'Knowledge Graph Platform — accessible knowledge', status: 'In Development', icon: 'mountain' }, |
||||
|
{ name: 'BuTu', desc: 'Growth Path Planning Tool — every step has value', status: 'Planned', icon: 'footprints' }, |
||||
|
{ name: 'YueSheng', desc: 'Capability Enhancement Engine — empowering career leaps', status: 'Planned', icon: 'rocket' }, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
key: 'payment', |
||||
|
name: 'Digital Payment', |
||||
|
products: [ |
||||
|
{ name: 'TuoDa', desc: 'Smart Digital Payment — secure, convenient, warm', status: 'In Development', icon: 'check-circle' }, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
key: 'ai', |
||||
|
name: 'Artificial Intelligence', |
||||
|
products: [ |
||||
|
{ name: 'Abu AI', desc: 'Multi-modal Life Assistant — warmth every day', status: 'In Development', icon: 'bot' }, |
||||
|
{ name: 'TingYuan', desc: 'Wish Understanding Engine — hearing every wish', status: 'Planned', icon: 'ear' }, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
statusLabels: { |
||||
|
developing: 'In Development', |
||||
|
planned: 'Planned', |
||||
|
}, |
||||
|
}, |
||||
|
join: { |
||||
|
title: 'Join Us', |
||||
|
subtitle: 'BaskBuddy — Together for Life', |
||||
|
manifesto: `I Promise
|
||||
|
To cherish this precious time of youth doubly |
||||
|
To love the cause in our hands boundlessly |
||||
|
We will surely spread our wings and fly high, realizing all our wishes |
||||
|
|
||||
|
At BaskBuddy, you're not just an employee. |
||||
|
You're a co-creator of culture, a practitioner of values, a writer of the future. |
||||
|
Let's follow our hearts and move toward the light, together!`,
|
||||
|
growthIncentive: { |
||||
|
title: 'Growth Incentives', |
||||
|
items: ['Transparent promotion paths — every step visible', 'Skill growth fund — investing in your learning', 'Mentorship program — senior partners one-on-one guidance', 'Cross-track rotation — broad vision, multi-dimensional growth'], |
||||
|
}, |
||||
|
longTermIncentive: { |
||||
|
title: 'Long-term Incentives', |
||||
|
items: ['Equity incentive plan — grow with the company, share the rewards', 'Project incubation support — your idea, our backing', 'Entrepreneurial partner mechanism — from employee to partner'], |
||||
|
}, |
||||
|
benefits: { |
||||
|
title: 'Benefits', |
||||
|
items: ['Flexible work hours — master your own time', 'Warm office environment — home-like comfort', 'Health care program — wellness is the greatest wealth', 'Team building activities — warm moments beyond work', 'Learning growth subsidy — always on the path of progress'], |
||||
|
}, |
||||
|
delivery: { |
||||
|
title: 'How to Apply', |
||||
|
email: 'baskbuddy@163.com', |
||||
|
note: 'Send your resume to the above email with subject "Position + Name"', |
||||
|
}, |
||||
|
}, |
||||
|
contact: { |
||||
|
title: 'Contact Us', |
||||
|
subtitle: 'Looking forward to meeting you with warmth', |
||||
|
job: { |
||||
|
title: 'Job Inquiry', |
||||
|
desc: 'Explore our open roles or send your resume — grow with BaskBuddy.', |
||||
|
cta: 'View Open Roles', |
||||
|
emailLabel: 'Resume to', |
||||
|
email: 'baskbuddy@163.com', |
||||
|
copy: 'Copy', |
||||
|
copied: 'Copied', |
||||
|
}, |
||||
|
biz: { |
||||
|
title: 'Business Cooperation', |
||||
|
desc: 'Tech partnership, product customization, channel agency or investment — reach out anytime.', |
||||
|
cta: 'Email Us', |
||||
|
emailLabel: 'Business email', |
||||
|
email: 'baskbuddy@163.com', |
||||
|
copy: 'Copy', |
||||
|
copied: 'Copied', |
||||
|
}, |
||||
|
privacyNote: 'We respect your privacy. See our Privacy Policy.', |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,25 @@ |
|||||
|
import { zh } from './zh'; |
||||
|
import { en } from './en'; |
||||
|
|
||||
|
export type Locale = 'zh' | 'en'; |
||||
|
export type TranslationKey = keyof typeof zh; |
||||
|
|
||||
|
const translations: Record<Locale, typeof zh> = { zh, en }; |
||||
|
|
||||
|
export function getTranslation(locale: Locale) { |
||||
|
return translations[locale] || translations.zh; |
||||
|
} |
||||
|
|
||||
|
// Replaces the old Astro `getLocaleFromUrl(url)` — locale is now derived
|
||||
|
// from the browser pathname (zh at `/`, en at `/en/*`).
|
||||
|
export function getLocaleFromPathname(): Locale { |
||||
|
if (typeof window !== 'undefined' && window.location.pathname.startsWith('/en')) return 'en'; |
||||
|
return 'zh'; |
||||
|
} |
||||
|
|
||||
|
export function getLocalizedPath(path: string, locale: Locale): string { |
||||
|
if (locale === 'en') { |
||||
|
return `/en${path}`; |
||||
|
} |
||||
|
return path; |
||||
|
} |
||||
@ -0,0 +1,272 @@ |
|||||
|
export const zh = { |
||||
|
brand: 'BaskBuddy', |
||||
|
brandSub: '巴适伙伴', |
||||
|
nav: { |
||||
|
home: '首页', |
||||
|
innovation: '创新实验室', |
||||
|
business: '业务版图', |
||||
|
about: '关于我们', |
||||
|
join: '加入我们', |
||||
|
contact: '联系我们', |
||||
|
}, |
||||
|
home: { |
||||
|
slogan: '巴适伙伴·一生相伴\n舒适相伴·一生信赖', |
||||
|
sloganSub: 'BaskBuddy · Warm Tech, Better Life', |
||||
|
sloganIntro: '覆盖内容社交 · 职场服务 · 数字支付 · AI 的数字生活服务商', |
||||
|
trackOverview: '四大核心赛道', |
||||
|
enterLab: '探索创新实验室', |
||||
|
enterBusiness: '查看业务版图', |
||||
|
icidpBadge: 'ICIDP 文化驱动', |
||||
|
tracks: { |
||||
|
social: { name: '内容社交', desc: '连接志同道合的人,打造真诚温暖的社交体验', color: '#E8A0BF' }, |
||||
|
career: { name: '职场服务', desc: '智能匹配机会,知识赋能成长,让每一步都清晰可见', color: '#7EC8E3' }, |
||||
|
payment: { name: '数字支付', desc: '安全便捷的场景化支付,让每笔交易都有温度', color: '#98D8AA' }, |
||||
|
ai: { name: '人工智能', desc: '多模态感知理解,让AI成为温暖的生活伙伴', color: '#C9B1FF' }, |
||||
|
}, |
||||
|
stats: { |
||||
|
eyebrow: 'BASKBUDDY IN NUMBERS', |
||||
|
title: '巴适速度', |
||||
|
subtitle: '以温暖科技,连接美好生活的坚实底座', |
||||
|
items: [ |
||||
|
{ value: '4', suffix: '大', label: '核心赛道', sub: '内容社交 · 职场服务 · 数字支付 · AI' }, |
||||
|
{ value: '10', suffix: '+', label: '规划产品', sub: '覆盖数字生活全场景' }, |
||||
|
{ value: '10', suffix: '年', label: '发展愿景', sub: '享誉全球高科技企业目标' }, |
||||
|
{ value: '20', suffix: '亿+', label: '目标用户', sub: '跨场景全生命周期服务' }, |
||||
|
], |
||||
|
}, |
||||
|
about: { |
||||
|
eyebrow: 'ABOUT BASKBUDDY', |
||||
|
title: '走进巴适伙伴', |
||||
|
text: '巴适伙伴(上海)科技有限公司是一家聚焦数字生活服务领域的高科技企业,依托多品牌、多垂直赛道的产品布局,覆盖内容社交、职场服务、数字支付与人工智能,以温暖科技连接每一个人的美好生活。', |
||||
|
cta: '进一步了解', |
||||
|
}, |
||||
|
dynamics: { |
||||
|
eyebrow: 'NEWSROOM', |
||||
|
title: '发展动态', |
||||
|
subtitle: '每一步,都朝着“一生相伴·一生信赖”的承诺迈进', |
||||
|
cta: '查看业务版图', |
||||
|
}, |
||||
|
footer: { |
||||
|
copyright: `© ${new Date().getFullYear()} 巴适伙伴(上海)科技有限公司`, |
||||
|
icidp: 'ICIDP: 诚信 · 勇敢 · 创新 · 奉献 · 激情', |
||||
|
}, |
||||
|
}, |
||||
|
innovation: { |
||||
|
title: '创新实验室', |
||||
|
subtitle: '在深邃中探索,以温暖照亮未来', |
||||
|
philosophy: { |
||||
|
title: 'ICIDP 研发哲学', |
||||
|
items: [ |
||||
|
{ title: '诚信', subtitle: 'Integrity', color: '#667EEA', desc: '功能上线前绝不虚标能力,数据安全从设计之初嵌入,用户隐私保护是底线之上的自觉追求' }, |
||||
|
{ title: '勇敢', subtitle: 'Courage', color: '#EF4444', desc: '不走别人走过的路,敢于在核心技术上做长期投入,敢于推翻重来不以沉没成本绑架未来' }, |
||||
|
{ title: '创新', subtitle: 'Innovation', color: '#8B5CF6', desc: '不做me-too,做only-one,每一条赛道都要找到属于自己的差异化解法' }, |
||||
|
{ title: '奉献', subtitle: 'Devotion', color: '#10B981', desc: '对每一行代码负责,用户体验细节打磨不设限,奉献者必得回报才可持续' }, |
||||
|
{ title: '激情', subtitle: 'Passion', color: '#FFEFD5', desc: '今天最好的表现是明天最低的要求,永远不满足于"够用"和"还行"' }, |
||||
|
], |
||||
|
}, |
||||
|
techPaths: { |
||||
|
title: '技术差异化路径', |
||||
|
items: [ |
||||
|
{ |
||||
|
track: '内容社交', |
||||
|
logic: 'Z世代社交需求碎片化、垂直化,现有平台无法满足"真实社交+内容创作+兴趣发现"的一体化需求', |
||||
|
products: '青核 / 音酷 / 蜜恋', |
||||
|
tech: 'AI社交引擎 + AI内容审核 + AI匹配算法', |
||||
|
desc: '理解用户情感,构建有温度的连接' |
||||
|
}, |
||||
|
{ |
||||
|
track: '职场服务', |
||||
|
logic: '求职线上化率超82%,但信息不对称仍严重,用户需要从"找到工作"到"找到方向"的全链路服务', |
||||
|
products: '职来 / 看山 / 步途 / 跃升', |
||||
|
tech: '企业评价分级系统 + AI志愿填报算法 + 职业技能图谱', |
||||
|
desc: '让成长路径清晰可见' |
||||
|
}, |
||||
|
{ |
||||
|
track: '数字支付', |
||||
|
logic: '数字支付市场超12万亿,但交易信任与安全担保仍是核心痛点', |
||||
|
products: '妥哒', |
||||
|
tech: '平台全程担保机制 + 支付即溯源 + 履约即验真', |
||||
|
desc: '让支付安全便捷有温度' |
||||
|
}, |
||||
|
{ |
||||
|
track: '人工智能', |
||||
|
logic: 'AI正从通用能力走向垂直深耕,与业务场景深度绑定的AI才是真正有用的AI', |
||||
|
products: '阿布AI / 听愿', |
||||
|
tech: '社交破冰 + 内容共创 + 简历优化 + 志愿填报', |
||||
|
desc: '让AI成为温暖的生活伙伴' |
||||
|
}, |
||||
|
], |
||||
|
}, |
||||
|
milestones: { |
||||
|
title: '发展里程碑', |
||||
|
items: [ |
||||
|
{ year: '2026', event: '启动青核、音酷、蜜恋、职来、看山(含步途、跃升)五大主力产品开发', status: 'completed' }, |
||||
|
{ year: '2027', event: '推进妥哒、阿布AI、听愿三大产品落地应用', status: 'in-progress' }, |
||||
|
{ year: '2028-2030', event: '各赛道深度协同与数据融通,建成覆盖全球主要市场的本地化数字生活服务生态', status: 'upcoming' }, |
||||
|
], |
||||
|
}, |
||||
|
selfDeveloped: { |
||||
|
title: '自研技术布局', |
||||
|
items: [ |
||||
|
{ name: '情感语义引擎', en: 'Emotional Semantic Engine', desc: '理解用户情感,构建有温度的社交连接' }, |
||||
|
{ name: '智能匹配引擎', en: 'Intelligent Matching Engine', desc: '精准匹配机会,让成长路径清晰可见' }, |
||||
|
{ name: '知识能力图谱', en: 'Knowledge-Capability Graph', desc: '打通知识壁垒,赋能职场学习成长' }, |
||||
|
{ name: '多模态感知框架', en: 'Multi-Modal Perception Framework', desc: '理解多维度输入,让AI更懂你' }, |
||||
|
{ name: '安全风控体系', en: 'Security Risk Control System', desc: '守护每笔交易,让支付安全有保障' }, |
||||
|
], |
||||
|
}, |
||||
|
}, |
||||
|
about: { |
||||
|
title: '关于我们', |
||||
|
subtitle: '巴适伙伴·一生相伴', |
||||
|
intro: `巴适伙伴(上海)科技有限公司是一家聚焦数字生活服务领域,依托多品牌、多垂直赛道的产品布局,面向不同用户群体提供优质产品与服务的高科技企业,通过旗下多家独立运营的子公司,分别深耕内容社交(青核、音酷、蜜恋)、职场服务(职来、看山、步途、跃升)、数字支付(妥哒)、人工智能(阿布 AI)等多个领域,始终坚持以用户需求为核心,坚持品质第一与技术创新,为员工创造成长空间,为用户创造美好生活,为社会创造长期价值。`, |
||||
|
visionMission: { |
||||
|
vision: '打造全球领先的数字生活服务生态,让不同需求的用户都能获得优质适配的产品体验', |
||||
|
mission: '以创新技术连接人与服务,依托多元垂直领域产品,为不同用户群体打造便捷高效、富有价值的美好生活', |
||||
|
purpose: '一心为员工成长 · 一心为公司发展 · 一心为社会进步 · 一心为改变世界', |
||||
|
culture: '让敢创新敢挑战的人突破边界,让距离用户最近的人定义价值,让听得见炮火声的人指挥战斗!', |
||||
|
}, |
||||
|
icidp: { |
||||
|
title: 'ICIDP 核心价值观', |
||||
|
integrity: { label: '诚信 Integrity', position: '根基', dialect: '靠谱才是硬道理', layers: ['对用户:值得信赖的产品承诺与服务品质', '对伙伴:透明共赢的合作态度与共同成长', '对自己:言行一致的做人底线'] }, |
||||
|
courage: { label: '勇敢 Courage', position: '动力', dialect: '敢想敢干才巴适', layers: ['敢于突破技术边界与认知极限', '敢于挑战行业惯性与既定格局', '敢于承认错误并快速修正前行方向'] }, |
||||
|
innovation: { label: '创新 Innovation', position: '灵魂', dialect: '不一样才有意思', layers: ['技术创新:不止于代码,更在于思维方式的革新', '产品创新:从用户痛点出发反向定义价值', '组织创新:让每个成员都成为创新的源头'] }, |
||||
|
devotion: { label: '奉献 Devotion', position: '温度', dialect: '舍得才是真格局', layers: ['将热爱融入产品的每一个细节', '将时间投入团队的每一次协作', '将价值回馈社会的每一步行动'] }, |
||||
|
passion: { label: '激情 Passion', position: '火焰', dialect: '热爱可抵岁月漫长', layers: ['对事业:清晨醒来眼里依然有光', '对团队:每个人都是可以点燃他人的火种', '对世界:工作之外依然热爱生活的每一个瞬间'] }, |
||||
|
}, |
||||
|
survivalTools: { |
||||
|
title: '生存法宝', |
||||
|
desc: '效率 · 效果 · 效益 —— 三者缺一不可', |
||||
|
items: ['效率决定响应速度 —— 快速迭代,敏捷响应市场变化', '效果验证价值实现 —— 用结果说话,用数据证明', '效益保障持续发展 —— 创造可持续的商业与社会价值'], |
||||
|
}, |
||||
|
fiveProactive: { |
||||
|
title: '五个主动', |
||||
|
items: [ |
||||
|
'主动沟通 —— 遇到问题风险及时反馈沟通,在解决问题中积累经验,在一次次破局中夯实干成事的能力,对自己负责,对公司负责', |
||||
|
'主动成长 —— 敢于迎接挑战,保持对新技术、新需求的敏锐洞察,在自我迭代中保持竞争力,永远向着更好的用户体验、更优的产品价值不断迈进', |
||||
|
'主动担当 —— 认可并践行公司的价值观,不计较一时得失,心甘情愿跟着公司步伐前行,在公司快速发展的浪潮中实现个人价值的快速提升', |
||||
|
'主动分享 —— 乐于将自己的工作经验、技巧心得分享给团队伙伴,在共享中推动团队整体能力提升,相互扶持共同成长,凝聚起推动公司发展的强大合力', |
||||
|
'主动创新 —— 时刻保持探索的好奇心,敢于尝试新方法、新方向,在产品和服务中融入新想法新思路,以创新技术创造价值,以真情服务温暖人心,把用心融进产品的每一处细节', |
||||
|
], |
||||
|
}, |
||||
|
growthManifesto: { |
||||
|
title: '成长宣言', |
||||
|
content: `我承诺
|
||||
|
加倍珍惜如此宝贵的青春时光 |
||||
|
无比热爱我们手中的这份事业 |
||||
|
我们必定将展翅高飞,实现所有愿望 |
||||
|
从现在开始,从此刻启程 |
||||
|
凭心出发,向光而行 |
||||
|
|
||||
|
让每一份努力都开花结果 |
||||
|
让每一位巴适伙伴的初心 |
||||
|
照亮公司的发展前路,点亮用户的美好人生! |
||||
|
|
||||
|
加油,让我们用踏实的行动践行这庄严的承诺! |
||||
|
加油,让我们用前行的脚印书写这崭新的篇章! |
||||
|
加油,让我们和巴适伙伴一同奔赴美好的未来!`,
|
||||
|
}, |
||||
|
goals: { |
||||
|
near: '2026 年:年内完成青核、音酷、蜜恋、职来、看山(步途、跃升)五大主力产品的上线运营,形成产品矩阵,并实现用户规模与活跃度的双提升;2027 年:完成前期五大产品的规模化落地,以及妥哒、阿布 AI、听愿三大主力产品的落地应用,构建完整地数字生活服务闭环;2028—2030 年:实现各垂直赛道的深度协同与数据融通,形成自驱动、可迭代的智能服务网络,建成覆盖全球主要市场(亚洲、欧洲、南美洲、非洲等地区)的本地化数字生活服务生态。', |
||||
|
mid: '10 年内陆续培育 3-4 家上市公司,其中将培育 1-2 家细分领域行业第一的企业,以及 2-3 家位居行业头部梯队的企业。', |
||||
|
far: '打造出多个被全球用户广泛认可的数字生活产品,在全球范围内构建起覆盖多元需求、生态协同共生的数字服务网络,构建覆盖超 20 亿用户的跨场景、全生命周期服务体系,持续引领行业的创新发展方向,成为数字生活服务领域持续引领行业变革、推动产业发展的标杆企业。同时在人工智能与产业深度融合的驱动下,实现技术自主可控、生态开放共赢、社会价值显著,共享发展机遇,共担成长责任。', |
||||
|
long: '10 年内成长为一家享誉全球的高科技企业,让每一位用户以使用我们的产品为荣,让每一位员工以身为巴适伙伴的一份子而自豪,持续为员工、用户、社会创造不可替代的长期价值,实现员工、用户、企业、社会的多方价值共赢。', |
||||
|
}, |
||||
|
}, |
||||
|
business: { |
||||
|
title: '业务版图', |
||||
|
subtitle: '四大核心赛道,数字生活全覆盖', |
||||
|
tracks: [ |
||||
|
{ |
||||
|
key: 'social', |
||||
|
name: '内容社交', |
||||
|
products: [ |
||||
|
{ name: '青核', desc: '内容社交引擎 — 连接人与人的温暖纽带', status: '开发中', icon: 'heart' }, |
||||
|
{ name: '音酷', desc: 'AI音乐创作平台 — 让每个人都能创作音乐', status: '开发中', icon: 'music' }, |
||||
|
{ name: '蜜恋', desc: '情感计算社交 — 理解情感,温暖连接每一颗心', status: '开发中', icon: 'sparkles' }, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
key: 'career', |
||||
|
name: '职场服务', |
||||
|
products: [ |
||||
|
{ name: '职来', desc: '智能职业匹配系统 — 让成长路径清晰可见', status: '开发中', icon: 'briefcase' }, |
||||
|
{ name: '看山', desc: '知识图谱平台 — 让知识触手可及', status: '开发中', icon: 'mountain' }, |
||||
|
{ name: '步途', desc: '成长路径规划工具 — 每一步都有价值', status: '规划中', icon: 'footprints' }, |
||||
|
{ name: '跃升', desc: '能力提升引擎 — 赋能职业跃迁', status: '规划中', icon: 'rocket' }, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
key: 'payment', |
||||
|
name: '数字支付', |
||||
|
products: [ |
||||
|
{ name: '妥哒', desc: '智能数字支付 — 安全便捷,让支付更有温度', status: '开发中', icon: 'check-circle' }, |
||||
|
], |
||||
|
}, |
||||
|
{ |
||||
|
key: 'ai', |
||||
|
name: '人工智能', |
||||
|
products: [ |
||||
|
{ name: '阿布 AI', desc: '多模态生活助手 — 温暖你的每一天', status: '开发中', icon: 'bot' }, |
||||
|
{ name: '听愿', desc: '心愿理解引擎 — 听懂你的每一个愿望', status: '规划中', icon: 'ear' }, |
||||
|
], |
||||
|
}, |
||||
|
], |
||||
|
statusLabels: { |
||||
|
developing: '开发中', |
||||
|
planned: '规划中', |
||||
|
}, |
||||
|
}, |
||||
|
join: { |
||||
|
title: '加入我们', |
||||
|
subtitle: '巴适伙伴·一生相伴', |
||||
|
manifesto: `我承诺
|
||||
|
加倍珍惜如此宝贵的青春时光 |
||||
|
无比热爱我们手中的这份事业 |
||||
|
我们必定将展翅高飞,实现所有愿望 |
||||
|
|
||||
|
在巴适伙伴,你不只是员工, |
||||
|
你是文化的共创者、价值的实践者、未来的书写者。 |
||||
|
让我们一起凭心出发,向光而行!`,
|
||||
|
growthIncentive: { |
||||
|
title: '成长激励', |
||||
|
items: ['透明晋升通道 — 每一步成长都清晰可见', '技能成长基金 — 为你的学习投资买单', '导师制度 — 资深伙伴一对一指导成长', '跨赛道轮岗 — 全面视野,多维能力成长'], |
||||
|
}, |
||||
|
longTermIncentive: { |
||||
|
title: '长期激励', |
||||
|
items: ['股权激励计划 — 与公司共同成长共享成果', '项目孵化支持 — 你的想法,我们来托底落地', '创业合伙人机制 — 从员工到合伙人的蜕变'], |
||||
|
}, |
||||
|
benefits: { |
||||
|
title: '福利待遇', |
||||
|
items: ['弹性工作时间 — 做自己时间的主人', '温暖办公环境 — 家一般的舒适感', '健康关怀计划 — 身心健康才是最大财富', '团队建设活动 — 工作之外的温暖时光', '学习成长补贴 — 永远在进步的路上'], |
||||
|
}, |
||||
|
delivery: { |
||||
|
title: '投递方式', |
||||
|
email: 'baskbuddy@163.com', |
||||
|
note: '请将简历发送至以上邮箱,邮件标题注明"应聘职位+姓名"', |
||||
|
}, |
||||
|
}, |
||||
|
contact: { |
||||
|
title: '联系我们', |
||||
|
subtitle: '期待与你温暖相遇', |
||||
|
job: { |
||||
|
title: '求职咨询', |
||||
|
desc: '关注我们的开放职位,或直接投递简历,与巴适伙伴一起温暖成长。', |
||||
|
cta: '查看开放职位', |
||||
|
emailLabel: '简历投递', |
||||
|
email: 'baskbuddy@163.com', |
||||
|
copy: '复制', |
||||
|
copied: '已复制', |
||||
|
}, |
||||
|
biz: { |
||||
|
title: '商务合作', |
||||
|
desc: '技术合作、产品定制、渠道代理、投资洽谈,欢迎随时联系。', |
||||
|
cta: '发起商务邮件', |
||||
|
emailLabel: '商务邮箱', |
||||
|
email: 'baskbuddy@163.com', |
||||
|
copy: '复制', |
||||
|
copied: '已复制', |
||||
|
}, |
||||
|
privacyNote: '我们重视你的隐私,详情见《隐私政策》', |
||||
|
}, |
||||
|
}; |
||||
@ -0,0 +1,23 @@ |
|||||
|
import { Outlet, useLocation } from 'react-router-dom'; |
||||
|
import SiteShell from '@/components/SiteShell'; |
||||
|
import { useClientEffects } from '@/hooks/useClientEffects'; |
||||
|
import { getLocaleFromPathname } from '@/i18n/utils'; |
||||
|
|
||||
|
// Root layout: persists the Header/Footer (SiteShell) across navigations,
|
||||
|
// re-runs client interaction effects on every path change, and replays the
|
||||
|
// page-enter animation on the routed content.
|
||||
|
export default function RootLayout() { |
||||
|
const location = useLocation(); |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
useClientEffects(location.pathname); |
||||
|
|
||||
|
return ( |
||||
|
<div className="min-h-screen font-[var(--font-body)] antialiased bg-[var(--bg-primary)] text-[var(--text-primary)]"> |
||||
|
<SiteShell locale={locale}> |
||||
|
<div key={location.pathname} className="page-enter"> |
||||
|
<Outlet /> |
||||
|
</div> |
||||
|
</SiteShell> |
||||
|
</div> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,10 @@ |
|||||
|
// Single source of truth for track accent colors.
|
||||
|
// The four track hues live ONLY in --track-* design tokens (global.css).
|
||||
|
// Components reference them via this map keyed by track id, so the
|
||||
|
// pastel card tint and the deep accent never drift apart.
|
||||
|
export const TRACK_TOKEN: Record<string, string> = { |
||||
|
social: 'var(--track-social)', |
||||
|
career: 'var(--track-career)', |
||||
|
payment: 'var(--track-payment)', |
||||
|
ai: 'var(--track-ai)', |
||||
|
}; |
||||
@ -0,0 +1,6 @@ |
|||||
|
import { clsx, type ClassValue } from 'clsx'; |
||||
|
import { twMerge } from 'tailwind-merge'; |
||||
|
|
||||
|
export function cn(...inputs: ClassValue[]) { |
||||
|
return twMerge(clsx(inputs)); |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
import { StrictMode } from 'react'; |
||||
|
import { createRoot } from 'react-dom/client'; |
||||
|
import { BrowserRouter } from 'react-router-dom'; |
||||
|
import App from './App'; |
||||
|
import './styles/global.css'; |
||||
|
|
||||
|
createRoot(document.getElementById('root')!).render( |
||||
|
<StrictMode> |
||||
|
<BrowserRouter> |
||||
|
<App /> |
||||
|
</BrowserRouter> |
||||
|
</StrictMode>, |
||||
|
); |
||||
@ -0,0 +1,14 @@ |
|||||
|
import About from '@/components/About'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname, getTranslation } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function AboutPage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
const t = getTranslation(locale); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo title={t.nav.about} description={t.about.intro} locale={locale} /> |
||||
|
<About locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import Business from '@/components/Business'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname, getTranslation } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function BusinessPage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
const t = getTranslation(locale); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo title={t.nav.business} description={t.business.subtitle} locale={locale} /> |
||||
|
<Business locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import Contact from '@/components/Contact'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname, getTranslation } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function ContactPage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
const t = getTranslation(locale); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo title={t.contact.title} description={t.contact.subtitle} locale={locale} /> |
||||
|
<Contact locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import Home from '@/components/Home'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname, getTranslation } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function HomePage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
const t = getTranslation(locale); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo title={t.nav.home} description={t.home.sloganIntro} locale={locale} /> |
||||
|
<Home locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import Innovation from '@/components/Innovation'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname, getTranslation } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function InnovationPage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
const t = getTranslation(locale); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo title={t.nav.innovation} description={t.innovation.subtitle} locale={locale} /> |
||||
|
<Innovation locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
import Join from '@/components/Join'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname, getTranslation } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function JoinPage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
const t = getTranslation(locale); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo title={t.join.title} description={t.join.subtitle} locale={locale} /> |
||||
|
<Join locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
import NotFound from '@/components/NotFound'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function NotFoundPage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo |
||||
|
title={locale === 'zh' ? '页面未找到' : 'Page Not Found'} |
||||
|
description={locale === 'zh' ? '您访问的页面不存在' : 'The page you requested does not exist'} |
||||
|
locale={locale} |
||||
|
/> |
||||
|
<NotFound locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
import Privacy from '@/components/Privacy'; |
||||
|
import Seo from '@/seo/Seo'; |
||||
|
import { getLocaleFromPathname } from '@/i18n/utils'; |
||||
|
|
||||
|
export default function PrivacyPage() { |
||||
|
const locale = getLocaleFromPathname(); |
||||
|
return ( |
||||
|
<> |
||||
|
<Seo title={locale === 'zh' ? '隐私政策' : 'Privacy Policy'} locale={locale} /> |
||||
|
<Privacy locale={locale} /> |
||||
|
</> |
||||
|
); |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
import { useEffect } from 'react'; |
||||
|
import { getTranslation, type Locale } from '@/i18n/utils'; |
||||
|
|
||||
|
interface SeoProps { |
||||
|
title: string; |
||||
|
description?: string; |
||||
|
keywords?: string; |
||||
|
locale: Locale; |
||||
|
/** Per-page og:image override (default: /og-default.png) */ |
||||
|
ogImageOverride?: string; |
||||
|
} |
||||
|
|
||||
|
const DEFAULT_DESC_ZH = |
||||
|
'BaskBuddy 巴适伙伴(上海)科技有限公司 — 以温暖科技连接美好生活,四大赛道覆盖内容社交、职场服务、数字支付、人工智能'; |
||||
|
const DEFAULT_DESC_EN = |
||||
|
'BaskBuddy (Bashi Partners Shanghai) — Connecting beautiful life with warm technology. Four tracks: Content & Social, Career Services, Digital Payment, Artificial Intelligence'; |
||||
|
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'; |
||||
|
|
||||
|
function setMeta(uniqueAttr: string, attrs: Record<string, string>, content: string) { |
||||
|
const selector = `meta[${uniqueAttr}]`; |
||||
|
let el = document.head.querySelector(selector) as HTMLMetaElement | null; |
||||
|
if (!el) { |
||||
|
el = document.createElement('meta'); |
||||
|
for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v); |
||||
|
document.head.appendChild(el); |
||||
|
} |
||||
|
el.setAttribute('content', content); |
||||
|
} |
||||
|
|
||||
|
function setLink(uniqueAttr: string, attrs: Record<string, string>, href: string) { |
||||
|
const selector = `link[${uniqueAttr}]`; |
||||
|
let el = document.head.querySelector(selector) as HTMLLinkElement | null; |
||||
|
if (!el) { |
||||
|
el = document.createElement('link'); |
||||
|
for (const [k, v] of Object.entries(attrs)) el.setAttribute(k, v); |
||||
|
document.head.appendChild(el); |
||||
|
} |
||||
|
el.setAttribute('href', href); |
||||
|
} |
||||
|
|
||||
|
// Recreates the per-page <head> 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) { |
||||
|
useEffect(() => { |
||||
|
const t = getTranslation(locale); |
||||
|
const brandName = t.brand || 'BaskBuddy'; |
||||
|
const finalDescription = description ?? (locale === 'zh' ? DEFAULT_DESC_ZH : DEFAULT_DESC_EN); |
||||
|
const finalKeywords = keywords ?? (locale === 'zh' ? DEFAULT_KW_ZH : DEFAULT_KW_EN); |
||||
|
const orgName = |
||||
|
locale === 'zh' ? '巴适伙伴(上海)科技有限公司' : 'Bashi Partners (Shanghai) Technology Co., Ltd.'; |
||||
|
|
||||
|
const origin = window.location.origin; |
||||
|
const pathname = window.location.pathname; |
||||
|
const canonicalUrl = origin + pathname; |
||||
|
const zhAlternate = locale === 'zh' ? canonicalUrl : canonicalUrl.replace('/en', ''); |
||||
|
const enAlternate = locale === 'zh' |
||||
|
? zhAlternate.endsWith('/') |
||||
|
? `${zhAlternate}en` |
||||
|
: `${zhAlternate}/en` |
||||
|
: canonicalUrl; |
||||
|
|
||||
|
const logoPath = '/baskbuddy-logo.webp'; |
||||
|
const ogImagePath = ogImageOverride || '/og-default.png'; |
||||
|
const ogImage = origin + ogImagePath; |
||||
|
|
||||
|
// Document level
|
||||
|
document.documentElement.lang = locale === 'zh' ? 'zh' : 'en'; |
||||
|
document.title = `${title} | ${brandName}`; |
||||
|
|
||||
|
// Basic meta
|
||||
|
setMeta('name="description"', { name: 'description' }, finalDescription); |
||||
|
setMeta('name="keywords"', { name: 'keywords' }, finalKeywords); |
||||
|
setMeta('name="author"', { name: 'author' }, orgName); |
||||
|
|
||||
|
// Canonical + hreflang alternates
|
||||
|
setLink('rel="canonical"', { rel: 'canonical' }, canonicalUrl); |
||||
|
setLink('hreflang="zh-CN"', { rel: 'alternate', hreflang: 'zh-CN' }, zhAlternate); |
||||
|
setLink('hreflang="en-US"', { rel: 'alternate', hreflang: 'en-US' }, enAlternate); |
||||
|
setLink('hreflang="x-default"', { rel: 'alternate', hreflang: 'x-default' }, zhAlternate); |
||||
|
|
||||
|
// Open Graph
|
||||
|
setMeta('property="og:type"', { property: 'og:type' }, 'website'); |
||||
|
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 === 'zh' ? '巴适伙伴·一生相伴,舒适相伴·一生信赖' : 'Together for Life · Trust for a Lifetime'}`, |
||||
|
); |
||||
|
setMeta('property="og:locale"', { property: 'og:locale' }, locale === 'zh' ? 'zh_CN' : 'en_US'); |
||||
|
setMeta( |
||||
|
'property="og:locale:alternate"', |
||||
|
{ property: 'og:locale:alternate' }, |
||||
|
locale === 'zh' ? 'en_US' : 'zh_CN', |
||||
|
); |
||||
|
setMeta('property="og:site_name"', { property: 'og:site_name' }, brandName); |
||||
|
|
||||
|
// Twitter Card
|
||||
|
setMeta('name="twitter:card"', { name: 'twitter:card' }, 'summary_large_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 = { |
||||
|
'@context': 'https://schema.org', |
||||
|
'@type': 'Organization', |
||||
|
name: orgName, |
||||
|
alternateName: brandName, |
||||
|
url: origin + '/', |
||||
|
logo: origin + logoPath, |
||||
|
email: 'baskbuddy@163.com', |
||||
|
address: { |
||||
|
'@type': 'PostalAddress', |
||||
|
streetAddress: '康宁路288弄2号410室', |
||||
|
addressLocality: '上海市', |
||||
|
addressRegion: '静安区', |
||||
|
addressCountry: 'CN', |
||||
|
}, |
||||
|
sameAs: [], |
||||
|
}; |
||||
|
let script = document.getElementById('json-ld-org') as HTMLScriptElement | null; |
||||
|
if (!script) { |
||||
|
script = document.createElement('script'); |
||||
|
script.type = 'application/ld+json'; |
||||
|
script.id = 'json-ld-org'; |
||||
|
document.head.appendChild(script); |
||||
|
} |
||||
|
script.textContent = JSON.stringify(jsonLdOrg); |
||||
|
}, [title, description, keywords, locale, ogImageOverride]); |
||||
|
|
||||
|
return null; |
||||
|
} |
||||
File diff suppressed because it is too large
@ -0,0 +1 @@ |
|||||
|
/// <reference types="vite/client" />
|
||||
@ -0,0 +1,26 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"target": "ES2020", |
||||
|
"useDefineForClassFields": true, |
||||
|
"lib": ["ES2020", "DOM", "DOM.Iterable"], |
||||
|
"module": "ESNext", |
||||
|
"skipLibCheck": true, |
||||
|
"moduleResolution": "bundler", |
||||
|
"resolveJsonModule": true, |
||||
|
"isolatedModules": true, |
||||
|
"moduleDetection": "force", |
||||
|
"noEmit": true, |
||||
|
"jsx": "react-jsx", |
||||
|
"jsxImportSource": "react", |
||||
|
"strict": true, |
||||
|
"noUnusedLocals": true, |
||||
|
"noUnusedParameters": true, |
||||
|
"noFallthroughCasesInSwitch": true, |
||||
|
"baseUrl": ".", |
||||
|
"paths": { |
||||
|
"@/*": ["src/*"] |
||||
|
} |
||||
|
}, |
||||
|
"include": ["src"], |
||||
|
"references": [{ "path": "./tsconfig.node.json" }] |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
{ |
||||
|
"compilerOptions": { |
||||
|
"composite": true, |
||||
|
"skipLibCheck": true, |
||||
|
"module": "ESNext", |
||||
|
"moduleResolution": "bundler", |
||||
|
"allowSyntheticDefaultImports": true, |
||||
|
"strict": true, |
||||
|
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo" |
||||
|
}, |
||||
|
"include": ["vite.config.ts"] |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
import { defineConfig } from 'vite'; |
||||
|
import { fileURLToPath, URL } from 'node:url'; |
||||
|
import os from 'node:os'; |
||||
|
import path from 'node:path'; |
||||
|
import react from '@vitejs/plugin-react'; |
||||
|
import tailwindcss from '@tailwindcss/vite'; |
||||
|
|
||||
|
// Pure React (Vite + React Router + Tailwind v4) build config.
|
||||
|
export default defineConfig({ |
||||
|
// Keep Vite's dep-optimization cache out of node_modules so the sandbox
|
||||
|
// safe-delete guard never blocks a bulk clear of node_modules/.vite/deps.
|
||||
|
cacheDir: path.join(os.tmpdir(), 'bashi-vite-cache'), |
||||
|
plugins: [react(), tailwindcss()], |
||||
|
resolve: { |
||||
|
alias: { |
||||
|
'@': fileURLToPath(new URL('./src', import.meta.url)), |
||||
|
}, |
||||
|
}, |
||||
|
server: { |
||||
|
port: 4321, |
||||
|
host: true, |
||||
|
}, |
||||
|
preview: { |
||||
|
port: 4321, |
||||
|
host: true, |
||||
|
}, |
||||
|
}); |
||||
Loading…
Reference in new issue