/* ============================================================ ROOY · HOME · 雪季/学习卡共享段(01q 复用) ─────────────────────────────────────────────────────────── 历史:本文件曾是 01e-01h 一族 home 变体的家(IQ 三格 / 01-hero / 淡底块 / 能力提升 hero),用 HomeBody + 可换统计块并排比较。那批 变体(含 01g HomeScreenH)已全部删除;本文件现仅保留 01q (home-q.jsx)复用的 SeasonCardF / LearningCardF / SeasonStatsOriginal。 屏序(用户口味):雪季置顶 → 能力提升 → 正在学习。 能力提升结构(单 InsetGroup): ① 关键数据 two-up 大数字(iOS Fitness/Health 口径): · 立刃 84 ↑11 —— 本周期突破(ROOY 判断的对象) · 压力 71 ↑3 —— 第二正向信号,稳步上行 ② ROOY 判断 cell:只说回顾侧结论(立刃稳了),不提下一步。 accent budget:成长块唯一 accent = data 蓝(spark + delta + hero 数字压 ink);rooy = 判断 pip;gold 0。 跨屏数据:立刃 84↑11 / 压力 71↑3 = review.jsx CAPS 前二。 ★ 对外导出:SeasonCardF / LearningCardF / SeasonStatsOriginal 供 01k(home-l.jsx)复用——babel 脚本间只有 window 上的名字可见, HTML 加载顺序:本文件在 home-l.jsx 之前。 依赖 home-v4.jsx helpers(SegmentedProgress / TickUp),加载在前。 ============================================================ */ /* CARD_SHADOW — 现已提为共享 token Shadow.cardSoft(纯柔和 drop、无 描边)。保留本地别名让下方引用不变。 */ const CARD_SHADOW = Shadow.cardSoft; /* HomeBody — 01g 共享屏体。统计块经 SeasonStats prop 注入(现只剩 淡底块 SeasonStatsChip 一种)。 */ function HomeBody({ onNav, SeasonStats }) { return (
{/* ── Header — 同 01 ───────────────────── */}

5 月 3 日 · 周五

你好,Yuki

onNav && onNav('settings')} style={{ marginTop: 12 }}/>
{/* ── 雪季白卡(置顶)── 3-up 统计 + skyline + 最近一次;整卡 → 09 */}
{/* ── 能力提升(回顾侧)── 当前水平内联行 + 2 关键数据 + ROOY 判断;整组 → 05 */} onNav && onNav('review')}> 能力提升
onNav && onNav('review')} style={{ cursor: 'pointer' }}> {/* ① 两个关键数据:Fitness/Health 式 two-up 大数字(卡片开场, 原「当前水平」锚点已移除——那条质化水平去 05 复盘看)。 */}
{/* ② ROOY 判断:只说回顾侧结论,别复读正在学习 */}

立刃稳住了,时机已经不用想了

