Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 6eb4958f50 | |||
| 7cefa3091f | |||
| 327828ea56 | |||
| ffe6dc7c86 | |||
| ab39fdead3 | |||
| 06185b67b4 | |||
| 832671e308 | |||
| 92cedd235b | |||
| 4ae452336c | |||
| 4c63c00a18 | |||
| 22baa715fd |
@@ -0,0 +1,21 @@
|
||||
# my-resume Agent Skills
|
||||
|
||||
本目录存放 `my-resume` 的项目级 Agent skill。规则参考 SeaCloud 项目的 agent skill 组织方式,并按当前 Vite、Three.js 和作品集页面场景保留最小必要规范。
|
||||
|
||||
## 使用入口
|
||||
|
||||
1. 进入仓库后先读 `AGENTS.md`。
|
||||
2. 创建或修改代码文件时,使用 `header-comment-sync` 保持中文文件头、导出声明和复杂逻辑注释同步。
|
||||
3. 提交或推送代码时,使用 `chinese-commit-message` 生成英文 type 前缀加中文摘要的 commit message。
|
||||
4. 所有 skill 的触达文件补齐规则只处理本次任务相关文件,不要求为了单次任务全仓扫描。
|
||||
|
||||
## 当前项目事实
|
||||
|
||||
- 应用类型:独立 Vite + Three.js 作品集站点。
|
||||
- 技术栈:Vite、Three.js、GSAP、Lenis、原生 JavaScript。
|
||||
- 关键边界:交互与动效逻辑需要关注移动端性能、 reduced-motion 降级和首屏可读性。
|
||||
|
||||
## Skill 索引
|
||||
|
||||
- `header-comment-sync`:中文文件头、导出声明、复杂逻辑和风险边界注释。
|
||||
- `chinese-commit-message`:中文提交信息格式。
|
||||
@@ -0,0 +1,54 @@
|
||||
---
|
||||
name: chinese-commit-message
|
||||
description: 在本仓库执行 git commit、整理提交说明或准备推送时使用,保持提交信息使用英文类型前缀加中文描述。
|
||||
---
|
||||
|
||||
# 中文提交信息
|
||||
|
||||
## 何时使用
|
||||
|
||||
- 用户要求提交、推送或整理 commit message。
|
||||
- 你准备执行 `git commit`。
|
||||
- 需要总结当前 diff 的提交说明。
|
||||
|
||||
## 核心要求
|
||||
|
||||
1. 提交信息必须使用英文 type 加中文摘要,例如 `feat: 补齐作品集 agent skill 规范`。
|
||||
2. 常用 type 优先使用 `feat`、`fix`、`refactor`、`docs`、`chore`。
|
||||
3. 不强制 scope;只有模块边界非常清楚时才使用 `feat(scene): ...`。
|
||||
4. 标题部分使用简洁中文,直接说明改动,不写“修改一下”“更新代码”这类空泛描述。
|
||||
5. 非 trivial 改动建议补中文正文,用扁平 bullet 按文件或内容块说明关键动作。
|
||||
6. 正文与标题之间空一行;正文每条 bullet 对应一个独立文件或明确内容块。
|
||||
7. 如果用户指定提交信息,优先尊重用户原意,只做必要格式整理。
|
||||
|
||||
## 触达文件补齐
|
||||
|
||||
- 不要求为了提交信息单独全仓扫描。
|
||||
- 提交前如果 diff 中的触达文件明显违反对应 skill 的触达补齐要求,应先修正当前相关链路,再整理 commit message。
|
||||
- commit 正文应概括本次触达补齐,例如“补齐触达文件注释”“同步 README 目录说明”。
|
||||
|
||||
## 推荐结构
|
||||
|
||||
```text
|
||||
feat: 补齐作品集 agent skill 规范
|
||||
|
||||
- .agents/README.md: 新增项目级 skill 索引和使用入口
|
||||
- .agents/skills: 补充中文提交与注释同步规则
|
||||
- AGENTS.md: 增加本地 skill 入口
|
||||
```
|
||||
|
||||
## 不推荐写法
|
||||
|
||||
- `新增规则`
|
||||
- `feat: add skills`
|
||||
- `fix bug`
|
||||
- `update`
|
||||
- 复杂改动只有标题没有正文。
|
||||
- 正文写成长段流水账或嵌套列表。
|
||||
|
||||
## 落地检查
|
||||
|
||||
- type 是否合理。
|
||||
- 中文摘要是否覆盖主改动。
|
||||
- 是否需要正文。
|
||||
- 正文是否按文件或内容块归纳。
|
||||
@@ -0,0 +1,66 @@
|
||||
---
|
||||
name: header-comment-sync
|
||||
description: 在本仓库创建或修改 ts、tsx、js、jsx、mjs、cjs、vue、astro 文件时使用,保持中文文件头、导出声明、复杂逻辑和风险边界注释准确。
|
||||
---
|
||||
|
||||
# 注释规范与同步
|
||||
|
||||
## 目标
|
||||
|
||||
让下一次进入文件的维护者能快速理解当前职责、关键约束和风险边界。注释解释“为什么”和“边界”,不复述代码表面行为。
|
||||
|
||||
## 适用场景
|
||||
|
||||
- 新增或修改页面入口、Three.js 场景、动效编排、渲染、数据、工具或脚本文件。
|
||||
- 修改滚动时间线、WebGL shader、响应式布局、性能降级、 reduced-motion 或首屏渲染逻辑。
|
||||
- 发现旧注释与当前代码行为不一致。
|
||||
|
||||
## 核心原则
|
||||
|
||||
- 注释使用中文,描述当前事实。
|
||||
- 简单局部变量不强行注释。
|
||||
- 文件头说明文件当前职责,1 到 2 行即可。
|
||||
- 导出的函数、类、类型、常量、配置对象和复杂模块方法应有用途说明。
|
||||
- 复杂动画相位、shader uniform、滚动联动、性能降级和浏览器兼容边界需要短注释。
|
||||
- TODO/FIXME 必须说明触发条件、剩余动作和可删除条件。
|
||||
|
||||
## 文件头规则
|
||||
|
||||
- 每个适用文件都要有准确文件头。
|
||||
- 如果文件必须以 `"use client"` 或 `"use server"` 开头,文件头注释放在指令之后、导入之前。
|
||||
- Vue 文件不为了补头注释重排模板结构;在 `<script>` 或 `<script setup>` 顶部补当前职责说明。
|
||||
- 文件头不要写“本文件用于...”这类空话,直接说明业务角色。
|
||||
|
||||
```js
|
||||
/**
|
||||
* 装配作品集滚动叙事、WebGL 背景和移动端性能降级策略。
|
||||
*/
|
||||
```
|
||||
|
||||
## JSDoc 与局部注释
|
||||
|
||||
- 导出的函数、类、类型、常量和配置对象优先使用 JSDoc。
|
||||
- 非导出但复杂的解析、格式化、请求构造、动画相位派生、回调工厂也要补。
|
||||
- 只在局部逻辑确实有约束时使用行内注释,例如性能降级、兼容字段、shader 参数和临时迁移。
|
||||
- 不要在每一行、简单 DOM 拼装、短生命周期变量上堆注释。
|
||||
|
||||
## 触达文件补齐
|
||||
|
||||
- 不要求为了注释规范单独全仓扫描。
|
||||
- 只要本次任务修改了适用文件,就顺手补齐明显缺失或过时的文件头、导出声明、共享类型、复杂回调和协议注释。
|
||||
- 大文件可按触达区域优先,但同文件内裸露的顶层导出和共享类型应一起补。
|
||||
- 如果一次补齐整个大文件会明显超出需求范围,至少补齐当前需求链路和顶层声明,并在交付说明里说明剩余范围。
|
||||
|
||||
## 不推荐的写法
|
||||
|
||||
- 注释只写“处理数据”“点击事件”这种无信息内容。
|
||||
- 注释描述旧方案,和当前代码矛盾。
|
||||
- 为简单 DOM 拼装、简单赋值、短生命周期变量写注释。
|
||||
- 用英文口号、emoji 或情绪化标记替代项目内中文说明。
|
||||
|
||||
## 落地检查
|
||||
|
||||
- 修改后的文件头是否准确。
|
||||
- 新增/修改的导出声明是否有必要说明。
|
||||
- 复杂逻辑是否解释了约束而不是复述代码。
|
||||
- 旧注释是否仍然可信。
|
||||
@@ -0,0 +1,17 @@
|
||||
# my-resume Agent 入口
|
||||
|
||||
## 项目规则
|
||||
|
||||
- 使用当前 Vite + Three.js + 原生 JavaScript 结构,不引入无关框架。
|
||||
- 页面内容、计划文档和交付说明优先使用中文。
|
||||
- 改动可运行入口、目录或重要文件时,同步更新 `README.md`。
|
||||
|
||||
## 必用 Skill
|
||||
|
||||
- 创建或修改 `ts`、`tsx`、`js`、`jsx`、`mjs`、`cjs`、`vue`、`astro` 文件时,使用 `./.agents/skills/header-comment-sync/SKILL.md`。
|
||||
- 执行 `git commit`、整理提交说明或准备推送时,使用 `./.agents/skills/chinese-commit-message/SKILL.md`。
|
||||
|
||||
## 本地规则
|
||||
|
||||
- 先读 `.agents/README.md`,再按任务读取匹配的 skill。
|
||||
- skill 的触达文件补齐只处理本次修改相关文件,不为单次任务全仓扫描。
|
||||
@@ -1,10 +1,20 @@
|
||||
# 王元有 - 前端工程师简历网站
|
||||
# Wang Yuanyou Fluid Portfolio
|
||||
|
||||
基于 Astro 的个人简历网站,内容整理自 `王元有-前端工程师.pdf`。
|
||||
Independent Vite + Three.js portfolio site for 王元有, focused on a more grounded frontend-engineering resume.
|
||||
|
||||
## Commands
|
||||
|
||||
```bash
|
||||
npm install
|
||||
npm run dev
|
||||
npm run dev -- --port 5177
|
||||
npm run build
|
||||
```
|
||||
|
||||
The page uses a restrained scroll-aware WebGL backdrop, critical first-paint styling, grounded resume summaries, responsive layout, subtle scroll reveals, project cards, skills, experience, and contact sections.
|
||||
|
||||
## Agent Notes
|
||||
|
||||
- `AGENTS.md`: Codex/Agent 入口规则。
|
||||
- `.agents/README.md`: 本项目 Agent skill 索引和使用入口。
|
||||
- `.agents/skills/chinese-commit-message/SKILL.md`: 中文提交信息规范。
|
||||
- `.agents/skills/header-comment-sync/SKILL.md`: 中文文件头和注释同步规范。
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import { defineConfig } from "astro/config";
|
||||
|
||||
export default defineConfig({
|
||||
output: "static",
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
# 设计文档:「与湛兮(花名)的数字思维体对话」作品集重设计
|
||||
|
||||
日期:2026-06-11
|
||||
状态:已批准
|
||||
备份:`../wang-yuanyou-fluid-portfolio-backup-20260611-110154/`
|
||||
|
||||
## 概念
|
||||
|
||||
整站是访客与「湛兮(花名)的 AI 分身」的一场对话记录。简历核心(最近一年做 SSE 流式对话、打字机渲染、思考态动画)即设计语言来源——网站本身就是其工作成果的活演示。3D 流体 =「思维体」,是画面中唯一的角色。
|
||||
|
||||
决策记录:
|
||||
|
||||
- 设计隐喻:AI 对话流(vs 职业河流 / 跨端粒子宇宙)
|
||||
- 推进方式:滚动驱动的对话(vs 真·对话界面 / 滚动+问答入口)
|
||||
- 流体形态:单一思维流体 + 三状态状态机(vs raymarched metaballs / GPGPU 粒子流)
|
||||
- 视觉基调:深色 AI 实验室(vs 浅色编辑风 / 双主题)
|
||||
- 技术路线:原生 Three.js 自写 shader + GSAP ScrollTrigger + Lenis(vs 纯手写 / R3F)
|
||||
|
||||
## 信息架构:7 轮问答
|
||||
|
||||
| # | 访客提问 | 回答内容 | 数据源 |
|
||||
|---|---|---|---|
|
||||
| 0 | —(开机自检) | 「你好,我是湛兮(花名)。」+ 身份一行 + 6 指标 token 式吐出 | metrics |
|
||||
| 1 | 先介绍一下你自己? | 4 条概述逐行流式输出 | resumeSignals |
|
||||
| 2 | 最近一年具体在做什么? | 4 张焦点卡依次「生成」 | focusAreas |
|
||||
| 3 | 有实际项目证明吗? | 4 个项目,各为一轮子对话:项目名打字机 → 模块逐条吐出 | projects |
|
||||
| 4 | 技能栈展开讲讲? | 7 组技能标签 token 流喷发归位 | skills |
|
||||
| 5 | 之前的团队经历? | 3 段经历沿垂直对话流时间线生成 | experiences |
|
||||
| 6 | 怎么联系你? | 联系方式 + 流体归于平静,「对话已保存」收尾 | — |
|
||||
|
||||
页面右侧(移动端顶部)设「会话进度轨」:7 节点对应 7 轮,可点击跳转,保证 HR 快速扫读。
|
||||
|
||||
## 思维流体(Three.js 自写 shader)
|
||||
|
||||
- 几何:高细分 IcosahedronGeometry;顶点着色器 3 层 FBM simplex 噪声液态位移
|
||||
- 片元:Fresnel 边缘辉光 + 双色渐变(青 #22d3ee ↔ 紫 #a78bfa)+ 加性内核光晕
|
||||
- 伴生粒子:约 2000 GPU 粒子,curl noise 流场
|
||||
- 状态机(uniform 插值过渡):
|
||||
- `idle`:低频低幅呼吸,粒子懒散环绕
|
||||
- `thinking`:收缩 0.85x,噪声频率 ×3 搅动,色温升高,粒子吸入
|
||||
- `answering`:回弹 1.1x 归位,粒子向内容区喷发消散,辉光脉冲与文字生成同步
|
||||
- 流体位置随轮次左右缓移(lerp)与内容互让;鼠标轻微视差
|
||||
|
||||
## 非线性动画编排(GSAP ScrollTrigger + Lenis)
|
||||
|
||||
每轮问答为一个 pin 区段,滚动量映射轮内时间轴:
|
||||
|
||||
1. 提问(0→15%):访客气泡逐字打出,steps() 离散节奏
|
||||
2. 思考(15→35%):流体 thinking,内容区仅 shimmer 占位——刻意停顿即非线性核心
|
||||
3. 爆发生成(35→75%):expo.out 爆发;标题打字机、卡片不等间隔 stagger(模拟 token 不均匀到达)、数字滚动跳变
|
||||
4. 余韵(75→100%):流体回 idle,内容微视差上浮,解除 pin
|
||||
|
||||
每轮思考时长 / 爆发曲线 / stagger 间隔均不同(场景配置驱动),避免节奏雷同。向上滚动时时间轴反播,内容「被收回」。
|
||||
|
||||
降级:`prefers-reduced-motion` → 关 pin 与打字机、内容直出、流体仅呼吸;移动端粒子减半、DPR ≤ 2、细分降档。
|
||||
|
||||
## 视觉系统
|
||||
|
||||
- 背景 #070b14 近黑蓝 + 极淡网格点阵 + 流体环境光溢出
|
||||
- 强调色:青 #22d3ee(访客/交互)、紫 #a78bfa(思维体/回答)、琥珀 #fbbf24(仅指标数字)
|
||||
- 气泡语言:提问 = 右对齐细边框气泡;回答 = 无框流式文本块 + 左侧渐变「生成光标」竖线
|
||||
- 字体:等宽(JetBrains Mono / 思源等宽回退)用于指标、token、技能标签;正文 Inter + 思源黑体
|
||||
- 细节:回答块尾闪烁光标 ▋;项目 logo 复用 public/logos/
|
||||
|
||||
## 文件结构
|
||||
|
||||
```
|
||||
src/
|
||||
main.js # 入口:装配 + Lenis + 进度轨
|
||||
resume-data.js # 不动(数据即简历)
|
||||
dialogue.js # 7 轮问答文案与节奏配置
|
||||
render.js # 由 resume-data 生成各轮 DOM
|
||||
choreography.js # GSAP ScrollTrigger 时间轴编排
|
||||
mind/
|
||||
mind.js # 流体场景、状态机、粒子
|
||||
shaders.js # GLSL
|
||||
styles.css # 重写
|
||||
index.html # 重写骨架
|
||||
```
|
||||
|
||||
新依赖:gsap、lenis。重写 main.js / styles.css / index.html(旧版已整目录备份)。
|
||||
|
||||
## 性能与验收
|
||||
|
||||
- 单 canvas 固定底层;rAF 与 GSAP ticker 合并;目标桌面 60fps、移动 30fps+
|
||||
- 文字全部真实 DOM(可选中/可索引),打字机仅控制 reveal;进度轨 + 锚点保证可跳转
|
||||
- 验收:7 轮完整走查(含回滚反播)、375px 移动端、reduced-motion 降级、Lighthouse 不低于现版本
|
||||
@@ -0,0 +1,56 @@
|
||||
# 「数字思维体对话」作品集重设计 实现计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**Goal:** 将现有作品集重写为「与湛兮(花名)的 AI 分身对话」式单页站点:滚动驱动的 7 轮问答 + shader 思维流体状态机。
|
||||
|
||||
**Architecture:** Vite 原生 JS。`resume-data.js` 不动;`dialogue.js` 定义 7 轮问答节奏配置;`render.js` 生成 DOM;`mind/` 为 Three.js shader 流体(idle/thinking/answering 状态机 + GPU 粒子);`choreography.js` 用 GSAP ScrollTrigger pin+scrub 编排每轮「提问→思考→爆发生成→余韵」;Lenis 平滑滚动。
|
||||
|
||||
**Tech Stack:** Vite 7, Three.js 0.184, GSAP ScrollTrigger, Lenis, 自写 GLSL(FBM simplex + Fresnel)。
|
||||
|
||||
**设计文档:** `docs/plans/2026-06-11-ai-dialogue-redesign-design.md`(已批准)
|
||||
**备份:** `../wang-yuanyou-fluid-portfolio-backup-20260611-110154/`
|
||||
|
||||
---
|
||||
|
||||
### Task 1: 依赖安装
|
||||
- `pnpm add gsap lenis`
|
||||
- 验证: package.json 出现两依赖,`pnpm dev` 可启动。
|
||||
|
||||
### Task 2: `src/dialogue.js` — 轮次配置
|
||||
- 7 轮:boot / intro(resumeSignals) / focus(focusAreas) / projects / skills / experience / contact。
|
||||
- 每轮字段:id、label(进度轨)、question、type、side(流体停靠 -1/1)、palette(双色)、pinLength、qEnd/thinkEnd(相位)、staggerEach——逐轮不同以保证节奏差异。
|
||||
- 联系信息常量:邮箱 419021733@qq.com / wmagmgema521@gmail.com、电话 19980439383、GitHub zhanBoss、男·29岁·电子科技大学 信息管理与信息系统 本科。
|
||||
|
||||
### Task 3: `index.html` — 骨架重写
|
||||
- canvas#mind-canvas(fixed 底层)、.grid-overlay、header.hud(左:姓名/DIGITAL MIND;右:● ONLINE 状态灯)、nav#session-rail、main#dialogue(空,由 render.js 填充)、Google Fonts(JetBrains Mono + Inter, display=swap)。
|
||||
|
||||
### Task 4: `src/styles.css` — 深色 AI 实验室
|
||||
- 背景 #070b14、点阵网格 overlay、青 #22d3ee / 紫 #a78bfa / 琥珀 #fbbf24。
|
||||
- 气泡语言:.q-bubble 右对齐细边框;.answer 左侧渐变生成竖线 + 尾部 ▋ 闪烁光标;token/指标/技能用等宽字体。
|
||||
- 各轮内容样式:metrics token、focus 卡、project 块、skill 标签云、timeline、contact。
|
||||
- 进度轨右侧固定(移动端转顶部水平);≤768px 移动端布局;prefers-reduced-motion 关闭闪烁。
|
||||
|
||||
### Task 5: `src/render.js` — DOM 生成
|
||||
- 每轮 section.round 结构:q-bubble(空文本,编排时打字)→ .thinking(三点 shimmer)→ .answer(.gen 子项供 stagger)。
|
||||
- 按 type 分发渲染器,全部读 resume-data.js;boot 轮含问候打字行 + 身份行 + metrics token。
|
||||
- 文案全部真实 DOM(初始由 CSS/GSAP 隐藏,reduced-motion 时直接可见)。
|
||||
|
||||
### Task 6: `src/mind/shaders.js` + `src/mind/mind.js` — 思维流体
|
||||
- 顶点:Ashima simplex 3D + 3 octave FBM 位移;片元:Fresnel 辉光 + uColorA/uColorB 渐变 + uHeat 色温 + AdditiveBlending。
|
||||
- 粒子:~2000(移动端 900),属性 seed/radius/speed/phase,轨道半径受 uAttract(吸入)与 uBurst(喷发,setState('answering') 时置 1 后衰减)控制,全 GPU。
|
||||
- 状态机 STATES{idle, thinking, answering},每帧 lerp uniforms;setSide(dir) 左右停靠(移动端居中缩小);setPalette 双色渐变;指针视差;DPR≤2;resize。
|
||||
|
||||
### Task 7: `src/choreography.js` — 滚动编排
|
||||
- Lenis + ScrollTrigger 集成(gsap.ticker 驱动)。
|
||||
- boot 轮:页面加载后时间驱动的开场时间轴(非滚动)。
|
||||
- 轮 1-6:pin + scrub 时间轴,相位 0→qEnd 打字(steps 离散)→thinkEnd 思考 shimmer→0.78 爆发 stagger(expo.out,不等间隔)→1 余韵视差;onUpdate 按相位切 mind 状态;enter/enterBack 切 side+palette。
|
||||
- 进度轨高亮与 lenis.scrollTo 跳转。
|
||||
- reduced-motion:跳过 pin/打字机,内容直出。
|
||||
|
||||
### Task 8: `src/main.js` — 装配
|
||||
- 渲染 DOM → 建 rail → createMind → choreography → boot 开场。
|
||||
|
||||
### Task 9: 验证
|
||||
- `pnpm dev` + Playwright:桌面 1440px 走查 7 轮(截图 hero/中段/尾段)、向上回滚反播、375px 移动端、console 无错误。
|
||||
- `pnpm build` 成功。
|
||||
+41
@@ -0,0 +1,41 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>湛兮(花名) · 数字思维体 / Digital Mind</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="湛兮(花名) — 7 年 Web 与跨端前端工程师。一场与他的数字思维体的对话:AI 对话链路、控制台业务、跨端内容流与工程实践。"
|
||||
/>
|
||||
<link rel="icon" href="/favicon.svg" type="image/svg+xml" />
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;700&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
<link rel="stylesheet" href="/src/styles.css" />
|
||||
</head>
|
||||
<body>
|
||||
<canvas id="mind-canvas" aria-hidden="true"></canvas>
|
||||
<div class="grid-overlay" aria-hidden="true"></div>
|
||||
|
||||
<header class="hud">
|
||||
<div class="hud-id">
|
||||
<span class="hud-name">ZHAN XI</span>
|
||||
<span class="hud-sub">DIGITAL MIND · v7.0</span>
|
||||
</div>
|
||||
<div class="hud-status">
|
||||
<span class="status-dot" aria-hidden="true"></span>
|
||||
<span class="status-text">ONLINE</span>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<nav id="session-rail" aria-label="会话进度"></nav>
|
||||
|
||||
<main id="dialogue"></main>
|
||||
|
||||
<script type="module" src="/src/main.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
Generated
+147
-3810
File diff suppressed because it is too large
Load Diff
+11
-9
@@ -1,17 +1,19 @@
|
||||
{
|
||||
"name": "wang-yuanyou-resume-site",
|
||||
"name": "wang-yuanyou-fluid-portfolio",
|
||||
"version": "1.0.0",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"build": "astro build",
|
||||
"build:test": "PUBLIC_APP_ENV_LABEL=测试环境 astro build",
|
||||
"build:prod": "PUBLIC_APP_ENV_LABEL=生产环境 astro build",
|
||||
"preview": "astro preview"
|
||||
"dev": "vite --host 0.0.0.0",
|
||||
"build": "vite build",
|
||||
"build:test": "PUBLIC_APP_ENV_LABEL=测试环境 vite build",
|
||||
"build:prod": "PUBLIC_APP_ENV_LABEL=生产环境 vite build",
|
||||
"preview": "vite preview --host 0.0.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"astro": "^6.3.1"
|
||||
},
|
||||
"devDependencies": {}
|
||||
"gsap": "^3.15.0",
|
||||
"lenis": "^1.3.23",
|
||||
"three": "^0.184.0",
|
||||
"vite": "^7.2.7"
|
||||
}
|
||||
}
|
||||
|
||||
Generated
+676
@@ -0,0 +1,676 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
dependencies:
|
||||
gsap:
|
||||
specifier: ^3.15.0
|
||||
version: 3.15.0
|
||||
lenis:
|
||||
specifier: ^1.3.23
|
||||
version: 1.3.23
|
||||
three:
|
||||
specifier: ^0.184.0
|
||||
version: 0.184.0
|
||||
vite:
|
||||
specifier: ^7.2.7
|
||||
version: 7.3.5
|
||||
|
||||
packages:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.61.1':
|
||||
resolution: {integrity: sha512-JnBB8MdXj45cajvTuO5FmPlvFVJRQgvrz1uSEl3NwqFnReAPGwb8EanbGi4z2nRaqLzjJSv5/JmycoTKlRZxHA==}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-android-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-Jx2g7iSjw4AOT0HDPHM9RV3GNjRXwybWtSFZiZAYUTjUwjVrYIwq3kBf+LnhqJlzXFAqTAh2F7IGI+O568exPw==}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-0F1L/Z3Eqv8mT2n3dCpeO8GcTvHvVqkP5/t6DMsn0KzhYVcg+s7Ncl5DS8qjKYEeio6Az0Gt6nyBORay5qIlCA==}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.61.1':
|
||||
resolution: {integrity: sha512-qLttcH871ujY4YcVfUSShhOw+CsoTatYz8gRbHO7Bb92QH059/P0y5do1KMs41fY0BpD2x4AJH/gID0zFiqVKQ==}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-fUI4RapGE0Oh3mb8mgfvC1O2nU1RpDZUKnDQm3xB1Ipg7C2wTs5Kstz7G2uWK99a8S2yTMq8/P4uycwNa0nJyw==}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.61.1':
|
||||
resolution: {integrity: sha512-H5YrdvJaDtI/U9/emrD4b++xkvp3y/JvOe4rizHbxvkyMfRS/CiRYdji+Pl8D0brEaNFWUh1drQxgAGIl6Xudw==}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.61.1':
|
||||
resolution: {integrity: sha512-Q8CBCCQtDFrYtXoeUXSrnFXKOnyUhx6bz+SkL6A0E7V8kAiCJ5pamq1WtbfpVGhR5TSpXY6ak3avmDc5fHTyJA==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.61.1':
|
||||
resolution: {integrity: sha512-nwnhk1581l0FBVellGcVCAT0Oi06onEA3WB53sf01VO3I0UPBkMH9sXONYME2K0ovXcNayJfNtHfm6mpJElatQ==}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-x5Xr49hwt3hdW75UOZm3395YwwzPyauktslv29KpWL/T+vVAzoT3azLcTWv0eMciBNrx+DYjH4paehHoLpPvpg==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-unMS3H73DpaoPyyEVPjGKleM/s0mkmsauTENpw4INQY8y4+IuLNjkueQ5QCtC0D3N38Y38yhAU8OoZ20S2Tm6w==}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-zNZzGRnAhwjFEYmvphJRV5XaQGjs62cCmeYYHUT//NbvEnHauw+I85nGG+SiVg5ld4GX8D1IbKIX+ozITQnhMQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-LdpWGL8X209B2SIvWjqlc8VZgM6PKfontSerGepuldQmHYrAOtnMCXeJkxXGbC+PPZVOuu5czJo7fNV6aeW8rQ==}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-EC5kTtNaNGOmbMGqar8dvJy6y/hg99GAwjfBz++pxZhQATXGcRjd6c5en5wcbru0vkRmiMGsQKdMJOOf6sza4g==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-8hiwp6D4acEcNK78I4rP0/XtS1sknWIAMJBPdR4l6zUtyTm5KiTDr5bXmWt4foY7nAN7AThDHgkLIEZOWKbzWw==}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-10dh/h/BqA7DuMPWSxkR8uks18FRwnwOEqr5zOTEl+NOwP/OMzKX8OFR/Of9xxDA7D5qef1Nzar5WDD2kCCr1g==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-YKJ5lg35DP17gcAOggnihe+APw9HLyj1Xn7gsmGumBJAUDa6NGXNixJzmkWLhcK9TOuuyQjdamzvJefkO7qHZQ==}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-Mlil5G2Jj6a7B3LWGctg+XPL9vdXYuzCtNXfxOQ0nPjc2m6ueUktocPGH9bnAM0bNRKb/bAWTujUU7IJQdQA+g==}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-bVWIOIk6pV01p4CdUbPP7CJ/434z+OooYjDuFcR+44N35YvKUC66G8MGnvcWx5mWKW3g61J+t74l3Kj15Kwn2Q==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.61.1':
|
||||
resolution: {integrity: sha512-qy5pBvZbqNFheBz61R1rzsezjm0J7O2oNGoWtGoY89SZYLUfxAJTBAqDChqAIdB4rCiIbi9nF7yZ83GnNiLwSw==}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.61.1':
|
||||
resolution: {integrity: sha512-E83TXjI4zm0+5f2qO+UOudaCYIhYwpJ5jq6YCZNIZ+6CbfhKrkAGezeiASBL9ElxAxFsRS9ZhESv8mfnj6TKeg==}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.61.1':
|
||||
resolution: {integrity: sha512-fbWnKqVkjrJN38vNe3ahkbk6iejS/3b0Nt7EEtPpE6RBacZcGXNKbzfHN3GUUlXOPghUg0j6XUGrtjX9z1sIvA==}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.61.1':
|
||||
resolution: {integrity: sha512-ArMl38iVAbk0New1ogihQNY6iphLi4ZaRsa037gUzv5yeKPY8TD3Dmy4x2RNC1VztU/uqm+G+/RwFrSka3Oy2g==}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.61.1':
|
||||
resolution: {integrity: sha512-0mYtjHS9ucAbcATycCNK9IGBk/cCe/ma7EmSLGZdsxnOA8cjRIyU04wDpVAD9NiOfLUR9KTxdiO53uOkherqjQ==}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.61.1':
|
||||
resolution: {integrity: sha512-gK1iCEPfpoSG9wfBihXxvBMi8ZfcWffYkEsC/Eih+iFENTaewvNcrEQ69lIOWYO5pePHKLHHO7nq5AILGO/HQQ==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.61.1':
|
||||
resolution: {integrity: sha512-X+zaP2x+j4RXGfbp/seSoRHWnPxzApilDszisZxbYH5C/jTxFhCtDNdPGZb9lJyYPs24wGxruPF7Y+sIXt9Gzw==}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
|
||||
'@types/estree@1.0.9':
|
||||
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
|
||||
|
||||
esbuild@0.27.7:
|
||||
resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
fdir@6.5.0:
|
||||
resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
peerDependencies:
|
||||
picomatch: ^3 || ^4
|
||||
peerDependenciesMeta:
|
||||
picomatch:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
gsap@3.15.0:
|
||||
resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==}
|
||||
|
||||
lenis@1.3.23:
|
||||
resolution: {integrity: sha512-YxYq3TJqj9sJNv0V9SkyQHejt14xwyIwgDaaMK89Uf9SxQfIszu+gTQSSphh6BWlLTNVKvvXAGkg+Zf+oFIevg==}
|
||||
peerDependencies:
|
||||
'@nuxt/kit': '>=3.0.0'
|
||||
react: '>=17.0.0'
|
||||
vue: '>=3.0.0'
|
||||
peerDependenciesMeta:
|
||||
'@nuxt/kit':
|
||||
optional: true
|
||||
react:
|
||||
optional: true
|
||||
vue:
|
||||
optional: true
|
||||
|
||||
nanoid@3.3.12:
|
||||
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==}
|
||||
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||
hasBin: true
|
||||
|
||||
picocolors@1.1.1:
|
||||
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||
|
||||
picomatch@4.0.4:
|
||||
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
postcss@8.5.15:
|
||||
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
|
||||
engines: {node: ^10 || ^12 || >=14}
|
||||
|
||||
rollup@4.61.1:
|
||||
resolution: {integrity: sha512-I4KW6iuRpuu2uHBLraZ1wNZe0DP7lnRha+VJ9tNaYVaVgKhW0aI3h4RYnoRPeql0flHm/Co55b7snEDcOfOJrA==}
|
||||
engines: {node: '>=18.0.0', npm: '>=8.0.0'}
|
||||
hasBin: true
|
||||
|
||||
source-map-js@1.2.1:
|
||||
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
||||
three@0.184.0:
|
||||
resolution: {integrity: sha512-wtTRjG92pM5eUg/KuUnHsqSAlPM296brTOcLgMRqEeylYTh/CdtvKUvCyyCQTzFuStieWxvZb8mVTMvdPyUpxg==}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
|
||||
engines: {node: '>=12.0.0'}
|
||||
|
||||
vite@7.3.5:
|
||||
resolution: {integrity: sha512-KuOaNhcnGFN2zIPGA7wRmzF+lJA1sea7rHq17aiJ++9lzY1WWG6Jpwqwe1KNbRVPIqHmr8GLYx7jbrQcN/7/ww==}
|
||||
engines: {node: ^20.19.0 || >=22.12.0}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@types/node': ^20.19.0 || >=22.12.0
|
||||
jiti: '>=1.21.0'
|
||||
less: ^4.0.0
|
||||
lightningcss: ^1.21.0
|
||||
sass: ^1.70.0
|
||||
sass-embedded: ^1.70.0
|
||||
stylus: '>=0.54.8'
|
||||
sugarss: ^5.0.0
|
||||
terser: ^5.16.0
|
||||
tsx: ^4.8.1
|
||||
yaml: ^2.4.2
|
||||
peerDependenciesMeta:
|
||||
'@types/node':
|
||||
optional: true
|
||||
jiti:
|
||||
optional: true
|
||||
less:
|
||||
optional: true
|
||||
lightningcss:
|
||||
optional: true
|
||||
sass:
|
||||
optional: true
|
||||
sass-embedded:
|
||||
optional: true
|
||||
stylus:
|
||||
optional: true
|
||||
sugarss:
|
||||
optional: true
|
||||
terser:
|
||||
optional: true
|
||||
tsx:
|
||||
optional: true
|
||||
yaml:
|
||||
optional: true
|
||||
|
||||
snapshots:
|
||||
|
||||
'@esbuild/aix-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.27.7':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm-eabi@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-android-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-darwin-x64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-freebsd-x64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-gnueabihf@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm-musleabihf@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-arm64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-loong64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-ppc64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-riscv64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-s390x-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-linux-x64-musl@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openbsd-x64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-openharmony-arm64@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-arm64-msvc@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-ia32-msvc@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-gnu@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@rollup/rollup-win32-x64-msvc@4.61.1':
|
||||
optional: true
|
||||
|
||||
'@types/estree@1.0.9': {}
|
||||
|
||||
esbuild@0.27.7:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.27.7
|
||||
'@esbuild/android-arm': 0.27.7
|
||||
'@esbuild/android-arm64': 0.27.7
|
||||
'@esbuild/android-x64': 0.27.7
|
||||
'@esbuild/darwin-arm64': 0.27.7
|
||||
'@esbuild/darwin-x64': 0.27.7
|
||||
'@esbuild/freebsd-arm64': 0.27.7
|
||||
'@esbuild/freebsd-x64': 0.27.7
|
||||
'@esbuild/linux-arm': 0.27.7
|
||||
'@esbuild/linux-arm64': 0.27.7
|
||||
'@esbuild/linux-ia32': 0.27.7
|
||||
'@esbuild/linux-loong64': 0.27.7
|
||||
'@esbuild/linux-mips64el': 0.27.7
|
||||
'@esbuild/linux-ppc64': 0.27.7
|
||||
'@esbuild/linux-riscv64': 0.27.7
|
||||
'@esbuild/linux-s390x': 0.27.7
|
||||
'@esbuild/linux-x64': 0.27.7
|
||||
'@esbuild/netbsd-arm64': 0.27.7
|
||||
'@esbuild/netbsd-x64': 0.27.7
|
||||
'@esbuild/openbsd-arm64': 0.27.7
|
||||
'@esbuild/openbsd-x64': 0.27.7
|
||||
'@esbuild/openharmony-arm64': 0.27.7
|
||||
'@esbuild/sunos-x64': 0.27.7
|
||||
'@esbuild/win32-arm64': 0.27.7
|
||||
'@esbuild/win32-ia32': 0.27.7
|
||||
'@esbuild/win32-x64': 0.27.7
|
||||
|
||||
fdir@6.5.0(picomatch@4.0.4):
|
||||
optionalDependencies:
|
||||
picomatch: 4.0.4
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
gsap@3.15.0: {}
|
||||
|
||||
lenis@1.3.23: {}
|
||||
|
||||
nanoid@3.3.12: {}
|
||||
|
||||
picocolors@1.1.1: {}
|
||||
|
||||
picomatch@4.0.4: {}
|
||||
|
||||
postcss@8.5.15:
|
||||
dependencies:
|
||||
nanoid: 3.3.12
|
||||
picocolors: 1.1.1
|
||||
source-map-js: 1.2.1
|
||||
|
||||
rollup@4.61.1:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.9
|
||||
optionalDependencies:
|
||||
'@rollup/rollup-android-arm-eabi': 4.61.1
|
||||
'@rollup/rollup-android-arm64': 4.61.1
|
||||
'@rollup/rollup-darwin-arm64': 4.61.1
|
||||
'@rollup/rollup-darwin-x64': 4.61.1
|
||||
'@rollup/rollup-freebsd-arm64': 4.61.1
|
||||
'@rollup/rollup-freebsd-x64': 4.61.1
|
||||
'@rollup/rollup-linux-arm-gnueabihf': 4.61.1
|
||||
'@rollup/rollup-linux-arm-musleabihf': 4.61.1
|
||||
'@rollup/rollup-linux-arm64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-arm64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-loong64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-loong64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-ppc64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-ppc64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-riscv64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-riscv64-musl': 4.61.1
|
||||
'@rollup/rollup-linux-s390x-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-x64-gnu': 4.61.1
|
||||
'@rollup/rollup-linux-x64-musl': 4.61.1
|
||||
'@rollup/rollup-openbsd-x64': 4.61.1
|
||||
'@rollup/rollup-openharmony-arm64': 4.61.1
|
||||
'@rollup/rollup-win32-arm64-msvc': 4.61.1
|
||||
'@rollup/rollup-win32-ia32-msvc': 4.61.1
|
||||
'@rollup/rollup-win32-x64-gnu': 4.61.1
|
||||
'@rollup/rollup-win32-x64-msvc': 4.61.1
|
||||
fsevents: 2.3.3
|
||||
|
||||
source-map-js@1.2.1: {}
|
||||
|
||||
three@0.184.0: {}
|
||||
|
||||
tinyglobby@0.2.17:
|
||||
dependencies:
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
|
||||
vite@7.3.5:
|
||||
dependencies:
|
||||
esbuild: 0.27.7
|
||||
fdir: 6.5.0(picomatch@4.0.4)
|
||||
picomatch: 4.0.4
|
||||
postcss: 8.5.15
|
||||
rollup: 4.61.1
|
||||
tinyglobby: 0.2.17
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.3
|
||||
@@ -0,0 +1,5 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64">
|
||||
<rect width="64" height="64" rx="14" fill="#07080d"/>
|
||||
<path d="M12 17h8l5 25 7-25h7l7 25 5-25h7L49 50h-8l-6-22-6 22h-8L12 17Z" fill="#56f7c5"/>
|
||||
<path d="M13 50h38" stroke="#ff5ea8" stroke-width="4" stroke-linecap="round"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 297 B |
@@ -0,0 +1,15 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" role="img" aria-labelledby="title">
|
||||
<title id="title">DevOps 运维平台</title>
|
||||
<defs>
|
||||
<linearGradient id="panel" x1="18" x2="78" y1="14" y2="82" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#22d3ee" />
|
||||
<stop offset="1" stop-color="#8b5cf6" />
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<rect width="96" height="96" rx="22" fill="#08111f" />
|
||||
<rect x="16" y="20" width="64" height="48" rx="10" fill="url(#panel)" opacity="0.18" />
|
||||
<rect x="20" y="24" width="56" height="40" rx="7" fill="#0f172a" stroke="#38bdf8" stroke-opacity="0.65" />
|
||||
<path d="M29 37h19M29 48h11M53 48h14" stroke="#e0f2fe" stroke-width="4" stroke-linecap="round" />
|
||||
<circle cx="62" cy="37" r="5" fill="#34d399" />
|
||||
<path d="M31 74h34M39 64v10M57 64v10" stroke="#a5b4fc" stroke-width="4" stroke-linecap="round" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 876 B |
@@ -0,0 +1,301 @@
|
||||
import gsap from "gsap";
|
||||
import { ScrollTrigger } from "gsap/ScrollTrigger";
|
||||
import Lenis from "lenis";
|
||||
import { rounds } from "./dialogue";
|
||||
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
/* Lenis 平滑滚动 + ScrollTrigger 集成 */
|
||||
export function initSmoothScroll() {
|
||||
const lenis = new Lenis({ lerp: 0.1 });
|
||||
lenis.on("scroll", ScrollTrigger.update);
|
||||
gsap.ticker.add((time) => lenis.raf(time * 1000));
|
||||
gsap.ticker.lagSmoothing(0);
|
||||
return lenis;
|
||||
}
|
||||
|
||||
/* 打字机:时间轴上的离散字符 reveal(时间驱动,进入视口后完整播放) */
|
||||
function addTypewriter(tl, textEl, caretEl, fullText, start, duration) {
|
||||
const proxy = { p: 0 };
|
||||
tl.to(
|
||||
proxy,
|
||||
{
|
||||
p: 1,
|
||||
duration,
|
||||
ease: `steps(${fullText.length})`,
|
||||
onUpdate() {
|
||||
const n = Math.round(proxy.p * fullText.length);
|
||||
textEl.textContent = fullText.slice(0, n);
|
||||
if (caretEl) caretEl.style.opacity = proxy.p >= 1 ? "0" : "1";
|
||||
},
|
||||
},
|
||||
start
|
||||
);
|
||||
}
|
||||
|
||||
/* 根据文案长度推导打字时长,保持各轮节奏差异 */
|
||||
const typingDuration = (text) => Math.min(1.5, 0.34 + text.length * 0.07);
|
||||
|
||||
/* 开场(时间驱动,非滚动)*/
|
||||
export function playBoot(mind) {
|
||||
const boot = document.querySelector("#r-boot");
|
||||
const typedLine = boot.querySelector(".typed-line");
|
||||
const items = boot.querySelectorAll(".gen");
|
||||
gsap.set(boot.querySelector(".answer"), { opacity: 1 });
|
||||
gsap.set(items, { y: 26, autoAlpha: 0 });
|
||||
|
||||
const text = "这是我的数字思维体,向它提问即可。";
|
||||
const proxy = { p: 0 };
|
||||
const tl = gsap.timeline({ delay: 0.3 });
|
||||
|
||||
tl.call(() => mind.setState("thinking"))
|
||||
.to(proxy, {
|
||||
p: 1,
|
||||
duration: 1.4,
|
||||
ease: `steps(${text.length})`,
|
||||
onUpdate: () => {
|
||||
typedLine.textContent = text.slice(0, Math.round(proxy.p * text.length));
|
||||
},
|
||||
}, 0.5)
|
||||
.call(() => mind.setState("answering"), null, 1.9)
|
||||
.to(items, {
|
||||
y: 0,
|
||||
autoAlpha: 1,
|
||||
duration: 0.7,
|
||||
ease: "expo.out",
|
||||
stagger: { each: 0.09, from: "start" },
|
||||
}, 2.0)
|
||||
.call(() => mind.setState("idle"), null, 3.4);
|
||||
return tl;
|
||||
}
|
||||
|
||||
/* 单轮问答时间轴:提问打字 → 思考 → 回答爆发生成(一次性播放) */
|
||||
function buildRoundTimeline({ round, qText, qCaret, thinking, answer, items, mind }) {
|
||||
const typeDur = typingDuration(round.question);
|
||||
const tl = gsap.timeline({ paused: true });
|
||||
|
||||
// 1. 提问打字
|
||||
addTypewriter(tl, qText, qCaret, round.question, 0, typeDur);
|
||||
// 2. 思考 shimmer(刻意停顿 = 非线性蓄力)
|
||||
tl.call(() => mind.setState("thinking"), null, typeDur)
|
||||
.to(thinking, { opacity: 1, duration: 0.22 }, typeDur)
|
||||
.to({}, { duration: 0.5 })
|
||||
.to(thinking, { opacity: 0, duration: 0.18 })
|
||||
// 3. 爆发生成:不等间隔 stagger 模拟 token 到达
|
||||
.call(() => mind.setState("answering"))
|
||||
.to(answer, { opacity: 1, duration: 0.2 }, "<")
|
||||
.to(
|
||||
items,
|
||||
{
|
||||
y: 0,
|
||||
autoAlpha: 1,
|
||||
duration: 0.7,
|
||||
ease: "expo.out",
|
||||
stagger: { each: Math.max(0.05, round.staggerEach), from: "start" },
|
||||
},
|
||||
"<0.05"
|
||||
)
|
||||
.call(() => mind.setState("idle"), null, "+=0.4");
|
||||
return tl;
|
||||
}
|
||||
|
||||
/* 轮次编排:进入视口即播放(不再依赖滚动 scrub,修复"不滚动则不加载") */
|
||||
export function initChoreography(mind, setActive = () => {}) {
|
||||
rounds
|
||||
.filter((r) => r.question)
|
||||
.forEach((round) => {
|
||||
const section = document.getElementById(`r-${round.id}`);
|
||||
const qText = section.querySelector(".q-text");
|
||||
const qCaret = section.querySelector(".q-caret");
|
||||
const thinking = section.querySelector(".thinking");
|
||||
const answer = section.querySelector(".answer");
|
||||
const items = section.querySelectorAll(".gen");
|
||||
|
||||
gsap.set(items, { y: 34, autoAlpha: 0 });
|
||||
|
||||
if (round.flow) {
|
||||
buildFlowRound({ round, section, qText, qCaret, thinking, answer, items, mind, setActive });
|
||||
return;
|
||||
}
|
||||
|
||||
const tl = buildRoundTimeline({ round, qText, qCaret, thinking, answer, items, mind });
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: section,
|
||||
start: "top 60%",
|
||||
end: "bottom 40%",
|
||||
onToggle(self) {
|
||||
if (self.isActive) setActive(round.id);
|
||||
},
|
||||
onEnter: () => {
|
||||
mind.setSide(round.side);
|
||||
mind.setPalette(round.palette);
|
||||
tl.play();
|
||||
},
|
||||
onEnterBack: () => {
|
||||
mind.setSide(round.side);
|
||||
mind.setPalette(round.palette);
|
||||
},
|
||||
onLeave: () => mind.setState("idle"),
|
||||
onLeaveBack: () => mind.setState("idle"),
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/* 长内容轮:提问/思考时间驱动一次播放,内容块各自进入视口时「生成」 */
|
||||
function buildFlowRound({ round, section, qText, qCaret, thinking, answer, items, mind, setActive }) {
|
||||
gsap.set(answer, { opacity: 1 });
|
||||
|
||||
const typeDur = typingDuration(round.question);
|
||||
const qTl = gsap.timeline({ paused: true });
|
||||
addTypewriter(qTl, qText, qCaret, round.question, 0, typeDur);
|
||||
qTl.to(thinking, { opacity: 1, duration: 0.2 }, typeDur)
|
||||
.to(thinking, { opacity: 0, duration: 0.2 }, "+=0.5");
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: section,
|
||||
start: "top 70%",
|
||||
once: true,
|
||||
onEnter: () => qTl.play(),
|
||||
});
|
||||
|
||||
items.forEach((item, i) => {
|
||||
gsap.to(item, {
|
||||
y: 0,
|
||||
autoAlpha: 1,
|
||||
duration: 0.8,
|
||||
delay: (i % 3) * 0.09, // 不等间隔,模拟 token 到达
|
||||
ease: "expo.out",
|
||||
scrollTrigger: { trigger: item, start: "top 88%" },
|
||||
});
|
||||
});
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: section,
|
||||
start: "top 60%",
|
||||
end: "bottom 55%",
|
||||
onToggle(self) {
|
||||
if (self.isActive) {
|
||||
setActive(round.id);
|
||||
mind.setSide(round.side);
|
||||
mind.setPalette(round.palette);
|
||||
} else {
|
||||
mind.setState("idle");
|
||||
}
|
||||
},
|
||||
onUpdate(self) {
|
||||
mind.setState(self.progress < 0.1 ? "thinking" : "answering");
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/* 进度轨:高亮由编排回调驱动,这里只负责节点与跳转 */
|
||||
export function initRail(lenis) {
|
||||
const setActive = (id) => {
|
||||
document.querySelectorAll(".rail-node").forEach((n) => {
|
||||
n.classList.toggle("active", n.dataset.round === id);
|
||||
});
|
||||
};
|
||||
|
||||
ScrollTrigger.create({
|
||||
trigger: "#r-boot",
|
||||
start: "top top",
|
||||
end: "bottom 40%",
|
||||
onToggle(self) {
|
||||
if (self.isActive) setActive("boot");
|
||||
},
|
||||
});
|
||||
|
||||
const jump = (id) => {
|
||||
const target = document.getElementById(`r-${id}`);
|
||||
if (!target) return;
|
||||
if (lenis) lenis.scrollTo(target, { offset: 2, duration: 1.2 });
|
||||
else target.scrollIntoView({ behavior: "smooth" });
|
||||
};
|
||||
|
||||
return { jump, setActive };
|
||||
}
|
||||
|
||||
/* 模块吸附:滚动停止后,若停在两轮之间的中间态,自动滚动到目标轮顶部,
|
||||
再由 onEnter 正常触发打字机动画(修复"滚不到位则动画错位") */
|
||||
export function initRoundSnap(lenis) {
|
||||
const sections = rounds
|
||||
.map((round) => document.getElementById(`r-${round.id}`))
|
||||
.filter(Boolean);
|
||||
if (!sections.length) return () => {};
|
||||
|
||||
let timer = 0;
|
||||
let lockUntil = 0;
|
||||
|
||||
const readY = () => (typeof lenis?.scroll === "number" ? lenis.scroll : window.scrollY);
|
||||
|
||||
const snapTo = (section) => {
|
||||
lockUntil = performance.now() + 700;
|
||||
const targetY = section.getBoundingClientRect().top + readY();
|
||||
if (lenis) {
|
||||
lenis.scrollTo(targetY, {
|
||||
duration: 0.55,
|
||||
easing: (t) => 1 - Math.pow(1 - t, 3),
|
||||
});
|
||||
} else {
|
||||
window.scrollTo({ top: targetY, behavior: "smooth" });
|
||||
}
|
||||
};
|
||||
|
||||
const settle = () => {
|
||||
if (performance.now() < lockUntil) return;
|
||||
const vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
|
||||
for (const section of sections) {
|
||||
const top = section.getBoundingClientRect().top;
|
||||
// 已有模块对齐视口顶部 → 状态正常,无需吸附
|
||||
if (Math.abs(top) < 8) return;
|
||||
// 模块露出一半(未到打字机区域)→ 吸附进入该模块
|
||||
const halfEntered = top > vh * 0.12 && top < vh * 0.72;
|
||||
// 模块占据主屏但未对齐 → 吸附回正
|
||||
const nearlyAligned = top > -vh * 0.45 && top <= vh * 0.12;
|
||||
if (halfEntered || nearlyAligned) {
|
||||
snapTo(section);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// 长内容轮(flow)内部深处不匹配任何区间 → 自由滚动,不干预
|
||||
};
|
||||
|
||||
const onScroll = () => {
|
||||
window.clearTimeout(timer);
|
||||
// lenis 惯性收尾阶段速度趋零:立即判定吸附,不等事件完全静止
|
||||
const velocity =
|
||||
lenis && typeof lenis.velocity === "number" ? Math.abs(lenis.velocity) : null;
|
||||
if (velocity !== null && velocity < 0.3) {
|
||||
settle();
|
||||
return;
|
||||
}
|
||||
timer = window.setTimeout(settle, 90);
|
||||
};
|
||||
|
||||
if (lenis) lenis.on("scroll", onScroll);
|
||||
window.addEventListener("scroll", onScroll, { passive: true });
|
||||
window.addEventListener("resize", onScroll);
|
||||
|
||||
return () => {
|
||||
window.clearTimeout(timer);
|
||||
window.removeEventListener("scroll", onScroll);
|
||||
window.removeEventListener("resize", onScroll);
|
||||
};
|
||||
}
|
||||
|
||||
/* 降级:reduced motion 直接展示全部内容 */
|
||||
export function applyReducedMotion() {
|
||||
rounds
|
||||
.filter((r) => r.question)
|
||||
.forEach((round) => {
|
||||
const section = document.getElementById(`r-${round.id}`);
|
||||
section.querySelector(".q-text").textContent = round.question;
|
||||
const answer = section.querySelector(".answer");
|
||||
answer.style.opacity = "1";
|
||||
});
|
||||
const boot = document.querySelector("#r-boot");
|
||||
boot.querySelector(".typed-line").textContent = "这是我的数字思维体。";
|
||||
document.querySelectorAll(".gen").forEach((n) => (n.style.opacity = "1"));
|
||||
}
|
||||
@@ -1,481 +0,0 @@
|
||||
export const resume = {
|
||||
name: "王元有",
|
||||
alias: "湛兮",
|
||||
title: "前端开发工程师",
|
||||
intent: "求职意向:前端开发工程师",
|
||||
profile:
|
||||
"7 年前端与跨端开发经验,长期负责 Web、App、H5、小程序与管理后台的架构设计、工程化建设和核心业务交付。熟悉 AI 产品工程、SSE 流式通信、Next.js/Vue/React Native 跨端体系、微前端和 Monorepo 协作模式。",
|
||||
basics: [
|
||||
{ label: "工作经验", value: "7 年" },
|
||||
{ label: "技术管理", value: "2+ 年" },
|
||||
{ label: "项目用户规模", value: "千万级 MAU" },
|
||||
{ label: "教育背景", value: "电子科技大学 本科" },
|
||||
],
|
||||
contact: {
|
||||
phone: "19980439383",
|
||||
email: "419021733@qq.com",
|
||||
emailAlt: "wmagmgema521@gmail.com",
|
||||
meta: "男 | 29 岁",
|
||||
},
|
||||
highlights: [
|
||||
"主导多款企业级产品从 0 到 1 搭建,覆盖 AI 模型服务、AI 资讯对话、供应链、门店数字化、工业互联网平台等场景。",
|
||||
"掌握 Vue3、React、Next.js、React Native、Expo、UniApp 等技术栈,能独立完成 Web、移动端、App 与管理后台方案落地。",
|
||||
"推动 Micro-Frontend、SSR、pnpm Monorepo、Git Flow、代码审查、CI/CD、监控体系等工程化实践。",
|
||||
"有明确的性能与效率结果:构建体积优化 30%、首屏白屏时间缩短 25%、组件库工程化提升开发效率 50%、重复开发成本减少约 40%。",
|
||||
],
|
||||
metrics: [
|
||||
{ value: "-30%", label: "构建体积优化" },
|
||||
{ value: "-25%", label: "首屏白屏时间" },
|
||||
{ value: "+50%", label: "组件库效率提升" },
|
||||
{ value: "+40%", label: "团队人效提升" },
|
||||
{ value: "40%", label: "重复开发成本降低" },
|
||||
{ value: "30%", label: "消息渲染性能提升" },
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
group: "前端框架",
|
||||
items: [
|
||||
"Next.js 16",
|
||||
"React 19",
|
||||
"Vue 3",
|
||||
"TypeScript 5",
|
||||
"Tailwind CSS 4",
|
||||
"Nuxt/SSR",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "跨端开发",
|
||||
items: [
|
||||
"React Native 0.79",
|
||||
"Expo 53",
|
||||
"UniApp",
|
||||
"H5",
|
||||
"微信/飞书小程序",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "AI 与通信",
|
||||
items: [
|
||||
"Vercel AI SDK v6",
|
||||
"SSE 流式通信",
|
||||
"多 Agent 协作",
|
||||
"Markdown/LaTeX 渲染",
|
||||
"AI 对话",
|
||||
"AI 生图/生视频",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "工程化",
|
||||
items: [
|
||||
"pnpm Monorepo",
|
||||
"Git Submodules",
|
||||
"Qiankun",
|
||||
"Lerna",
|
||||
"Git Flow",
|
||||
"Playwright",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "状态与 UI",
|
||||
items: [
|
||||
"Zustand",
|
||||
"MobX",
|
||||
"Pinia",
|
||||
"Radix UI",
|
||||
"Ant Design",
|
||||
"Element Plus",
|
||||
"ProComponents",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "基础设施",
|
||||
items: [
|
||||
"Alova",
|
||||
"next-intl",
|
||||
"i18next",
|
||||
"ARMS 监控",
|
||||
"CDN 优化",
|
||||
"Adyen 支付",
|
||||
],
|
||||
},
|
||||
],
|
||||
experiences: [
|
||||
{
|
||||
company: "成都海艺互娱科技有限公司",
|
||||
role: "React Native 开发工程师",
|
||||
period: "2025.05 - 至今",
|
||||
points: [
|
||||
"参与多款 AI 产品从 0 到 1 搭建与架构设计,覆盖前端性能、交互体验、国际化体系与多端组件复用。",
|
||||
"在 AI 对话、生图、生视频与多 Agent 协作领域沉淀工程经验,负责流式通信与前端渲染体验优化。",
|
||||
"支撑千万级海外用户产品,推动多端组件库统一建设,减少重复开发成本约 40%。",
|
||||
],
|
||||
},
|
||||
{
|
||||
company: "四川茶姬企业管理有限公司",
|
||||
role: "高级 Web 前端开发",
|
||||
period: "2024.03 - 2025.03",
|
||||
points: [
|
||||
"主导供应链管理系统前端开发,实现采购、仓储、配送等核心模块。",
|
||||
"独立负责门店报损与食安管理核心功能搭建及迭代,覆盖国内、东南亚与北美业务逻辑。",
|
||||
"引入阿里云 ARMS 前端监控体系,落地 CDN 静态资源优化方案,并制定代码审查与 Git 操作规范。",
|
||||
],
|
||||
},
|
||||
{
|
||||
company: "中钧科技有限公司四川分公司",
|
||||
role: "前端开发组长",
|
||||
period: "2022.04 - 2024.03",
|
||||
points: [
|
||||
"负责经营帮 PC 端微前端、门户和商管核心模块开发,完成 UI 还原、接口联调及性能优化。",
|
||||
"从零搭建新 E 畅行小程序基础框架,完成技术方案制定与全流程开发。",
|
||||
"主导经营帮小程序、H5、Admin 后台迭代开发与跨端技术方案设计,建立 Git Flow、代码审查和新人培训机制。",
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
id: "vtrix",
|
||||
name: "SeaCloud / Vtrix",
|
||||
subtitle: "AI 模型服务平台(Web 端)",
|
||||
period: "2025.09 - 至今",
|
||||
tech: [
|
||||
"Next.js 16",
|
||||
"React 19",
|
||||
"TypeScript",
|
||||
"Tailwind CSS",
|
||||
"Zustand",
|
||||
"Alova",
|
||||
"Vercel AI SDK",
|
||||
"Radix UI",
|
||||
"next-intl",
|
||||
],
|
||||
summary:
|
||||
"面向全球用户的 AI 模型聚合服务平台,聚合 LLM、图像、视频、音频、3D 等多模态模型能力,覆盖 C 端调用与 B 端组织/分销管理。",
|
||||
modules: [
|
||||
"分销商客户邀请、折扣模板、额度分配、销售配置与利润率计算。",
|
||||
"多币种与汇率体系,封装 useCurrency Hook 并贯穿 Pricing、Billing、API Keys 等模块。",
|
||||
"组织成员、角色权限守卫、配额设置、支出限额、账单报表、交易筛选与 Excel 导出。",
|
||||
"i18n Submodule 架构迁移、企业微信同步工作流、通用表格/筛选器/分页组件增强。",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "seabuzz",
|
||||
name: "SeaBuzz",
|
||||
subtitle: "AI 智能资讯与对话平台",
|
||||
period: "2025.05 - 至今",
|
||||
tech: [
|
||||
"React Native",
|
||||
"Expo 53",
|
||||
"Expo Router",
|
||||
"Zustand",
|
||||
"NativeWind",
|
||||
"SSE",
|
||||
"i18next",
|
||||
"Adyen",
|
||||
"Lerna",
|
||||
],
|
||||
summary:
|
||||
"海艺 AI 旗下 AI 新闻聚合、智能搜索与多模态对话平台,基于 Expo 实现 iOS、Android、Web 三端统一开发。",
|
||||
modules: [
|
||||
"AI Agent 对话完整链路:SSE 流式聊天、打字机渲染、Markdown/LaTeX、思考动画、来源引用与历史同步。",
|
||||
"Discover 发现页与新闻详情,支持瀑布流、大/小卡动态布局、骨架屏与 Smart Image。",
|
||||
"Google、Facebook、Discord、Email 登录与 SeaArt Auth SDK 对接。",
|
||||
"Monorepo 公共包体系、API 层、数据模型、UI 组件、状态管理、OTA 热更新与代码签名机制。",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "bawang",
|
||||
name: "霸王功夫",
|
||||
subtitle: "门店数字化与供应链管理平台",
|
||||
period: "2024.03 - 2025.03",
|
||||
tech: [
|
||||
"React 18",
|
||||
"Vue 3",
|
||||
"UniApp",
|
||||
"MobX",
|
||||
"Pinia",
|
||||
"Element Plus",
|
||||
"Ant Design",
|
||||
"ProComponents",
|
||||
],
|
||||
summary:
|
||||
"服务全球 6000+ 门店及运营伙伴的数字化管理平台,覆盖门店运营、食品安全、供应链协同等核心业务。",
|
||||
modules: [
|
||||
"小程序报损模块:摄像头扫码、在线报损登记,兼容微信小程序与飞书 H5。",
|
||||
"食安管理模块:低频蓝牙连接 TSPL 指令集打印机,支持国内、东南亚、北美三套业务逻辑。",
|
||||
"经营信息模块:多门店经营状态移动端看板,按区域、时间、指标筛选。",
|
||||
"供应链模块:采购系统、供应商合同管理、供应商结算系统,对接云厉、费控等外部系统。",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "jingyingbang",
|
||||
name: "经营帮平台 + 经营帮拉新",
|
||||
subtitle: "工业互联网与微前端平台",
|
||||
period: "2022.04 - 2024.03",
|
||||
tech: [
|
||||
"Vue",
|
||||
"Qiankun",
|
||||
"Element UI",
|
||||
"华为云 OBS",
|
||||
"百度地图",
|
||||
"高德地图",
|
||||
"IM",
|
||||
"UniApp",
|
||||
],
|
||||
summary:
|
||||
"基于信息化设计理念和区块链技术的工业互联网平台,为企业和个人提供数字化运营服务。",
|
||||
modules: [
|
||||
"参与单体前端到 Qiankun 微前端拆分,拆分出 12 个基础项目。",
|
||||
"负责商管、门户、经营帮系列小程序/H5/Admin 后台核心业务交付。",
|
||||
"引入华为云 OBS 直传减轻请求链路性能浪费。",
|
||||
"开发内部 Chrome 插件 zjkj-decryption,提升数据解析效率。",
|
||||
],
|
||||
},
|
||||
],
|
||||
education: {
|
||||
school: "电子科技大学",
|
||||
degree: "本科",
|
||||
major: "信息管理与信息系统",
|
||||
period: "2023 - 2025",
|
||||
},
|
||||
};
|
||||
|
||||
export const resumeEn = {
|
||||
name: "Wang Yuanyou",
|
||||
alias: "mrZhan",
|
||||
title: "Frontend Engineer",
|
||||
intent: "Target Role: Frontend Engineer",
|
||||
profile:
|
||||
"Frontend and cross-platform engineer with 7 years of experience building Web, App, H5, mini-program and admin systems. Strong in AI product engineering, SSE streaming, Next.js/Vue/React Native ecosystems, micro-frontends and Monorepo collaboration.",
|
||||
basics: [
|
||||
{ label: "Experience", value: "7 years" },
|
||||
{ label: "Tech Leadership", value: "2+ years" },
|
||||
{ label: "Product Scale", value: "10M+ MAU" },
|
||||
{ label: "Education", value: "UESTC Bachelor" },
|
||||
],
|
||||
contact: {
|
||||
phone: "19980439383",
|
||||
email: "419021733@qq.com",
|
||||
emailAlt: "wmagmgema521@gmail.com",
|
||||
meta: "Male | 29",
|
||||
},
|
||||
highlights: [
|
||||
"Led multiple enterprise products from 0 to 1 across AI model services, AI news and chat, supply chain, store digitization and industrial internet platforms.",
|
||||
"Hands-on with Vue3, React, Next.js, React Native, Expo and UniApp, capable of delivering Web, mobile, App and admin products end to end.",
|
||||
"Drove engineering practices including Micro-Frontend, SSR, pnpm Monorepo, Git Flow, code review, CI/CD and frontend observability.",
|
||||
"Delivered measurable outcomes: 30% smaller bundles, 25% faster first screen, 50% faster component-driven delivery and around 40% less duplicated work.",
|
||||
],
|
||||
metrics: [
|
||||
{ value: "-30%", label: "Bundle Size" },
|
||||
{ value: "-25%", label: "First Screen Blank Time" },
|
||||
{ value: "+50%", label: "Component Delivery Efficiency" },
|
||||
{ value: "+40%", label: "Team Productivity" },
|
||||
{ value: "40%", label: "Duplicated Work Reduced" },
|
||||
{ value: "30%", label: "Message Rendering Performance" },
|
||||
],
|
||||
skills: [
|
||||
{
|
||||
group: "Frontend Frameworks",
|
||||
items: [
|
||||
"Next.js 16",
|
||||
"React 19",
|
||||
"Vue 3",
|
||||
"TypeScript 5",
|
||||
"Tailwind CSS 4",
|
||||
"Nuxt/SSR",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "Cross-platform",
|
||||
items: [
|
||||
"React Native 0.79",
|
||||
"Expo 53",
|
||||
"UniApp",
|
||||
"H5",
|
||||
"WeChat/Lark Mini Programs",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "AI & Streaming",
|
||||
items: [
|
||||
"Vercel AI SDK v6",
|
||||
"SSE Streaming",
|
||||
"Multi-Agent Collaboration",
|
||||
"Markdown/LaTeX",
|
||||
"AI Chat",
|
||||
"AI Image/Video",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "Engineering",
|
||||
items: [
|
||||
"pnpm Monorepo",
|
||||
"Git Submodules",
|
||||
"Qiankun",
|
||||
"Lerna",
|
||||
"Git Flow",
|
||||
"Playwright",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "State & UI",
|
||||
items: [
|
||||
"Zustand",
|
||||
"MobX",
|
||||
"Pinia",
|
||||
"Radix UI",
|
||||
"Ant Design",
|
||||
"Element Plus",
|
||||
"ProComponents",
|
||||
],
|
||||
},
|
||||
{
|
||||
group: "Infrastructure",
|
||||
items: [
|
||||
"Alova",
|
||||
"next-intl",
|
||||
"i18next",
|
||||
"ARMS Monitoring",
|
||||
"CDN Optimization",
|
||||
"Adyen Payments",
|
||||
],
|
||||
},
|
||||
],
|
||||
experiences: [
|
||||
{
|
||||
company: "Chengdu Haiyi Interactive Entertainment Technology Co., Ltd.",
|
||||
role: "React Native Engineer",
|
||||
period: "2025.05 - Present",
|
||||
points: [
|
||||
"Contributed to architecture and 0-to-1 delivery of multiple AI products, covering performance, UX, i18n and multi-platform component reuse.",
|
||||
"Built engineering experience in AI chat, image/video generation and multi-agent collaboration, with focus on streaming and frontend rendering performance.",
|
||||
"Supported products serving 10M+ overseas users and promoted unified multi-platform component libraries, reducing duplicate work by around 40%.",
|
||||
],
|
||||
},
|
||||
{
|
||||
company: "Sichuan Chaji Enterprise Management Co., Ltd.",
|
||||
role: "Senior Web Frontend Developer",
|
||||
period: "2024.03 - 2025.03",
|
||||
points: [
|
||||
"Led frontend development of supply-chain management systems, including procurement, warehousing and delivery modules.",
|
||||
"Owned store loss-reporting and food-safety features across China, Southeast Asia and North America business rules.",
|
||||
"Introduced Alibaba Cloud ARMS frontend monitoring, delivered CDN optimization and established code review and Git workflow standards.",
|
||||
],
|
||||
},
|
||||
{
|
||||
company: "Zhongjun Technology Sichuan Branch",
|
||||
role: "Frontend Team Lead",
|
||||
period: "2022.04 - 2024.03",
|
||||
points: [
|
||||
"Delivered core modules for the Jingyingbang PC micro-frontend platform, portal and business management systems, including UI implementation, API integration and performance optimization.",
|
||||
"Built the New E Travel mini-program foundation from scratch and owned technical planning through delivery.",
|
||||
"Led mini-program, H5 and admin iterations, designed cross-platform solutions, and established Git Flow, code review and onboarding practices.",
|
||||
],
|
||||
},
|
||||
],
|
||||
projects: [
|
||||
{
|
||||
id: "vtrix",
|
||||
name: "SeaCloud / Vtrix",
|
||||
subtitle: "AI Model Service Platform (Web)",
|
||||
period: "2025.09 - Present",
|
||||
tech: [
|
||||
"Next.js 16",
|
||||
"React 19",
|
||||
"TypeScript",
|
||||
"Tailwind CSS",
|
||||
"Zustand",
|
||||
"Alova",
|
||||
"Vercel AI SDK",
|
||||
"Radix UI",
|
||||
"next-intl",
|
||||
],
|
||||
summary:
|
||||
"A global AI model aggregation platform covering LLM, image, video, audio and 3D model capabilities for developers, organizations and distributors.",
|
||||
modules: [
|
||||
"Built distributor invitation, discount templates, credit allocation, sales configuration and profit margin workflows.",
|
||||
"Implemented global currency switching and exchange-rate conversion via a reusable useCurrency hook across Pricing, Billing and API Keys.",
|
||||
"Delivered organization roles, quota controls, spending limits, billing reports, transaction filtering and Excel export flows.",
|
||||
"Supported i18n submodule migration, WeCom translation sync and shared table/filter/pagination component enhancements.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "seabuzz",
|
||||
name: "SeaBuzz",
|
||||
subtitle: "AI News and Conversation Platform",
|
||||
period: "2025.05 - Present",
|
||||
tech: [
|
||||
"React Native",
|
||||
"Expo 53",
|
||||
"Expo Router",
|
||||
"Zustand",
|
||||
"NativeWind",
|
||||
"SSE",
|
||||
"i18next",
|
||||
"Adyen",
|
||||
"Lerna",
|
||||
],
|
||||
summary:
|
||||
"An AI-powered news aggregation, smart search and multimodal conversation platform under SeaArt AI, built with Expo for iOS, Android and Web.",
|
||||
modules: [
|
||||
"Built the AI Agent chat flow with SSE streaming, typewriter rendering, Markdown/LaTeX, thinking animation, citations and history sync.",
|
||||
"Delivered Discover feed and news detail pages with masonry layout, large/small dynamic cards, skeleton loading and Smart Image.",
|
||||
"Integrated Google, Facebook, Discord and Email login with SeaArt Auth SDK.",
|
||||
"Built Monorepo shared packages for API, data models, UI components, state, OTA updates and code signing.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "bawang",
|
||||
name: "Bawang Kungfu",
|
||||
subtitle: "Store Digitization and Supply Chain Platform",
|
||||
period: "2024.03 - 2025.03",
|
||||
tech: [
|
||||
"React 18",
|
||||
"Vue 3",
|
||||
"UniApp",
|
||||
"MobX",
|
||||
"Pinia",
|
||||
"Element Plus",
|
||||
"Ant Design",
|
||||
"ProComponents",
|
||||
],
|
||||
summary:
|
||||
"A digital operations platform serving 6,000+ stores and partners, covering store operations, food safety and supply-chain collaboration.",
|
||||
modules: [
|
||||
"Built mini-program loss reporting with camera scanning and online registration, compatible with WeChat mini-program and Lark H5.",
|
||||
"Implemented food safety flows with Bluetooth TSPL printers and separate China, Southeast Asia and North America business rules.",
|
||||
"Delivered mobile dashboards for multi-store operation metrics with region, time and KPI filtering.",
|
||||
"Built procurement, supplier contract and settlement modules, integrating multiple external systems.",
|
||||
],
|
||||
},
|
||||
{
|
||||
id: "jingyingbang",
|
||||
name: "Jingyingbang Platform",
|
||||
subtitle: "Industrial Internet and Micro-frontend Platform",
|
||||
period: "2022.04 - 2024.03",
|
||||
tech: [
|
||||
"Vue",
|
||||
"Qiankun",
|
||||
"Element UI",
|
||||
"Huawei Cloud OBS",
|
||||
"Baidu Map",
|
||||
"Amap",
|
||||
"IM",
|
||||
"UniApp",
|
||||
],
|
||||
summary:
|
||||
"An industrial internet platform based on informatization and blockchain concepts, providing digital operation services for companies and individuals.",
|
||||
modules: [
|
||||
"Participated in splitting a large frontend monolith into 12 Qiankun-based micro-frontend projects.",
|
||||
"Delivered core business features for portals, business management, mini-program, H5 and admin systems.",
|
||||
"Introduced Huawei Cloud OBS direct upload to reduce request-chain overhead.",
|
||||
"Built an internal Chrome extension, zjkj-decryption, to improve data parsing efficiency.",
|
||||
],
|
||||
},
|
||||
],
|
||||
education: {
|
||||
school: "University of Electronic Science and Technology of China",
|
||||
degree: "Bachelor",
|
||||
major: "Information Management and Information Systems",
|
||||
period: "2023 - 2025",
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,73 @@
|
||||
// 7 轮问答的文案与节奏配置 —— 每轮节奏参数刻意不同,避免线性重复
|
||||
export const contact = {
|
||||
emails: ["419021733@qq.com", "wmagmgema521@gmail.com"],
|
||||
phone: "19980439383",
|
||||
github: "https://github.com/zhanBoss",
|
||||
identity: "男 · 29 岁 · 电子科技大学 信息管理与信息系统 本科",
|
||||
};
|
||||
|
||||
export const rounds = [
|
||||
{
|
||||
id: "boot",
|
||||
label: "BOOT",
|
||||
type: "boot",
|
||||
question: null,
|
||||
side: 1,
|
||||
palette: ["#22d3ee", "#a78bfa"],
|
||||
},
|
||||
{
|
||||
id: "intro",
|
||||
label: "WHO",
|
||||
type: "signals",
|
||||
question: "先介绍一下你自己?",
|
||||
side: -1,
|
||||
palette: ["#2dd4bf", "#a78bfa"],
|
||||
staggerEach: 0.055,
|
||||
},
|
||||
{
|
||||
id: "focus",
|
||||
label: "NOW",
|
||||
type: "focus",
|
||||
question: "最近一年具体在做什么?",
|
||||
side: 1,
|
||||
palette: ["#22d3ee", "#c084fc"],
|
||||
staggerEach: 0.085,
|
||||
},
|
||||
{
|
||||
id: "projects",
|
||||
label: "PROOF",
|
||||
type: "projects",
|
||||
question: "有实际项目证明吗?",
|
||||
side: -1,
|
||||
palette: ["#38bdf8", "#a78bfa"],
|
||||
flow: true, // 内容超过一屏:逐块流式生成
|
||||
staggerEach: 0.045,
|
||||
},
|
||||
{
|
||||
id: "skills",
|
||||
label: "STACK",
|
||||
type: "skills",
|
||||
question: "技能栈展开讲讲?",
|
||||
side: 1,
|
||||
palette: ["#67e8f9", "#8b5cf6"],
|
||||
staggerEach: 0.02,
|
||||
},
|
||||
{
|
||||
id: "experience",
|
||||
label: "PATH",
|
||||
type: "experience",
|
||||
question: "之前的团队经历?",
|
||||
side: -1,
|
||||
palette: ["#5eead4", "#a78bfa"],
|
||||
staggerEach: 0.1,
|
||||
},
|
||||
{
|
||||
id: "contact",
|
||||
label: "PING",
|
||||
type: "contact",
|
||||
question: "怎么联系你?",
|
||||
side: 0,
|
||||
palette: ["#22d3ee", "#f0abfc"],
|
||||
staggerEach: 0.09,
|
||||
},
|
||||
];
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
import gsap from "gsap";
|
||||
import { renderDialogue, renderRail } from "./render";
|
||||
import { createMind } from "./mind/mind";
|
||||
import {
|
||||
applyReducedMotion,
|
||||
initChoreography,
|
||||
initRail,
|
||||
initRoundSnap,
|
||||
initSmoothScroll,
|
||||
playBoot,
|
||||
} from "./choreography";
|
||||
|
||||
const reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
||||
|
||||
/* 1. 由简历数据生成对话 DOM */
|
||||
renderDialogue(document.getElementById("dialogue"));
|
||||
|
||||
/* 2. 思维流体 */
|
||||
const mind = createMind(document.getElementById("mind-canvas"), { reduced });
|
||||
window.__mind = mind;
|
||||
gsap.ticker.add(() => mind.tick());
|
||||
|
||||
/* 3. 进度轨 + 滚动编排 */
|
||||
let lenis = null;
|
||||
if (!reduced) lenis = initSmoothScroll();
|
||||
|
||||
const rail = initRail(lenis);
|
||||
renderRail(document.getElementById("session-rail"), rail.jump);
|
||||
|
||||
if (!reduced) {
|
||||
initChoreography(mind, rail.setActive);
|
||||
initRoundSnap(lenis);
|
||||
playBoot(mind);
|
||||
} else {
|
||||
applyReducedMotion();
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
import * as THREE from "three";
|
||||
import { blobFragment, blobVertex, particleFragment, particleVertex } from "./shaders";
|
||||
|
||||
// 三状态目标值 —— 每帧向目标插值,无硬切
|
||||
const STATES = {
|
||||
idle: { freq: 1.15, amp: 0.24, speed: 0.22, heat: 0.0, scale: 1.0, attract: 0.12 },
|
||||
thinking: { freq: 2.6, amp: 0.27, speed: 0.95, heat: 0.7, scale: 0.86, attract: 1.0 },
|
||||
answering: { freq: 1.9, amp: 0.46, speed: 0.5, heat: 0.22, scale: 1.09, attract: 0.0 },
|
||||
};
|
||||
|
||||
const lerp = (a, b, t) => a + (b - a) * t;
|
||||
|
||||
export function createMind(canvas, { reduced = false } = {}) {
|
||||
const isMobile = window.innerWidth < 880;
|
||||
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
|
||||
renderer.setPixelRatio(Math.min(window.devicePixelRatio, isMobile ? 1.5 : 2));
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
|
||||
const scene = new THREE.Scene();
|
||||
const camera = new THREE.PerspectiveCamera(42, window.innerWidth / window.innerHeight, 0.1, 30);
|
||||
camera.position.z = 6;
|
||||
|
||||
const group = new THREE.Group();
|
||||
scene.add(group);
|
||||
|
||||
/* ---- 流体球 ---- */
|
||||
const blobUniforms = {
|
||||
uTime: { value: 0 },
|
||||
uFreq: { value: STATES.idle.freq },
|
||||
uAmp: { value: STATES.idle.amp },
|
||||
uSpeed: { value: STATES.idle.speed },
|
||||
uHeat: { value: 0 },
|
||||
uOpacity: { value: 1 },
|
||||
uColorA: { value: new THREE.Color("#22d3ee") },
|
||||
uColorB: { value: new THREE.Color("#a78bfa") },
|
||||
};
|
||||
const blob = new THREE.Mesh(
|
||||
new THREE.IcosahedronGeometry(1.35, isMobile ? 48 : 96),
|
||||
new THREE.ShaderMaterial({
|
||||
vertexShader: blobVertex,
|
||||
fragmentShader: blobFragment,
|
||||
uniforms: blobUniforms,
|
||||
transparent: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
})
|
||||
);
|
||||
group.add(blob);
|
||||
|
||||
/* ---- 伴生粒子 ---- */
|
||||
const COUNT = isMobile ? 900 : 2000;
|
||||
const seeds = new Float32Array(COUNT * 3);
|
||||
const radii = new Float32Array(COUNT);
|
||||
const speeds = new Float32Array(COUNT);
|
||||
for (let i = 0; i < COUNT; i++) {
|
||||
seeds[i * 3] = Math.random();
|
||||
seeds[i * 3 + 1] = Math.random();
|
||||
seeds[i * 3 + 2] = Math.random();
|
||||
radii[i] = 1.7 + Math.random() * 1.5;
|
||||
speeds[i] = 0.25 + Math.random() * 0.6;
|
||||
}
|
||||
const pGeo = new THREE.BufferGeometry();
|
||||
pGeo.setAttribute("position", new THREE.BufferAttribute(new Float32Array(COUNT * 3), 3));
|
||||
pGeo.setAttribute("aSeed", new THREE.BufferAttribute(seeds, 3));
|
||||
pGeo.setAttribute("aRadius", new THREE.BufferAttribute(radii, 1));
|
||||
pGeo.setAttribute("aSpeed", new THREE.BufferAttribute(speeds, 1));
|
||||
|
||||
const particleUniforms = {
|
||||
uTime: { value: 0 },
|
||||
uAttract: { value: STATES.idle.attract },
|
||||
uBurst: { value: 0 },
|
||||
uDir: { value: -1 },
|
||||
uSize: { value: isMobile ? 9 : 12 },
|
||||
uMouse: { value: new THREE.Vector3(999, 999, 999) },
|
||||
uMouseForce: { value: 0 },
|
||||
uGrab: { value: 0 },
|
||||
uHeat: { value: 0 },
|
||||
uOpacity: { value: 1 },
|
||||
uColorA: blobUniforms.uColorA,
|
||||
uColorB: blobUniforms.uColorB,
|
||||
};
|
||||
const particles = new THREE.Points(
|
||||
pGeo,
|
||||
new THREE.ShaderMaterial({
|
||||
vertexShader: particleVertex,
|
||||
fragmentShader: particleFragment,
|
||||
uniforms: particleUniforms,
|
||||
transparent: true,
|
||||
blending: THREE.AdditiveBlending,
|
||||
depthWrite: false,
|
||||
})
|
||||
);
|
||||
group.add(particles);
|
||||
|
||||
/* ---- 状态机 ---- */
|
||||
let current = { ...STATES.idle };
|
||||
let target = STATES.idle;
|
||||
let stateName = "idle";
|
||||
let burst = 0;
|
||||
|
||||
const colorA = blobUniforms.uColorA.value;
|
||||
const colorB = blobUniforms.uColorB.value;
|
||||
const targetColorA = colorA.clone();
|
||||
const targetColorB = colorB.clone();
|
||||
|
||||
// 停靠位置:side -1 内容在右 → 流体去左;1 → 右;0 → 居中偏后
|
||||
const dock = new THREE.Vector3();
|
||||
const dockTarget = new THREE.Vector3();
|
||||
const pointer = { x: 0, y: 0 };
|
||||
|
||||
function dockFor(side) {
|
||||
if (window.innerWidth < 880) {
|
||||
// 移动端:居中上移、退后、缩小,让内容可读
|
||||
return new THREE.Vector3(side * 0.3, 1.25, -1.6);
|
||||
}
|
||||
if (side === 0) return new THREE.Vector3(0, 0.95, -2.8);
|
||||
return new THREE.Vector3(side * 2.05, 0.1, -0.7);
|
||||
}
|
||||
|
||||
let side = 1;
|
||||
let opacityTarget = 1;
|
||||
let hasPointer = false;
|
||||
let mouseSnapped = false;
|
||||
// 按压牵引弹簧(欠阻尼 → 过冲回弹的阻尼感)
|
||||
let grab = 0;
|
||||
let grabVel = 0;
|
||||
let grabTarget = 0;
|
||||
const mouseRay = new THREE.Vector3();
|
||||
const mouseLocal = new THREE.Vector3();
|
||||
|
||||
const api = {
|
||||
setState(name) {
|
||||
if (name === stateName || reduced) return;
|
||||
stateName = name;
|
||||
target = STATES[name];
|
||||
if (name === "answering") burst = 1; // 喷发脉冲,随帧衰减
|
||||
},
|
||||
setSide(s) {
|
||||
side = s;
|
||||
dockTarget.copy(dockFor(s));
|
||||
particleUniforms.uDir.value = s === 0 ? 0 : -s; // 粒子喷向内容区
|
||||
opacityTarget = s === 0 ? 0.42 : 1; // 居中停靠时减淡,保证内容可读
|
||||
},
|
||||
setPalette([a, b]) {
|
||||
targetColorA.set(a);
|
||||
targetColorB.set(b);
|
||||
},
|
||||
get state() {
|
||||
return stateName;
|
||||
},
|
||||
debug() {
|
||||
return { stateName, side, dock: dock.toArray(), dockTarget: dockTarget.toArray(), group: group.position.toArray() };
|
||||
},
|
||||
};
|
||||
|
||||
api.setSide(1);
|
||||
dock.copy(dockTarget);
|
||||
group.position.copy(dock);
|
||||
|
||||
window.addEventListener("pointermove", (e) => {
|
||||
pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
|
||||
pointer.y = (e.clientY / window.innerHeight) * 2 - 1;
|
||||
hasPointer = true;
|
||||
});
|
||||
|
||||
window.addEventListener("pointerdown", (e) => {
|
||||
pointer.x = (e.clientX / window.innerWidth) * 2 - 1;
|
||||
pointer.y = (e.clientY / window.innerHeight) * 2 - 1;
|
||||
hasPointer = true;
|
||||
grabTarget = 1;
|
||||
});
|
||||
const release = () => { grabTarget = 0; };
|
||||
window.addEventListener("pointerup", release);
|
||||
window.addEventListener("pointercancel", release);
|
||||
window.addEventListener("blur", release);
|
||||
|
||||
window.addEventListener("resize", () => {
|
||||
renderer.setSize(window.innerWidth, window.innerHeight);
|
||||
camera.aspect = window.innerWidth / window.innerHeight;
|
||||
camera.updateProjectionMatrix();
|
||||
api.setSide(side);
|
||||
});
|
||||
|
||||
/* ---- 帧循环(由外部 ticker 驱动) ---- */
|
||||
const clock = new THREE.Clock();
|
||||
function tick() {
|
||||
const dt = Math.min(clock.getDelta(), 0.05);
|
||||
const t = clock.elapsedTime;
|
||||
const k = 1 - Math.pow(0.0014, dt); // 帧率无关的平滑系数
|
||||
|
||||
current.freq = lerp(current.freq, target.freq, k);
|
||||
current.amp = lerp(current.amp, target.amp, k);
|
||||
current.speed = lerp(current.speed, target.speed, k);
|
||||
current.heat = lerp(current.heat, target.heat, k);
|
||||
current.scale = lerp(current.scale, target.scale, k);
|
||||
current.attract = lerp(current.attract, target.attract, k);
|
||||
burst = lerp(burst, 0, 1 - Math.pow(0.25, dt)); // 脉冲衰减
|
||||
|
||||
blobUniforms.uTime.value = reduced ? t * 0.25 : t;
|
||||
blobUniforms.uFreq.value = current.freq;
|
||||
blobUniforms.uAmp.value = current.amp;
|
||||
blobUniforms.uSpeed.value = current.speed;
|
||||
blobUniforms.uHeat.value = current.heat;
|
||||
|
||||
particleUniforms.uTime.value = reduced ? t * 0.25 : t;
|
||||
particleUniforms.uAttract.value = current.attract;
|
||||
particleUniforms.uBurst.value = burst;
|
||||
particleUniforms.uHeat.value = current.heat;
|
||||
|
||||
colorA.lerp(targetColorA, k);
|
||||
colorB.lerp(targetColorB, k);
|
||||
|
||||
const op = lerp(blobUniforms.uOpacity.value, opacityTarget, k);
|
||||
blobUniforms.uOpacity.value = op;
|
||||
particleUniforms.uOpacity.value = op;
|
||||
|
||||
const breathe = 1 + Math.sin(t * 1.4) * 0.018;
|
||||
group.scale.setScalar(current.scale * breathe);
|
||||
|
||||
dock.lerp(dockTarget, 1 - Math.pow(0.02, dt));
|
||||
group.position.set(
|
||||
dock.x + pointer.x * 0.14,
|
||||
dock.y - pointer.y * 0.1,
|
||||
dock.z
|
||||
);
|
||||
group.rotation.y += dt * 0.12;
|
||||
group.rotation.x = pointer.y * 0.08;
|
||||
|
||||
// 鼠标牵引:把指针投影到流体所在深度平面,转到 group 局部空间后平滑跟随
|
||||
if (hasPointer && !reduced) {
|
||||
mouseRay.set(pointer.x, -pointer.y, 0.5).unproject(camera).sub(camera.position).normalize();
|
||||
const planeDist = (group.position.z - camera.position.z) / mouseRay.z;
|
||||
mouseLocal.copy(camera.position).addScaledVector(mouseRay, planeDist);
|
||||
group.updateMatrixWorld();
|
||||
group.worldToLocal(mouseLocal);
|
||||
if (!mouseSnapped) {
|
||||
particleUniforms.uMouse.value.copy(mouseLocal);
|
||||
mouseSnapped = true;
|
||||
} else {
|
||||
particleUniforms.uMouse.value.lerp(mouseLocal, 1 - Math.pow(0.01, dt));
|
||||
}
|
||||
particleUniforms.uMouseForce.value = lerp(particleUniforms.uMouseForce.value, 0.85, k);
|
||||
|
||||
// 按压牵引:欠阻尼弹簧积分 —— 按下缓冲吸入,松开过冲散开再归位
|
||||
grabVel += (grabTarget - grab) * 26 * dt;
|
||||
grabVel *= Math.exp(-3.2 * dt);
|
||||
grab += grabVel * dt;
|
||||
particleUniforms.uGrab.value = grab;
|
||||
}
|
||||
|
||||
renderer.render(scene, camera);
|
||||
}
|
||||
|
||||
return { ...api, tick };
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// 思维流体 GLSL —— Ashima simplex noise + 3 octave FBM + Fresnel
|
||||
const SIMPLEX = /* glsl */ `
|
||||
vec3 mod289(vec3 x){ return x - floor(x * (1.0/289.0)) * 289.0; }
|
||||
vec4 mod289(vec4 x){ return x - floor(x * (1.0/289.0)) * 289.0; }
|
||||
vec4 permute(vec4 x){ return mod289(((x*34.0)+1.0)*x); }
|
||||
vec4 taylorInvSqrt(vec4 r){ return 1.79284291400159 - 0.85373472095314 * r; }
|
||||
|
||||
float snoise(vec3 v){
|
||||
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
|
||||
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
||||
|
||||
vec3 i = floor(v + dot(v, C.yyy));
|
||||
vec3 x0 = v - i + dot(i, C.xxx);
|
||||
|
||||
vec3 g = step(x0.yzx, x0.xyz);
|
||||
vec3 l = 1.0 - g;
|
||||
vec3 i1 = min(g.xyz, l.zxy);
|
||||
vec3 i2 = max(g.xyz, l.zxy);
|
||||
|
||||
vec3 x1 = x0 - i1 + C.xxx;
|
||||
vec3 x2 = x0 - i2 + C.yyy;
|
||||
vec3 x3 = x0 - D.yyy;
|
||||
|
||||
i = mod289(i);
|
||||
vec4 p = permute(permute(permute(
|
||||
i.z + vec4(0.0, i1.z, i2.z, 1.0))
|
||||
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
|
||||
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
|
||||
|
||||
float n_ = 0.142857142857;
|
||||
vec3 ns = n_ * D.wyz - D.xzx;
|
||||
|
||||
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
||||
|
||||
vec4 x_ = floor(j * ns.z);
|
||||
vec4 y_ = floor(j - 7.0 * x_);
|
||||
|
||||
vec4 x = x_ * ns.x + ns.yyyy;
|
||||
vec4 y = y_ * ns.x + ns.yyyy;
|
||||
vec4 h = 1.0 - abs(x) - abs(y);
|
||||
|
||||
vec4 b0 = vec4(x.xy, y.xy);
|
||||
vec4 b1 = vec4(x.zw, y.zw);
|
||||
|
||||
vec4 s0 = floor(b0) * 2.0 + 1.0;
|
||||
vec4 s1 = floor(b1) * 2.0 + 1.0;
|
||||
vec4 sh = -step(h, vec4(0.0));
|
||||
|
||||
vec4 a0 = b0.xzyw + s0.xzyw * sh.xxyy;
|
||||
vec4 a1 = b1.xzyw + s1.xzyw * sh.zzww;
|
||||
|
||||
vec3 p0 = vec3(a0.xy, h.x);
|
||||
vec3 p1 = vec3(a0.zw, h.y);
|
||||
vec3 p2 = vec3(a1.xy, h.z);
|
||||
vec3 p3 = vec3(a1.zw, h.w);
|
||||
|
||||
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
|
||||
p0 *= norm.x; p1 *= norm.y; p2 *= norm.z; p3 *= norm.w;
|
||||
|
||||
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
||||
m = m * m;
|
||||
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
|
||||
}
|
||||
|
||||
float fbm(vec3 p){
|
||||
float sum = 0.0;
|
||||
float amp = 0.5;
|
||||
for (int i = 0; i < 3; i++) {
|
||||
sum += amp * snoise(p);
|
||||
p *= 2.1;
|
||||
amp *= 0.5;
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
`;
|
||||
|
||||
export const blobVertex = /* glsl */ `
|
||||
uniform float uTime;
|
||||
uniform float uFreq;
|
||||
uniform float uAmp;
|
||||
uniform float uSpeed;
|
||||
|
||||
varying float vDisp;
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vViewDir;
|
||||
|
||||
${SIMPLEX}
|
||||
|
||||
void main(){
|
||||
float t = uTime * uSpeed;
|
||||
float d = fbm(normal * uFreq + vec3(t, t * 0.7, -t * 0.4));
|
||||
vDisp = d;
|
||||
vec3 displaced = position + normal * d * uAmp;
|
||||
|
||||
vec4 mvPosition = modelViewMatrix * vec4(displaced, 1.0);
|
||||
vNormal = normalize(normalMatrix * normal);
|
||||
vViewDir = normalize(-mvPosition.xyz);
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}
|
||||
`;
|
||||
|
||||
export const blobFragment = /* glsl */ `
|
||||
uniform vec3 uColorA;
|
||||
uniform vec3 uColorB;
|
||||
uniform float uHeat;
|
||||
uniform float uOpacity;
|
||||
|
||||
varying float vDisp;
|
||||
varying vec3 vNormal;
|
||||
varying vec3 vViewDir;
|
||||
|
||||
void main(){
|
||||
float fresnel = pow(1.0 - max(dot(vNormal, vViewDir), 0.0), 2.4);
|
||||
vec3 base = mix(uColorA, uColorB, vDisp * 0.5 + 0.5);
|
||||
base = mix(base, vec3(1.0, 0.62, 0.38), uHeat * 0.4);
|
||||
vec3 color = base * (0.32 + fresnel * 1.6) + base * smoothstep(0.35, 0.9, vDisp) * 0.45;
|
||||
float alpha = (0.22 + fresnel * 0.85) * uOpacity;
|
||||
gl_FragColor = vec4(color, alpha);
|
||||
}
|
||||
`;
|
||||
|
||||
export const particleVertex = /* glsl */ `
|
||||
uniform float uTime;
|
||||
uniform float uAttract; // 1 = 吸入核心 (thinking)
|
||||
uniform float uBurst; // 1 = 向外喷发 (answering)
|
||||
uniform float uDir; // 喷发的水平方向(指向内容区)
|
||||
uniform float uSize;
|
||||
uniform vec3 uMouse; // 指针在粒子局部空间的位置
|
||||
uniform float uMouseForce;
|
||||
uniform float uGrab; // 按压牵引强度(弹簧驱动,可为负 → 回弹散开)
|
||||
|
||||
attribute vec3 aSeed; // 每粒子随机种子
|
||||
attribute float aRadius;
|
||||
attribute float aSpeed;
|
||||
|
||||
varying float vFade;
|
||||
|
||||
void main(){
|
||||
float t = uTime * aSpeed;
|
||||
// 进动轨道:两组相位叠加,避免规则圆轨
|
||||
float theta = aSeed.x * 6.2831 + t;
|
||||
float phi = aSeed.y * 3.1415 + sin(t * 0.6 + aSeed.z * 6.2831) * 0.7;
|
||||
|
||||
float r = aRadius * mix(1.0, 0.3, uAttract);
|
||||
r += uBurst * (0.8 + aSeed.z * 2.6);
|
||||
|
||||
vec3 pos = vec3(
|
||||
cos(theta) * sin(phi) * r,
|
||||
cos(phi) * r * 0.85,
|
||||
sin(theta) * sin(phi) * r
|
||||
);
|
||||
// 喷发时整体偏向内容区一侧
|
||||
pos.x += uBurst * uDir * (0.6 + aSeed.y * 1.8);
|
||||
|
||||
// 鼠标牵引:靠近指针的粒子被吸过去,绕指针形成小漩涡
|
||||
float md = distance(pos, uMouse);
|
||||
float pull = smoothstep(2.8, 0.2, md) * uMouseForce * (0.35 + aSeed.z * 0.65);
|
||||
vec3 swirl = vec3(
|
||||
sin(t * 2.2 + aSeed.x * 6.2831),
|
||||
cos(t * 1.7 + aSeed.y * 6.2831),
|
||||
sin(t * 1.3 + aSeed.z * 6.2831)
|
||||
) * (0.12 + aSeed.y * 0.3);
|
||||
pos = mix(pos, uMouse + swirl, pull);
|
||||
|
||||
// 按住鼠标:全体粒子被牵向按压点,逐粒错相往复脉动;
|
||||
// uGrab 为负(松开回弹)时 mix 外推 → 粒子向外散开再被拉回
|
||||
float osc = 0.55 + 0.45 * sin(uTime * (1.2 + aSeed.x * 1.6) + aSeed.y * 6.2831);
|
||||
float gpull = clamp(uGrab, -0.35, 1.25) * osc * (0.5 + aSeed.z * 0.5);
|
||||
gpull = clamp(gpull, -0.4, 0.96);
|
||||
vec3 gswirl = vec3(
|
||||
sin(t * 1.8 + aSeed.y * 6.2831),
|
||||
cos(t * 1.4 + aSeed.z * 6.2831),
|
||||
sin(t * 1.1 + aSeed.x * 6.2831)
|
||||
) * (0.18 + aSeed.x * 0.5);
|
||||
pos = mix(pos, uMouse + gswirl, gpull);
|
||||
|
||||
vFade = 1.0 - uBurst * aSeed.z * 0.9;
|
||||
vFade = min(vFade + pull * 0.5 + max(gpull, 0.0) * 0.35, 1.2);
|
||||
|
||||
vec4 mvPosition = modelViewMatrix * vec4(pos, 1.0);
|
||||
gl_PointSize = uSize * (0.6 + aSeed.z) * (1.0 + pull * 0.5 + max(gpull, 0.0) * 0.3) * (3.4 / -mvPosition.z);
|
||||
gl_Position = projectionMatrix * mvPosition;
|
||||
}
|
||||
`;
|
||||
|
||||
export const particleFragment = /* glsl */ `
|
||||
uniform vec3 uColorA;
|
||||
uniform vec3 uColorB;
|
||||
uniform float uHeat;
|
||||
uniform float uOpacity;
|
||||
|
||||
varying float vFade;
|
||||
|
||||
void main(){
|
||||
vec2 uv = gl_PointCoord - 0.5;
|
||||
float d = length(uv);
|
||||
float mask = smoothstep(0.5, 0.05, d);
|
||||
vec3 color = mix(uColorA, uColorB, vFade);
|
||||
color = mix(color, vec3(1.0, 0.62, 0.38), uHeat * 0.35);
|
||||
gl_FragColor = vec4(color, mask * vFade * 0.75 * uOpacity);
|
||||
}
|
||||
`;
|
||||
File diff suppressed because it is too large
Load Diff
+264
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* 将简历数据渲染为对话式作品集页面,负责不同回合的 DOM 结构装配。
|
||||
*/
|
||||
import {
|
||||
experiences,
|
||||
focusAreas,
|
||||
metrics,
|
||||
projects,
|
||||
resumeSignals,
|
||||
skills,
|
||||
} from "./resume-data";
|
||||
import { contact, rounds } from "./dialogue";
|
||||
|
||||
const el = (tag, className, html) => {
|
||||
const node = document.createElement(tag);
|
||||
if (className) node.className = className;
|
||||
if (html != null) node.innerHTML = html;
|
||||
return node;
|
||||
};
|
||||
|
||||
const externalLink = (className, href, text, ariaLabel = text) => {
|
||||
const node = el("a", className);
|
||||
node.href = href;
|
||||
node.target = "_blank";
|
||||
node.rel = "noreferrer";
|
||||
node.textContent = text;
|
||||
node.setAttribute("aria-label", ariaLabel);
|
||||
return node;
|
||||
};
|
||||
|
||||
/* ---------- 各轮回答内容 ---------- */
|
||||
|
||||
function bootContent(answer) {
|
||||
answer.append(
|
||||
el(
|
||||
"h1",
|
||||
"boot-greeting",
|
||||
`你好,我是<span class="grad">湛兮(花名)</span>。<br /><span class="typed-line"></span>`,
|
||||
),
|
||||
el(
|
||||
"p",
|
||||
"boot-identity gen",
|
||||
`7 年 Web 与跨端前端 · 最近一年在 <em>AI 产品团队</em> 做对话链路与控制台`,
|
||||
),
|
||||
);
|
||||
const stream = el("div", "metric-stream");
|
||||
metrics.forEach((m) => {
|
||||
const token = el("div", "metric-token gen");
|
||||
token.append(el("span", "v", m.value), el("span", "l", m.label));
|
||||
stream.append(token);
|
||||
});
|
||||
answer.append(stream, el("div", "boot-hint gen", "SCROLL TO ASK"));
|
||||
}
|
||||
|
||||
function signalsContent(answer) {
|
||||
answer.append(el("div", "a-title gen", "Self Introduction"));
|
||||
resumeSignals.forEach((line, i) => {
|
||||
const row = el("div", "signal-line gen");
|
||||
row.append(
|
||||
el("span", "idx", String(i + 1).padStart(2, "0")),
|
||||
el("p", null, line),
|
||||
);
|
||||
answer.append(row);
|
||||
});
|
||||
}
|
||||
|
||||
function focusContent(answer) {
|
||||
answer.append(el("div", "a-title gen", "Last 12 Months"));
|
||||
const grid = el("div", "focus-grid");
|
||||
answer.append(grid);
|
||||
focusAreas.forEach((f) => {
|
||||
const card = el("div", "focus-card gen");
|
||||
card.append(el("h3", null, f.title), el("p", null, f.summary));
|
||||
const tags = el("div", "tag-row");
|
||||
f.points.forEach((p) => tags.append(el("span", "tag", p)));
|
||||
card.append(tags);
|
||||
grid.append(card);
|
||||
});
|
||||
}
|
||||
|
||||
function projectsContent(answer) {
|
||||
answer.append(el("div", "a-title gen", "Selected Proof"));
|
||||
projects.forEach((p) => {
|
||||
const block = el("article", "project-block gen");
|
||||
const head = el("div", "project-head");
|
||||
const meta = el("div", "project-meta");
|
||||
meta.append(
|
||||
p.url
|
||||
? externalLink("name entity-link", p.url, p.name, `打开 ${p.name}`)
|
||||
: el("span", "name", p.name),
|
||||
);
|
||||
if (p.links?.length > 1) {
|
||||
const links = el("div", "project-links");
|
||||
p.links.forEach((link) => {
|
||||
links.append(
|
||||
externalLink("mini-link", link.url, link.label, `打开 ${link.label}`),
|
||||
);
|
||||
});
|
||||
meta.append(links);
|
||||
}
|
||||
head.append(
|
||||
Object.assign(el("img"), { src: p.logo, alt: p.name, loading: "lazy" }),
|
||||
meta,
|
||||
el("span", "period", p.period),
|
||||
);
|
||||
block.append(
|
||||
head,
|
||||
el("div", "project-sub", p.subtitle),
|
||||
el("p", "project-summary", p.summary),
|
||||
);
|
||||
const ul = el("ul", "project-modules");
|
||||
p.modules.forEach((m) => ul.append(el("li", "gen-sub", m)));
|
||||
block.append(ul);
|
||||
const tags = el("div", "tag-row");
|
||||
p.tech.forEach((t) => tags.append(el("span", "tag", t)));
|
||||
block.append(tags);
|
||||
answer.append(block);
|
||||
});
|
||||
}
|
||||
|
||||
function skillsContent(answer) {
|
||||
answer.append(el("div", "a-title gen", "Token Stream / Skills"));
|
||||
const HOT = new Set([
|
||||
"SSE 流式通信",
|
||||
"Next.js 16",
|
||||
"React 19",
|
||||
"React Native",
|
||||
"TypeScript",
|
||||
"NestJS",
|
||||
"MySQL",
|
||||
"Redis",
|
||||
"Jenkins",
|
||||
"Gitea",
|
||||
"发布步骤轨迹",
|
||||
"Qiankun",
|
||||
]);
|
||||
const grid = el("div", "skills-grid");
|
||||
skills.forEach((group) => {
|
||||
const wrap = el("div", "skill-group gen");
|
||||
const head = el("div", "g-head");
|
||||
head.append(
|
||||
el("div", "g-name", group.group),
|
||||
el("span", "g-count", `${group.items.length} 项`),
|
||||
);
|
||||
wrap.append(head);
|
||||
if (group.summary) wrap.append(el("p", "g-summary", group.summary));
|
||||
const row = el("div", "tag-row");
|
||||
group.items.forEach((item) => {
|
||||
row.append(
|
||||
el("span", `skill-token gen${HOT.has(item) ? " hot" : ""}`, item),
|
||||
);
|
||||
});
|
||||
wrap.append(row);
|
||||
grid.append(wrap);
|
||||
});
|
||||
answer.append(grid);
|
||||
}
|
||||
|
||||
function experienceContent(answer) {
|
||||
answer.append(el("div", "a-title gen", "Career Path"));
|
||||
experiences.forEach((exp) => {
|
||||
const item = el("div", "exp-item gen");
|
||||
const head = el("div", "exp-head");
|
||||
head.append(
|
||||
Object.assign(el("img"), {
|
||||
src: exp.logo,
|
||||
alt: exp.company,
|
||||
loading: "lazy",
|
||||
}),
|
||||
exp.url
|
||||
? externalLink(
|
||||
"co entity-link",
|
||||
exp.url,
|
||||
exp.company,
|
||||
`打开 ${exp.company}`,
|
||||
)
|
||||
: el("span", "co", exp.company),
|
||||
el("span", "period", exp.period),
|
||||
);
|
||||
item.append(head, el("div", "exp-role", exp.role));
|
||||
const ul = el("ul", "exp-points");
|
||||
exp.points.forEach((pt) => ul.append(el("li", null, pt)));
|
||||
item.append(ul);
|
||||
answer.append(item);
|
||||
});
|
||||
}
|
||||
|
||||
function contactContent(answer) {
|
||||
answer.append(
|
||||
el("h2", "contact-lead gen", "随时可以开启下一轮对话。"),
|
||||
el("p", "contact-id gen", contact.identity),
|
||||
);
|
||||
const actions = el("div", "contact-actions gen");
|
||||
const mail = el("a", "contact-btn primary", "发送邮件");
|
||||
mail.href = `mailto:${contact.emails[0]}`;
|
||||
const mail2 = el("a", "contact-btn", contact.emails[1]);
|
||||
mail2.href = `mailto:${contact.emails[1]}`;
|
||||
const gh = el("a", "contact-btn", "GitHub / zhanBoss");
|
||||
gh.href = contact.github;
|
||||
gh.target = "_blank";
|
||||
gh.rel = "noreferrer";
|
||||
const tel = el("a", "contact-btn", contact.phone);
|
||||
tel.href = `tel:${contact.phone}`;
|
||||
actions.append(mail, mail2, gh, tel);
|
||||
answer.append(actions, el("div", "session-end gen", "SESSION SAVED · 2026"));
|
||||
}
|
||||
|
||||
const CONTENT = {
|
||||
boot: bootContent,
|
||||
signals: signalsContent,
|
||||
focus: focusContent,
|
||||
projects: projectsContent,
|
||||
skills: skillsContent,
|
||||
experience: experienceContent,
|
||||
contact: contactContent,
|
||||
};
|
||||
|
||||
/* ---------- 装配 ---------- */
|
||||
|
||||
/**
|
||||
* 根据对话回合配置生成主内容区,保留滚动叙事所需的 section 结构。
|
||||
*/
|
||||
export function renderDialogue(mount) {
|
||||
rounds.forEach((round) => {
|
||||
const section = el("section", "round");
|
||||
section.id = `r-${round.id}`;
|
||||
section.dataset.side = String(round.side);
|
||||
const inner = el("div", "round-inner");
|
||||
|
||||
if (round.question) {
|
||||
const qRow = el("div", "q-row");
|
||||
const bubble = el("div", "q-bubble");
|
||||
bubble.append(el("span", "q-text"), el("span", "q-caret"));
|
||||
qRow.append(bubble);
|
||||
const thinking = el("div", "thinking");
|
||||
const dots = el("span", "dots");
|
||||
dots.append(el("span", "dot"), el("span", "dot"), el("span", "dot"));
|
||||
thinking.append(dots, el("span", null, "THINKING"));
|
||||
inner.append(qRow, thinking);
|
||||
}
|
||||
|
||||
const answer = el("div", "answer");
|
||||
CONTENT[round.type](answer);
|
||||
if (round.type !== "boot") answer.append(el("span", "a-caret", "▋"));
|
||||
inner.append(answer);
|
||||
section.append(inner);
|
||||
mount.append(section);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成右侧回合导航节点,并把点击行为交给滚动编排模块处理。
|
||||
*/
|
||||
export function renderRail(mount, onJump) {
|
||||
rounds.forEach((round) => {
|
||||
const node = el("button", "rail-node");
|
||||
node.type = "button";
|
||||
node.dataset.round = round.id;
|
||||
node.setAttribute("aria-label", `跳转到 ${round.label}`);
|
||||
node.append(el("span", "rail-label", round.label));
|
||||
node.addEventListener("click", () => onJump(round.id));
|
||||
mount.append(node);
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,219 @@
|
||||
/**
|
||||
* 维护作品集页面的简历指标、能力标签、项目经历和职业路径文案。
|
||||
*/
|
||||
export const metrics = [
|
||||
{ value: "7 年", label: "Web 与跨端前端经验" },
|
||||
{ value: "2 条", label: "近一年参与的产品线" },
|
||||
{ value: "6 个", label: "纳管项目发布闭环" },
|
||||
{ value: "SSE", label: "对话流、思考态、消息重试" },
|
||||
{ value: "12 个", label: "微前端拆分基础项目" },
|
||||
{ value: "30%", label: "消息渲染链路优化结果" },
|
||||
{ value: "6000+", label: "门店业务系统服务规模" },
|
||||
];
|
||||
|
||||
export const focusAreas = [
|
||||
{
|
||||
title: "对话链路",
|
||||
summary: "做过流式消息、思考态、来源引用、历史同步和失败重试,重点是让对话过程稳定、可恢复。",
|
||||
points: ["SSE", "Markdown/LaTeX", "来源引用", "消息重试"],
|
||||
},
|
||||
{
|
||||
title: "控制台业务",
|
||||
summary: "参与过 API Key、账单、组织权限、额度、交易筛选和导出,知道复杂后台最怕状态不清和边界不稳。",
|
||||
points: ["API Key", "Billing", "RBAC", "Excel 导出"],
|
||||
},
|
||||
{
|
||||
title: "跨端内容流",
|
||||
summary: "在 Expo / React Native 项目里做过新闻流、详情页、瀑布流、图片组件和游客转登录数据处理。",
|
||||
points: ["Expo", "瀑布流", "Smart Image", "游客绑定"],
|
||||
},
|
||||
{
|
||||
title: "工程习惯",
|
||||
summary: "经历过微前端拆分、跨项目公共包、i18n 协作、监控接入、发布平台和代码审查,能把模块交付和团队维护放在一起考虑。",
|
||||
points: ["Qiankun", "Monorepo", "DevOps", "Code Review"],
|
||||
},
|
||||
];
|
||||
|
||||
/**
|
||||
* 首页自我介绍回合的关键叙事点,突出近一年方向与个人项目信号。
|
||||
*/
|
||||
export const resumeSignals = [
|
||||
"前端经验覆盖 PC 管理后台、小程序、H5、React Native 和微前端,最近一年主要在 AI 产品团队做业务前端。",
|
||||
"SeaBuzz 侧更偏用户体验:对话、内容流、新闻详情、游客数据、分享和反馈链路。",
|
||||
"SeaCloud / Vtrix 侧更偏控制台:Pricing、Billing、API Keys、组织权限、交易筛选和分销配置。",
|
||||
"个人工程实践里搭建了 DevOps 运维平台,把 Gitea、Jenkins、发布步骤轨迹、通知、审计、Agent 诊断和 Jenkins 直构导入串成发布闭环。",
|
||||
"技术栈以 React / Next.js / React Native / TypeScript 为主,能覆盖 Vue、UniApp、Qiankun、NestJS、MySQL、Redis 和 Jenkins/Gitea 发布链路。",
|
||||
];
|
||||
|
||||
/**
|
||||
* 技能栏按招聘方扫读路径分组,避免基础设施与数据层能力被埋在项目详情里。
|
||||
*/
|
||||
export const skills = [
|
||||
{
|
||||
group: "前端主栈",
|
||||
summary: "主力交付栈,覆盖 AI 产品、控制台、移动端和多端内容流。",
|
||||
items: ["React 19", "Next.js 16", "React Native", "TypeScript", "Vue 3", "Element Plus", "Tailwind CSS", "Zustand", "Alova"],
|
||||
},
|
||||
{
|
||||
group: "数据与后端协作",
|
||||
summary: "能理解接口、数据模型和状态一致性,不只停留在页面拼装。",
|
||||
items: ["NestJS", "OpenAPI", "DTO 校验", "Prisma", "MySQL", "Redis", "BullMQ", "幂等键", "乐观锁"],
|
||||
},
|
||||
{
|
||||
group: "DevOps 与发布",
|
||||
summary: "从个人项目实践中补齐发布闭环、可观测性和运维诊断意识。",
|
||||
items: ["DevOps 平台", "Jenkins", "Jenkins 直构导入", "Gitea", "发布步骤轨迹", "Outbox", "审计日志", "构建日志脱敏", "Runbook", "高风险操作确认"],
|
||||
},
|
||||
{
|
||||
group: "AI 对话与内容",
|
||||
summary: "最近一年重点投入的方向,关注流式体验、上下文恢复和内容可读性。",
|
||||
items: ["SSE 流式通信", "打字机响应", "Markdown/LaTeX", "来源引用", "思考态", "对话历史", "新闻详情"],
|
||||
},
|
||||
{
|
||||
group: "控制台业务",
|
||||
summary: "长期处理权限、账务、额度、筛选导出等后台业务边界。",
|
||||
items: ["Pricing", "Billing", "API Keys", "组织权限", "额度限制", "交易筛选", "Excel 导出", "成员权限"],
|
||||
},
|
||||
{
|
||||
group: "跨端与小程序",
|
||||
summary: "覆盖 App、H5、小程序和门店设备链路,能处理端侧能力差异。",
|
||||
items: ["Expo Router", "NativeWind", "UniApp", "微信小程序", "飞书小程序", "游客转登录", "Smart Image", "摄像头扫码", "低功耗蓝牙"],
|
||||
},
|
||||
{
|
||||
group: "工程协作与质量",
|
||||
summary: "偏团队长期维护视角,关注复用、规范、监控和评审质量。",
|
||||
items: ["pnpm Monorepo", "Git Submodules", "Qiankun", "Lerna", "i18n 同步", "ARMS 监控", "Playwright", "Git Flow", "Code Review"],
|
||||
},
|
||||
];
|
||||
|
||||
export const projects = [
|
||||
{
|
||||
id: "devops-platform",
|
||||
name: "DevOps 运维平台",
|
||||
logo: "/logos/devops-platform.svg",
|
||||
period: "2026.06 - 至今",
|
||||
subtitle: "个人项目发布与可观测控制台",
|
||||
summary:
|
||||
"从零搭建一套自用 DevOps 运维平台,把本地与线上项目的发布、Jenkins 构建与直构导入、Gitea refs、发布步骤轨迹、通知 outbox、审计日志和 Agent 诊断收敛到一个控制台。",
|
||||
modules: [
|
||||
"前端采用 Vue 3、Vite、TypeScript、Element Plus 和 Pinia,提供登录、成员权限、项目诊断、发布中心、运行记录、系统配置、发布步骤轨迹和全局 Agent 抽屉。",
|
||||
"后端采用 NestJS、Prisma、MySQL、Redis/BullMQ,接入统一 envelope、DTO 校验、结构化日志、审计记录、幂等键和发布单乐观锁。",
|
||||
"打通 Gitea 分支/tag/commit/PR 摘要、平台触发 Jenkins queue/build 同步、Jenkins 直接构建反向导入、构建日志脱敏读取、取消/重试发布和后端步骤轨迹展示。",
|
||||
"将通知投递改为 outbox 持久化与可重试模型,并把 LLM Agent 限定在发布风险、失败诊断、Runbook、发布说明和事故复盘场景。",
|
||||
"完成产品整改:移除未使用的流程图依赖,修复发布中心跨项目 run 误操作风险,补齐发布、同步、重试、成员权限和通知重发确认,并通过路由懒加载与 Element Plus 按需注册消除大 chunk 警告。",
|
||||
],
|
||||
tech: ["Vue 3", "NestJS", "TypeScript", "Prisma", "MySQL", "Redis", "BullMQ", "Jenkins", "Gitea", "发布步骤轨迹"],
|
||||
},
|
||||
{
|
||||
id: "vtrix",
|
||||
name: "SeaCloud / Vtrix",
|
||||
logo: "/logos/vtrix.png",
|
||||
url: "https://cloud.seaart.ai/",
|
||||
links: [
|
||||
{ label: "SeaCloud", url: "https://cloud.seaart.ai/" },
|
||||
{ label: "Vtrix", url: "https://www.vtrix.ai/" },
|
||||
],
|
||||
period: "2025.09 - 至今",
|
||||
subtitle: "模型服务平台控制台",
|
||||
summary:
|
||||
"参与模型服务平台的前端建设,主要处理 Pricing、Billing、API Keys、组织权限、额度和分销相关页面。项目复杂度不在单个页面,而在状态、权限和账务边界。",
|
||||
modules: [
|
||||
"建设分销控制台:客户邀请、折扣模板、额度分配、销售配置与利润率计算。",
|
||||
"封装 useCurrency Hook,贯穿 Pricing、Billing、API Keys 等模型调用和费用模块。",
|
||||
"落地组织角色权限、成员配额、支出限额、账单报表、交易筛选与 Excel 导出。",
|
||||
"参与 i18n Submodule 架构迁移、企业微信翻译同步和通用表格/筛选器/分页组件增强。",
|
||||
],
|
||||
tech: ["Next.js 16", "React 19", "TypeScript", "Tailwind CSS", "Zustand", "Alova", "Radix UI", "next-intl"],
|
||||
},
|
||||
{
|
||||
id: "seabuzz",
|
||||
name: "SeaBuzz",
|
||||
logo: "/logos/seabuzz.webp",
|
||||
url: "https://play.google.com/store/apps/details?id=app.seahot.ai",
|
||||
period: "2025.05 - 至今",
|
||||
subtitle: "资讯、搜索与对话应用",
|
||||
summary:
|
||||
"基于 Expo 的跨端应用,覆盖 iOS、Android、Web。我的工作集中在对话链路、内容流、登录绑定、分享和公共包沉淀。",
|
||||
modules: [
|
||||
"实现对话链路:SSE 流式聊天、打字机渲染、Markdown/LaTeX、思考动画、来源引用、消息重写与历史同步。",
|
||||
"处理游客聊天记录本地存储、登录后自动绑定、分享链接只读模式和反馈机制。",
|
||||
"交付 Discover 发现页、新闻详情、瀑布流动态布局、骨架屏、Smart Image 与富文本章节渲染。",
|
||||
"集成 Google、Facebook、Discord、Email 登录与 SeaArt Auth SDK。",
|
||||
"封装 Monorepo 公共包体系,覆盖 API、数据模型、UI 组件、状态、OTA 热更新与代码签名。",
|
||||
],
|
||||
tech: ["React Native", "Expo 53", "Expo Router", "Zustand", "NativeWind", "SSE", "i18next", "Adyen"],
|
||||
},
|
||||
{
|
||||
id: "bawang",
|
||||
name: "霸王功夫",
|
||||
logo: "/logos/chagee.png",
|
||||
url: "https://bwcj.com/",
|
||||
period: "2024.03 - 2025.03",
|
||||
subtitle: "门店、食安与供应链系统",
|
||||
summary:
|
||||
"服务全球 6000+ 门店及运营伙伴,覆盖门店运营、食品安全、供应链协同等核心业务。",
|
||||
modules: [
|
||||
"搭建小程序报损模块,调用摄像头扫码并兼容微信小程序与飞书 H5。",
|
||||
"实现食安管理模块,通过低频蓝牙连接 TSPL 指令集打印机,覆盖国内、东南亚、北美三套业务逻辑。",
|
||||
"交付多门店经营状态移动看板,支持区域、时间、指标筛选。",
|
||||
"负责采购、供应商合同、供应商结算等供应链系统,对接云厉、费控等外部系统。",
|
||||
],
|
||||
tech: ["React 18", "Vue 3", "UniApp", "MobX", "Pinia", "Element Plus", "Ant Design"],
|
||||
},
|
||||
{
|
||||
id: "jingyingbang",
|
||||
name: "经营帮平台 + 经营帮拉新",
|
||||
logo: "/logos/jingyingbang.png",
|
||||
url: "https://jingyingbang.com/",
|
||||
period: "2022.04 - 2024.03",
|
||||
subtitle: "微前端平台与小程序",
|
||||
summary:
|
||||
"基于信息化设计理念和区块链技术的工业互联网平台,为企业和个人提供数字化运营服务。",
|
||||
modules: [
|
||||
"参与单体前端到 Qiankun 微前端拆分,共拆分出 12 个基础项目。",
|
||||
"负责商管、门户、经营帮小程序、H5、Admin 后台核心业务交付。",
|
||||
"引入华为云 OBS 直传,降低请求链路性能浪费。",
|
||||
"开发内部 Chrome 插件 zjkj-decryption,提升数据解析效率。",
|
||||
],
|
||||
tech: ["Vue", "Qiankun", "Element UI", "华为云 OBS", "百度地图", "高德地图", "UniApp"],
|
||||
},
|
||||
];
|
||||
|
||||
export const experiences = [
|
||||
{
|
||||
company: "成都海艺互娱科技有限公司",
|
||||
logo: "/logos/seaart.webp",
|
||||
url: "https://www.seaart.ai/",
|
||||
role: "React Native 开发工程师",
|
||||
period: "2025.05 - 至今",
|
||||
points: [
|
||||
"参与 SeaBuzz、SeaCloud / Vtrix 等项目,主要负责 React Native 跨端页面和 Web 控制台模块。",
|
||||
"负责对话流、内容流、分享、游客数据绑定、账单、API Keys、组织权限等业务页面开发。",
|
||||
"优化消息渲染链路和跨端公共包复用,消息渲染性能提升约 30%。",
|
||||
],
|
||||
},
|
||||
{
|
||||
company: "四川茶姬企业管理有限公司",
|
||||
logo: "/logos/chagee.png",
|
||||
url: "https://bwcj.com/",
|
||||
role: "高级 Web 前端开发",
|
||||
period: "2024.03 - 2025.03",
|
||||
points: [
|
||||
"主导供应链管理系统前端开发,实现采购、仓储、配送等核心模块功能。",
|
||||
"独立负责门店报损与食安核心功能搭建及迭代。",
|
||||
"完成前端监控体系引入、CDN 静态资源优化、代码审查机制与 Git 操作规范建设。",
|
||||
],
|
||||
},
|
||||
{
|
||||
company: "中钧科技有限公司四川分公司",
|
||||
logo: "/logos/zhongjun.png",
|
||||
url: "https://zhongjunkeji.com/",
|
||||
role: "前端开发组长",
|
||||
period: "2022.04 - 2024.03",
|
||||
points: [
|
||||
"负责经营帮 PC 端微前端、门户和商管核心模块开发,完成 UI 还原、接口联调与性能优化。",
|
||||
"从零搭建新 E 畅行小程序基础框架,制定技术方案并完成全流程开发。",
|
||||
"设计 Git Flow、代码审查与分支管理机制,主导新人培训与项目分工协调。",
|
||||
],
|
||||
},
|
||||
];
|
||||
+837
@@ -0,0 +1,837 @@
|
||||
/* ============ 深色 AI 实验室 ============ */
|
||||
:root {
|
||||
color-scheme: dark;
|
||||
--bg: #070b14;
|
||||
--bg-soft: #0b1120;
|
||||
--ink: #e6edf7;
|
||||
--ink-dim: #8b97ad;
|
||||
--ink-faint: #4d586e;
|
||||
--cyan: #22d3ee;
|
||||
--violet: #a78bfa;
|
||||
--amber: #fbbf24;
|
||||
--line: rgba(139, 151, 173, 0.16);
|
||||
--mono: "JetBrains Mono", "SFMono-Regular", ui-monospace, Menlo, monospace;
|
||||
--sans: "Inter", "PingFang SC", "Noto Sans SC", "Microsoft YaHei", sans-serif;
|
||||
}
|
||||
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
html {
|
||||
scroll-behavior: auto;
|
||||
}
|
||||
|
||||
body {
|
||||
background: var(--bg);
|
||||
color: var(--ink);
|
||||
font-family: var(--sans);
|
||||
font-size: 16px;
|
||||
line-height: 1.7;
|
||||
overflow-x: hidden;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
::selection {
|
||||
background: rgba(34, 211, 238, 0.35);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--cyan);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.entity-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--ink);
|
||||
transition: color 0.18s ease;
|
||||
}
|
||||
|
||||
.entity-link::after {
|
||||
content: "↗";
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
color: var(--cyan);
|
||||
opacity: 0.72;
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.entity-link:hover {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
/* ============ 底层画布与氛围 ============ */
|
||||
#mind-canvas {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.grid-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
z-index: 0;
|
||||
pointer-events: none;
|
||||
background-image: radial-gradient(rgba(139, 151, 173, 0.13) 1px, transparent 1px);
|
||||
background-size: 34px 34px;
|
||||
mask-image: radial-gradient(ellipse 90% 80% at 50% 45%, black 30%, transparent 100%);
|
||||
-webkit-mask-image: radial-gradient(ellipse 90% 80% at 50% 45%, black 30%, transparent 100%);
|
||||
}
|
||||
|
||||
/* ============ HUD ============ */
|
||||
.hud {
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 18px 28px;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.14em;
|
||||
background: linear-gradient(to bottom, rgba(7, 11, 20, 0.85), transparent);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.hud-id {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.hud-name {
|
||||
color: var(--ink);
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hud-sub {
|
||||
color: var(--ink-faint);
|
||||
font-size: 10px;
|
||||
}
|
||||
|
||||
.hud-status {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.status-dot {
|
||||
width: 7px;
|
||||
height: 7px;
|
||||
border-radius: 50%;
|
||||
background: var(--cyan);
|
||||
box-shadow: 0 0 10px var(--cyan);
|
||||
animation: pulse 2.2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.35; }
|
||||
}
|
||||
|
||||
/* ============ 会话进度轨 ============ */
|
||||
#session-rail {
|
||||
position: fixed;
|
||||
right: 22px;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.rail-node {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-direction: row-reverse;
|
||||
background: none;
|
||||
border: 0;
|
||||
padding: 6px 0;
|
||||
cursor: pointer;
|
||||
font-family: var(--mono);
|
||||
font-size: 9px;
|
||||
letter-spacing: 0.18em;
|
||||
color: var(--ink-faint);
|
||||
transition: color 0.3s;
|
||||
}
|
||||
|
||||
.rail-node::after {
|
||||
content: "";
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid var(--ink-faint);
|
||||
transition: all 0.3s;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
.rail-node .rail-label {
|
||||
opacity: 0;
|
||||
transform: translateX(6px);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.rail-node:hover .rail-label {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
.rail-node.active {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.rail-node.active::after {
|
||||
background: var(--cyan);
|
||||
border-color: var(--cyan);
|
||||
box-shadow: 0 0 12px var(--cyan);
|
||||
}
|
||||
|
||||
.rail-node.active .rail-label {
|
||||
opacity: 1;
|
||||
transform: none;
|
||||
}
|
||||
|
||||
/* ============ 对话轮次 ============ */
|
||||
main {
|
||||
position: relative;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.round {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 72px 7vw;
|
||||
}
|
||||
|
||||
.round-inner {
|
||||
width: min(640px, 100%);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.round[data-side="-1"] .round-inner { margin-left: auto; }
|
||||
.round[data-side="1"] .round-inner { margin-right: auto; }
|
||||
.round[data-side="0"] .round-inner { margin: 0 auto; }
|
||||
|
||||
/* 提问气泡(访客) */
|
||||
.q-bubble {
|
||||
display: inline-flex;
|
||||
align-items: baseline;
|
||||
margin-left: auto;
|
||||
margin-bottom: 24px;
|
||||
padding: 12px 20px;
|
||||
border: 1px solid rgba(34, 211, 238, 0.45);
|
||||
border-radius: 18px 18px 4px 18px;
|
||||
font-family: var(--mono);
|
||||
font-size: 15px;
|
||||
color: var(--cyan);
|
||||
background: rgba(34, 211, 238, 0.05);
|
||||
min-height: 46px;
|
||||
min-width: 60px;
|
||||
}
|
||||
|
||||
.q-row {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.q-caret {
|
||||
display: inline-block;
|
||||
width: 8px;
|
||||
height: 1.1em;
|
||||
margin-left: 4px;
|
||||
background: var(--cyan);
|
||||
transform: translateY(2px);
|
||||
animation: blink 0.9s steps(1) infinite;
|
||||
}
|
||||
|
||||
@keyframes blink {
|
||||
50% { opacity: 0; }
|
||||
}
|
||||
|
||||
/* 思考态 */
|
||||
.thinking {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 22px;
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
letter-spacing: 0.2em;
|
||||
color: var(--violet);
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.thinking .dots {
|
||||
display: inline-flex;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.thinking .dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
border-radius: 50%;
|
||||
background: var(--violet);
|
||||
animation: think 1.1s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.thinking .dot:nth-child(2) { animation-delay: 0.18s; }
|
||||
.thinking .dot:nth-child(3) { animation-delay: 0.36s; }
|
||||
|
||||
@keyframes think {
|
||||
0%, 100% { transform: translateY(0); opacity: 0.4; }
|
||||
50% { transform: translateY(-5px); opacity: 1; }
|
||||
}
|
||||
|
||||
/* 回答块 */
|
||||
.answer {
|
||||
position: relative;
|
||||
padding-left: 26px;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.answer::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 4px;
|
||||
bottom: 4px;
|
||||
width: 2px;
|
||||
border-radius: 2px;
|
||||
background: linear-gradient(to bottom, var(--cyan), var(--violet));
|
||||
box-shadow: 0 0 14px rgba(167, 139, 250, 0.5);
|
||||
}
|
||||
|
||||
.a-caret {
|
||||
display: inline-block;
|
||||
margin-top: 14px;
|
||||
color: var(--violet);
|
||||
font-family: var(--mono);
|
||||
animation: blink 1s steps(1) infinite;
|
||||
}
|
||||
|
||||
.a-title {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
letter-spacing: 0.22em;
|
||||
color: var(--violet);
|
||||
margin-bottom: 18px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
/* ============ Round 0: BOOT ============ */
|
||||
#r-boot .round-inner {
|
||||
width: min(760px, 100%);
|
||||
}
|
||||
|
||||
.boot-greeting {
|
||||
font-size: clamp(34px, 5.4vw, 58px);
|
||||
font-weight: 700;
|
||||
line-height: 1.18;
|
||||
letter-spacing: -0.01em;
|
||||
min-height: 1.2em;
|
||||
}
|
||||
|
||||
.boot-greeting .grad {
|
||||
background: linear-gradient(100deg, var(--cyan), var(--violet));
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.boot-identity {
|
||||
margin-top: 20px;
|
||||
font-family: var(--mono);
|
||||
font-size: 14px;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
.boot-identity em {
|
||||
font-style: normal;
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.metric-stream {
|
||||
margin-top: 44px;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.metric-token {
|
||||
font-family: var(--mono);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 10px;
|
||||
padding: 10px 16px;
|
||||
background: rgba(11, 17, 32, 0.7);
|
||||
backdrop-filter: blur(6px);
|
||||
}
|
||||
|
||||
.metric-token .v {
|
||||
color: var(--amber);
|
||||
font-weight: 700;
|
||||
font-size: 17px;
|
||||
}
|
||||
|
||||
.metric-token .l {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
color: var(--ink-dim);
|
||||
margin-top: 2px;
|
||||
}
|
||||
|
||||
.boot-hint {
|
||||
margin-top: 56px;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.24em;
|
||||
color: var(--ink-faint);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.boot-hint::after {
|
||||
content: "↓";
|
||||
animation: bob 1.6s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes bob {
|
||||
0%, 100% { transform: translateY(0); }
|
||||
50% { transform: translateY(6px); }
|
||||
}
|
||||
|
||||
/* ============ Round 1: 概述 ============ */
|
||||
.signal-line {
|
||||
display: flex;
|
||||
gap: 14px;
|
||||
margin-bottom: 18px;
|
||||
font-size: 16px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.signal-line .idx {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--ink-faint);
|
||||
padding-top: 5px;
|
||||
flex: none;
|
||||
}
|
||||
|
||||
/* ============ Round 2: 焦点卡 ============ */
|
||||
.focus-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.focus-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 14px;
|
||||
padding: 20px;
|
||||
background: rgba(11, 17, 32, 0.72);
|
||||
backdrop-filter: blur(8px);
|
||||
transition: border-color 0.3s;
|
||||
}
|
||||
|
||||
.focus-card:hover {
|
||||
border-color: rgba(34, 211, 238, 0.4);
|
||||
}
|
||||
|
||||
.focus-card h3 {
|
||||
font-size: 16px;
|
||||
margin-bottom: 8px;
|
||||
color: var(--ink);
|
||||
}
|
||||
|
||||
.focus-card p {
|
||||
font-size: 13px;
|
||||
color: var(--ink-dim);
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.tag-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.tag {
|
||||
font-family: var(--mono);
|
||||
font-size: 10.5px;
|
||||
padding: 3px 9px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(167, 139, 250, 0.35);
|
||||
color: var(--violet);
|
||||
}
|
||||
|
||||
/* ============ Round 3: 项目 ============ */
|
||||
.project-block {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 16px;
|
||||
padding: 24px;
|
||||
margin-bottom: 18px;
|
||||
background: rgba(11, 17, 32, 0.78);
|
||||
backdrop-filter: blur(8px);
|
||||
}
|
||||
|
||||
.project-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-wrap: wrap;
|
||||
gap: 14px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.project-head img {
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
border-radius: 10px;
|
||||
object-fit: cover;
|
||||
background: var(--bg-soft);
|
||||
}
|
||||
|
||||
.project-head .name {
|
||||
font-size: 18px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.project-meta {
|
||||
min-width: 0;
|
||||
flex: 1 1 220px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.project-links {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.mini-link {
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
line-height: 1.4;
|
||||
padding: 2px 7px;
|
||||
border-radius: 999px;
|
||||
border: 1px solid rgba(34, 211, 238, 0.28);
|
||||
color: var(--cyan);
|
||||
background: rgba(34, 211, 238, 0.06);
|
||||
transition: border-color 0.18s ease, background 0.18s ease;
|
||||
}
|
||||
|
||||
.mini-link:hover {
|
||||
border-color: rgba(34, 211, 238, 0.62);
|
||||
background: rgba(34, 211, 238, 0.12);
|
||||
}
|
||||
|
||||
.project-head .period {
|
||||
margin-left: auto;
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
.project-sub {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--cyan);
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.project-summary {
|
||||
font-size: 13.5px;
|
||||
color: var(--ink-dim);
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.project-modules {
|
||||
list-style: none;
|
||||
margin-bottom: 14px;
|
||||
}
|
||||
|
||||
.project-modules li {
|
||||
position: relative;
|
||||
padding-left: 18px;
|
||||
font-size: 13px;
|
||||
color: var(--ink);
|
||||
margin-bottom: 7px;
|
||||
}
|
||||
|
||||
.project-modules li::before {
|
||||
content: "▸";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--violet);
|
||||
}
|
||||
|
||||
/* ============ Round 4: 技能 ============ */
|
||||
.skills-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.skill-group {
|
||||
min-width: 0;
|
||||
padding: 14px;
|
||||
border: 1px solid rgba(139, 151, 173, 0.18);
|
||||
border-radius: 8px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 255, 255, 0.035), rgba(255, 255, 255, 0.012)),
|
||||
rgba(11, 17, 32, 0.54);
|
||||
}
|
||||
|
||||
.g-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.skill-group .g-name {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0;
|
||||
color: var(--ink);
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.g-count {
|
||||
flex: 0 0 auto;
|
||||
font-family: var(--mono);
|
||||
font-size: 10px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
.g-summary {
|
||||
min-height: 42px;
|
||||
margin-bottom: 12px;
|
||||
font-size: 12px;
|
||||
line-height: 1.65;
|
||||
color: var(--ink-dim);
|
||||
}
|
||||
|
||||
.skill-token {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
padding: 6px 12px;
|
||||
border-radius: 8px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(11, 17, 32, 0.7);
|
||||
color: var(--ink);
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.skill-token.hot {
|
||||
border-color: rgba(34, 211, 238, 0.5);
|
||||
background: rgba(34, 211, 238, 0.08);
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
/* ============ Round 5: 经历 ============ */
|
||||
.exp-item {
|
||||
position: relative;
|
||||
padding: 0 0 30px 30px;
|
||||
}
|
||||
|
||||
.exp-item::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 6px;
|
||||
top: 8px;
|
||||
bottom: -4px;
|
||||
width: 1px;
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
.exp-item:last-child::before { bottom: auto; height: calc(100% - 8px); }
|
||||
|
||||
.exp-item::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
left: 2px;
|
||||
top: 8px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border-radius: 50%;
|
||||
background: var(--bg);
|
||||
border: 2px solid var(--cyan);
|
||||
box-shadow: 0 0 10px rgba(34, 211, 238, 0.6);
|
||||
}
|
||||
|
||||
.exp-head {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.exp-head img {
|
||||
width: 26px;
|
||||
height: 26px;
|
||||
border-radius: 7px;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.exp-head .co {
|
||||
font-weight: 600;
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
.exp-head .period {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
.exp-role {
|
||||
font-family: var(--mono);
|
||||
font-size: 12px;
|
||||
color: var(--violet);
|
||||
margin: 4px 0 8px;
|
||||
}
|
||||
|
||||
.exp-points {
|
||||
list-style: none;
|
||||
}
|
||||
|
||||
.exp-points li {
|
||||
font-size: 13px;
|
||||
color: var(--ink-dim);
|
||||
margin-bottom: 5px;
|
||||
padding-left: 16px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.exp-points li::before {
|
||||
content: "—";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: var(--ink-faint);
|
||||
}
|
||||
|
||||
/* ============ Round 6: 联系 ============ */
|
||||
.contact-lead {
|
||||
font-size: clamp(24px, 3.4vw, 36px);
|
||||
font-weight: 700;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.contact-id {
|
||||
font-family: var(--mono);
|
||||
font-size: 12.5px;
|
||||
color: var(--ink-dim);
|
||||
margin-bottom: 28px;
|
||||
}
|
||||
|
||||
.contact-actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.contact-btn {
|
||||
font-family: var(--mono);
|
||||
font-size: 13px;
|
||||
padding: 12px 22px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--line);
|
||||
color: var(--ink);
|
||||
transition: all 0.3s;
|
||||
}
|
||||
|
||||
.contact-btn.primary {
|
||||
border-color: var(--cyan);
|
||||
color: #06222b;
|
||||
background: var(--cyan);
|
||||
box-shadow: 0 0 24px rgba(34, 211, 238, 0.35);
|
||||
}
|
||||
|
||||
.contact-btn:not(.primary):hover {
|
||||
border-color: var(--violet);
|
||||
color: var(--violet);
|
||||
}
|
||||
|
||||
.session-end {
|
||||
font-family: var(--mono);
|
||||
font-size: 11px;
|
||||
letter-spacing: 0.24em;
|
||||
color: var(--ink-faint);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.session-end::before,
|
||||
.session-end::after {
|
||||
content: "";
|
||||
height: 1px;
|
||||
flex: 1;
|
||||
background: var(--line);
|
||||
}
|
||||
|
||||
/* ============ 响应式 ============ */
|
||||
@media (max-width: 880px) {
|
||||
.round {
|
||||
padding: 80px 6vw;
|
||||
align-items: flex-end;
|
||||
}
|
||||
|
||||
.round-inner {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.focus-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.skills-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
#session-rail {
|
||||
right: auto;
|
||||
top: 54px;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
flex-direction: row;
|
||||
gap: 14px;
|
||||
}
|
||||
|
||||
.rail-node .rail-label { display: none; }
|
||||
|
||||
.hud { padding: 14px 18px; }
|
||||
|
||||
.project-head .period {
|
||||
margin-left: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
/* ============ 降级 ============ */
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.q-caret, .a-caret, .status-dot, .thinking .dot, .boot-hint::after {
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.thinking { display: none; }
|
||||
.answer { opacity: 1; }
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"extends": "astro/tsconfigs/strict"
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from "vite";
|
||||
|
||||
export default defineConfig({
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
three: ["three"],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user