/* ============================================================ ROOY · 09 滑行历史 · 雪场地图 hero(history-b,原 09b 转正) ─────────────────────────────────────────────────────────── 把原 09 的「生涯黑卡」hero 换成「生涯打卡雪场分布地图」,参考 08b(season-c)的地图 hero 语言。原 09(history.jsx)已删,本屏 转正为唯一 09——prototype 'history' 路由 → HistoryScreenB。黑卡 点击跳转(旧 09 → 08)随之废弃(本屏无黑卡)。 两态 hero: · 折叠态 = 地图(生涯打卡雪场世界底图)+ 3 个生涯数字(天/里程/雪场) 浮在地图底部,底部渐变托底。 · 展开态 = 点「展开」地图向下加高(顶部间距不变),浮层数字(第一行 天/里程/雪场)原位不动仍浮在渐变上,下面铺开生涯详情——第二行 stats (雪季/垂降/心流)/ 滑行模式 / 雪况 / 高光。结构搬 08b(season-c)的 地图下段,数据换全生涯口径(非单雪季)。展开后点 pin 更新最常去读出。 · 展开/收起键 = sticky 浮在地图右上角(参考 08b season-c),静止贴角、 展开后下滚也钉在视口同一位置,保证随时一键收起。 复用:SeasonMapCanvas / frameOf / LONLAT + BStatRow / BRecordRow / CModeSplit / CSnowMix / SectionTitle / Trophy·Flag·MedalGlyph(均 window 导出);DayRow 本屏自带(原 history.jsx 已删,DayRow 随本屏保留)。 地图 pin = 生涯打卡雪场坐标。 accent budget:data 蓝主(地图 pin + 折叠数字 + 最常去名);gold 仅 心流 stat + ROOY 见证(成长语义,沿用 08b);rooy 仅 ROOY 指数最高分。 list 里 lesson 绿 pip 沿用原 09。 ============================================================ */ /* 生涯打卡雪场(经纬度真实,用于投影 + 自适应取景 + 聚类都吃这份坐标)。 跨阿尔卑斯 / 日本 / 中国 → frameOf 自动框到欧亚带。hi = 生涯最常去。 本季列表只展开近段,Hakuba 是更早雪季的日本行——生涯图比列表更全, 属正常。 */ const CAREER_B_RESORTS = [ { id: 'zermatt', name: 'Zermatt', area: '阿尔卑斯', country: '瑞士', lon: 7.75, lat: 46.02, visits: 20, hours: 96, hi: true }, { id: 'verbier', name: 'Verbier', area: '阿尔卑斯', country: '瑞士', lon: 7.23, lat: 46.10, visits: 4, hours: 18 }, { id: 'hakuba', name: '白马 Hakuba', area: '日本', country: '日本', lon: 137.86, lat: 36.70, visits: 6, hours: 28 }, { id: 'wanlong', name: '万龙', area: '中国', country: '中国', lon: 115.47, lat: 40.93, visits: 12, hours: 45 }, ]; function HistoryScreenB({ onNav }) { const [openSeasons, setOpenSeasons] = React.useState({ '25-26': true }); const [selectedId, setSelectedId] = React.useState('zermatt'); // 最常去读出(展开后随 pin 更新) const [expanded, setExpanded] = React.useState(false); // 展开 = 地图加高 + 生涯详情(搬 08b) const seasons = [ { id: '25-26', year: '2025–26', summary: '12 天 | 64.8 km | 最爱 Zermatt', days: [ { date: '4.30', place: 'Zermatt', runs: 13, km: 8.4, time: '2:18 h', lesson: true }, { date: '4.22', place: 'Verbier', runs: 9, km: 6.2, time: '1:42 h', lesson: false }, { date: '4.18', place: 'Zermatt', runs: 11, km: 7.1, time: '2:04 h', lesson: true }, { date: '4.10', place: 'Saas-Fee', runs: 7, km: 4.6, time: '1:18 h', lesson: false }, { date: '3.16', place: 'Zermatt', runs: 14, km: 9.8, time: '2:42 h', lesson: true }, ], }, { id: '24-25', year: '2024–25', summary: '8 天 | 42.8 km | 最爱 万龙', days: [ { date: '3.04', place: '万龙雪场', runs: 12, km: 7.4, time: '3:12 h', lesson: true }, { date: '2.18', place: '万龙雪场', runs: 9, km: 5.8, time: '2:38 h', lesson: false }, { date: '1.25', place: '南山雪场', runs: 7, km: 4.2, time: '1:46 h', lesson: false }, ], }, { id: '23-24', year: '2023–24', summary: '4 天 | 18.2 km | 最爱 南山', days: [], }, ]; const sel = CAREER_B_RESORTS.find(r => r.id === selectedId) || CAREER_B_RESORTS[0]; // 地图 hero:full-bleed。pad 0.35 = 原始取景比例(不放大地图内容,展示完整 // 世界带;放大只作用于 pin 的 markerScale)。展开时地图向下加高(顶部间距不 // 变),并按新宽高比重算取景。biasY 把 pin 顶到上部。 const HERO_W = 390; const mapH = expanded ? 460 : 340; const heroView = (() => { const v = frameOf(CAREER_B_RESORTS, HERO_W / mapH, { pad: 0.35, minW: 150 }); return { ...v, y: v.y + v.h * 0.20 }; })(); /* 展开/收起键:收起态固定贴地图右上角(随地图一起滚走,不浮空于列表 之上);展开态 sticky 浮在视口右上角(下滚也钉住、随时一键收起)。 按钮样式两态共用,定位策略由 btnStyle 注入。 */ const renderToggle = (btnStyle) => ( ); return (
{s.l}
{s.l}
{s.summary.split(' | ').map((part, i, arr) => (
{day.place}
{day.runs} 趟 | {day.km} km | {day.time}