/* ============================================================ ROOY · 05 复盘 · 变体资料室 ─────────────────────────────────────────────────────────── 主线(canvas · 滑雪学院): 05n(六边形宝石 hero,IQ 降级)= 收敛案:把 01q Home 的 HexGemBadge(反射宝石,顶点半径=六维能力值,与 05 雷达同形)直接 提到 hero,放大 200 居中、复用 01q 原件一笔不改,六个顶点外侧补 「能力名+数值」标签,从「徽标」升级成「可读形状图」。ROOY 指数 173 降级为同卡角注(eyebrow + 纯 span 173 黑 + 近 7 天 IQSpark 走势)。 能力卡行留卡外、可点击展开(CapRowN,展开机制源自已删的 05 CapRow; delta 用 data 蓝而非 gold,gold budget=0)。判断行已删。accent 对齐 01q(gold 0):rooy 主导(宝石整体 + 173 + 节奏焦点标签),data 蓝降级(cap delta)。 ── 废案(canvas · 滑雪学院末尾「废案」分区,代码保留备查)──── 05d RadarRose — 圆瓣弧缘:tint 楔形 + 外缘 3px 实色弧。径向读法的 另一基准型,与 05i 二选一时让位给 05i(蛛网三层读法更明确)。 05i RadarSpokesWebbed — 圆辐条 + 蛛网:灰 track / 实色粗棒 / 托底多边形 三层读法。径向读法基准型,现退入废案。 05o(黑卡 · 宝石空间延伸)= 只把 05n 的六边形 hero 白卡翻成 R.ink 黑 卡,能力明细仍退回 05n 浅底列表(用户:只把六边形做成黑卡)。黑卡不 再是平黑底——加线条 + 光影,让整张卡读作「宝石 / 六边形的空间延伸」: HexField 以宝石为中心辐射 rooy 环境辉光 + 蜂窝晶格(六边形 tessellation, 径向淡出),卡面再叠左上对角 sheen(同宝石光源 -3π/4,给晶面体积 感),宝石是这块晶体最亮的一个面。accent 同 05n(gold 0,rooy 唯一主 色)。HexGemO 沿用 HexGemN 几何,只调黑底着色(辉光更强 / rooy 准星 框 / 琢面白混范围拉宽)。 ── 已删除(病因备查)──────────────────────────────────────── 05 原 flat 雷达基础(旧 review.jsx)—— 雷达与 ROOY 指数争 hero、 主次乱;05n 把六维数据升为宝石 hero 后,flat 基础版无存在必要, 整个 review.jsx 删除(其 RadarChart/Legend/CapRow 早已被各变体自带 版本取代,无残留依赖)。 05p gem 受光卡 + 05o 宝石光影(白卡版)—— 05o 档光影搬上白卡太亮、 受光与宝石争焦点,与 05n 拉不开足够差距,删。 05j–m 四个方向均已淘汰(共同教训:六边形当小徽标读不清、当雷达又与 数字争主次,不如直接升为 hero 形状图): 05j 六边形小徽标 + 横向区间条 —— 六边形当小图标「读不清」。 05k 值驱动徽标 + 堆叠填充条 —— 区间条深浅两段「含义有歧义」。 05l 段位徽章(边数=等级 + 液位填充)—— 偏游戏化,与复盘语境不符。 05m flat 数字 hero + 雷达 —— 数字与雷达争 hero,主次仍乱。 ============================================================ */ const RCAPS = [ { id: 'balance', l: '平衡', v: 78, prev: 74, dv: '+4', hi: false, verdict: '', explainer: '滑行中重心稳定的能力。衡量来自不平地形、转弯中、高速的 IMU 偏移。', next: '不是当前重点。保持即可。', subs: [ ['站位', 82], ['动态偏移', 74], ['恢复率', 77] ] }, { id: 'pressure', l: '压力', v: 71, prev: 68, dv: '+3', hi: false, verdict: '', explainer: '踏板压载的控制量。高者能让雪板"吃着"雪面,不发飘。', next: '随节奏变好会自然提高。', subs: [ ['入弯压力', 68], ['出弯压力', 75], ['均匀度', 69] ] }, { id: 'rotation', l: '旋转', v: 55, prev: 55, dv: '—', hi: false, verdict: '', explainer: '上体转动带动全身的能力。这是短弯、雪沟场景的基础。', next: '短弯关卡解锁后会上上去。', subs: [ ['上体领转', 52], ['下体跟转', 60], ['轴心稳定', 53] ] }, { id: 'edge', l: '立刃', v: 84, prev: 73, dv: '+11', hi: false, verdict: '已稳定 40°', explainer: '雪板与雪面的立刃角。越高越能"划"出干净弧线。', next: '平台期。接下来不是"再压",是节奏。', subs: [ ['最高立刃角', 41], ['稳定率', 88], ['双腕对称', 80] ] }, { id: 'tempo', l: '节奏', v: 62, prev: 61, dv: '+1', hi: true, verdict: 'ROOY 下一站', explainer: '转弯间隔与身体动作的同步。节奏好 = 状态好。', next: '右弯入弯踩脚趾·3 趟连续。', subs: [ ['左右对称', 58], ['转换用时', 64], ['心跳同步', 65] ] }, { id: 'coord', l: '协调', v: 68, prev: 66, dv: '+2', hi: false, verdict: '', explainer: '上下肢动作设计与眼睛/路线预判的配合。', next: '节奏上去后高会随之提升。', subs: [ ['眼手同步', 70], ['预判路线', 64], ['动作连缝', 70] ] }, ]; const RAD_ANGLE = (i) => -Math.PI / 2 + i * (Math.PI / 3); const radPt = (cx, cy, i, r) => [ cx + Math.cos(RAD_ANGLE(i)) * r, cy + Math.sin(RAD_ANGLE(i)) * r, ]; /* shared polar geometry — one set of metrics so the variants compare purely on rendering style */ const RG = { W: 326, H: 256, cx: 163, cy: 130, rIn: 13, rOut: 98, half: Math.PI * 26 / 180 }; const rOf = (v) => RG.rIn + (RG.rOut - RG.rIn) * (v / 100); const ppt = (r, a) => `${RG.cx + Math.cos(a) * r} ${RG.cy + Math.sin(a) * r}`; const sectorPath = (r0, r1, a0, a1) => `M ${ppt(r0, a0)} L ${ppt(r1, a0)} A ${r1} ${r1} 0 0 1 ${ppt(r1, a1)} ` + `L ${ppt(r0, a1)} A ${r0} ${r0} 0 0 0 ${ppt(r0, a0)} Z`; const arcPath = (r, a0, a1) => `M ${ppt(r, a0)} A ${r} ${r} 0 0 1 ${ppt(r, a1)}`; /* ── shared screen shell (05d/05i) ────────────────────────── */ function RadarShellScreen({ chart, onNav }) { const [tab, setTab] = React.useState('review'); return (
onNav && onNav('home')} title="滑雪学院"/>
{ setTab(v); if (v === 'course') onNav && onNav('course'); if (v === 'medals') onNav && onNav('achievement'); }}/>
{chart}
{RCAPS.map((c, i) => )}
); } function VLegend({ swatch, label: l }) { return (
{swatch} {l}
); } /* white-card chart wrapper + legend — identical across variants */ function RoseCard({ children, legendPrev }) { return (
{children}
} label="ROOY 焦点"/>
); } /* labels — inline name + number outside each axis, shared */ function RoseLabels({ caps }) { return caps.map((c, i) => { const [lx, ly] = radPt(RG.cx, RG.cy, i, RG.rOut + 14); let anchor = 'middle'; if (Math.abs(lx - RG.cx) > 30) anchor = lx > RG.cx ? 'start' : 'end'; return ( {c.l} {c.v} ); }); } /* 05 CapRow 的收起态复刻 — 展开逻辑留在 05 */ function VCapRow({ c, first }) { return (
{c.hi && } {c.l} {c.verdict && ( {c.verdict} )}
{c.dv !== '—' ? '↑' : ''} {c.dv}
); } /* ── 05d · RadarRose — 圆瓣弧缘(基准型)────────────────── 六片 52° 环形扇区:灰 track 到满量程,数值 = 平涂 tint 楔形 + 外缘 3px 实色弧(进度读法)。上季 = 楔形内的虚线 弧刻度。 */ function RadarRose({ caps }) { return ( {caps.map((c, i) => { const ac = RAD_ANGLE(i); const a0 = ac - RG.half, a1 = ac + RG.half; const rv = rOf(c.v); const fill = c.hi ? 'color-mix(in srgb, var(--r-rooy) 20%, transparent)' : 'color-mix(in srgb, var(--r-data) 20%, transparent)'; const edge = c.hi ? 'var(--r-rooy)' : 'var(--r-data)'; return ( {c.prev !== c.v && ( )} ); })} ); } /* ── 05i · RadarSpokesWebbed — 圆辐条 + 蛛网 ────────────── 05b 的圆辐条叠到 05h 的蛛网托底上。读法明确三层:灰 track = 满量程 0→100;实色粗棒 = 本季值;浅填多边形连"蓝棒尖"(顶点 = 本季值,含圆帽 w/2 偏移,so 面积正好套住蓝棒、明显外于上季 虚线);上季 = 虚线多边形(负空间里显形)+ 棒上 ink 刻痕(顶点 藏在棒后,靠刻痕补上棒身的精确位;两者同色)。棒身 14,track 0.10。 */ function RadarSpokesWebbed({ caps }) { const r0 = 20, w = 14; const prevInk = 'rgba(14,17,22,0.35)'; // 上季统一色:虚线多边形 + 棒上刻痕同色 /* fill vertices reach the round-cap tip (rOf(v)+w/2), not the stroke end (rOf(v)) — otherwise the fill sits inside the bar tips and collapses onto the prev dashed polygon. */ const poly = caps.map((c, i) => radPt(RG.cx, RG.cy, i, rOf(c.v) + w / 2).join(',')).join(' '); const prevPoly = caps.map((c, i) => radPt(RG.cx, RG.cy, i, rOf(c.prev)).join(',')).join(' '); return ( {[46, 76, 105].map(r => ( ))} {caps.map((c, i) => { const a = RAD_ANGLE(i); const [x0, y0] = radPt(RG.cx, RG.cy, i, r0); const [xt, yt] = radPt(RG.cx, RG.cy, i, RG.rOut - 5); const [xv, yv] = radPt(RG.cx, RG.cy, i, rOf(c.v)); const [xp, yp] = radPt(RG.cx, RG.cy, i, rOf(c.prev)); const px = -Math.sin(a), py = Math.cos(a); const bar = c.hi ? 'var(--r-rooy)' : 'var(--r-data)'; return ( ); })} ); } /* ── screens (05d / 05i) ──────────────────────────────────── */ function ReviewScreenD({ onNav }) { return ( }> }/> ); } function ReviewScreenI({ onNav }) { return ( }> }/> ); } /* CapRowN — 05n 能力卡行,可点击展开(复用 05 基础 CapRow 的展开机制: ROOY 下一步 + 解释段 + 详细数据 sub-bars +「和 ROOY 试一试」CTA)。 收起态:能力名 + verdict / delta + 值 + 细条 + trailing chevron(展开时 旋转 90°,暗示可点);行间 hairline 分隔、padding 20(与 05 一致)。 与 05 的差别:① delta 用 data 蓝而非 gold(守 05n gold-0 budget) ② trailing chevron(05 收起态无暗示,05n 补展开信号)。 */ function CapRowN({ c, first, expanded, onClick, onTry }) { return (
{c.hi && } {c.l} {c.verdict && ( {c.verdict} )}
{c.dv !== '—' ? '↑' : ''} {c.dv}
{expanded && (
{/* ROOY 下一步 — kicker + 结论句 */}
ROOY · 下一步

{c.next}

{/* 解释段 */}

{c.explainer}

{/* 详细数据 — 子参数 sub-bars(高度 3 与上面的 2.5 拉开层级) */}

详细数据

{c.subs.map((s, i) => (
{s[0]}
{s[1]}
))}
{/* CTA */}
和 ROOY 试一试
)}
); } /* ============================================================ 05n · 六边形 hero(01q 宝石 · 浅 rooy 轮廓框 · 上部分白卡) ============================================================ 05n 翻转之前方向的主次——把 01q Home 的反射宝石提到 hero 位(200)。 宝石本体(rooy 辉光 + 6 琢面 + 双层高光、顶点按数据 min/max 归一化) 沿用 01q HexGemBadge 语法,另起 HexGemN(不动 01q 原件):「背景轮廓 线」(外圈 rim 深一档 60% 读作满量程边界 + 6 辐条/内刻度环 浅 35%)、 统一 0.6 粗细,在数据形内凹处露一截。 六个顶点外补「能力名+数值」标签(节奏=rooy 焦点);平衡/立刃(顶/底顶 点)标签半径收小、更贴近宝石(用户:更紧凑),wrap 改非方形(横向让侧 标签、纵向收紧顶/底)。 整个上部分白卡(用户:整个上部分内容做成一个白卡)IQHeroCardN:卡内 = 六边形宝石 hero + hairline + ROOY 指数 行。ROOY 行两栏(中间 hairline, 同 01q InsetGroup 两列语法):左=ROOY 指数 eyebrow + 173(纯 span、不 走 StatNumber、IQ Oswald Reg 400 正体、黑色;↑6·本季 文案已删),右= 提升趋势历史记录(近 7 天滑行 IQSpark:rooy 折线+逐点小圆,末点加粗= 本季,走势即 ↑6)。形状 + 分数 + 走势 归成一块「本期总结」,能力卡 行留卡外、可点击展开(CapRowN,复用 05;行间 hairline、卡↔列表无分隔)。判断行已删(用户)。 accent 对齐 01q(gold 0):rooy 主导(宝石+sparkline+节奏标签), 173 用黑、data 蓝降级(cap delta)。 ============================================================ */ /* HexGemN — 05n hero 宝石。宝石本体(rooy 辉光底盘 + 外层 halo 氛围 + 6 琢面(按数据 min/max 归一化)+ 双层高光)一字不改地沿用 01q HexGemBadge 语法;满量 程骨架(外圈 rim + 6 辐条 + 内刻度环)的「背景轮廓线」外圈 rim 60%、 辐条 + 内刻度环 35%(皆比 rooy 浅)、统一 0.6 粗细,在数据 形内凹的负空间里露出一截、读作量纲刻度。宝石仍是宝石(辉光/琢面/高 光/归一化不动)。01q 的 HexGemBadge 不动(那里是单圈 rim 的纯 rooy 徽标)。 */ function HexGemN({ caps, size = 200 }) { const uid = React.useId().replace(/[:]/g, ''); const cx = 50, cy = 50; const rMin = 20, rMax = 44, rOut = 47; const ang = (i) => -Math.PI / 2 + i * (Math.PI / 3); const polyAt = (r) => Array.from({ length: 6 }, (_, i) => [cx + Math.cos(ang(i)) * r, cy + Math.sin(ang(i)) * r].map(n => n.toFixed(2)).join(',')).join(' '); const vs = caps.map(c => c.v); const vmin = Math.min(...vs), vmax = Math.max(...vs); const rv = (v) => rMin + (rMax - rMin) * ((v - vmin) / (vmax - vmin)); const vp = (i) => [cx + Math.cos(ang(i)) * rv(caps[i].v), cy + Math.sin(ang(i)) * rv(caps[i].v)]; const pts = caps.map((_, i) => vp(i).map(n => n.toFixed(2)).join(',')).join(' '); const frame = 'color-mix(in srgb, var(--r-rooy) 35%, white)'; // 辐条 + 内刻度环:比 rooy 浅(35% rooy + 65% 白) const frameRim = 'color-mix(in srgb, var(--r-rooy) 60%, white)'; // 外圈 rim:深一档(60% rooy + 40% 白)—读作满量程边界 const lightAng = -3 * Math.PI / 4; const lx = Math.cos(lightAng), ly = Math.sin(lightAng); const facetBright = (i) => Math.max(0, Math.cos(ang(i) + Math.PI / 6) * lx + Math.sin(ang(i) + Math.PI / 6) * ly); const mixFor = (i) => { // 05o 光影(白卡版):38..92 const vf = (caps[i].v - vmin) / (vmax - vmin); const b = facetBright(i) * 0.68 + vf * 0.32; return Math.round(92 - b * 54); }; return ( {/* ⓪ rooy 氛围:外层宽柔 halo(溢出宝石形成环境光)+ 内层辉光底盘(浮起感) */} {/* ① 背景轮廓:外圈 rim 深一档(60%)读作满量程边界,6 辐条 + 内刻度环浅(35%);统一粗细 0.6 */} {caps.map((_, i) => { const a = ang(i); return ; })} {/* ② 6 琢面(光照 + 能力值深浅) */} {caps.map((_, i) => { const j = (i + 1) % 6; return ( ); })} {/* ③ 两层高光(柔光 + 锐 sparkle),clip 在剪影内 */} ); } /* HexGemHero — HexGemN(rooy 刻度框宝石)放大居中作 hero,六个顶点外侧补 「能力名 + 数值」标签(焦点=节奏 用 rooy,其余 ink)。标签位用同一套 pointy-top 极坐标(ang(i) = -π/2 + i·π/3)算出、HTML 绝对定位叠在外圈。 hero 整个住进 IQHeroCardN 白卡(与 ROOY 指数 同卡),margin 留卡内呼吸。 */ function HexGemHero({ caps, size = 200 }) { const padX = 46, padY = 32; // 非方形 wrap:横向让侧标签、纵向收紧顶/底 const wrapW = size + padX * 2, wrapH = size + padY * 2; const cX = wrapW / 2, cY = wrapH / 2; const frameR = size * 0.47; // 外缘六边形框半径(同 HexGemN rOut=47%) const rTopBot = frameR + 18; // 平衡/立刃(顶/底顶点)更贴近宝石(用户:更紧凑) const rSide = frameR + 30; // 侧标签保持 const ang = (i) => -Math.PI / 2 + i * (Math.PI / 3); return (
{caps.map((cap, i) => { const a = ang(i); const r = (i === 0 || i === 3) ? rTopBot : rSide; const x = cX + Math.cos(a) * r; const y = cY + Math.sin(a) * r; const focus = cap.hi; const isTopBot = (i === 0 || i === 3); return (
{cap.l} {cap.v}
); })}
); } /* IQ_HISTORY — ROOY 指数 近 7 天滑行走势(末点=本季 173,倒数第二 167 → 本季 ↑6)。卡片右侧「提升趋势历史记录」sparkline 的数据源。 */ const IQ_HISTORY = [150, 155, 159, 162, 164, 167, 173]; /* IQSpark — ROOY 指数 提升趋势历史记录:rooy 折线 + 浅面积 + 逐点小圆 (离散的「历史记录」,不是平滑曲线),末点加粗=当前季。width:100% 撑 满卡片右半。 */ function IQSpark({ data = IQ_HISTORY, w = 150, h = 46 }) { const uid = React.useId().replace(/[:]/g, ''); const min = Math.min(...data), max = Math.max(...data); const padX = 4, padY = 5; const x = (i) => padX + (w - 2 * padX) * (i / (data.length - 1)); const y = (v) => padY + (h - 2 * padY) * (1 - (v - min) / (max - min || 1)); const line = data.map((v, i) => `${i ? 'L' : 'M'} ${x(i).toFixed(1)} ${y(v).toFixed(1)}`).join(' '); const area = `${line} L ${x(data.length - 1).toFixed(1)} ${(h - padY).toFixed(1)} L ${x(0).toFixed(1)} ${(h - padY).toFixed(1)} Z`; return ( {data.map((v, i) => ( ))} ); } /* IQHeroCardN — 整个「上部分」白卡(用户:整个上部分内容做成一个白卡): 卡内 = 六边形宝石 hero(HexGemHero)+ hairline 分隔 + ROOY 指数 行。 ROOY 行两栏(中间 hairline,同 01q InsetGroup 两列语法):左 = ROOY 指数 eyebrow + 173(IQ Oswald Reg 400 正体、不走 StatNumber、 黑色),右 = 提升趋势历史记录(近 7 天滑行 caption 13px + IQSpark)。hero 与 ROOY 指数 同卡——形状 + 分数 + 走势 归成一块「本期 总结」,cap 行留卡外做明细。 */ function IQHeroCardN() { return (

ROOY 指数

173

近 7 天滑行

); } /* ReviewScreenN(05n)— 整个上部分白卡 IQHeroCardN(六边形宝石 hero + ROOY 指数 173 + 近 7 季走势 同卡)→ 能力卡行(可点击展开)。顶部不再放 ROOY 角注,宝石与 ROOY 指数 一起住进白卡;判断行已删。cap 行用 CapRowN(复用 05 CapRow 展开;行间 hairline,卡↔列表无分隔)。 */ function ReviewScreenN({ onNav }) { const [tab, setTab] = React.useState('review'); const [expanded, setExpanded] = React.useState(null); return (
onNav && onNav('home')} title="滑雪学院"/>
{ setTab(v); if (v === 'course') onNav && onNav('course'); if (v === 'medals') onNav && onNav('achievement'); }}/>
{RCAPS.map((c, i) => ( setExpanded(expanded === c.id ? null : c.id)} onTry={() => {}} /> ))}
{/* ROOY Pro 入口 — 复盘底部,对齐设置页最新设计(rooy 描边 + PRO 字标 + 贴边光晕),文案适配复盘语境。 */}
); } /* ============================================================ 05o · 黑卡变体(科技 / 运动感)— 05n 的反相:白卡 → 黑卡 ============================================================ 05n 是「近白底 + 单张白卡 hero」;05o 把同一份内容(六边形宝石 hero + ROOY 指数 + 六维明细)整体翻到 R.ink 黑卡上,靠「近黑底 + 单一 rooy 辉光 + 高对比白字」做运动手表 / 仪表盘的科技运动感。 bg 仍是 R.bg 近白——黑卡在白底上 pop,保留 iOS app 身份,不做成 全屏暗色 fitness dashboard(那是 Carv 类 anti-reference)。 两张黑卡并排叠放(用户:白卡换成黑卡): ① IQCoreCardO — 宝石 hero + ROOY 指数(05n IQHeroCardN 的反相) ② CapConsoleO — 6 条能力明细行(05n 卡外列表的反相,收进黑卡) accent 与 05n 同源(gold 0):rooy 是唯一发光主色(宝石辉光 + sparkline + 节奏焦点 + ROOY 下一步 + CTA 反相白丸)。delta 在黑卡 上降级为中性白字(inkD2 + ↑),不再用 data 蓝——黑底单色读数更像 遥测,也避免 data 蓝在近黑底上发闷。 ============================================================ */ /* HexGemO — 05o 黑卡上的宝石。几何(按 min/max 归一化的 6 顶点 + 双层 高光)与 HexGemN 一字一致「宝石仍是宝石」;只把着色调成黑底版: · 辉光更强(黑底上的发光体,0.34 起跳) · 背景轮廓框改 rooy 24% 透明(读作 HUD 准星 / 量纲刻度,不再是 白底版的 rooy+白淡框) · 琢面白混范围拉宽(28..82 → 深青到亮青,切割感更强) HexGemN 不动(白底版)。 */ function HexGemO({ caps, size = 200 }) { const uid = React.useId().replace(/[:]/g, ''); const cx = 50, cy = 50; const rMin = 20, rMax = 44, rOut = 47; const ang = (i) => -Math.PI / 2 + i * (Math.PI / 3); const polyAt = (r) => Array.from({ length: 6 }, (_, i) => [cx + Math.cos(ang(i)) * r, cy + Math.sin(ang(i)) * r].map(n => n.toFixed(2)).join(',')).join(' '); const vs = caps.map(c => c.v); const vmin = Math.min(...vs), vmax = Math.max(...vs); const rv = (v) => rMin + (rMax - rMin) * ((v - vmin) / (vmax - vmin)); const vp = (i) => [cx + Math.cos(ang(i)) * rv(caps[i].v), cy + Math.sin(ang(i)) * rv(caps[i].v)]; const pts = caps.map((_, i) => vp(i).map(n => n.toFixed(2)).join(',')).join(' '); const frame = 'color-mix(in srgb, var(--r-rooy) 24%, transparent)'; // 黑底轮廓框:rooy 24%(准星 / 刻度感) const lightAng = -3 * Math.PI / 4; const lx = Math.cos(lightAng), ly = Math.sin(lightAng); const facetBright = (i) => Math.max(0, Math.cos(ang(i) + Math.PI / 6) * lx + Math.sin(ang(i) + Math.PI / 6) * ly); const mixFor = (i) => { // 黑底琢面白混范围拉宽(18..90 → 深青到近白青) const vf = (caps[i].v - vmin) / (vmax - vmin); const b = facetBright(i) * 0.68 + vf * 0.32; return Math.round(90 - b * 72); }; return ( {/* ⓪ rooy 辉光底盘 — 黑底上的发光体 */} {/* ① rooy 准星框:外圈 rim + 内圈 + 6 辐条,统一 0.6 / rooy 24% */} {caps.map((_, i) => { const a = ang(i); return ; })} {/* ② 6 琢面(光照 + 能力值深浅,白混范围拉宽) */} {caps.map((_, i) => { const j = (i + 1) % 6; return ( ); })} {/* ③ 两层高光(柔光 + 锐 sparkle),clip 在剪影内 */} ); } /* HexGemHeroO — HexGemO(黑底发光宝石)放大居中作 hero,六个顶点外侧 补「能力名 + 数值」标签。定位数学与 HexGemHero 一致;标签颜色翻到 暗底版:焦点(节奏)= rooy 发光,其余 = inkD 白。hero 住进 IQCoreCardO 黑卡。 */ function HexGemHeroO({ caps, size = 200 }) { const padX = 46, padY = 32; const wrapW = size + padX * 2, wrapH = size + padY * 2; const cX = wrapW / 2, cY = wrapH / 2; const frameR = size * 0.47; const rTopBot = frameR + 18; const rSide = frameR + 30; const ang = (i) => -Math.PI / 2 + i * (Math.PI / 3); return (
{caps.map((cap, i) => { const a = ang(i); const r = (i === 0 || i === 3) ? rTopBot : rSide; const x = cX + Math.cos(a) * r; const y = cY + Math.sin(a) * r; const focus = cap.hi; return (
{cap.l} {cap.v}
); })}
); } /* HexField — 黑卡①的「六边形空间场」。以宝石为中心(top 对齐宝石中心 140px = 卡顶 margin8 + wrap padY32 + 半径100),把宝石的六边形几何 + 光 延伸到整张卡,让黑卡读作「宝石是这块晶体最亮的一个面」而非平黑底: · rooy 环境辉光(宝石照亮卡内空间) · 6 扇形琢面 wash(与宝石同源光源 -3π/4,卡面 = 被照亮的 6 瓣晶面) · 蜂窝晶格(六边形 tessellation,径向 mask 由内向外淡出) 有晶格承担几何纹理,就不再叠全屏轮廓线(同心环 / 辐条已删)。晶格线条 用预混实色 teal(rooy 往 #0E1116 混),不用 stroke-opacity——低透明度 rooy 在黑底上发灰发糊,实色利落。光影层(辉光 / 琢面 wash / 卡面 sheen)才用 透明度 / 渐变。宝石仍是唯一焦点。 */ function HexField({ size = 600 }) { const uid = React.useId().replace(/[:]/g, ''); const cx = size / 2, cy = size / 2; const ang = (i) => -Math.PI / 2 + i * (Math.PI / 3); // 预混实色 teal(rooy 往 #0E1116 混)—— 晶格线条专用,不用透明度 const inkHex = '#0E1116'; const tealLat = `color-mix(in srgb, var(--r-rooy) 34%, ${inkHex})`; // 6 扇形琢面 wash:与宝石同源光源(-3π/4 左上),每个扇区按受光亮度给 // 一层淡白 wash,卡面读作「被宝石同一束光照亮的 6 瓣晶面」。 const lightAng = -3 * Math.PI / 4; const lx = Math.cos(lightAng), ly = Math.sin(lightAng); const facetBright = (i) => Math.max(0, Math.cos(ang(i) + Math.PI / 6) * lx + Math.sin(ang(i) + Math.PI / 6) * ly); const wedgeR = 292; // 蜂窝晶格 —— pointy-top 六边形 tessellation,圆心对齐到宝石中心 const hr = 30; // 单格半径 const hw = Math.sqrt(3) * hr; // 同行圆心横距 const vstep = 1.5 * hr; // 行间纵距 const hexPts = (x, y) => Array.from({ length: 6 }, (_, k) => { const a = -Math.PI / 2 + k * (Math.PI / 3); return `${(x + Math.cos(a) * hr).toFixed(1)},${(y + Math.sin(a) * hr).toFixed(1)}`; }).join(' '); const cells = []; for (let row = -1; row * vstep < size + hr; row++) { const y = row * vstep + (cy % vstep); const xoff = (row % 2 === 0) ? 0 : hw / 2; for (let col = -1; col * hw < size + hw; col++) { const x = col * hw + xoff + (cx % hw); cells.push(); } } return ( {/* 环境辉光 —— 宝石照亮卡内空间 */} {/* 6 扇形琢面 wash —— 卡面 = 被宝石同源光照亮的 6 瓣晶面(光/影) */} {Array.from({ length: 6 }, (_, i) => { const j = (i + 1) % 6; const p = `${cx},${cy} ${(cx + Math.cos(ang(i)) * wedgeR).toFixed(1)},${(cy + Math.sin(ang(i)) * wedgeR).toFixed(1)} ${(cx + Math.cos(ang(j)) * wedgeR).toFixed(1)},${(cy + Math.sin(ang(j)) * wedgeR).toFixed(1)}`; return ; })} {/* 蜂窝晶格 —— 径向 mask 由内向外淡出(几何纹理,无需再叠全屏轮廓线) */} {cells} ); } /* IQCoreCardO — 05o 黑卡①(唯一的黑卡):纯宝石 hero + HexField 六边形空 间场,没有别的内容(用户:ROOY 指数行移出黑卡、不做卡片)。R.ink 底 + 左上对角 sheen(linear-gradient 135°,同宝石光源,给晶面体积感)+ HexField(辉光 + 琢面 wash + 蜂窝晶格)。ROOY 指数 173 + sparkline 平铺到卡外白底,见 ReviewScreenO。 */ function IQCoreCardO() { return (
); } /* ReviewScreenO(05o)— 只把六边形 hero 做成黑卡(IQCoreCardO:纯宝石 + HexField 空间场);ROOY 指数 173 + sparkline 平铺到黑卡外白底(不做卡 片,两栏 + 中间 hairline);能力明细退回 05n 浅底列表(CapRowN,hairline 分隔、可点击展开)。bg 仍 R.bg 近白。 */ function ReviewScreenO({ onNav }) { const [tab, setTab] = React.useState('review'); const [expanded, setExpanded] = React.useState(null); return (
onNav && onNav('home')} title="滑雪学院"/>
{ setTab(v); if (v === 'course') onNav && onNav('course'); if (v === 'medals') onNav && onNav('achievement'); }}/>
{/* ROOY 指数行 —— 不做卡片,平铺到黑卡外白底(两栏 + 中间 hairline) */}

ROOY 指数

173

近 7 天滑行

{RCAPS.map((c, i) => ( setExpanded(expanded === c.id ? null : c.id)} onTry={() => {}} /> ))}
); }