feat(portal): Genex 门户官网 — 20 页 Next.js SSG 站点

新增 frontend/portal/ 独立 Next.js 15 应用,作为 gogenex.com 品牌官网。

页面清单 (20 pages):
- 首页 (/) — Hero + 数据看板 + 三大优势 + 产品展示 + 信任背书 + CTA
- 关于我们 (/about) — 愿景 + 团队 + 里程碑时间线 (2025 Q1 起)
- 产品功能 (/features) — 购券流程 + 交易 + 清算 + 多端支持
- 解决方案 (/solutions) — 消费者/商户场景 + 用例故事
- 开发者 (/developers) — API 文档入口 + SDK + 区块链浏览器
- 联系我们 (/contact) — 联系表单 + 地址 + 社交
- 下载 (/download) — iOS/Android/Web/小程序 四端下载
- 职位招聘 (/careers) — 6 个岗位卡片
- 博客 (/blog) — 6 篇文章摘要
- 新闻稿 (/press) — 3 篇新闻发布
- 帮助中心 (/help) — 6 大帮助主题
- 社区 (/community) — 6 个社交渠道卡片
- 系统状态 (/status) — 6 项服务运行状态
- 服务条款 (/terms) — 完整法律条款
- 隐私政策 (/privacy) — 完整隐私声明
- Cookie 政策 (/cookie-policy)
- 风险披露 (/risk)

技术栈:
- Next.js 15 + React 18 + TypeScript + CSS Modules
- Framer Motion 滚动动画
- 复用 admin-web design-tokens.css 变量体系
- 完整 i18n (zh-CN / en-US, 800+ 翻译键)
- Docker 多阶段构建 + Nginx 反向代理配置
- SSG 静态生成,SEO 友好

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
hailin 2026-03-03 05:03:53 -08:00
parent 8af65a3a48
commit bd6ecaa0fd
49 changed files with 5123 additions and 0 deletions

4
frontend/portal/.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
node_modules/
.next/
out/
.env*.local

View File

@ -0,0 +1,47 @@
# 阶段1: 依赖安装
FROM node:20-alpine AS deps
RUN apk add --no-cache libc6-compat
WORKDIR /app
COPY package.json ./
COPY package-lock.json* ./
RUN npm config set registry https://registry.npmmirror.com && \
if [ -f package-lock.json ]; then npm ci; else npm install; fi
# 阶段2: 构建
FROM node:20-alpine AS builder
WORKDIR /app
COPY --from=deps /app/node_modules ./node_modules
COPY . .
ENV NEXT_TELEMETRY_DISABLED=1
ENV NODE_ENV=production
RUN npm run build
# 阶段3: 生产运行
FROM node:20-alpine AS runner
WORKDIR /app
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
RUN apk add --no-cache curl
RUN addgroup --system --gid 1001 nodejs
RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
USER nextjs
EXPOSE 3000
ENV PORT=3000
ENV HOSTNAME="0.0.0.0"
CMD ["node", "server.js"]

249
frontend/portal/deploy.sh Normal file
View File

@ -0,0 +1,249 @@
#!/bin/bash
# =============================================================================
# Genex Portal - 部署管理脚本
# =============================================================================
#
# Docker 命令:
# ./deploy.sh build 仅构建 Docker 镜像
# ./deploy.sh start 构建并启动服务 (默认)
# ./deploy.sh stop 停止服务
# ./deploy.sh restart 重启服务
# ./deploy.sh logs 查看服务日志
# ./deploy.sh status 查看服务状态
# ./deploy.sh clean 清理容器和镜像
#
# Nginx + SSL 命令 (需要 root 权限):
# sudo ./deploy.sh nginx install [domain] [email] 安装 Nginx + SSL 证书
# sudo ./deploy.sh nginx ssl [domain] [email] 仅申请/续期 SSL 证书
# ./deploy.sh nginx status 查看 Nginx 状态
# ./deploy.sh nginx reload 重载 Nginx 配置
#
# 默认域名: gogenex.com
# 默认邮箱: admin@gogenex.com
#
# =============================================================================
set -e
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m'
PROJECT_NAME="genex-portal"
IMAGE_NAME="genex-portal"
CONTAINER_NAME="genex-portal"
DEFAULT_PORT=3001
DEFAULT_DOMAIN="gogenex.com"
DEFAULT_EMAIL="admin@gogenex.com"
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
log_success() { echo -e "${GREEN}[SUCCESS]${NC} $1"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
check_docker() {
if ! command -v docker &> /dev/null; then
log_error "Docker 未安装"
exit 1
fi
if ! docker info &> /dev/null; then
log_error "Docker 服务未运行"
exit 1
fi
}
check_docker_compose() {
if docker compose version &> /dev/null; then
COMPOSE_CMD="docker compose"
elif command -v docker-compose &> /dev/null; then
COMPOSE_CMD="docker-compose"
else
log_error "Docker Compose 未安装"
exit 1
fi
}
build() {
log_info "构建 Docker 镜像..."
$COMPOSE_CMD build --no-cache
log_success "镜像构建完成"
}
start() {
log_info "部署 Genex Portal..."
PORT=${PORT:-$DEFAULT_PORT}
if lsof -Pi :$PORT -sTCP:LISTEN -t >/dev/null 2>&1; then
log_warn "端口 $PORT 已被占用,停止旧服务..."
stop
fi
$COMPOSE_CMD up -d --build
log_info "等待服务启动..."
sleep 5
if docker ps | grep -q $CONTAINER_NAME; then
log_success "服务部署成功! http://localhost:$PORT"
else
log_error "服务启动失败,查看日志: ./deploy.sh logs"
exit 1
fi
}
stop() {
log_info "停止服务..."
$COMPOSE_CMD down
log_success "服务已停止"
}
restart() { stop; start; }
logs() { $COMPOSE_CMD logs -f; }
clean() {
log_info "清理容器和镜像..."
$COMPOSE_CMD down --rmi local --volumes --remove-orphans
docker image prune -f
log_success "清理完成"
}
status() {
log_info "服务状态:"
docker ps -a --filter "name=$CONTAINER_NAME" --format "table {{.Names}}\t{{.Status}}\t{{.Ports}}"
}
cmd_nginx_install() {
local domain="${1:-$DEFAULT_DOMAIN}"
local email="${2:-$DEFAULT_EMAIL}"
local conf_file="$SCRIPT_DIR/nginx/${domain}.conf"
log_info "$domain 安装 Nginx + SSL..."
if [ ! -f "$conf_file" ]; then
log_error "配置文件不存在: $conf_file"
exit 1
fi
if [ "$EUID" -ne 0 ]; then
log_error "需要 root 权限: sudo ./deploy.sh nginx install $domain $email"
exit 1
fi
# 安装依赖
log_info "[1/5] 检查依赖..."
command -v nginx &> /dev/null || { apt update && apt install -y nginx; systemctl enable nginx; systemctl start nginx; }
command -v certbot &> /dev/null || apt install -y certbot python3-certbot-nginx
# HTTP 临时配置
log_info "[2/5] 部署 HTTP 临时配置..."
mkdir -p /var/www/certbot
cat > /etc/nginx/sites-available/$domain << HTTPEOF
server {
listen 80;
listen [::]:80;
server_name $domain www.$domain;
location /.well-known/acme-challenge/ { root /var/www/certbot; }
location / {
proxy_pass http://127.0.0.1:${DEFAULT_PORT};
proxy_set_header Host \$host;
proxy_set_header X-Real-IP \$remote_addr;
}
}
HTTPEOF
ln -sf /etc/nginx/sites-available/$domain /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
# SSL 证书
log_info "[3/5] 申请 SSL 证书..."
if [ -d "/etc/letsencrypt/live/$domain" ]; then
log_warn "证书已存在,跳过"
else
certbot certonly --webroot --webroot-path=/var/www/certbot \
--email $email --agree-tos --no-eff-email \
-d $domain -d www.$domain
log_success "SSL 证书申请成功!"
fi
# HTTPS 完整配置
log_info "[4/5] 部署 HTTPS 配置..."
cp "$conf_file" /etc/nginx/sites-available/$domain
nginx -t && systemctl reload nginx
# 自动续期
log_info "[5/5] 配置自动续期..."
if [ ! -f /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh ]; then
mkdir -p /etc/letsencrypt/renewal-hooks/deploy
cat > /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh << 'HOOKEOF'
#!/bin/bash
systemctl reload nginx
HOOKEOF
chmod +x /etc/letsencrypt/renewal-hooks/deploy/reload-nginx.sh
fi
log_success "完成! https://$domain"
}
cmd_nginx_ssl() {
local domain="${1:-$DEFAULT_DOMAIN}"
local email="${2:-$DEFAULT_EMAIL}"
[ "$EUID" -ne 0 ] && { log_error "需要 root 权限"; exit 1; }
mkdir -p /var/www/certbot
if [ -d "/etc/letsencrypt/live/$domain" ]; then
certbot renew --cert-name $domain
else
certbot certonly --webroot --webroot-path=/var/www/certbot \
--email $email --agree-tos --no-eff-email \
-d $domain -d www.$domain
fi
log_success "SSL 证书完成"
}
cmd_nginx_status() {
systemctl is-active nginx &>/dev/null && echo -e "Nginx: ${GREEN}运行中${NC}" || echo -e "Nginx: ${RED}未运行${NC}"
command -v certbot &>/dev/null && certbot certificates 2>/dev/null | grep -E "(Certificate Name|Expiry Date|Domains)" | sed 's/^/ /'
}
cmd_nginx_reload() {
[ "$EUID" -ne 0 ] && { log_error "需要 root 权限"; exit 1; }
nginx -t && systemctl reload nginx
log_success "Nginx 已重载"
}
show_help() {
echo ""
echo "Genex Portal 部署脚本"
echo ""
echo "Docker: build | start | stop | restart | logs | status | clean"
echo "Nginx: nginx install [domain] [email] | nginx ssl | nginx status | nginx reload"
echo ""
echo "默认域名: $DEFAULT_DOMAIN 端口: $DEFAULT_PORT"
echo ""
}
main() {
cd "$SCRIPT_DIR"
case "${1:-start}" in
build) check_docker; check_docker_compose; build ;;
start) check_docker; check_docker_compose; start ;;
stop) check_docker; check_docker_compose; stop ;;
restart) check_docker; check_docker_compose; restart ;;
logs) check_docker; check_docker_compose; logs ;;
status) check_docker; status ;;
clean) check_docker; check_docker_compose; clean ;;
nginx)
case "${2:-install}" in
install) cmd_nginx_install "$3" "$4" ;;
ssl) cmd_nginx_ssl "$3" "$4" ;;
status) cmd_nginx_status ;;
reload) cmd_nginx_reload ;;
*) log_error "未知命令: $2"; exit 1 ;;
esac
;;
help|--help|-h) show_help ;;
*) log_error "未知命令: $1"; show_help; exit 1 ;;
esac
}
main "$@"

View File

@ -0,0 +1,26 @@
services:
portal:
build:
context: .
dockerfile: Dockerfile
image: genex-portal:latest
container_name: genex-portal
restart: unless-stopped
ports:
- "${PORT:-4080}:3000"
environment:
- TZ=Asia/Shanghai
- NODE_ENV=production
- NEXT_TELEMETRY_DISABLED=1
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000"]
interval: 30s
timeout: 3s
retries: 3
start_period: 40s
networks:
- genex-network
networks:
genex-network:
driver: bridge

5
frontend/portal/next-env.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.

View File

@ -0,0 +1,7 @@
import type { NextConfig } from 'next';
const nextConfig: NextConfig = {
output: 'standalone',
};
export default nextConfig;

View File