{/* ── 正在学习(展望侧)── 课程头 + 4 步状态列表;整组 → 06 */}
onNav && onNav(tab)}/>
); } /* KeyStatF — 成长组的单个关键数据 cell(two-up 其一)。 竖排:维度名 + 定性 tag → hero 数字 + delta → sparkline。 数字是主角(N.hero 32 ink 压阵),delta 是成长信号(data 蓝), spark 给趋势质感。两 cell 等宽 flex,中间一条竖 hair 分隔。 */ function KeyStatF({ name, tag, value, delta, spark, style = {} }) { return (
{name} {tag}
{value} {delta}
); } /* StepRowF — 正在学习组的步骤行,三态: done — ✓(ink3 中性)+ 名 ink3 receded + 尾「已完成」 active — rooy 光点(带 halo) + 名 ink semibold + 极淡 rooy 底色 + 尾描述 ink2 upcoming — 序号(ink4) + 名 ink2 + 尾描述 ink3 左标记 = 同尺寸(18px)圆形图标系统,三态成套而非三种画法: done — systemFill 浅底圆 + ink3 ✓(中性、轻) active — rooy 描边环 + rooy 实心圆心(靶心「你在这」,唯一彩色点) upcoming — hair2 描边空心圆 + 内嵌序号 ink4(未开始、静音) rooy 只收在这颗 active 图标上;不加整行灰底(那条底色带读着像 pressed 态、反而怪)——当前步靠 rooy 靶心图标 + 名加粗点出即可。 */ function StepRowF({ n, name, trailing, state }) { const done = state === 'done'; const active = state === 'active'; return (
{done ? ( ) : active ? ( ) : ( {n} )} {name} {trailing}
); } /* SEASON_KMS_F — 同 01d:24 周里程,总和 = 64.8 对齐 history.jsx。 */ const SEASON_KMS_F = [ 0, 3.4, 0, 4.1, 6.8, 0, 5.2, 0, 7.6, 0, 0, 2.8, 0, 5.9, 0, 4.6, 0, 6.4, 0, 5.3, 0, 4.3, 0, 8.4, ]; /* SeasonSkylineF — 雪季周里程 ridge。有滑的周 = 实 data 蓝柱;空周 = 浅 data 蓝地面条(同色系低浓度,让整条 skyline 都在蓝色家族里,不 再掺灰)。空周浓度 ~15% 足以读作「地面」又不与实柱抢。 */ const SKYLINE_OFF = 'color-mix(in srgb, var(--r-data) 15%, transparent)'; function SeasonSkylineF({ kms, height = 36 }) { const max = Math.max(...kms) || 1; return (
{kms.map((km, i) => { const on = km > 0; return (
); })}
); } /* TrendSparkF — 迷你趋势曲线(data 蓝,渐变面积填充 + 平滑曲线 + 端点光晕)。视觉沿用 01 CapSpotlightCard:面积 fill 上 0.18 → 下 0.02 竖向渐变、中点二次贝塞尔平滑、端点 halo 圈。 但 y 轴映射不抄 01——01 各自归一到自身 min/max 会把 ↑3 画得和 ↑11 一样陡(视觉撒谎);这里保留固定跨度(≥12 分、中心对齐), 让 立刃(↑11) 明显比 压力(↑3) 陡,two-up 并排时诚实可比。 宽度用 ref 实测(非 preserveAspectRatio 拉伸)——否则端点 halo 圆 会被横向拉成椭圆。React.useId 给 gradient 一个安全的唯一 id。 */ function TrendSparkF({ values, h = 44, color = R.data }) { const ref = React.useRef(null); const [w, setW] = React.useState(150); React.useLayoutEffect(() => { const el = ref.current; if (!el) return; const measure = () => { const cw = el.clientWidth; if (cw) setW(cw); }; measure(); const ro = new ResizeObserver(measure); ro.observe(el); return () => ro.disconnect(); }, []); const min = Math.min(...values), max = Math.max(...values); const span = Math.max(max - min, 12); const mid = (min + max) / 2; const padY = 7, padX = 3; const pts = values.map((v, i) => ({ x: padX + (i / (values.length - 1)) * (w - padX * 2), y: padY + (1 - ((v - mid) / span + 0.5)) * (h - padY * 2), })); // 中点二次贝塞尔平滑(同 01) let path = `M ${pts[0].x} ${pts[0].y}`; if (pts.length === 2) { path += ` L ${pts[1].x} ${pts[1].y}`; } else { const m1x = (pts[0].x + pts[1].x) / 2, m1y = (pts[0].y + pts[1].y) / 2; path += ` L ${m1x} ${m1y}`; for (let i = 1; i < pts.length - 1; i++) { const mx = (pts[i].x + pts[i + 1].x) / 2, my = (pts[i].y + pts[i + 1].y) / 2; path += ` Q ${pts[i].x} ${pts[i].y}, ${mx} ${my}`; } path += ` L ${pts[pts.length - 1].x} ${pts[pts.length - 1].y}`; } const last = pts[pts.length - 1]; const gid = 'sparkf-' + React.useId().replace(/[:]/g, ''); return (
); } /* SeasonStatsChip(01g)— 雪季卡统计块:三格 space-between,ROOY 指数 包在一枚极淡 rooy 底色圆角块里(highlighted tile),和左边两个中性 统计分层。屏体其余部分共享 HomeBody。 */ function SeasonStatsChip() { return (
} u="天"/>
); } /* ── 可复用卡片段(本文件 HomeBody 01g + home-l.jsx 01k 共用,零重复) ── */ /* SeasonCardF — 雪季卡:SectionTitle + 白卡(统计块 + skyline + 最近 一次)。统计块可换(Stats prop)。 */ function SeasonCardF({ onNav, Stats }) { return ( onNav && onNav('history')}> 雪季
onNav && onNav('history')} style={{ padding: '16px 20px', cursor: 'pointer' }}>
11 月 4 月
最近一次 3 月 30 日 · Zermatt · 8.4 km
); } /* LearningCardF — 正在学习卡:课程头 + 4 步状态列表。 */ function LearningCardF({ onNav }) { return ( onNav && onNav('course')}>正在学习
onNav && onNav('course')} style={{ cursor: 'pointer' }}>
短弯节奏 第 2 / 4 步
); } /* SeasonStatsOriginal — 雪季卡「原始无 IQ」三格(滑行 / 里程 / 垂降), space-between 排版(content-width、间距均匀)。01k 雪季卡复用。 */ function SeasonStatsOriginal() { return (
} u="天"/>
); } /* —— IQ 拍平组件(原 home-n.jsx;01m 屏废弃后组件归入 home-f 共享库)—— home-q / home-q-en 复用。字体走 CSS 变量 var(--r-iq-font/-weight/ -style),Tweaks「IQ 字体」下拉全局切换。 */ /* IQFlat — 拍平的 ROOY 指数 数字。纯色 rooy、无任何打光/描边,字体族 与字重/斜体全部读 CSS 变量(Tweaks 可切)。静态值(不 count-up)——比字体用,动画留待方案定稿。不设 tabular-nums:Satoshi 本地字重的 tnum 变体「1」画法与默认比例数字不同(多一横底座), 会破坏字形;数字是静态值,不需要等宽保稳。 */ function IQFlat({ value = 76, size = 150, style = {} }) { return ( {value} ); } /* StatColFlat — 浮卡分项列:数值 ink,delta data 蓝。 */ function StatColFlat({ name, tag, value, delta, style = {} }) { return (
{name} {tag}
{value} {delta}
); } /* IQJudgeRow — ROOY 判断行(rooy pip + 结论)。 */ function IQJudgeRow() { return (

立刃稳住了,时机已经不用想了

); } /* SeasonCardF / LearningCardF / SeasonStatsOriginal + IQ 拍平组件一并导出 —— babel 脚本间只有 window 上的名字可见。 */ Object.assign(window, { SeasonCardF, LearningCardF, SeasonStatsOriginal, IQFlat, StatColFlat, IQJudgeRow, });