/* ============================================================ ROOY · 06 课程 ─────────────────────────────────────────────────────────── 需求:关卡式学习路径。主干一条 + 少量分支。给用户的感受是 "这棵树是 ROOY 给我画的学习地图"。 结构:NavBar + Segmented (复盘/课程/成就) → 路径卡 horizontal swipe (5 条 path) → 当前选中 path 的 node tree 纵向展开 → 节点 点击进入该阶段详情(06b course-detail),locked 节点不可点; current 节点「试一试」→ 04 ROOY 对话。 path 4 态: done → ink-filled rail + check current → ROOY 绿左边 + "ROOY 推荐" badge + 当前节点 next → muted outline + "下一个" locked → dashed faded accent budget:ROOY 绿(current path 标识 + 推荐 pill)+ gold (下一步动作建议)。 ============================================================ */ function CourseScreen({ onNav }) { const [tab, setTab] = React.useState('course'); const [active, setActive] = React.useState(1); // index of carving (current) const scrollerRef = React.useRef(null); const paths = [ { id: 'parallel', name: '平行式', st: 'done', nodes: 3, doneCount: 3, desc: '基础站位与平行下滑' }, { id: 'carving', name: '刃滑路径', st: 'current', nodes: 5, doneCount: 2, desc: '把立刃从想着做变成本能', currentNode: '立刃 · 40°' }, { id: 'short', name: '短弯节奏', st: 'next', nodes: 4, doneCount: 0, desc: '高频小幅转弯' }, { id: 'mogul', name: '雪沟地形', st: 'locked', nodes: 6, doneCount: 0, desc: '不规则地形适应' }, { id: 'powder', name: '粉雪适应', st: 'locked', nodes: 4, doneCount: 0, desc: '在松软雪面上的浮感' }, ]; const treesByPath = { parallel: [ { st: 'done', name: '平地站位', desc: '建立中立站位与触感', boosts: ['平衡'] }, { st: 'done', name: '平行下滑', desc: '直线下滑保持平行', boosts: ['平衡', '压力'] }, { st: 'done', name: '左右弯转换', desc: '把平行的感觉串起来', boosts: ['节奏'] }, ], carving: [ { st: 'done', name: '平行式', desc: '基础站位与平行下滑', boosts: ['平衡', '压力'] }, { st: 'done', name: '转弯衔接', desc: '左右弯之间的过渡', boosts: ['节奏', '协调'] }, { st: 'current', name: '立刃 · 40°', desc: '把刃角变成本能', boosts: ['立刃', '压力'], tasks: [ { l: '转换时轻跳一下', done: true }, { l: '右弯入弯踩脚趾', done: false, hi: true }, { l: '完整连接 5 趟', done: false }, ], }, { st: 'next', name: '短弯节奏', desc: '高频小幅转弯', boosts: ['节奏', '旋转'] }, { st: 'locked', name: '雪沟地形', desc: '不规则地形适应', boosts: ['平衡', '协调'] }, ], short: [ { st: 'current', name: '上半身预转', desc: '上体领转的基础动作', boosts: ['旋转'] }, { st: 'next', name: '高频短弯', desc: '把节奏压到 1.2s 内', boosts: ['节奏', '旋转'] }, { st: 'locked', name: '原地短弯', desc: '小坡度高密度练习', boosts: ['节奏'] }, { st: 'locked', name: '随心切换', desc: '长短弯自由切换', boosts: ['节奏', '协调'] }, ], mogul: [ { st: 'locked', name: '雪沟入门', desc: '在雪沟里站稳', boosts: ['平衡'] }, { st: 'locked', name: '雪沟节奏', desc: '随雪沟起伏', boosts: ['节奏', '协调'] }, { st: 'locked', name: '吸吐控制', desc: '主动吸收冲击', boosts: ['压力'] }, { st: 'locked', name: '雪沟刃滑', desc: '不规则地形划弧', boosts: ['立刃', '协调'] }, { st: 'locked', name: '全雪道穿越', desc: '在杂乱地形稳定', boosts: ['协调'] }, { st: 'locked', name: '随心而行', desc: '不再思考雪沟', boosts: ['协调'] }, ], powder: [ { st: 'locked', name: '粉雪站位', desc: '更靠后的重心', boosts: ['平衡'] }, { st: 'locked', name: '浮起来', desc: '保持板尖朝上', boosts: ['压力'] }, { st: 'locked', name: '粉雪转弯', desc: '在浮感里转动', boosts: ['旋转', '压力'] }, { st: 'locked', name: '深粉适应', desc: '过膝雪深里行进', boosts: ['平衡', '协调'] }, ], }; const activePath = paths[active]; const tree = treesByPath[activePath.id] || []; const onScroll = () => { const sc = scrollerRef.current; if (!sc) return; const cards = sc.querySelectorAll('[data-path-card]'); if (!cards.length) return; const center = sc.scrollLeft + sc.clientWidth / 2; let best = 0, bestDist = Infinity; cards.forEach((c, i) => { const cardCenter = c.offsetLeft + c.offsetWidth / 2; const d = Math.abs(cardCenter - center); if (d < bestDist) { bestDist = d; best = i; } }); if (best !== active) setActive(best); }; return (
{isCurrent && p.currentNode ? <>当前 · {p.currentNode}> : p.desc}
{node.name}
{node.desc}
{node.boosts && (任务 · {node.tasks.length} 项