@ -0,0 +1,95 @@
# =============================================================================
# Genex Portal - gogenex.com / www.gogenex.com
# 官网门户 (Next.js SSG)
# 部署端口: 3001 (Docker 容器内部 3000)
# =============================================================================
# HTTP → HTTPS 重定向
server {
listen 80;
listen [::]:80;
server_name gogenex.com www.gogenex.com;
# Let's Encrypt ACME 验证
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# www → 裸域重定向
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name www.gogenex.com;
ssl_certificate /etc/letsencrypt/live/gogenex.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gogenex.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
return 301 https://gogenex.com$request_uri;
}
# 主站 HTTPS
server {
listen 443 ssl http2;
listen [::]:443 ssl http2;
server_name gogenex.com;
# ---- SSL ----
ssl_certificate /etc/letsencrypt/live/gogenex.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/gogenex.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
# ---- 安全头 ----
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
# ---- Gzip ----
gzip on;
gzip_vary on;
gzip_proxied any;
gzip_comp_level 6;
gzip_types text/plain text/css text/xml application/json application/javascript application/xml+rss image/svg+xml;
# ---- 日志 ----
access_log /var/log/nginx/gogenex.com.access.log;
error_log /var/log/nginx/gogenex.com.error.log;
# ---- Next.js 静态资源 (长期缓存) ----
location /_next/static/ {
proxy_pass http://127.0.0.1:4080;
proxy_cache_valid 200 365d;
add_header Cache-Control "public, max-age=31536000, immutable";
}
# ---- 反向代理到 Docker ----
location / {
proxy_pass http://127.0.0.1:4080;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# ---- 健康检查 ----
location = /nginx-health {
access_log off;
return 200 '{"status":"ok","service":"genex-portal-nginx"}';
add_header Content-Type application/json;
}
}

1021
frontend/portal/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,23 @@
{
"name": "genex-portal",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"react": "^18.3.1",
"react-dom": "^18.3.1",
"next": "15.1.11",
"framer-motion": "^11.0.0"
},
"devDependencies": {
"typescript": "^5.9.3",
"@types/react": "^18.3.0",
"@types/react-dom": "^18.3.0",
"@types/node": "^20.0.0"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 835 B

View File

@ -0,0 +1,30 @@
<svg viewBox="0 0 200 72" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="L" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#6C5CE7"/>
<stop offset="100%" stop-color="#7B6DEE"/>
</linearGradient>
<linearGradient id="R" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#9B8FFF"/>
<stop offset="100%" stop-color="#B8ADFF"/>
</linearGradient>
<linearGradient id="EX" x1="0%" y1="0%" x2="100%" y2="0%">
<stop offset="0%" stop-color="#9B8FFF"/>
<stop offset="100%" stop-color="#B8ADFF"/>
</linearGradient>
<mask id="notch">
<rect x="-36" y="-36" width="72" height="72" fill="white"/>
<circle cx="0" cy="-28" r="4" fill="black"/>
<circle cx="0" cy="28" r="4" fill="black"/>
</mask>
</defs>
<g transform="translate(36,36)">
<g transform="rotate(45) scale(0.7)" mask="url(#notch)">
<path d="M-28 -28 L-0.05 -28 L-6 -2 L0 2 L-0.05 28 L-28 28 Z" fill="url(#L)"/>
<path d="M0.05 -28 L28 -28 L28 28 L0.05 28 L6 2 L0 -2 Z" fill="url(#R)"/>
</g>
</g>
<text x="78" y="41" font-family="Sora, sans-serif" font-weight="800" font-size="20" letter-spacing="-0.3">
<tspan fill="#1A103A">GEN</tspan><tspan fill="url(#EX)">EX</tspan>
</text>
</svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@ -0,0 +1,23 @@
<svg viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="L" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#6C5CE7"/>
<stop offset="100%" stop-color="#7B6DEE"/>
</linearGradient>
<linearGradient id="R" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stop-color="#9B8FFF"/>
<stop offset="100%" stop-color="#B8ADFF"/>
</linearGradient>
<mask id="notch">
<rect x="-36" y="-36" width="72" height="72" fill="white"/>
<circle cx="0" cy="-28" r="4" fill="black"/>
<circle cx="0" cy="28" r="4" fill="black"/>
</mask>
</defs>
<g transform="translate(50,50)">
<g transform="rotate(45)" mask="url(#notch)">
<path d="M-28 -28 L-0.05 -28 L-6 -2 L0 2 L-0.05 28 L-28 28 Z" fill="url(#L)"/>
<path d="M0.05 -28 L28 -28 L28 28 L0.05 28 L6 2 L0 -2 Z" fill="url(#R)"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 920 B

View File

@ -0,0 +1,159 @@
/* ---- About Hero ---- */
.hero {
padding: 160px 24px 80px;
text-align: center;
background: linear-gradient(180deg, var(--color-primary-surface) 0%, var(--color-surface) 100%);
}
.title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
}
.subtitle {
font-size: 20px;
color: var(--color-text-secondary);
}
/* ---- Vision / Mission ---- */
.visionSection {
padding: 80px 24px;
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 48px;
}
.visionCard {
background: var(--color-gray-50);
border-radius: var(--radius-lg);
padding: 48px 40px;
}
.visionTitle {
font-size: 24px;
font-weight: 600;
margin-bottom: 16px;
color: var(--color-primary);
}
.visionDesc {
font-size: 16px;
color: var(--color-text-secondary);
line-height: 1.8;
}
/* ---- Values ---- */
.values {
padding: 80px 24px;
background: var(--color-gray-50);
}
.valuesTitle {
font-size: 36px;
font-weight: 700;
text-align: center;
margin-bottom: 48px;
}
.valuesGrid {
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
}
.valueCard {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 32px 24px;
text-align: center;
border: 1px solid var(--color-border-light);
}
.valueIcon {
font-size: 40px;
margin-bottom: 16px;
}
.valueName {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.valueDesc {
font-size: 14px;
color: var(--color-text-secondary);
line-height: 1.6;
}
/* ---- Timeline ---- */
.timeline {
padding: 80px 24px;
}
.timelineTitle {
font-size: 36px;
font-weight: 700;
text-align: center;
margin-bottom: 64px;
}
.timelineTrack {
max-width: 700px;
margin: 0 auto;
position: relative;
padding-left: 40px;
}
.timelineTrack::before {
content: '';
position: absolute;
left: 11px;
top: 0;
bottom: 0;
width: 2px;
background: var(--color-primary-container);
}
.timelineItem {
position: relative;
padding-bottom: 40px;
}
.timelineItem:last-child {
padding-bottom: 0;
}
.timelineDot {
position: absolute;
left: -33px;
top: 4px;
width: 14px;
height: 14px;
border-radius: 50%;
background: var(--color-primary);
border: 3px solid var(--color-primary-surface);
}
.timelineDate {
font-size: 14px;
font-weight: 600;
color: var(--color-primary);
margin-bottom: 4px;
}
.timelineText {
font-size: 16px;
color: var(--color-text-secondary);
}
@media (max-width: 768px) {
.title { font-size: 32px; }
.visionSection { grid-template-columns: 1fr; }
.valuesGrid { grid-template-columns: 1fr 1fr; }
}

View File

@ -0,0 +1,82 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from './page.module.css';
export default function AboutPage() {
const t = useT();
const values = [
{ icon: '\u{1F465}', nameKey: 'about_value_1', descKey: 'about_value_1_desc' },
{ icon: '\u{1F50D}', nameKey: 'about_value_2', descKey: 'about_value_2_desc' },
{ icon: '\u{1F680}', nameKey: 'about_value_3', descKey: 'about_value_3_desc' },
{ icon: '\u{1F3DB}', nameKey: 'about_value_4', descKey: 'about_value_4_desc' },
];
const milestones = [
{ date: '2025 Q1', key: 'about_milestone_2025_q1' },
{ date: '2025 Q2', key: 'about_milestone_2025_q2' },
{ date: '2025 Q3', key: 'about_milestone_2025_q3' },
{ date: '2025 Q4', key: 'about_milestone_2025_q4' },
{ date: '2026 Q1', key: 'about_milestone_2026_q1' },
{ date: '2026 Q2', key: 'about_milestone_2026_q2' },
];
return (
<>
<section className={s.hero}>
<AnimateOnScroll direction="none">
<h1 className={s.title}>{t('about_title')}</h1>
<p className={s.subtitle}>{t('about_subtitle')}</p>
</AnimateOnScroll>
</section>
<div className={s.visionSection}>
<AnimateOnScroll direction="left">
<div className={s.visionCard}>
<h2 className={s.visionTitle}>{t('about_vision_title')}</h2>
<p className={s.visionDesc}>{t('about_vision_desc')}</p>
</div>
</AnimateOnScroll>
<AnimateOnScroll direction="right">
<div className={s.visionCard}>
<h2 className={s.visionTitle}>{t('about_mission_title')}</h2>
<p className={s.visionDesc}>{t('about_mission_desc')}</p>
</div>
</AnimateOnScroll>
</div>
<section className={s.values}>
<h2 className={s.valuesTitle}>{t('about_value_title')}</h2>
<div className={s.valuesGrid}>
{values.map((v, i) => (
<AnimateOnScroll key={v.nameKey} delay={i * 0.1}>
<div className={s.valueCard}>
<div className={s.valueIcon}>{v.icon}</div>
<h3 className={s.valueName}>{t(v.nameKey)}</h3>
<p className={s.valueDesc}>{t(v.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</section>
<section className={s.timeline}>
<h2 className={s.timelineTitle}>{t('about_milestone_title')}</h2>
<div className={s.timelineTrack}>
{milestones.map((m, i) => (
<AnimateOnScroll key={m.key} delay={i * 0.1} direction="left">
<div className={s.timelineItem}>
<div className={s.timelineDot} />
<div className={s.timelineDate}>{m.date}</div>
<div className={s.timelineText}>{t(m.key)}</div>
</div>
</AnimateOnScroll>
))}
</div>
</section>
</>
);
}

View File

@ -0,0 +1,40 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from '../content.module.css';
export default function BlogPage() {
const t = useT();
const posts = [
{ icon: '\u{1F680}', dateKey: 'blog_p1_date', titleKey: 'blog_p1_title', excerptKey: 'blog_p1_excerpt' },
{ icon: '\u{26D3}', dateKey: 'blog_p2_date', titleKey: 'blog_p2_title', excerptKey: 'blog_p2_excerpt' },
{ icon: '\u{1F4B0}', dateKey: 'blog_p3_date', titleKey: 'blog_p3_title', excerptKey: 'blog_p3_excerpt' },
{ icon: '\u{2B50}', dateKey: 'blog_p4_date', titleKey: 'blog_p4_title', excerptKey: 'blog_p4_excerpt' },
{ icon: '\u{1F916}', dateKey: 'blog_p5_date', titleKey: 'blog_p5_title', excerptKey: 'blog_p5_excerpt' },
{ icon: '\u{1F30F}', dateKey: 'blog_p6_date', titleKey: 'blog_p6_title', excerptKey: 'blog_p6_excerpt' },
];
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('blog_title')}</h1>
<p className={s.subtitle}>{t('blog_subtitle')}</p>
</section>
<div className={s.grid}>
{posts.map((p, i) => (
<AnimateOnScroll key={p.titleKey} delay={i * 0.1}>
<div className={s.blogCard}>
<div className={s.blogThumb}>{p.icon}</div>
<div className={s.blogBody}>
<div className={s.blogDate}>{t(p.dateKey)}</div>
<h3 className={s.blogTitle}>{t(p.titleKey)}</h3>
<p className={s.blogExcerpt}>{t(p.excerptKey)}</p>
</div>
</div>
</AnimateOnScroll>
))}
</div>
</>
);
}

View File

@ -0,0 +1,38 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from '../content.module.css';
export default function CareersPage() {
const t = useT();
const jobs = [
{ icon: '\u{1F4BB}', titleKey: 'careers_job1_title', descKey: 'careers_job1_desc' },
{ icon: '\u{26D3}', titleKey: 'careers_job2_title', descKey: 'careers_job2_desc' },
{ icon: '\u{1F4F1}', titleKey: 'careers_job3_title', descKey: 'careers_job3_desc' },
{ icon: '\u{1F6E1}', titleKey: 'careers_job4_title', descKey: 'careers_job4_desc' },
{ icon: '\u{1F4CA}', titleKey: 'careers_job5_title', descKey: 'careers_job5_desc' },
{ icon: '\u{1F3A8}', titleKey: 'careers_job6_title', descKey: 'careers_job6_desc' },
];
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('careers_title')}</h1>
<p className={s.subtitle}>{t('careers_subtitle')}</p>
</section>
<div className={s.grid}>
{jobs.map((j, i) => (
<AnimateOnScroll key={j.titleKey} delay={i * 0.1}>
<div className={s.card}>
<div className={s.cardIcon}>{j.icon}</div>
<h3 className={s.cardTitle}>{t(j.titleKey)}</h3>
<p className={s.cardDesc}>{t(j.descKey)}</p>
<a href="mailto:hr@gogenex.com" className={s.cardLink}>{t('careers_apply')} &rarr;</a>
</div>
</AnimateOnScroll>
))}
</div>
</>
);
}

View File

@ -0,0 +1,37 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from '../content.module.css';
export default function CommunityPage() {
const t = useT();
const channels = [
{ icon: '\u{1F4AC}', titleKey: 'community_c1_title', descKey: 'community_c1_desc', link: '#' },
{ icon: '\u{1F426}', titleKey: 'community_c2_title', descKey: 'community_c2_desc', link: '#' },
{ icon: '\u{1F4E2}', titleKey: 'community_c3_title', descKey: 'community_c3_desc', link: '#' },
{ icon: '\u{1F310}', titleKey: 'community_c4_title', descKey: 'community_c4_desc', link: '#' },
{ icon: '\u{1F4F1}', titleKey: 'community_c5_title', descKey: 'community_c5_desc', link: '#' },
{ icon: '\u{1F3AE}', titleKey: 'community_c6_title', descKey: 'community_c6_desc', link: '#' },
];
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('community_title')}</h1>
<p className={s.subtitle}>{t('community_subtitle')}</p>
</section>
<div className={s.grid}>
{channels.map((ch, i) => (
<AnimateOnScroll key={ch.titleKey} delay={i * 0.1}>
<div className={s.card}>
<div className={s.cardIcon}>{ch.icon}</div>
<h3 className={s.cardTitle}>{t(ch.titleKey)}</h3>
<p className={s.cardDesc}>{t(ch.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</>
);
}

View File

@ -0,0 +1,137 @@
.hero {
padding: 160px 24px 80px;
text-align: center;
background: linear-gradient(180deg, var(--color-primary-surface) 0%, var(--color-surface) 100%);
}
.title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
}
.subtitle {
font-size: 20px;
color: var(--color-text-secondary);
}
.content {
max-width: var(--max-width);
margin: 0 auto;
padding: 80px 24px;
display: grid;
grid-template-columns: 1.2fr 0.8fr;
gap: 64px;
}
/* ---- Form ---- */
.form {
display: flex;
flex-direction: column;
gap: 20px;
}
.label {
font-size: 14px;
font-weight: 500;
color: var(--color-text-secondary);
margin-bottom: 6px;
display: block;
}
.input {
width: 100%;
padding: 12px 16px;
font-size: 15px;
font-family: var(--font-family);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
outline: none;
transition: border-color 0.2s;
}
.input:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-primary-surface);
}
.textarea {
width: 100%;
padding: 12px 16px;
font-size: 15px;
font-family: var(--font-family);
border: 1px solid var(--color-border);
border-radius: var(--radius-sm);
outline: none;
min-height: 140px;
resize: vertical;
transition: border-color 0.2s;
}
.textarea:focus {
border-color: var(--color-primary);
box-shadow: 0 0 0 3px var(--color-primary-surface);
}
.submitBtn {
background: var(--color-primary);
color: #fff;
border: none;
border-radius: var(--radius-sm);
padding: 14px 32px;
font-size: 16px;
font-weight: 600;
transition: all 0.2s;
align-self: flex-start;
}
.submitBtn:hover {
background: var(--color-primary-dark);
box-shadow: var(--shadow-primary);
}
.successMsg {
font-size: 15px;
color: var(--color-success);
font-weight: 500;
}
/* ---- Info ---- */
.info {
display: flex;
flex-direction: column;
gap: 40px;
}
.infoCard {
display: flex;
gap: 16px;
}
.infoIcon {
width: 48px;
height: 48px;
border-radius: 14px;
background: var(--color-primary-surface);
display: flex;
align-items: center;
justify-content: center;
font-size: 22px;
flex-shrink: 0;
}
.infoTitle {
font-size: 16px;
font-weight: 600;
margin-bottom: 4px;
}
.infoText {
font-size: 15px;
color: var(--color-text-secondary);
}
@media (max-width: 768px) {
.title { font-size: 32px; }
.content { grid-template-columns: 1fr; gap: 48px; }
}

View File

@ -0,0 +1,83 @@
'use client';
import React, { useState, FormEvent } from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from './page.module.css';
export default function ContactPage() {
const t = useT();
const [submitted, setSubmitted] = useState(false);
const handleSubmit = (e: FormEvent) => {
e.preventDefault();
setSubmitted(true);
};
return (
<>
<section className={s.hero}>
<AnimateOnScroll direction="none">
<h1 className={s.title}>{t('contact_title')}</h1>
<p className={s.subtitle}>{t('contact_subtitle')}</p>
</AnimateOnScroll>
</section>
<div className={s.content}>
<AnimateOnScroll direction="left">
{submitted ? (
<p className={s.successMsg}>{t('contact_form_success')}</p>
) : (
<form className={s.form} onSubmit={handleSubmit}>
<div>
<label className={s.label}>{t('contact_form_name')}</label>
<input className={s.input} type="text" required />
</div>
<div>
<label className={s.label}>{t('contact_form_email')}</label>
<input className={s.input} type="email" required />
</div>
<div>
<label className={s.label}>{t('contact_form_subject')}</label>
<input className={s.input} type="text" required />
</div>
<div>
<label className={s.label}>{t('contact_form_message')}</label>
<textarea className={s.textarea} required />
</div>
<button type="submit" className={s.submitBtn}>
{t('contact_form_submit')}
</button>
</form>
)}
</AnimateOnScroll>
<AnimateOnScroll direction="right">
<div className={s.info}>
<div className={s.infoCard}>
<div className={s.infoIcon}>{'\u{1F3E2}'}</div>
<div>
<div className={s.infoTitle}>{t('contact_office_title')}</div>
<div className={s.infoText}>{t('contact_office_addr')}</div>
</div>
</div>
<div className={s.infoCard}>
<div className={s.infoIcon}>{'\u{2709}'}</div>
<div>
<div className={s.infoTitle}>{t('contact_email_title')}</div>
<div className={s.infoText}>{t('contact_email_addr')}</div>
</div>
</div>
<div className={s.infoCard}>
<div className={s.infoIcon}>{'\u{1F310}'}</div>
<div>
<div className={s.infoTitle}>{t('contact_social_title')}</div>
<div className={s.infoText}>WeChat / Twitter / Telegram</div>
</div>
</div>
</div>
</AnimateOnScroll>
</div>
</>
);
}

View File

@ -0,0 +1,193 @@
/* Shared styles for content/legal pages */
.hero {
padding: 160px 24px 60px;
text-align: center;
background: linear-gradient(180deg, var(--color-primary-surface) 0%, var(--color-surface) 100%);
}
.title {
font-size: 42px;
font-weight: 700;
margin-bottom: 12px;
}
.subtitle {
font-size: 18px;
color: var(--color-text-secondary);
max-width: 600px;
margin: 0 auto;
}
.body {
max-width: 800px;
margin: 0 auto;
padding: 60px 24px 100px;
}
.body h2 {
font-size: 24px;
font-weight: 600;
margin: 40px 0 16px;
color: var(--color-text-primary);
}
.body h3 {
font-size: 18px;
font-weight: 600;
margin: 28px 0 12px;
color: var(--color-text-primary);
}
.body p {
font-size: 15px;
line-height: 1.8;
color: var(--color-text-secondary);
margin-bottom: 16px;
}
.body ul {
padding-left: 24px;
margin-bottom: 16px;
}
.body li {
font-size: 15px;
line-height: 1.8;
color: var(--color-text-secondary);
margin-bottom: 8px;
}
.updated {
font-size: 14px;
color: var(--color-text-tertiary);
margin-bottom: 40px;
}
/* ---- Grid layout for pages like careers, help, status ---- */
.grid {
max-width: var(--max-width);
margin: 0 auto;
padding: 80px 24px;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 28px;
}
.card {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 36px 28px;
border: 1px solid var(--color-border-light);
transition: all 0.3s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.cardIcon {
font-size: 36px;
margin-bottom: 16px;
}
.cardTitle {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
}
.cardDesc {
font-size: 14px;
color: var(--color-text-secondary);
line-height: 1.7;
}
.cardLink {
display: inline-block;
margin-top: 16px;
font-size: 14px;
font-weight: 600;
color: var(--color-primary);
}
/* ---- Status indicator ---- */
.statusDot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--color-success);
margin-right: 8px;
}
.statusRow {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 0;
border-bottom: 1px solid var(--color-border-light);
font-size: 15px;
}
.statusLabel {
display: flex;
align-items: center;
gap: 8px;
}
.statusBadge {
font-size: 13px;
font-weight: 500;
color: var(--color-success);
}
/* ---- Blog card ---- */
.blogCard {
background: var(--color-surface);
border-radius: var(--radius-lg);
overflow: hidden;
border: 1px solid var(--color-border-light);
transition: all 0.3s;
}
.blogCard:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.blogThumb {
height: 180px;
background: linear-gradient(135deg, var(--color-primary-surface), var(--color-primary-container));
display: flex;
align-items: center;
justify-content: center;
font-size: 48px;
}
.blogBody {
padding: 24px;
}
.blogDate {
font-size: 13px;
color: var(--color-text-tertiary);
margin-bottom: 8px;
}
.blogTitle {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
}
.blogExcerpt {
font-size: 14px;
color: var(--color-text-secondary);
line-height: 1.6;
}
@media (max-width: 768px) {
.title { font-size: 28px; }
.grid { grid-template-columns: 1fr; max-width: 480px; }
}

View File

@ -0,0 +1,31 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import s from '../content.module.css';
export default function CookiePolicyPage() {
const t = useT();
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('cookie_title')}</h1>
</section>
<div className={s.body}>
<p className={s.updated}>{t('cookie_updated')}</p>
<h2>{t('cookie_s1_title')}</h2>
<p>{t('cookie_s1_p1')}</p>
<h2>{t('cookie_s2_title')}</h2>
<p>{t('cookie_s2_p1')}</p>
<h3>{t('cookie_s2_h3_1')}</h3>
<p>{t('cookie_s2_p2')}</p>
<h3>{t('cookie_s2_h3_2')}</h3>
<p>{t('cookie_s2_p3')}</p>
<h3>{t('cookie_s2_h3_3')}</h3>
<p>{t('cookie_s2_p4')}</p>
<h2>{t('cookie_s3_title')}</h2>
<p>{t('cookie_s3_p1')}</p>
</div>
</>
);
}

