/* ============================================================ ROOY · 07 成就 ─────────────────────────────────────────────────────────── 需求:成就墙。三类分组 —— 能力成就(关卡通关)/ 瞬间成就 (ROOY 见证的"第一次")/ 极限挑战(高难度)。 结构:NavBar + Segmented (复盘/课程/成就) → hero(italic 7 + 脚标 "/28")+ 3 行 RarityRow → 三类成就的 2-col MedalCard 网格。 italic anchor:已解锁数 7(hero)。 accent budget:gold(瞬间成就高光 + 刚解锁卡边框 + 呼吸光)。 刚解锁状态(justUnlocked):1px gold border + 22px halo(0.26 alpha) 呼吸光 —— Apple Fitness "ring closed" 量级,不是 Duolingo 宝石。 ============================================================ */ function AchievementScreen({ onNav }) { const [tab, setTab] = React.useState('medals'); return (
onNav && onNav('home')} title="滑雪学院"/>
{ setTab(v); if (v === 'review') onNav && onNav('review'); if (v === 'course') onNav && onNav('course'); }}/>
{/* Hero —— italic 7 + 脚标 /28 + 右侧 rarity strip. alignItems: center 让左右在 hero 垂直中心轴对齐 (italic 7 的 lineHeight 0.82 line box 较矮, 不能用 flex-end). */}
{/* italic 7 是 anchor; 28 作为 7 的脚标 baseline-aligned, upright + N.reg, 表示 "已解锁 / 全部" 的比例. 不再额外 出 "已解锁 · 共 28 个" 副标——脚标已经承担这条信息. */} 7 /28 {/* Rarity completion strip — 3 thin rows, each gets a name + fraction + 2px progress. */}
{/* ── 能力成就 ─────────────────────────── */} 能力成就
{/* ── 瞬间成就 ─────────────────────────── */} 瞬间成就
{/* ── 极限挑战 ─────────────────────────── */} 极限挑战
); } function MedalGrid({ items }) { return (
{items.map((m, i) => )}
); } /* RarityRow — hero 右侧三档稀有度完成率行。单色 ink,靠 progress 填充长度表达"离这档还差多少"。能力/瞬间/极限的语义差异由 name 本身承担——三档同色避免触发"三色齐出"。 */ function RarityRow({ l, cur, max }) { const pct = max ? (cur / max) * 100 : 0; return (
{l} {cur} / {max}
); } function MedalCard({ m }) { const isLocked = m.locked; const isCurrent = m.current; const isJustUnlocked = m.justUnlocked; const earned = m.e && !isCurrent && !isLocked; const Glyph = m.kind === 'moment' ? (m.hi ? TrophyGlyph : FlagGlyph) : m.kind === 'extreme' ? MountainGlyph : MedalGlyph; const iconBg = isCurrent ? `${R.rooy}1a` : isLocked ? R.systemFill : m.hi ? `${R.gold}1a` : R.ink; const iconColor = isCurrent ? 'var(--r-rooy)' : isLocked ? R.ink3 : m.hi ? 'var(--r-gold)' : '#fff'; return (
{isJustUnlocked && (
刚刚
)}
{earned && !isJustUnlocked && ( )}

{m.n}

{m.d}

{m.w}

); } /* gold-glow keyframe — injected once */ if (typeof document !== 'undefined' && !document.getElementById('rooy-glow-style')) { const s = document.createElement('style'); s.id = 'rooy-glow-style'; s.textContent = ` @keyframes rooyMedalGlow { 0%,100% { box-shadow: 0 0 0 0 rgba(217,162,58,0); } 50% { box-shadow: 0 0 22px 1px rgba(217,162,58,0.26); } } .medal-glow { animation: rooyMedalGlow 3.2s ease-in-out infinite; } `; document.head.appendChild(s); } function FlagGlyph({ size = 18, color = '#0E1116' }) { return ( ); } function MountainGlyph({ size = 18, color = '#0E1116' }) { return ( ); } function MedalGlyph({ size = 18, color = '#0E1116' }) { return ( ); } function TrophyGlyph({ size = 20, color = '#0E1116' }) { return ( ); } Object.assign(window, { AchievementScreen, FlagGlyph, MountainGlyph, MedalGlyph, TrophyGlyph });