View File

@ -0,0 +1,122 @@
.hero {
padding: 160px 24px 80px;
text-align: center;
background: linear-gradient(135deg, var(--color-gray-900), #2d1b69);
color: #fff;
}
.title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
}
.subtitle {
font-size: 20px;
color: rgba(255, 255, 255, 0.6);
max-width: 600px;
margin: 0 auto;
}
.grid {
max-width: var(--max-width);
margin: 0 auto;
padding: 80px 24px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 28px;
}
.card {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 40px 36px;
border: 1px solid var(--color-border-light);
transition: all 0.3s;
}
.card:hover {
border-color: var(--color-primary-container);
box-shadow: var(--shadow-lg);
}
.cardIcon {
font-size: 36px;
margin-bottom: 20px;
}
.cardTitle {
font-size: 22px;
font-weight: 600;
margin-bottom: 10px;
}
.cardDesc {
font-size: 15px;
color: var(--color-text-secondary);
line-height: 1.7;
margin-bottom: 20px;
}
.cardLink {
display: inline-block;
font-size: 14px;
font-weight: 600;
color: var(--color-primary);
transition: color 0.2s;
}
.cardLink:hover {
color: var(--color-primary-dark);
}
/* ---- Quick Start ---- */
.quickstart {
padding: 80px 24px;
background: var(--color-gray-50);
}
.qsTitle {
font-size: 32px;
font-weight: 700;
text-align: center;
margin-bottom: 40px;
}
.codeBlock {
max-width: 700px;
margin: 0 auto;
background: var(--color-gray-900);
border-radius: var(--radius-md);
padding: 32px;
overflow-x: auto;
}
.codeBlock pre {
font-family: var(--font-family-mono);
font-size: 14px;
color: #e0e0e0;
line-height: 1.7;
margin: 0;
}
.codeComment {
color: #6a9955;
}
.codeKeyword {
color: #c586c0;
}
.codeString {
color: #ce9178;
}
.codeFunc {
color: #dcdcaa;
}
@media (max-width: 768px) {
.title { font-size: 32px; }
.grid { grid-template-columns: 1fr; }
}

View File

@ -0,0 +1,50 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from './page.module.css';
export default function DevelopersPage() {
const t = useT();
const resources = [
{ icon: '\u{1F4E1}', titleKey: 'dev_api_title', descKey: 'dev_api_desc', link: '#', linkKey: 'dev_doc_link' },
{ icon: '\u{1F4E6}', titleKey: 'dev_sdk_title', descKey: 'dev_sdk_desc', link: '#', linkKey: 'dev_doc_link' },
{ icon: '\u{1F50D}', titleKey: 'dev_explorer_title', descKey: 'dev_explorer_desc', link: '#', linkKey: 'dev_explorer_link' },
{ icon: '\u{1F9EA}', titleKey: 'dev_sandbox_title', descKey: 'dev_sandbox_desc', link: '#', linkKey: 'dev_doc_link' },
];
return (
<>
<section className={s.hero}>
<AnimateOnScroll direction="none">
<h1 className={s.title}>{t('dev_title')}</h1>
<p className={s.subtitle}>{t('dev_subtitle')}</p>
</AnimateOnScroll>
</section>
<div className={s.grid}>
{resources.map((r, i) => (
<AnimateOnScroll key={r.titleKey} delay={i * 0.1}>
<div className={s.card}>
<div className={s.cardIcon}>{r.icon}</div>
<h3 className={s.cardTitle}>{t(r.titleKey)}</h3>
<p className={s.cardDesc}>{t(r.descKey)}</p>
<a href={r.link} className={s.cardLink}>{t(r.linkKey)} &rarr;</a>
</div>
</AnimateOnScroll>
))}
</div>
<section className={s.quickstart}>
<AnimateOnScroll>
<h2 className={s.qsTitle}>{t('dev_quickstart')}</h2>
<div className={s.codeBlock}>
<pre>{`// Install the Genex SDK\nnpm install @genex/sdk\n\n// Initialize the client\nimport { GenexClient } from '@genex/sdk';\n\nconst client = new GenexClient({\n apiKey: 'your-api-key',\n network: 'mainnet',\n});\n\n// Query available coupons\nconst coupons = await client.coupons.list({\n category: 'dining',\n maxPrice: 100,\n});\n\nconsole.log(coupons);`}</pre>
</div>
</AnimateOnScroll>
</section>
</>
);
}

View File

@ -0,0 +1,36 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from '../content.module.css';
export default function DownloadPage() {
const t = useT();
const platforms = [
{ icon: '\u{1F4F1}', titleKey: 'download_ios_title', descKey: 'download_ios_desc', btnKey: 'download_ios_btn' },
{ icon: '\u{1F4F2}', titleKey: 'download_android_title', descKey: 'download_android_desc', btnKey: 'download_android_btn' },
{ icon: '\u{1F4BB}', titleKey: 'download_web_title', descKey: 'download_web_desc', btnKey: 'download_web_btn' },
{ icon: '\u{2B50}', titleKey: 'download_miniapp_title', descKey: 'download_miniapp_desc', btnKey: 'download_miniapp_btn' },
];
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('download_title')}</h1>
<p className={s.subtitle}>{t('download_subtitle')}</p>
</section>
<div className={s.grid} style={{ gridTemplateColumns: 'repeat(2, 1fr)', maxWidth: 800 }}>
{platforms.map((p, i) => (
<AnimateOnScroll key={p.titleKey} delay={i * 0.1}>
<div className={s.card} style={{ textAlign: 'center' }}>
<div className={s.cardIcon}>{p.icon}</div>
<h3 className={s.cardTitle}>{t(p.titleKey)}</h3>
<p className={s.cardDesc}>{t(p.descKey)}</p>
<span className={s.cardLink}>{t(p.btnKey)} &rarr;</span>
</div>
</AnimateOnScroll>
))}
</div>
</>
);
}

View File

@ -0,0 +1,70 @@
.hero {
padding: 160px 24px 80px;
text-align: center;
background: linear-gradient(180deg, var(--color-primary-surface) 0%, var(--color-surface) 100%);
}
.title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
}
.subtitle {
font-size: 20px;
color: var(--color-text-secondary);
max-width: 600px;
margin: 0 auto;
}
.grid {
max-width: var(--max-width);
margin: 0 auto;
padding: 80px 24px;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 32px;
}
.card {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 48px 40px;
border: 1px solid var(--color-border-light);
transition: all 0.3s;
}
.card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
border-color: var(--color-primary-container);
}
.cardIcon {
width: 64px;
height: 64px;
border-radius: 18px;
background: var(--color-primary-surface);
display: flex;
align-items: center;
justify-content: center;
font-size: 28px;
margin-bottom: 24px;
}
.cardTitle {
font-size: 22px;
font-weight: 600;
margin-bottom: 12px;
}
.cardDesc {
font-size: 15px;
color: var(--color-text-secondary);
line-height: 1.7;
}
@media (max-width: 768px) {
.title { font-size: 32px; }
.grid { grid-template-columns: 1fr; }
}

View File

@ -0,0 +1,42 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from './page.module.css';
export default function FeaturesPage() {
const t = useT();
const features = [
{ icon: '\u{1F6D2}', titleKey: 'feat_buy_title', descKey: 'feat_buy_desc' },
{ icon: '\u{1F4C8}', titleKey: 'feat_market_title', descKey: 'feat_market_desc' },
{ icon: '\u{1F4DC}', titleKey: 'feat_contract_title', descKey: 'feat_contract_desc' },
{ icon: '\u{2B50}', titleKey: 'feat_rating_title', descKey: 'feat_rating_desc' },
{ icon: '\u{1F4F1}', titleKey: 'feat_multi_title', descKey: 'feat_multi_desc' },
{ icon: '\u{1F916}', titleKey: 'feat_ai_title', descKey: 'feat_ai_desc' },
];
return (
<>
<section className={s.hero}>
<AnimateOnScroll direction="none">
<h1 className={s.title}>{t('features_title')}</h1>
<p className={s.subtitle}>{t('features_subtitle')}</p>
</AnimateOnScroll>
</section>
<div className={s.grid}>
{features.map((f, i) => (
<AnimateOnScroll key={f.titleKey} delay={i * 0.1}>
<div className={s.card}>
<div className={s.cardIcon}>{f.icon}</div>
<h3 className={s.cardTitle}>{t(f.titleKey)}</h3>
<p className={s.cardDesc}>{t(f.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</>
);
}

View File

@ -0,0 +1,135 @@
/* ============================================================
Genex Portal - Global Styles + Design Tokens
============================================================ */
:root {
/* ---- Primary Purple ---- */
--color-primary: #6C5CE7;
--color-primary-light: #9B8FFF;
--color-primary-dark: #4834D4;
--color-primary-surface: #F3F1FF;
--color-primary-container: #E8E5FF;
/* ---- Neutral ---- */
--color-gray-50: #F8F9FC;
--color-gray-100: #F1F3F8;
--color-gray-200: #E4E7F0;
--color-gray-300: #CDD2DE;
--color-gray-400: #A0A8BE;
--color-gray-500: #7A839E;
--color-gray-600: #5C6478;
--color-gray-700: #3D4459;
--color-gray-800: #262B3A;
--color-gray-900: #141723;
/* ---- Semantic ---- */
--color-success: #00C48C;
--color-warning: #FFAB2E;
--color-error: #FF4757;
--color-info: #3B82F6;
/* ---- Background & Surface ---- */
--color-bg: #F8F9FC;
--color-surface: #FFFFFF;
/* ---- Text ---- */
--color-text-primary: #141723;
--color-text-secondary: #5C6478;
--color-text-tertiary: #A0A8BE;
--color-text-on-primary: #FFFFFF;
/* ---- Border ---- */
--color-border: #E4E7F0;
--color-border-light: #F1F3F8;
/* ---- Typography ---- */
--font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Noto Sans SC', sans-serif;
--font-family-mono: 'JetBrains Mono', 'Fira Code', monospace;
/* ---- Spacing ---- */
--space-xs: 4px;
--space-sm: 8px;
--space-md: 12px;
--space-lg: 16px;
--space-xl: 20px;
--space-2xl: 24px;
--space-3xl: 32px;
--space-4xl: 40px;
/* ---- Radius ---- */
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--radius-full: 999px;
/* ---- Shadow ---- */
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.04);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.06);
--shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.08);
--shadow-primary: 0 4px 16px rgba(108, 92, 231, 0.2);
/* ---- Layout ---- */
--header-height: 72px;
--max-width: 1200px;
}
/* ---- Reset ---- */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
html {
scroll-behavior: smooth;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font-family: var(--font-family);
color: var(--color-text-primary);
background: var(--color-surface);
line-height: 1.6;
}
a {
color: inherit;
text-decoration: none;
}
img, svg {
display: block;
max-width: 100%;
}
button {
font-family: inherit;
cursor: pointer;
}
/* ---- Utility ---- */
.container {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 24px;
}
@media (max-width: 768px) {
.container {
padding: 0 16px;
}
}
/* ---- Scrollbar ---- */
::-webkit-scrollbar {
width: 6px;
}
::-webkit-scrollbar-thumb {
background: var(--color-gray-300);
border-radius: 3px;
}
::-webkit-scrollbar-track {
background: transparent;
}

View File

@ -0,0 +1,42 @@
'use client';
import React from 'react';
import Link from 'next/link';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from '../content.module.css';
export default function HelpPage() {
const t = useT();
const topics = [
{ icon: '\u{1F6D2}', titleKey: 'help_t1_title', descKey: 'help_t1_desc' },
{ icon: '\u{1F4B1}', titleKey: 'help_t2_title', descKey: 'help_t2_desc' },
{ icon: '\u{1F4B3}', titleKey: 'help_t3_title', descKey: 'help_t3_desc' },
{ icon: '\u{1F512}', titleKey: 'help_t4_title', descKey: 'help_t4_desc' },
{ icon: '\u{1F4F1}', titleKey: 'help_t5_title', descKey: 'help_t5_desc' },
{ icon: '\u{2753}', titleKey: 'help_t6_title', descKey: 'help_t6_desc' },
];
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('help_title')}</h1>
<p className={s.subtitle}>{t('help_subtitle')}</p>
</section>
<div className={s.grid}>
{topics.map((tp, i) => (
<AnimateOnScroll key={tp.titleKey} delay={i * 0.1}>
<div className={s.card}>
<div className={s.cardIcon}>{tp.icon}</div>
<h3 className={s.cardTitle}>{t(tp.titleKey)}</h3>
<p className={s.cardDesc}>{t(tp.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
<div style={{ textAlign: 'center', padding: '0 24px 80px' }}>
<p style={{ fontSize: 16, color: 'var(--color-text-secondary)', marginBottom: 16 }}>{t('help_contact_hint')}</p>
<Link href="/contact" style={{ fontSize: 16, fontWeight: 600, color: 'var(--color-primary)' }}>{t('help_contact_link')} &rarr;</Link>
</div>
</>
);
}

View File

@ -0,0 +1,37 @@
'use client';
import React, { useState } from 'react';
import './globals.css';
import Header from '@/components/Header';
import Footer from '@/components/Footer';
import { LocaleContext, type Locale } from '@/i18n';
export default function RootLayout({
children,
}: {
children: React.ReactNode;
}) {
const [locale, setLocale] = useState<Locale>('zh-CN');
return (
<html lang={locale}>
<head>
<title>Genex - </title>
<meta name="description" content="区块链驱动的券资产交易平台,折扣购券、自由交易、安全保障" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="icon" href="/favicon.ico" />
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap"
rel="stylesheet"
/>
</head>
<body>
<LocaleContext.Provider value={{ locale, setLocale }}>
<Header />
<main>{children}</main>
<Footer />
</LocaleContext.Provider>
</body>
</html>
);
}

View File

@ -0,0 +1,374 @@
/* ---- Hero ---- */
.hero {
position: relative;
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #4834D4 0%, #2d1b69 40%, #1a1145 100%);
overflow: hidden;
padding: 120px 24px 80px;
}
.heroBg {
position: absolute;
inset: 0;
overflow: hidden;
}
.heroBg::before {
content: '';
position: absolute;
width: 600px;
height: 600px;
border-radius: 50%;
background: radial-gradient(circle, rgba(108, 92, 231, 0.3) 0%, transparent 70%);
top: -200px;
right: -100px;
animation: floatSlow 12s ease-in-out infinite;
}
.heroBg::after {
content: '';
position: absolute;
width: 400px;
height: 400px;
border-radius: 50%;
background: radial-gradient(circle, rgba(155, 143, 255, 0.2) 0%, transparent 70%);
bottom: -100px;
left: -50px;
animation: floatSlow 10s ease-in-out infinite reverse;
}
@keyframes floatSlow {
0%, 100% { transform: translate(0, 0) scale(1); }
50% { transform: translate(30px, -20px) scale(1.05); }
}
.heroContent {
position: relative;
z-index: 1;
text-align: center;
max-width: 800px;
}
.heroTitle {
font-size: 64px;
font-weight: 800;
color: #fff;
line-height: 1.15;
letter-spacing: -1px;
margin-bottom: 24px;
}
.heroSubtitle {
font-size: 20px;
color: rgba(255, 255, 255, 0.7);
line-height: 1.6;
margin-bottom: 48px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.heroBtns {
display: flex;
gap: 16px;
justify-content: center;
flex-wrap: wrap;
}
.btnPrimary {
background: var(--color-primary);
color: #fff;
border: none;
border-radius: var(--radius-full);
padding: 16px 40px;
font-size: 17px;
font-weight: 600;
transition: all 0.3s;
box-shadow: 0 4px 24px rgba(108, 92, 231, 0.4);
}
.btnPrimary:hover {
transform: translateY(-2px);
box-shadow: 0 8px 32px rgba(108, 92, 231, 0.5);
}
.btnOutline {
background: transparent;
color: #fff;
border: 2px solid rgba(255, 255, 255, 0.3);
border-radius: var(--radius-full);
padding: 14px 40px;
font-size: 17px;
font-weight: 600;
transition: all 0.3s;
}
.btnOutline:hover {
border-color: #fff;
background: rgba(255, 255, 255, 0.1);
}
/* ---- Stats ---- */
.stats {
background: linear-gradient(135deg, #2d1b69, #1a1145);
padding: 64px 24px;
}
.statsGrid {
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 32px;
}
/* ---- Section Generic ---- */
.section {
padding: 100px 24px;
}
.sectionDark {
padding: 100px 24px;
background: var(--color-gray-50);
}
.sectionTitle {
font-size: 40px;
font-weight: 700;
text-align: center;
margin-bottom: 16px;
color: var(--color-text-primary);
}
.sectionSubtitle {
font-size: 18px;
color: var(--color-text-secondary);
text-align: center;
margin-bottom: 64px;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
/* ---- Advantages ---- */
.advGrid {
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 32px;
}
.advCard {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 40px 32px;
text-align: center;
border: 1px solid var(--color-border-light);
transition: all 0.3s;
}
.advCard:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
border-color: var(--color-primary-container);
}
.advIcon {
width: 72px;
height: 72px;
border-radius: 20px;
background: var(--color-primary-surface);
display: flex;
align-items: center;
justify-content: center;
margin: 0 auto 24px;
font-size: 32px;
}
.advTitle {
font-size: 22px;
font-weight: 600;
margin-bottom: 12px;
color: var(--color-text-primary);
}
.advDesc {
font-size: 15px;
color: var(--color-text-secondary);
line-height: 1.6;
}
/* ---- Product Showcase ---- */
.prodGrid {
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 24px;
}
.prodCard {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 36px 32px;
border: 1px solid var(--color-border-light);
display: flex;
gap: 20px;
align-items: flex-start;
transition: all 0.3s;
}
.prodCard:hover {
box-shadow: var(--shadow-md);
border-color: var(--color-primary-container);
}
.prodIcon {
width: 52px;
height: 52px;
border-radius: 14px;
background: var(--color-primary-surface);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
flex-shrink: 0;
}
.prodTitle {
font-size: 18px;
font-weight: 600;
margin-bottom: 8px;
color: var(--color-text-primary);
}
.prodDesc {
font-size: 14px;
color: var(--color-text-secondary);
line-height: 1.6;
}
/* ---- Trust ---- */
.trustGrid {
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 24px;
}
.trustCard {
text-align: center;
padding: 32px 24px;
}
.trustIcon {
width: 56px;
height: 56px;
border-radius: 16px;
background: var(--color-primary-surface);
display: flex;
align-items: center;
justify-content: center;
font-size: 24px;
margin: 0 auto 20px;
}
.trustTitle {
font-size: 17px;
font-weight: 600;
margin-bottom: 10px;
color: var(--color-text-primary);
}
.trustDesc {
font-size: 14px;
color: var(--color-text-secondary);
line-height: 1.6;
}
/* ---- CTA ---- */
.cta {
padding: 100px 24px;
background: linear-gradient(135deg, var(--color-primary) 0%, var(--color-primary-dark) 100%);
text-align: center;
}
.ctaTitle {
font-size: 40px;
font-weight: 700;
color: #fff;
margin-bottom: 16px;
}
.ctaSubtitle {
font-size: 18px;
color: rgba(255, 255, 255, 0.7);
margin-bottom: 48px;
}
.ctaBtns {
display: flex;
gap: 16px;
justify-content: center;
flex-wrap: wrap;
}
.ctaBtn {
background: #fff;
color: var(--color-primary);
border: none;
border-radius: var(--radius-full);
padding: 14px 32px;
font-size: 16px;
font-weight: 600;
display: flex;
align-items: center;
gap: 8px;
transition: all 0.3s;
}
.ctaBtn:hover {
transform: translateY(-2px);
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.15);
}
/* ---- Responsive ---- */
@media (max-width: 768px) {
.heroTitle {
font-size: 36px;
}
.heroSubtitle {
font-size: 16px;
}
.statsGrid {
grid-template-columns: repeat(2, 1fr);
}
.sectionTitle {
font-size: 28px;
}
.advGrid {
grid-template-columns: 1fr;
max-width: 480px;
}
.prodGrid {
grid-template-columns: 1fr;
}
.trustGrid {
grid-template-columns: repeat(2, 1fr);
}
.ctaTitle {
font-size: 28px;
}
}

View File

@ -0,0 +1,120 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import StatsCounter from '@/components/StatsCounter';
import s from './page.module.css';
export default function HomePage() {
const t = useT();
return (
<>
{/* ===== Hero ===== */}
<section className={s.hero}>
<div className={s.heroBg} />
<div className={s.heroContent}>
<AnimateOnScroll direction="none">
<h1 className={s.heroTitle}>{t('hero_title')}</h1>
<p className={s.heroSubtitle}>{t('hero_subtitle')}</p>
<div className={s.heroBtns}>
<button className={s.btnPrimary}>{t('hero_cta_download')}</button>
<button className={s.btnOutline}>{t('hero_cta_register')}</button>
</div>
</AnimateOnScroll>
</div>
</section>
{/* ===== Stats ===== */}
<section className={s.stats}>
<div className={s.statsGrid}>
<StatsCounter end={12.8} suffix={t('stats_unit_yi')} label={t('stats_volume')} decimals={1} />
<StatsCounter end={56} suffix={t('stats_unit_wan')} label={t('stats_users')} />
<StatsCounter end={3200} suffix={t('stats_unit_ge')} label={t('stats_coupons')} />
<StatsCounter end={860} suffix={t('stats_unit_ge')} label={t('stats_merchants')} />
</div>
</section>
{/* ===== Advantages ===== */}
<section className={s.section}>
<h2 className={s.sectionTitle}>{t('adv_title')}</h2>
<div className={s.sectionSubtitle} />
<div className={s.advGrid}>
{[
{ icon: '\u{1F3F7}', titleKey: 'adv_discount_title', descKey: 'adv_discount_desc' },
{ icon: '\u{1F4C8}', titleKey: 'adv_trade_title', descKey: 'adv_trade_desc' },
{ icon: '\u{1F6E1}', titleKey: 'adv_secure_title', descKey: 'adv_secure_desc' },
].map((item, i) => (
<AnimateOnScroll key={item.titleKey} delay={i * 0.15}>
<div className={s.advCard}>
<div className={s.advIcon}>{item.icon}</div>
<h3 className={s.advTitle}>{t(item.titleKey)}</h3>
<p className={s.advDesc}>{t(item.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</section>
{/* ===== Product Showcase ===== */}
<section className={s.sectionDark}>
<h2 className={s.sectionTitle}>{t('prod_title')}</h2>
<p className={s.sectionSubtitle}>{t('prod_subtitle')}</p>
<div className={s.prodGrid}>
{[
{ icon: '\u{1F6D2}', titleKey: 'prod_buy', descKey: 'prod_buy_desc' },
{ icon: '\u{1F4B1}', titleKey: 'prod_trade', descKey: 'prod_trade_desc' },
{ icon: '\u{1F4F1}', titleKey: 'prod_use', descKey: 'prod_use_desc' },
{ icon: '\u{1F4CA}', titleKey: 'prod_earn', descKey: 'prod_earn_desc' },
].map((item, i) => (
<AnimateOnScroll key={item.titleKey} delay={i * 0.1}>
<div className={s.prodCard}>
<div className={s.prodIcon}>{item.icon}</div>
<div>
<h3 className={s.prodTitle}>{t(item.titleKey)}</h3>
<p className={s.prodDesc}>{t(item.descKey)}</p>
</div>
</div>
</AnimateOnScroll>
))}
</div>
</section>
{/* ===== Trust ===== */}
<section className={s.section}>
<h2 className={s.sectionTitle}>{t('trust_title')}</h2>
<div className={s.sectionSubtitle} />
<div className={s.trustGrid}>
{[
{ icon: '\u{26D3}', titleKey: 'trust_blockchain', descKey: 'trust_blockchain_desc' },
{ icon: '\u{2705}', titleKey: 'trust_compliance', descKey: 'trust_compliance_desc' },
{ icon: '\u{2B50}', titleKey: 'trust_rating', descKey: 'trust_rating_desc' },
{ icon: '\u{1F512}', titleKey: 'trust_insurance', descKey: 'trust_insurance_desc' },
].map((item, i) => (
<AnimateOnScroll key={item.titleKey} delay={i * 0.1}>
<div className={s.trustCard}>
<div className={s.trustIcon}>{item.icon}</div>
<h3 className={s.trustTitle}>{t(item.titleKey)}</h3>
<p className={s.trustDesc}>{t(item.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</section>
{/* ===== CTA ===== */}
<section className={s.cta}>
<AnimateOnScroll>
<h2 className={s.ctaTitle}>{t('cta_title')}</h2>
<p className={s.ctaSubtitle}>{t('cta_subtitle')}</p>
<div className={s.ctaBtns}>
<button className={s.ctaBtn}>{t('cta_ios')}</button>
<button className={s.ctaBtn}>{t('cta_android')}</button>
<button className={s.ctaBtn}>{t('cta_miniapp')}</button>
</div>
</AnimateOnScroll>
</section>
</>
);
}

View File

@ -0,0 +1,34 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from '../content.module.css';
export default function PressPage() {
const t = useT();
const items = [
{ dateKey: 'press_p1_date', titleKey: 'press_p1_title', descKey: 'press_p1_desc' },
{ dateKey: 'press_p2_date', titleKey: 'press_p2_title', descKey: 'press_p2_desc' },
{ dateKey: 'press_p3_date', titleKey: 'press_p3_title', descKey: 'press_p3_desc' },
];
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('press_title')}</h1>
<p className={s.subtitle}>{t('press_subtitle')}</p>
</section>
<div className={s.body}>
{items.map((item, i) => (
<AnimateOnScroll key={item.titleKey} delay={i * 0.1}>
<div style={{ marginBottom: 40, paddingBottom: 40, borderBottom: '1px solid var(--color-border-light)' }}>
<div style={{ fontSize: 13, color: 'var(--color-text-tertiary)', marginBottom: 8 }}>{t(item.dateKey)}</div>
<h3 style={{ fontSize: 20, fontWeight: 600, marginBottom: 10 }}>{t(item.titleKey)}</h3>
<p style={{ fontSize: 15, color: 'var(--color-text-secondary)', lineHeight: 1.7 }}>{t(item.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</>
);
}

View File

@ -0,0 +1,37 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import s from '../content.module.css';
export default function PrivacyPage() {
const t = useT();
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('privacy_title')}</h1>
</section>
<div className={s.body}>
<p className={s.updated}>{t('privacy_updated')}</p>
<h2>{t('privacy_s1_title')}</h2>
<p>{t('privacy_s1_p1')}</p>
<h2>{t('privacy_s2_title')}</h2>
<p>{t('privacy_s2_p1')}</p>
<ul>
<li>{t('privacy_s2_li1')}</li>
<li>{t('privacy_s2_li2')}</li>
<li>{t('privacy_s2_li3')}</li>
<li>{t('privacy_s2_li4')}</li>
</ul>
<h2>{t('privacy_s3_title')}</h2>
<p>{t('privacy_s3_p1')}</p>
<h2>{t('privacy_s4_title')}</h2>
<p>{t('privacy_s4_p1')}</p>
<h2>{t('privacy_s5_title')}</h2>
<p>{t('privacy_s5_p1')}</p>
<h2>{t('privacy_s6_title')}</h2>
<p>{t('privacy_s6_p1')}</p>
</div>
</>
);
}

View File

@ -0,0 +1,32 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import s from '../content.module.css';
export default function RiskPage() {
const t = useT();
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('risk_title')}</h1>
</section>
<div className={s.body}>
<p className={s.updated}>{t('risk_updated')}</p>
<h2>{t('risk_s1_title')}</h2>
<p>{t('risk_s1_p1')}</p>
<h2>{t('risk_s2_title')}</h2>
<ul>
<li>{t('risk_s2_li1')}</li>
<li>{t('risk_s2_li2')}</li>
<li>{t('risk_s2_li3')}</li>
<li>{t('risk_s2_li4')}</li>
</ul>
<h2>{t('risk_s3_title')}</h2>
<p>{t('risk_s3_p1')}</p>
<h2>{t('risk_s4_title')}</h2>
<p>{t('risk_s4_p1')}</p>
</div>
</>
);
}

View File

@ -0,0 +1,88 @@
.hero {
padding: 160px 24px 80px;
text-align: center;
background: linear-gradient(180deg, var(--color-primary-surface) 0%, var(--color-surface) 100%);
}
.title {
font-size: 48px;
font-weight: 700;
margin-bottom: 16px;
}
.subtitle {
font-size: 20px;
color: var(--color-text-secondary);
max-width: 600px;
margin: 0 auto;
}
/* ---- Solution Block ---- */
.block {
padding: 80px 24px;
}
.blockAlt {
padding: 80px 24px;
background: var(--color-gray-50);
}
.blockHeader {
max-width: var(--max-width);
margin: 0 auto 48px;
}
.blockTitle {
font-size: 32px;
font-weight: 700;
margin-bottom: 8px;
}
.blockSubtitle {
font-size: 17px;
color: var(--color-text-secondary);
}
.solGrid {
max-width: var(--max-width);
margin: 0 auto;
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 28px;
}
.solCard {
background: var(--color-surface);
border-radius: var(--radius-lg);
padding: 36px 28px;
border: 1px solid var(--color-border-light);
transition: all 0.3s;
}
.solCard:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
}
.solIcon {
font-size: 36px;
margin-bottom: 20px;
}
.solTitle {
font-size: 20px;
font-weight: 600;
margin-bottom: 10px;
}
.solDesc {
font-size: 14px;
color: var(--color-text-secondary);
line-height: 1.7;
}
@media (max-width: 768px) {
.title { font-size: 32px; }
.blockTitle { font-size: 24px; }
.solGrid { grid-template-columns: 1fr; max-width: 480px; }
}

View File

@ -0,0 +1,75 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from './page.module.css';
export default function SolutionsPage() {
const t = useT();
const consumerSolutions = [
{ icon: '\u{1F4B0}', titleKey: 'sol_c_save_title', descKey: 'sol_c_save_desc' },
{ icon: '\u{1F4C8}', titleKey: 'sol_c_invest_title', descKey: 'sol_c_invest_desc' },
{ icon: '\u{1F381}', titleKey: 'sol_c_gift_title', descKey: 'sol_c_gift_desc' },
];
const merchantSolutions = [
{ icon: '\u{1F3AF}', titleKey: 'sol_m_acquire_title', descKey: 'sol_m_acquire_desc' },
{ icon: '\u{1F4B5}', titleKey: 'sol_m_finance_title', descKey: 'sol_m_finance_desc' },
{ icon: '\u{1F4CA}', titleKey: 'sol_m_loyalty_title', descKey: 'sol_m_loyalty_desc' },
];
return (
<>
<section className={s.hero}>
<AnimateOnScroll direction="none">
<h1 className={s.title}>{t('solutions_title')}</h1>
<p className={s.subtitle}>{t('solutions_subtitle')}</p>
</AnimateOnScroll>
</section>
{/* Consumer */}
<section className={s.block}>
<AnimateOnScroll>
<div className={s.blockHeader}>
<h2 className={s.blockTitle}>{t('sol_consumer_title')}</h2>
<p className={s.blockSubtitle}>{t('sol_consumer_subtitle')}</p>
</div>
</AnimateOnScroll>
<div className={s.solGrid}>
{consumerSolutions.map((sol, i) => (
<AnimateOnScroll key={sol.titleKey} delay={i * 0.1}>
<div className={s.solCard}>
<div className={s.solIcon}>{sol.icon}</div>
<h3 className={s.solTitle}>{t(sol.titleKey)}</h3>
<p className={s.solDesc}>{t(sol.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</section>
{/* Merchant */}
<section className={s.blockAlt}>
<AnimateOnScroll>
<div className={s.blockHeader}>
<h2 className={s.blockTitle}>{t('sol_merchant_title')}</h2>
<p className={s.blockSubtitle}>{t('sol_merchant_subtitle')}</p>
</div>
</AnimateOnScroll>
<div className={s.solGrid}>
{merchantSolutions.map((sol, i) => (
<AnimateOnScroll key={sol.titleKey} delay={i * 0.1}>
<div className={s.solCard}>
<div className={s.solIcon}>{sol.icon}</div>
<h3 className={s.solTitle}>{t(sol.titleKey)}</h3>
<p className={s.solDesc}>{t(sol.descKey)}</p>
</div>
</AnimateOnScroll>
))}
</div>
</section>
</>
);
}

View File

@ -0,0 +1,46 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import AnimateOnScroll from '@/components/AnimateOnScroll';
import s from '../content.module.css';
export default function StatusPage() {
const t = useT();
const services = [
{ nameKey: 'status_s1_name', statusKey: 'status_s1_status' },
{ nameKey: 'status_s2_name', statusKey: 'status_s2_status' },
{ nameKey: 'status_s3_name', statusKey: 'status_s3_status' },
{ nameKey: 'status_s4_name', statusKey: 'status_s4_status' },
{ nameKey: 'status_s5_name', statusKey: 'status_s5_status' },
{ nameKey: 'status_s6_name', statusKey: 'status_s6_status' },
];
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('status_title')}</h1>
<p className={s.subtitle}>{t('status_subtitle')}</p>
</section>
<div className={s.body}>
<AnimateOnScroll>
<div style={{ background: 'var(--color-success-surface, #e8f5e9)', borderRadius: 'var(--radius-lg)', padding: '24px 32px', marginBottom: 40, display: 'flex', alignItems: 'center', gap: 12 }}>
<span className={s.statusDot} />
<span style={{ fontSize: 18, fontWeight: 600, color: 'var(--color-success)' }}>{t('status_all_ok')}</span>
</div>
</AnimateOnScroll>
{services.map((svc, i) => (
<AnimateOnScroll key={svc.nameKey} delay={i * 0.05}>
<div className={s.statusRow}>
<div className={s.statusLabel}>
<span className={s.statusDot} />
<span>{t(svc.nameKey)}</span>
</div>
<span className={s.statusBadge}>{t(svc.statusKey)}</span>
</div>
</AnimateOnScroll>
))}
<p style={{ fontSize: 13, color: 'var(--color-text-tertiary)', marginTop: 32, textAlign: 'center' }}>{t('status_updated')}</p>
</div>
</>
);
}

View File

@ -0,0 +1,38 @@
'use client';
import React from 'react';
import { useT } from '@/i18n';
import s from '../content.module.css';
export default function TermsPage() {
const t = useT();
return (
<>
<section className={s.hero}>
<h1 className={s.title}>{t('terms_title')}</h1>
</section>
<div className={s.body}>
<p className={s.updated}>{t('terms_updated')}</p>
<h2>{t('terms_s1_title')}</h2>
<p>{t('terms_s1_p1')}</p>
<p>{t('terms_s1_p2')}</p>
<h2>{t('terms_s2_title')}</h2>
<p>{t('terms_s2_p1')}</p>
<ul>
<li>{t('terms_s2_li1')}</li>
<li>{t('terms_s2_li2')}</li>
<li>{t('terms_s2_li3')}</li>
</ul>
<h2>{t('terms_s3_title')}</h2>
<p>{t('terms_s3_p1')}</p>
<p>{t('terms_s3_p2')}</p>
<h2>{t('terms_s4_title')}</h2>
<p>{t('terms_s4_p1')}</p>
<h2>{t('terms_s5_title')}</h2>
<p>{t('terms_s5_p1')}</p>
<h2>{t('terms_s6_title')}</h2>
<p>{t('terms_s6_p1')}</p>
</div>
</>
);
}

View File

@ -0,0 +1,38 @@
'use client';
import React from 'react';
import { motion } from 'framer-motion';
interface Props {
children: React.ReactNode;
className?: string;
delay?: number;
direction?: 'up' | 'down' | 'left' | 'right' | 'none';
}
const offsets = {
up: { y: 40 },
down: { y: -40 },
left: { x: 40 },
right: { x: -40 },
none: {},
};
export default function AnimateOnScroll({
children,
className,
delay = 0,
direction = 'up',
}: Props) {
return (
<motion.div
className={className}
initial={{ opacity: 0, ...offsets[direction] }}
whileInView={{ opacity: 1, x: 0, y: 0 }}
viewport={{ once: true, margin: '-60px' }}
transition={{ duration: 0.6, delay, ease: 'easeOut' }}
>
{children}
</motion.div>
);
}

View File

@ -0,0 +1,104 @@
.footer {
background: var(--color-gray-900);
color: rgba(255, 255, 255, 0.7);
padding: 80px 0 32px;
}
.top {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 24px;
display: grid;
grid-template-columns: 1.5fr 1fr 1fr 1fr 1fr;
gap: 48px;
}
.brandCol {
display: flex;
flex-direction: column;
gap: 16px;
}
.logoRow {
display: flex;
align-items: center;
gap: 10px;
}
.brandName {
font-size: 22px;
font-weight: 700;
color: #fff;
}
.slogan {
font-size: 14px;
color: rgba(255, 255, 255, 0.5);
line-height: 1.6;
}
.colTitle {
font-size: 14px;
font-weight: 600;
color: #fff;
margin-bottom: 16px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.colLinks {
display: flex;
flex-direction: column;
gap: 10px;
}
.colLink {
font-size: 14px;
color: rgba(255, 255, 255, 0.55);
transition: color 0.2s;
}
.colLink:hover {
color: var(--color-primary-light);
}
.divider {
max-width: var(--max-width);
margin: 48px auto 0;
padding: 0 24px;
border-top: 1px solid rgba(255, 255, 255, 0.08);
}
.bottom {
max-width: var(--max-width);
margin: 0 auto;
padding: 24px 24px 0;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 13px;
color: rgba(255, 255, 255, 0.35);
}
@media (max-width: 900px) {
.top {
grid-template-columns: 1fr 1fr;
gap: 32px;
}
.brandCol {
grid-column: 1 / -1;
}
}
@media (max-width: 480px) {
.top {
grid-template-columns: 1fr;
}
.bottom {
flex-direction: column;
gap: 8px;
text-align: center;
}
}

View File

@ -0,0 +1,84 @@
'use client';
import React from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { useT } from '@/i18n';
import s from './Footer.module.css';
export default function Footer() {
const t = useT();
const columns = [
{
title: t('footer_product'),
links: [
{ label: t('footer_mobile_app'), href: '/download' },
{ label: t('footer_miniapp'), href: '/download' },
{ label: t('footer_web_trade'), href: '/download' },
{ label: t('footer_api'), href: '/developers' },
],
},
{
title: t('footer_company'),
links: [
{ label: t('footer_about'), href: '/about' },
{ label: t('footer_careers'), href: '/careers' },
{ label: t('footer_blog'), href: '/blog' },
{ label: t('footer_press'), href: '/press' },
],
},
{
title: t('footer_support'),
links: [
{ label: t('footer_help'), href: '/help' },
{ label: t('footer_contact'), href: '/contact' },
{ label: t('footer_community'), href: '/community' },
{ label: t('footer_status'), href: '/status' },
],
},
{
title: t('footer_legal'),
links: [
{ label: t('footer_terms'), href: '/terms' },
{ label: t('footer_privacy'), href: '/privacy' },
{ label: t('footer_cookie'), href: '/cookie-policy' },
{ label: t('footer_risk'), href: '/risk' },
],
},
];
return (
<footer className={s.footer}>
<div className={s.top}>
<div className={s.brandCol}>
<div className={s.logoRow}>
<Image src="/logo.svg" alt="Genex" width={32} height={32} />
<span className={s.brandName}>Genex</span>
</div>
<p className={s.slogan}>{t('footer_slogan')}</p>
</div>
{columns.map((col) => (
<div key={col.title}>
<div className={s.colTitle}>{col.title}</div>
<div className={s.colLinks}>
{col.links.map((link) => (
<Link key={link.label} href={link.href} className={s.colLink}>
{link.label}
</Link>
))}
</div>
</div>
))}
</div>
<div className={s.divider} />
<div className={s.bottom}>
<span>{t('footer_copyright')}</span>
<span>{t('footer_icp')}</span>
</div>
</footer>
);
}

View File

@ -0,0 +1,215 @@
.header {
position: fixed;
top: 0;
left: 0;
right: 0;
height: var(--header-height);
z-index: 1000;
transition: background 0.3s, box-shadow 0.3s;
}
.header.scrolled {
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(12px);
box-shadow: var(--shadow-sm);
}
.inner {
max-width: var(--max-width);
margin: 0 auto;
padding: 0 24px;
height: 100%;
display: flex;
align-items: center;
justify-content: space-between;
}
.logoArea {
display: flex;
align-items: center;
gap: 10px;
}
.brand {
font-size: 22px;
font-weight: 700;
color: var(--color-primary);
transition: color 0.3s;
}
.scrolled .brand {
color: var(--color-primary);
}
.transparentText .brand {
color: #fff;
}
.nav {
display: flex;
align-items: center;
gap: 32px;
}
.navLink {
font-size: 15px;
font-weight: 500;
color: var(--color-text-secondary);
transition: color 0.2s;
position: relative;
}
.navLink:hover {
color: var(--color-primary);
}
.navLink.active {
color: var(--color-primary);
}
.transparentText .navLink {
color: rgba(255, 255, 255, 0.85);
}
.transparentText .navLink:hover,
.transparentText .navLink.active {
color: #fff;
}
.scrolled .navLink {
color: var(--color-text-secondary);
}
.scrolled .navLink:hover,
.scrolled .navLink.active {
color: var(--color-primary);
}
.actions {
display: flex;
align-items: center;
gap: 12px;
}
.langBtn {
background: none;
border: 1px solid var(--color-border);
border-radius: var(--radius-full);
padding: 6px 14px;
font-size: 13px;
font-weight: 500;
color: var(--color-text-secondary);
transition: all 0.2s;
}
.langBtn:hover {
border-color: var(--color-primary);
color: var(--color-primary);
}
.transparentText .langBtn {
border-color: rgba(255, 255, 255, 0.4);
color: rgba(255, 255, 255, 0.85);
}
.transparentText .langBtn:hover {
border-color: #fff;
color: #fff;
}
.scrolled .langBtn {
border-color: var(--color-border);
color: var(--color-text-secondary);
}
.ctaBtn {
background: var(--color-primary);
color: #fff;
border: none;
border-radius: var(--radius-full);
padding: 8px 20px;
font-size: 14px;
font-weight: 600;
transition: all 0.2s;
}
.ctaBtn:hover {
background: var(--color-primary-dark);
box-shadow: var(--shadow-primary);
}
/* Mobile actions (hidden on desktop) */
.mobileActions {
display: none;
}
/* Mobile menu */
.menuToggle {
display: none;
background: none;
border: none;
width: 32px;
height: 32px;
flex-direction: column;
justify-content: center;
align-items: center;
gap: 5px;
}
.menuToggle span {
display: block;
width: 22px;
height: 2px;
background: var(--color-text-primary);
transition: all 0.3s;
border-radius: 1px;
}
.transparentText .menuToggle span {
background: #fff;
}
.scrolled .menuToggle span {
background: var(--color-text-primary);
}
@media (max-width: 900px) {
.nav {
display: none;
}
.nav.mobileOpen {
display: flex;
flex-direction: column;
position: fixed;
top: var(--header-height);
left: 0;
right: 0;
bottom: 0;
background: rgba(255, 255, 255, 0.98);
backdrop-filter: blur(12px);
padding: 32px 24px;
gap: 24px;
z-index: 999;
}
.nav.mobileOpen .navLink {
font-size: 18px;
color: var(--color-text-primary);
}
.menuToggle {
display: flex;
}
.actions {
display: none;
}
.nav.mobileOpen .mobileActions {
display: flex;
flex-direction: column;
gap: 12px;
margin-top: 16px;
}
}

View File

@ -0,0 +1,97 @@
'use client';
import React, { useState, useEffect } from 'react';
import Link from 'next/link';
import Image from 'next/image';
import { usePathname } from 'next/navigation';
import { useT, useLocale } from '@/i18n';
import type { Locale } from '@/i18n';
import s from './Header.module.css';
export default function Header() {
const t = useT();
const { locale, setLocale } = useLocale();
const pathname = usePathname();
const [scrolled, setScrolled] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const isHome = pathname === '/';
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 40);
window.addEventListener('scroll', onScroll, { passive: true });
onScroll();
return () => window.removeEventListener('scroll', onScroll);
}, []);
useEffect(() => {
setMenuOpen(false);
}, [pathname]);
const transparent = isHome && !scrolled && !menuOpen;
const links = [
{ href: '/', label: t('nav_home') },
{ href: '/about', label: t('nav_about') },
{ href: '/features', label: t('nav_features') },
{ href: '/solutions', label: t('nav_solutions') },
{ href: '/developers', label: t('nav_developers') },
{ href: '/contact', label: t('nav_contact') },
];
const toggleLang = () => {
setLocale(locale === 'zh-CN' ? 'en-US' : 'zh-CN' as Locale);
};
const cls = [
s.header,
scrolled || menuOpen ? s.scrolled : '',
transparent ? s.transparentText : '',
].join(' ');
return (
<header className={cls}>
<div className={s.inner}>
<Link href="/" className={s.logoArea}>
<Image src="/logo.svg" alt="Genex" width={36} height={36} />
<span className={s.brand}>Genex</span>
</Link>
<nav className={`${s.nav} ${menuOpen ? s.mobileOpen : ''}`}>
{links.map((l) => (
<Link
key={l.href}
href={l.href}
className={`${s.navLink} ${pathname === l.href ? s.active : ''}`}
>
{l.label}
</Link>
))}
<div className={s.mobileActions}>
<button className={s.langBtn} onClick={toggleLang}>
{locale === 'zh-CN' ? 'EN' : '中文'}
</button>
<button className={s.ctaBtn}>{t('nav_register')}</button>
</div>
</nav>
<div className={s.actions}>
<button className={s.langBtn} onClick={toggleLang}>
{locale === 'zh-CN' ? 'EN' : '中文'}
</button>
<button className={s.ctaBtn}>{t('nav_register')}</button>
</div>
<button
className={s.menuToggle}
onClick={() => setMenuOpen(!menuOpen)}
aria-label="Menu"
>
<span />
<span />
<span />
</button>
</div>
</header>
);
}

View File

@ -0,0 +1,73 @@
'use client';
import React, { useEffect, useRef, useState } from 'react';
import { motion, useInView } from 'framer-motion';
interface Props {
end: number;
suffix?: string;
label: string;
decimals?: number;
duration?: number;
}
export default function StatsCounter({
end,
suffix = '',
label,
decimals = 0,
duration = 2,
}: Props) {
const ref = useRef<HTMLDivElement>(null);
const inView = useInView(ref, { once: true, margin: '-40px' });
const [value, setValue] = useState(0);
useEffect(() => {
if (!inView) return;
const start = 0;
const startTime = performance.now();
const tick = (now: number) => {
const elapsed = (now - startTime) / 1000;
const progress = Math.min(elapsed / duration, 1);
// ease out cubic
const eased = 1 - Math.pow(1 - progress, 3);
setValue(start + (end - start) * eased);
if (progress < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}, [inView, end, duration]);
return (
<motion.div
ref={ref}
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true }}
transition={{ duration: 0.5 }}
style={{ textAlign: 'center' }}
>
<div
style={{
fontSize: 48,
fontWeight: 700,
color: '#fff',
lineHeight: 1.2,
}}
>
{value.toFixed(decimals)}
<span style={{ fontSize: 24, marginLeft: 4 }}>{suffix}</span>
</div>
<div
style={{
fontSize: 15,
color: 'rgba(255,255,255,0.6)',
marginTop: 8,
}}
>
{label}
</div>
</motion.div>
);
}

View File

@ -0,0 +1,375 @@
const enUS: Record<string, string> = {
// -- Nav --
nav_home: 'Home',
nav_about: 'About',
nav_features: 'Features',
nav_solutions: 'Solutions',
nav_developers: 'Developers',
nav_contact: 'Contact',
nav_download: 'Download App',
nav_register: 'Sign Up',
// -- Hero --
hero_title: 'Every Coupon, Real Value',
hero_subtitle: 'Blockchain-powered coupon asset trading platform — buy at discount, trade freely, stay secure',
hero_cta_download: 'Download App',
hero_cta_register: 'Sign Up Free',
// -- Stats --
stats_volume: 'Total Volume',
stats_users: 'Active Users',
stats_coupons: 'Coupons Issued',
stats_merchants: 'Partner Merchants',
stats_unit_yi: 'B',
stats_unit_wan: 'K+',
stats_unit_ge: '+',
// -- Advantages --
adv_title: 'Why Genex',
adv_discount_title: 'Buy at Discount',
adv_discount_desc: 'Purchase coupons below face value and save on every transaction',
adv_trade_title: 'Trade Freely',
adv_trade_desc: 'Buy and sell on the secondary market with transparent pricing',
adv_secure_title: 'Secure & Safe',
adv_secure_desc: 'Smart contract settlement with blockchain security and privacy protection',
// -- Product --
prod_title: 'All-in-One Coupon Trading',
prod_subtitle: 'From purchase to trade to redemption — intelligent management at every step',
prod_buy: 'Easy Purchase',
prod_buy_desc: 'Browse curated coupons and buy at a discount with one tap',
prod_trade: 'Flexible Trading',
prod_trade_desc: 'Place orders and trade like stocks — limit and market orders supported',
prod_use: 'Quick Redemption',
prod_use_desc: 'Scan to redeem, settle directly with the merchant',
prod_earn: 'Portfolio Tracking',
prod_earn_desc: 'Monitor your holdings in real time with smart trading insights',
// -- Trust --
trust_title: 'A Platform You Can Trust',
trust_blockchain: 'Blockchain Tech',
trust_blockchain_desc: 'Every transaction is recorded on-chain and immutable',
trust_compliance: 'Fully Compliant',
trust_compliance_desc: 'Licensed operations in strict compliance with financial regulations',
trust_rating: 'Credit Rating',
trust_rating_desc: 'Transparent merchant credit rating system for confident choices',
trust_insurance: 'Fund Protection',
trust_insurance_desc: 'Insurance mechanism safeguards every transaction',
// -- CTA --
cta_title: 'Start Smart Spending Today',
cta_subtitle: 'Download Genex App and enjoy the benefits of discounted coupons',
cta_ios: 'App Store',
cta_android: 'Google Play',
cta_miniapp: 'WeChat Mini',
// -- Footer --
footer_slogan: 'Blockchain-Powered Coupon Finance Platform',
footer_product: 'Product',
footer_company: 'Company',
footer_support: 'Support',
footer_legal: 'Legal',
footer_mobile_app: 'Mobile App',
footer_miniapp: 'WeChat Mini',
footer_web_trade: 'Web Trading',
footer_api: 'API Docs',
footer_about: 'About Us',
footer_careers: 'Careers',
footer_blog: 'Blog',
footer_press: 'Press',
footer_help: 'Help Center',
footer_contact: 'Contact Us',
footer_community: 'Community',
footer_status: 'System Status',
footer_terms: 'Terms of Service',
footer_privacy: 'Privacy Policy',
footer_cookie: 'Cookie Policy',
footer_risk: 'Risk Disclosure',
footer_icp: '',
footer_copyright: '© 2025 Genex. All rights reserved.',
// -- About --
about_title: 'About Genex',
about_subtitle: 'Making Consumption More Valuable',
about_vision_title: 'Our Vision',
about_vision_desc: 'Build an open, transparent, and efficient coupon asset trading ecosystem where every coupon can be discovered, traded, and realized.',
about_mission_title: 'Our Mission',
about_mission_desc: 'Break down information barriers through blockchain technology — real savings for consumers and efficient customer acquisition for merchants.',
about_value_title: 'Core Values',
about_value_1: 'User First',
about_value_1_desc: 'Always driven by user needs, delivering the best product experience',
about_value_2: 'Transparency',
about_value_2_desc: 'All transactions are open and transparent, credit ratings are fair',
about_value_3: 'Innovation',
about_value_3_desc: 'Continuous investment in cutting-edge technology R&D',
about_value_4: 'Compliance',
about_value_4_desc: 'Strict adherence to laws and regulations for platform stability',
about_milestone_title: 'Milestones',
about_milestone_2025_q1: 'Project launched, core architecture designed',
about_milestone_2025_q2: 'Smart contracts completed, testnet live',
about_milestone_2025_q3: 'Mobile App & Admin dashboard released',
about_milestone_2025_q4: 'Mainnet launch, first merchants onboarded',
about_milestone_2026_q1: 'Secondary market trading goes live',
about_milestone_2026_q2: 'Users surpass 100K, ecosystem expanding',
about_partners_title: 'Partners',
// -- Features --
features_title: 'Powerful Features',
features_subtitle: 'Full lifecycle coverage from issuance to trading to redemption',
feat_buy_title: 'Smart Purchase',
feat_buy_desc: 'Browse thousands of coupons with AI-powered recommendations. Filter by category, sort by price, and filter by credit rating.',
feat_market_title: 'Secondary Market',
feat_market_desc: 'Trade coupons like stocks. Limit orders, market orders, real-time pricing, millisecond matching engine.',
feat_contract_title: 'Contract Settlement',
feat_contract_desc: 'Blockchain smart contracts handle settlement automatically. Direct consumer-to-merchant settlement, no middleman.',
feat_rating_title: 'Credit Rating',
feat_rating_desc: 'Multi-dimensional credit scoring based on fulfillment rate, reviews, and business history.',
feat_multi_title: 'Multi-Platform',
feat_multi_desc: 'iOS, Android, WeChat Mini Program, and Web — real-time sync across all devices.',
feat_ai_title: 'AI Assistant',
feat_ai_desc: 'Built-in AI assistant for market analysis, optimal buy timing, and risk alerts.',
// -- Solutions --
solutions_title: 'Solutions',
solutions_subtitle: 'Whether you\'re a consumer or a merchant, Genex creates value for you',
sol_consumer_title: 'For Consumers',
sol_consumer_subtitle: 'A Smarter Way to Spend',
sol_c_save_title: 'Save on Shopping',
sol_c_save_desc: 'Buy dining, retail, and service coupons at 85-95% of face value — save 5-15% on daily spending',
sol_c_invest_title: 'Coupon Trading',
sol_c_invest_desc: 'Buy low, sell high on trending coupons to capture market gains',
sol_c_gift_title: 'Gift Coupons',
sol_c_gift_desc: 'Purchase premium brand coupons as gifts — practical and classy',
sol_merchant_title: 'For Merchants',
sol_merchant_subtitle: 'Efficient Customer Acquisition',
sol_m_acquire_title: 'Targeted Acquisition',
sol_m_acquire_desc: 'Issue coupons to attract target consumers at lower cost than traditional advertising',
sol_m_finance_title: 'Short-term Financing',
sol_m_finance_desc: 'Pre-sell coupons for zero-interest short-term financing, flexible and convenient',
sol_m_loyalty_title: 'Customer Insights',
sol_m_loyalty_desc: 'Analyze user profiles through coupon distribution and trading data to boost retention',
// -- Developers --
dev_title: 'Developer Center',
dev_subtitle: 'Connect to the Genex ecosystem and build your application',
dev_api_title: 'RESTful API',
dev_api_desc: 'Comprehensive REST API documentation covering issuance, trading, and settlement',
dev_sdk_title: 'SDK',
dev_sdk_desc: 'JavaScript, Go, and Dart SDKs for quick integration',
dev_explorer_title: 'Block Explorer',
dev_explorer_desc: 'Query on-chain transactions, contract state, and block information',
dev_sandbox_title: 'Sandbox',
dev_sandbox_desc: 'Full test environment to try all API features for free',
dev_quickstart: 'Quick Start',
dev_doc_link: 'View Full Docs',
dev_explorer_link: 'Open Explorer',
// -- Contact --
contact_title: 'Contact Us',
contact_subtitle: 'We\'d love to hear from you',
contact_form_name: 'Your Name',
contact_form_email: 'Email Address',
contact_form_subject: 'Subject',
contact_form_message: 'Message',
contact_form_submit: 'Send Message',
contact_form_success: 'Message sent! We\'ll get back to you soon.',
contact_office_title: 'Office',
contact_office_addr: 'Tech Park, Nanshan, Shenzhen',
contact_email_title: 'Email',
contact_email_addr: 'contact@gogenex.com',
contact_social_title: 'Follow Us',
// -- Terms of Service --
terms_title: 'Terms of Service',
terms_updated: 'Last updated: January 1, 2025',
terms_s1_title: '1. Acceptance of Terms',
terms_s1_p1: 'Welcome to the Genex platform ("Platform"). Please read these Terms of Service ("Terms") carefully before using any services provided by the Platform. By registering, logging in, or using the Platform, you acknowledge that you have read, understood, and agree to be bound by these Terms.',
terms_s1_p2: 'If you do not agree to any of these Terms, please do not use the Platform services.',
terms_s2_title: '2. Account Registration & Security',
terms_s2_p1: 'You must provide true, accurate, and complete personal information when registering an account, and update it promptly when changes occur. You are responsible for:',
terms_s2_li1: 'Keeping your account credentials secure and not transferring or lending your account to others',
terms_s2_li2: 'All activities conducted through your account',
terms_s2_li3: 'Immediately notifying the Platform of any account anomalies or security concerns',
terms_s3_title: '3. Services',
terms_s3_p1: 'The Platform provides services for browsing, purchasing, selling, trading, and redeeming coupon assets. The Platform acts solely as a transaction facilitator and does not participate in coupon issuance or redemption.',
terms_s3_p2: 'Coupon product information displayed on the Platform is provided by issuers. While the Platform conducts reasonable reviews, it does not guarantee the absolute accuracy of such information.',
terms_s4_title: '4. Trading Rules',
terms_s4_p1: 'All transactions on the Platform must comply with the published trading rules. Completed transactions are irrevocable. Users should fully understand the associated risks and make prudent decisions.',
terms_s5_title: '5. Intellectual Property',
terms_s5_p1: 'All content on the Platform, including but not limited to text, images, software, trademarks, and interface designs, is protected by intellectual property laws. No one may copy, modify, distribute, or otherwise use such content without written authorization from the Platform.',
terms_s6_title: '6. Disclaimer',
terms_s6_p1: 'To the maximum extent permitted by law, the Platform shall not be liable for losses caused by force majeure, third-party actions, or user\'s own actions. The Platform may suspend services for maintenance or upgrades and will endeavor to provide advance notice.',
// -- Privacy Policy --
privacy_title: 'Privacy Policy',
privacy_updated: 'Last updated: January 1, 2025',
privacy_s1_title: '1. Introduction',
privacy_s1_p1: 'Genex values your privacy. This Privacy Policy explains how we collect, use, store, and protect your personal information. By using the Platform, you consent to the data processing practices described in this policy.',
privacy_s2_title: '2. Information Collection',
privacy_s2_p1: 'We may collect the following types of information:',
privacy_s2_li1: 'Identity information: name, phone number, email address, ID documents (for identity verification)',
privacy_s2_li2: 'Transaction information: purchase records, transaction history, wallet balance',
privacy_s2_li3: 'Device information: device model, operating system version, unique device identifiers',
privacy_s2_li4: 'Usage data: browsing history, search keywords, feature usage frequency',
privacy_s3_title: '3. Use of Information',
privacy_s3_p1: 'We use the collected information to provide, maintain, and improve Platform services, perform identity verification and security protection, send you transaction notifications and platform updates, and conduct data analysis to optimize user experience.',
privacy_s4_title: '4. Information Sharing',
privacy_s4_p1: 'We will not sell or rent your personal information to third parties without your consent. We may share your information with: regulatory authorities for compliance requirements, counterparties necessary to complete transactions, and third-party partners with your explicit authorization.',
privacy_s5_title: '5. Data Security',
privacy_s5_p1: 'We employ industry-standard encryption and security measures to protect your personal information, including but not limited to SSL encrypted transmission, encrypted data storage, access control, and security auditing. Please note that no method of internet transmission is completely secure.',
privacy_s6_title: '6. Your Rights',
privacy_s6_p1: 'You have the right to access, correct, or delete your personal information. You may exercise these rights through account settings or by contacting customer support. To delete your account and related data, please contact our support team.',
// -- Cookie Policy --
cookie_title: 'Cookie Policy',
cookie_updated: 'Last updated: January 1, 2025',
cookie_s1_title: '1. What Are Cookies',
cookie_s1_p1: 'Cookies are small text files stored on your device when you browse a website. They help websites remember your preferences and provide a better browsing experience.',
cookie_s2_title: '2. Types of Cookies We Use',
cookie_s2_p1: 'We use the following types of cookies to enhance your experience:',
cookie_s2_h3_1: 'Essential Cookies',
cookie_s2_p2: 'Used to maintain your login status, language preferences, and security verification. These cookies are necessary for the Platform to function properly.',
cookie_s2_h3_2: 'Analytics Cookies',
cookie_s2_p3: 'Help us understand how users interact with the Platform so we can improve the product experience. This data is anonymized and aggregated.',
cookie_s2_h3_3: 'Functional Cookies',
cookie_s2_p4: 'Remember your personalized settings such as language selection and theme preferences, providing a customized experience.',
cookie_s3_title: '3. Managing Cookies',
cookie_s3_p1: 'You can manage or delete cookies through your browser settings. Please note that disabling certain cookies may affect some Platform features.',
// -- Risk Disclosure --
risk_title: 'Risk Disclosure',
risk_updated: 'Last updated: January 1, 2025',
risk_s1_title: '1. General Risk Notice',
risk_s1_p1: 'Coupon asset trading carries inherent market risks. Coupon prices may fluctuate due to market supply and demand, issuer business conditions, macroeconomic factors, and other variables. You should fully understand the associated risks before trading on the Platform.',
risk_s2_title: '2. Specific Risk Factors',
risk_s2_li1: 'Market risk: Secondary market prices may fall below your purchase price, resulting in losses',
risk_s2_li2: 'Credit risk: Issuers may fail to honor coupons due to business difficulties, causing loss of value',
risk_s2_li3: 'Liquidity risk: Some coupons may have low trading volume, making it difficult to execute trades at desired prices',
risk_s2_li4: 'Technical risk: Blockchain network congestion, system failures, and other technical factors may affect trade execution',
risk_s3_title: '3. Investment Advice',
risk_s3_p1: 'Make investment decisions based on your financial situation and risk tolerance. Do not invest more than you can afford to lose. Past trading performance does not guarantee future returns.',
risk_s4_title: '4. Disclaimer',
risk_s4_p1: 'The Platform does not provide investment advice. All information on the Platform is for reference only and does not constitute a recommendation or offer to buy or sell any coupon product. Users should make their own judgments and bear the consequences of their trading decisions.',
// -- Careers --
careers_title: 'Careers',
careers_subtitle: 'Join talented people doing meaningful work',
careers_job1_title: 'Senior Full-Stack Engineer',
careers_job1_desc: 'Build core platform features. Proficiency in TypeScript, React, and Node.js required. Experience in fintech or blockchain preferred.',
careers_job2_title: 'Blockchain Engineer',
careers_job2_desc: 'Develop and maintain smart contracts and on-chain data indexing. Solidity and Cosmos SDK experience required.',
careers_job3_title: 'Mobile Engineer',
careers_job3_desc: 'Build cross-platform Flutter apps with a focus on user experience and performance optimization.',
careers_job4_title: 'Security Engineer',
careers_job4_desc: 'Design platform security architecture, conduct penetration testing, and fix vulnerabilities to protect user assets and data.',
careers_job5_title: 'Data Analyst',
careers_job5_desc: 'Analyze trading data, model user behavior, and optimize risk control models to drive business growth.',
careers_job6_title: 'UI/UX Designer',
careers_job6_desc: 'Design visual interfaces and interaction experiences to create clean, intuitive fintech product interfaces.',
careers_apply: 'Apply Now',
// -- Blog --
blog_title: 'Blog',
blog_subtitle: 'Industry insights, product updates, and technical deep dives',
blog_p1_date: 'Mar 1, 2025',
blog_p1_title: 'Genex 2.0 Launch: New Trading Engine & User Experience',
blog_p1_excerpt: 'After months of refinement, Genex 2.0 brings a brand-new matching engine, more intuitive trading interface, and a more powerful AI recommendation system.',
blog_p2_date: 'Feb 15, 2025',
blog_p2_title: 'How Blockchain Secures Every Coupon Transaction',
blog_p2_excerpt: 'A deep dive into Genex\'s blockchain architecture and how smart contracts enable automated settlement and asset security.',
blog_p3_date: 'Feb 1, 2025',
blog_p3_title: 'Coupon Trading 101: From Discount Buying to Secondary Market',
blog_p3_excerpt: 'Everything you need to know about coupon trading concepts, strategies, and risk management to start your smart spending journey.',
blog_p4_date: 'Jan 20, 2025',
blog_p4_title: 'Success Story: How a Restaurant Chain Grew 300% with Genex',
blog_p4_excerpt: 'See how a leading restaurant brand leveraged Genex coupon issuance to achieve 300% monthly customer acquisition growth.',
blog_p5_date: 'Jan 10, 2025',
blog_p5_title: 'AI Assistant Launch: Smarter Trading Decisions',
blog_p5_excerpt: 'The Genex AI Assistant is live — analyzing market trends, recommending optimal trade timing, and serving as your personal trading advisor.',
blog_p6_date: 'Dec 25, 2024',
blog_p6_title: 'Genex Ecosystem: From Local to Global',
blog_p6_excerpt: 'Reflecting on Genex\'s journey and previewing our 2025 global strategy and product roadmap.',
// -- Press --
press_title: 'Press',
press_subtitle: 'The latest news from Genex',
press_p1_date: 'March 2025',
press_p1_title: 'Genex Raises Series A to Accelerate Global Expansion',
press_p1_desc: 'Genex announces the completion of its Series A funding round. The capital will be used for technology R&D, team expansion, and overseas market development, with plans to enter Southeast Asian and European markets within the next year.',
press_p2_date: 'January 2025',
press_p2_title: 'Genex Partners with Leading Consumer Brands',
press_p2_desc: 'The platform has established partnerships with over 200 brand merchants across dining, retail, and lifestyle services, offering users a richer selection of coupons.',
press_p3_date: 'November 2024',
press_p3_title: 'Genex Platform Officially Launches',
press_p3_desc: 'The Genex coupon finance trading platform officially launched, surpassing 10,000 registered users on day one. Powered by blockchain technology, the platform provides consumers and merchants with a secure, transparent coupon trading marketplace.',
// -- Help Center --
help_title: 'Help Center',
help_subtitle: 'Find answers to your questions quickly',
help_t1_title: 'How do I buy coupons?',
help_t1_desc: 'Browse the homepage or search for brands you like, select the coupon denomination and quantity, and complete your purchase using a supported payment method.',
help_t2_title: 'How do I trade on the secondary market?',
help_t2_desc: 'Go to the "Trade" page where you can list your coupons for sale or browse and purchase coupons listed by other users.',
help_t3_title: 'What payment methods are supported?',
help_t3_desc: 'The platform supports Alipay, WeChat Pay, bank cards, and platform balance as payment methods.',
help_t4_title: 'Is my money safe?',
help_t4_desc: 'The platform uses blockchain smart contract settlement. Funds are held in escrow by the contract, never passing through platform accounts, ensuring security and transparency.',
help_t5_title: 'How do I use my coupons?',
help_t5_desc: 'Open your coupon in the App or Mini Program and present the coupon code to the merchant or let them scan it for redemption.',
help_t6_title: 'What if I have a problem?',
help_t6_desc: 'You can submit a ticket through our Contact page or call our customer service hotline for immediate assistance.',
help_contact_hint: 'Can\'t find your answer?',
help_contact_link: 'Contact Support',
// -- Community --
community_title: 'Community',
community_subtitle: 'Join the Genex community and connect with like-minded people',
community_c1_title: 'WeChat Groups',
community_c1_desc: 'Join official WeChat groups for platform updates, trading strategy sharing, and exclusive benefits.',
community_c2_title: 'Twitter / X',
community_c2_desc: 'Follow @Genex for the latest product updates and industry news.',
community_c3_title: 'Telegram',
community_c3_desc: 'Join our Telegram channel for global community discussions and international market insights.',
community_c4_title: 'Developer Forum',
community_c4_desc: 'Technical discussions, API support, SDK integration help — build the ecosystem with fellow developers.',
community_c5_title: 'WeChat Official Account',
community_c5_desc: 'Follow "Genex" on WeChat for in-depth analysis articles and tutorials.',
community_c6_title: 'Online Events',
community_c6_desc: 'Regular AMAs, trading competitions, and airdrop events with generous rewards.',
// -- System Status --
status_title: 'System Status',
status_subtitle: 'Real-time monitoring of all service status',
status_all_ok: 'All Systems Operational',
status_s1_name: 'Trading Engine',
status_s1_status: 'Operational',
status_s2_name: 'User Service',
status_s2_status: 'Operational',
status_s3_name: 'Payment Gateway',
status_s3_status: 'Operational',
status_s4_name: 'Blockchain Network',
status_s4_status: 'Operational',
status_s5_name: 'API Service',
status_s5_status: 'Operational',
status_s6_name: 'Notification Service',
status_s6_status: 'Operational',
status_updated: 'Status auto-updates every 5 minutes',
// -- Download --
download_title: 'Download Genex',
download_subtitle: 'Start your smart spending journey anytime, anywhere',
download_ios_title: 'iOS App',
download_ios_desc: 'Supports iPhone and iPad with a smooth native experience',
download_ios_btn: 'Download on App Store',
download_android_title: 'Android App',
download_android_desc: 'Supports Android 8.0 and above',
download_android_btn: 'Get it on Google Play',
download_web_title: 'Web Version',
download_web_desc: 'No installation needed — access all features directly in your browser',
download_web_btn: 'Open Web App',
download_miniapp_title: 'WeChat Mini Program',
download_miniapp_desc: 'Search "Genex" in WeChat — lightweight and convenient',
download_miniapp_btn: 'Scan to Try',
};
export default enUS;

View File

@ -0,0 +1,33 @@
'use client';
import { createContext, useContext } from 'react';
import zhCN from './zh-CN';
import enUS from './en-US';
export type Locale = 'zh-CN' | 'en-US';
const locales: Record<Locale, Record<string, string>> = {
'zh-CN': zhCN,
'en-US': enUS,
};
export function t(key: string, locale: Locale = 'zh-CN'): string {
return locales[locale]?.[key] ?? locales['zh-CN']?.[key] ?? key;
}
export const LocaleContext = createContext<{
locale: Locale;
setLocale: (l: Locale) => void;
}>({
locale: 'zh-CN',
setLocale: () => {},
});
export function useLocale() {
return useContext(LocaleContext);
}
export function useT() {
const { locale } = useLocale();
return (key: string) => t(key, locale);
}

View File

@ -0,0 +1,375 @@
const zhCN: Record<string, string> = {
// -- Nav --
nav_home: '首页',
nav_about: '关于我们',
nav_features: '产品功能',
nav_solutions: '解决方案',
nav_developers: '开发者',
nav_contact: '联系我们',
nav_download: '下载 App',
nav_register: '立即注册',
// -- Hero --
hero_title: '让每一张券,都有价值',
hero_subtitle: '区块链驱动的券资产交易平台,折扣购券、自由交易、安全保障',
hero_cta_download: '下载 App',
hero_cta_register: '免费注册',
// -- Stats --
stats_volume: '累计交易额',
stats_users: '活跃用户',
stats_coupons: '发行券数',
stats_merchants: '合作商户',
stats_unit_yi: '亿',
stats_unit_wan: '万+',
stats_unit_ge: '+',
// -- Advantages --
adv_title: '为什么选择 Genex',
adv_discount_title: '折扣购券',
adv_discount_desc: '以低于面值的价格购买优惠券,每笔消费都能省钱',
adv_trade_title: '自由交易',
adv_trade_desc: '二级市场自由买卖,价格透明,随时变现',
adv_secure_title: '安全保障',
adv_secure_desc: '区块链智能合约清算,资金安全,隐私保护',
// -- Product --
prod_title: '一站式券交易体验',
prod_subtitle: '从购买到交易到使用,全流程智能管理',
prod_buy: '轻松购券',
prod_buy_desc: '浏览精选优惠券,一键折扣购买',
prod_trade: '灵活交易',
prod_trade_desc: '挂单出售、市价购买,像股票一样交易',
prod_use: '便捷使用',
prod_use_desc: '扫码核销,直接与商户结算',
prod_earn: '收益管理',
prod_earn_desc: '实时查看持仓收益,智能推荐交易时机',
// -- Trust --
trust_title: '值得信赖的平台',
trust_blockchain: '区块链技术',
trust_blockchain_desc: '所有交易上链存证,不可篡改',
trust_compliance: '合规运营',
trust_compliance_desc: '持牌经营,严格遵守金融监管法规',
trust_rating: '信用评级',
trust_rating_desc: '透明的商户信用评级体系,让选择更安心',
trust_insurance: '资金保障',
trust_insurance_desc: '引入保险机制,为每一笔交易保驾护航',
// -- CTA --
cta_title: '开启智慧消费新时代',
cta_subtitle: '立即下载 Genex App享受折扣购券的乐趣',
cta_ios: 'App Store',
cta_android: 'Google Play',
cta_miniapp: '微信小程序',
// -- Footer --
footer_slogan: '区块链驱动的券金融平台',
footer_product: '产品',
footer_company: '公司',
footer_support: '支持',
footer_legal: '法律',
footer_mobile_app: '移动 App',
footer_miniapp: '微信小程序',
footer_web_trade: 'Web 交易',
footer_api: 'API 文档',
footer_about: '关于我们',
footer_careers: '加入我们',
footer_blog: '博客',
footer_press: '媒体',
footer_help: '帮助中心',
footer_contact: '联系我们',
footer_community: '社区',
footer_status: '系统状态',
footer_terms: '用户协议',
footer_privacy: '隐私政策',
footer_cookie: 'Cookie 政策',
footer_risk: '风险提示',
footer_icp: '粤ICP备XXXXXXXX号',
footer_copyright: '© 2025 Genex. 保留所有权利。',
// -- About --
about_title: '关于 Genex',
about_subtitle: '让消费更有价值',
about_vision_title: '我们的愿景',
about_vision_desc: '构建一个开放、透明、高效的券资产交易生态系统,让每一张优惠券都能被发现、交易和实现价值。',
about_mission_title: '我们的使命',
about_mission_desc: '通过区块链技术打破信息壁垒,让消费者享受真正的优惠,让商户获得高效的获客渠道。',
about_value_title: '核心价值观',
about_value_1: '用户至上',
about_value_1_desc: '始终以用户需求为导向,提供极致的产品体验',
about_value_2: '透明公正',
about_value_2_desc: '所有交易公开透明,信用评级公正客观',
about_value_3: '技术创新',
about_value_3_desc: '持续投入前沿技术研发,引领行业变革',
about_value_4: '合规经营',
about_value_4_desc: '严格遵守法律法规,保障平台安全稳定',
about_milestone_title: '发展历程',
about_milestone_2025_q1: '项目启动,完成核心架构设计',
about_milestone_2025_q2: '智能合约开发完成,测试网上线',
about_milestone_2025_q3: '移动 App 与管理后台发布',
about_milestone_2025_q4: '主网上线,首批商户入驻',
about_milestone_2026_q1: '二级市场交易功能上线',
about_milestone_2026_q2: '用户突破 10 万,生态持续扩展',
about_partners_title: '合作伙伴',
// -- Features --
features_title: '强大的产品功能',
features_subtitle: '从券的发行到交易到核销,全链路覆盖',
feat_buy_title: '智能购券',
feat_buy_desc: '浏览海量优惠券AI 智能推荐最适合你的折扣券。支持分类筛选、价格排序、信用评级过滤。',
feat_market_title: '二级市场',
feat_market_desc: '像交易股票一样交易优惠券。限价单、市价单、挂单交易,价格实时更新,撮合引擎毫秒级响应。',
feat_contract_title: '合约清算',
feat_contract_desc: '区块链智能合约自动清算,消费者直接与商户结算,平台不触碰资金,安全透明。',
feat_rating_title: '信用评级',
feat_rating_desc: '基于履约率、用户评价、经营年限等多维度指标,为每个发行方建立透明的信用画像。',
feat_multi_title: '多端同步',
feat_multi_desc: 'iOS App、Android App、微信小程序、Web 端全覆盖,数据实时同步,随时随地交易。',
feat_ai_title: 'AI 助手',
feat_ai_desc: '内置智能 AI 助手,帮你分析市场趋势、推荐最佳买入时机、自动预警风险。',
// -- Solutions --
solutions_title: '解决方案',
solutions_subtitle: '无论你是消费者还是商户Genex 都能为你创造价值',
sol_consumer_title: '消费者方案',
sol_consumer_subtitle: '更聪明的消费方式',
sol_c_save_title: '省钱购物',
sol_c_save_desc: '以 85-95 折购买餐饮、零售、生活服务优惠券,日常消费立省 5%-15%',
sol_c_invest_title: '券投资',
sol_c_invest_desc: '低买高卖热门券,把握市场波动获取收益',
sol_c_gift_title: '礼品券',
sol_c_gift_desc: '购买精选品牌券作为礼物赠送,实惠又体面',
sol_merchant_title: '商户方案',
sol_merchant_subtitle: '高效的获客工具',
sol_m_acquire_title: '精准获客',
sol_m_acquire_desc: '通过发行优惠券吸引目标消费者,获客成本低于传统广告',
sol_m_finance_title: '短期融资',
sol_m_finance_desc: '券的预售模式实现短期融资,零利息,灵活便捷',
sol_m_loyalty_title: '会员运营',
sol_m_loyalty_desc: '通过券的分发和交易数据,精准分析用户画像,提升复购率',
// -- Developers --
dev_title: '开发者中心',
dev_subtitle: '接入 Genex 生态,构建你的应用',
dev_api_title: 'RESTful API',
dev_api_desc: '完整的 REST API 文档,覆盖券发行、交易、清算全流程',
dev_sdk_title: 'SDK',
dev_sdk_desc: '提供 JavaScript、Go、Dart 多语言 SDK快速集成',
dev_explorer_title: '区块链浏览器',
dev_explorer_desc: '查询链上交易记录、合约状态、区块信息',
dev_sandbox_title: '沙箱环境',
dev_sandbox_desc: '完整的测试环境,免费体验所有 API 功能',
dev_quickstart: '快速开始',
dev_doc_link: '查看完整文档',
dev_explorer_link: '打开浏览器',
// -- Contact --
contact_title: '联系我们',
contact_subtitle: '我们很乐意听取您的意见和建议',
contact_form_name: '您的姓名',
contact_form_email: '电子邮箱',
contact_form_subject: '主题',
contact_form_message: '留言内容',
contact_form_submit: '发送消息',
contact_form_success: '消息已发送,我们会尽快回复您!',
contact_office_title: '办公地址',
contact_office_addr: '深圳市南山区科技园',
contact_email_title: '电子邮箱',
contact_email_addr: 'contact@gogenex.com',
contact_social_title: '关注我们',
// -- Terms of Service --
terms_title: '用户协议',
terms_updated: '最后更新2025 年 1 月 1 日',
terms_s1_title: '1. 协议接受',
terms_s1_p1: '欢迎使用 Genex 平台(以下简称"平台")。在使用平台提供的任何服务之前,请您仔细阅读本用户协议(以下简称"本协议")。您注册、登录或使用本平台即表示您已阅读、理解并同意受本协议的约束。',
terms_s1_p2: '如果您不同意本协议的任何条款,请勿使用本平台服务。',
terms_s2_title: '2. 账户注册与安全',
terms_s2_p1: '您在注册账户时必须提供真实、准确、完整的个人信息,并在信息变更时及时更新。您有责任:',
terms_s2_li1: '妥善保管您的账户登录凭证,不得将账户转让或借予他人',
terms_s2_li2: '对通过您的账户进行的所有操作承担全部责任',
terms_s2_li3: '发现账户异常或安全隐患时,立即通知平台',
terms_s3_title: '3. 服务内容',
terms_s3_p1: '本平台为用户提供券资产的浏览、购买、出售、交易和核销等服务。平台仅作为交易撮合方,不参与券的发行和兑付。',
terms_s3_p2: '平台上展示的券产品信息由发行方提供,平台会进行合理审核,但不对信息的绝对准确性做出保证。',
terms_s4_title: '4. 交易规则',
terms_s4_p1: '用户在平台上进行的所有交易均须遵守平台公布的交易规则。交易一旦成交,不可撤销。用户应充分了解交易风险,审慎决策。',
terms_s5_title: '5. 知识产权',
terms_s5_p1: '本平台的所有内容,包括但不限于文字、图片、软件、商标、界面设计等,均受知识产权法律保护。未经平台书面授权,任何人不得复制、修改、传播或以其他方式使用。',
terms_s6_title: '6. 免责声明',
terms_s6_p1: '在法律允许的最大范围内,平台不对因不可抗力、第三方原因、用户自身原因导致的损失承担责任。平台可能因系统维护、升级等原因暂停服务,会尽量提前通知用户。',
// -- Privacy Policy --
privacy_title: '隐私政策',
privacy_updated: '最后更新2025 年 1 月 1 日',
privacy_s1_title: '1. 引言',
privacy_s1_p1: 'Genex 重视您的隐私保护。本隐私政策说明了我们如何收集、使用、存储和保护您的个人信息。使用本平台即表示您同意本政策所述的数据处理方式。',
privacy_s2_title: '2. 信息收集',
privacy_s2_p1: '我们可能收集以下类型的信息:',
privacy_s2_li1: '身份信息:姓名、手机号码、电子邮箱、身份证信息(实名认证时)',
privacy_s2_li2: '交易信息:购买记录、交易历史、钱包余额',
privacy_s2_li3: '设备信息:设备型号、操作系统版本、唯一设备标识符',
privacy_s2_li4: '使用数据:浏览记录、搜索关键词、功能使用频率',
privacy_s3_title: '3. 信息使用',
privacy_s3_p1: '我们使用收集的信息来提供、维护和改进平台服务,进行身份验证和安全防护,向您发送交易通知和平台更新,以及进行数据分析以优化用户体验。',
privacy_s4_title: '4. 信息共享',
privacy_s4_p1: '未经您的同意,我们不会向第三方出售或出租您的个人信息。在以下情况下,我们可能会共享您的信息:与合规要求相关的监管机构、为完成交易而必要的对手方、经您明确授权的第三方合作伙伴。',
privacy_s5_title: '5. 数据安全',
privacy_s5_p1: '我们采用行业标准的加密技术和安全措施保护您的个人信息,包括但不限于 SSL 加密传输、数据加密存储、访问控制和安全审计。但请注意,没有任何互联网传输方式是完全安全的。',
privacy_s6_title: '6. 您的权利',
privacy_s6_p1: '您有权访问、更正或删除您的个人信息。您可以通过账户设置或联系客服行使这些权利。如需删除账户及相关数据,请联系我们的客服团队。',
// -- Cookie Policy --
cookie_title: 'Cookie 政策',
cookie_updated: '最后更新2025 年 1 月 1 日',
cookie_s1_title: '1. 什么是 Cookie',
cookie_s1_p1: 'Cookie 是网站在您浏览时存储在您设备上的小型文本文件。它们帮助网站记住您的偏好设置,提供更好的浏览体验。',
cookie_s2_title: '2. 我们使用的 Cookie 类型',
cookie_s2_p1: '我们使用以下几类 Cookie 来提升您的使用体验:',
cookie_s2_h3_1: '必要性 Cookie',
cookie_s2_p2: '用于维持您的登录状态、语言偏好和安全验证。这些 Cookie 是平台正常运行所必需的。',
cookie_s2_h3_2: '分析性 Cookie',
cookie_s2_p3: '帮助我们了解用户如何使用平台,以便改进产品体验。这些数据是匿名聚合的。',
cookie_s2_h3_3: '功能性 Cookie',
cookie_s2_p4: '记住您的个性化设置,如语言选择、主题偏好等,为您提供定制化的使用体验。',
cookie_s3_title: '3. 管理 Cookie',
cookie_s3_p1: '您可以通过浏览器设置管理或删除 Cookie。请注意禁用某些 Cookie 可能影响平台部分功能的正常使用。',
// -- Risk Disclosure --
risk_title: '风险提示',
risk_updated: '最后更新2025 年 1 月 1 日',
risk_s1_title: '1. 一般风险提示',
risk_s1_p1: '券资产交易存在固有的市场风险。券的价格可能因市场供需、发行方经营状况、宏观经济环境等因素而发生波动。您在使用本平台进行交易之前,应充分了解相关风险。',
risk_s2_title: '2. 具体风险因素',
risk_s2_li1: '市场风险:券的二级市场价格可能低于您的买入价格,导致亏损',
risk_s2_li2: '信用风险:券的发行方可能因经营不善无法兑付,导致券失去价值',
risk_s2_li3: '流动性风险:部分券可能交易量较低,难以在短时间内以理想价格成交',
risk_s2_li4: '技术风险:区块链网络拥堵、系统故障等技术因素可能影响交易执行',
risk_s3_title: '3. 投资建议',
risk_s3_p1: '请根据自身财务状况和风险承受能力进行投资决策。不要投入超出自己承受能力的资金。过去的交易表现不代表未来的收益。',
risk_s4_title: '4. 免责声明',
risk_s4_p1: '本平台不提供任何投资建议。平台上的所有信息仅供参考,不构成购买或出售任何券产品的推荐或要约。用户应自行判断并承担交易结果。',
// -- Careers --
careers_title: '加入我们',
careers_subtitle: '与优秀的人一起,做有意义的事',
careers_job1_title: '高级全栈工程师',
careers_job1_desc: '负责平台核心功能开发,要求精通 TypeScript、React、Node.js有金融或区块链项目经验优先。',
careers_job2_title: '区块链工程师',
careers_job2_desc: '负责智能合约开发与维护、链上数据索引,要求熟悉 Solidity、Cosmos SDK。',
careers_job3_title: '移动端工程师',
careers_job3_desc: '负责 Flutter 跨平台 App 开发,追求极致的用户体验和性能优化。',
careers_job4_title: '安全工程师',
careers_job4_desc: '负责平台安全架构设计、渗透测试、漏洞修复,保障用户资产和数据安全。',
careers_job5_title: '数据分析师',
careers_job5_desc: '负责交易数据分析、用户行为建模、风控模型优化,驱动业务增长。',
careers_job6_title: 'UI/UX 设计师',
careers_job6_desc: '负责平台产品的视觉设计与交互体验,打造简洁、直觉的金融产品界面。',
careers_apply: '投递简历',
// -- Blog --
blog_title: '博客',
blog_subtitle: '行业洞察、产品动态、技术分享',
blog_p1_date: '2025-03-01',
blog_p1_title: 'Genex 2.0 正式上线:全新交易引擎与用户体验',
blog_p1_excerpt: '经过数月的精心打磨Genex 2.0 带来了全新的撮合引擎、更直观的交易界面和更强大的 AI 推荐系统。',
blog_p2_date: '2025-02-15',
blog_p2_title: '区块链如何保障您的每一笔券交易',
blog_p2_excerpt: '深入解析 Genex 底层区块链架构,了解智能合约如何实现自动清算与资产安全保障。',
blog_p3_date: '2025-02-01',
blog_p3_title: '券资产投资入门指南:从折扣购券到二级市场交易',
blog_p3_excerpt: '一篇文章带你了解券交易的基本概念、策略和风险管理,开启你的智慧消费之旅。',
blog_p4_date: '2025-01-20',
blog_p4_title: '商户成功案例:某连锁餐饮品牌的获客之道',
blog_p4_excerpt: '看某知名餐饮品牌如何通过 Genex 平台发行优惠券,实现月均获客量增长 300%。',
blog_p5_date: '2025-01-10',
blog_p5_title: 'AI 助手上线:让交易决策更智能',
blog_p5_excerpt: 'Genex AI 助手正式上线,它能帮你分析市场趋势、推荐最佳交易时机,做你的专属交易顾问。',
blog_p6_date: '2024-12-25',
blog_p6_title: 'Genex 生态版图:从国内走向全球',
blog_p6_excerpt: '回顾 Genex 的发展历程,展望 2025 年的全球化战略布局与产品路线图。',
// -- Press --
press_title: '媒体资讯',
press_subtitle: '了解 Genex 的最新动态',
press_p1_date: '2025 年 3 月',
press_p1_title: 'Genex 获得 A 轮融资,加速全球化布局',
press_p1_desc: 'Genex 宣布完成 A 轮融资,本轮融资将用于技术研发、团队扩张和海外市场拓展。公司表示将在未来一年内进入东南亚和欧洲市场。',
press_p2_date: '2025 年 1 月',
press_p2_title: 'Genex 与多家知名品牌达成战略合作',
press_p2_desc: '平台已与超过 200 家品牌商户建立合作关系,覆盖餐饮、零售、生活服务等多个领域,为用户提供更丰富的优惠券选择。',
press_p3_date: '2024 年 11 月',
press_p3_title: 'Genex 平台正式上线,开启券金融新时代',
press_p3_desc: 'Genex 券金融交易平台正式上线运营,首日注册用户突破万人。平台以区块链技术为核心,为消费者和商户搭建了安全、透明的券交易市场。',
// -- Help Center --
help_title: '帮助中心',
help_subtitle: '快速找到您需要的答案',
help_t1_title: '如何购买优惠券?',
help_t1_desc: '浏览首页或搜索您感兴趣的品牌,选择券面额和数量,使用支持的支付方式完成购买即可。',
help_t2_title: '如何在二级市场交易?',
help_t2_desc: '进入"交易"页面,您可以挂单出售持有的券,也可以浏览市场上其他用户出售的券并购买。',
help_t3_title: '支持哪些支付方式?',
help_t3_desc: '平台支持支付宝、微信支付、银行卡和平台余额等多种支付方式。',
help_t4_title: '我的资金安全吗?',
help_t4_desc: '平台采用区块链智能合约清算,资金由合约托管,不经过平台账户,确保安全透明。',
help_t5_title: '如何使用优惠券?',
help_t5_desc: '在 App 或小程序中打开您持有的券,到店出示券码或让商户扫描即可核销使用。',
help_t6_title: '遇到问题怎么办?',
help_t6_desc: '您可以通过联系我们页面提交工单,或拨打客服热线获取即时帮助。',
help_contact_hint: '没有找到答案?',
help_contact_link: '联系客服',
// -- Community --
community_title: '社区',
community_subtitle: '加入 Genex 社区,与志同道合的伙伴交流',
community_c1_title: '微信社群',
community_c1_desc: '加入官方微信群,第一时间获取平台动态、交易策略分享和专属福利。',
community_c2_title: 'Twitter / X',
community_c2_desc: '关注 @Genex 官方账号,获取最新产品更新和行业资讯。',
community_c3_title: 'Telegram',
community_c3_desc: '加入 Telegram 频道,参与全球社区讨论,了解海外市场动态。',
community_c4_title: '开发者论坛',
community_c4_desc: '技术交流、API 使用答疑、SDK 集成讨论,与其他开发者共建生态。',
community_c5_title: '微信公众号',
community_c5_desc: '关注「Genex 券金融」公众号,阅读深度分析文章和使用教程。',
community_c6_title: '线上活动',
community_c6_desc: '定期举办 AMA、交易大赛、空投活动丰厚奖励等你来拿。',
// -- System Status --
status_title: '系统状态',
status_subtitle: '实时监控所有服务运行状态',
status_all_ok: '所有系统运行正常',
status_s1_name: '交易撮合引擎',
status_s1_status: '正常',
status_s2_name: '用户服务',
status_s2_status: '正常',
status_s3_name: '支付网关',
status_s3_status: '正常',
status_s4_name: '区块链网络',
status_s4_status: '正常',
status_s5_name: 'API 服务',
status_s5_status: '正常',
status_s6_name: '通知服务',
status_s6_status: '正常',
status_updated: '状态每 5 分钟自动更新',
// -- Download --
download_title: '下载 Genex',
download_subtitle: '随时随地,开始您的智慧消费之旅',
download_ios_title: 'iOS App',
download_ios_desc: '支持 iPhone 和 iPad流畅的原生体验',
download_ios_btn: 'App Store 下载',
download_android_title: 'Android App',
download_android_desc: '支持 Android 8.0 及以上版本',
download_android_btn: 'Google Play 下载',
download_web_title: 'Web 版',
download_web_desc: '无需安装,浏览器直接使用全部功能',
download_web_btn: '打开 Web 版',
download_miniapp_title: '微信小程序',
download_miniapp_desc: '微信搜索"Genex"即可使用,轻量便捷',
download_miniapp_btn: '扫码体验',
};
export default zhCN;

View File

@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [{ "name": "next" }],
"paths": { "@/*": ["./src/*"] }
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}