/* ============================================================ ROOY · 故障页弹窗(modal alerts) ─────────────────────────────────────────────────────────── 需求:滑行 / 配对 / 后台同步过程中可能遇到的硬件 / 网络 / 权限 故障,用统一的 iOS HIG alert 风格弹出(dim backdrop + 居中 alert card + icon + title + body + 底部按钮 hairline 分隔)。 故障清单(按发生频率 / 严重度排序): · F1 传感器断开 — 蓝牙连接中断,必须重连(关键,alert red) · F2 传感器电量低 — 还能滑一段,仅提示(警告,gold) · F3 GPS 信号弱 — 缆车厢 / 隧道遮挡,自动恢复(提示,ink3) · F4 位置权限拒 — 用户首次未授权,必须去设置(关键,data 蓝) accent budget(modal 浮层,不计屏内 budget): · alert red — 关键故障(连接断开) · gold — 警告(电量低) · ink3 — 中性提示(GPS 弱,"会自动恢复"不需要 alert 色) · data 蓝 — 跳设置的引导(用 data 色暗示"系统级"动作) iOS HIG 标准: · alert 宽 ~270pt(这里 286 适配中文留呼吸) · 按钮在 card 底部,hairline 分隔 · 单按钮全宽;双按钮横排中间 vertical hairline · primary 按钮 fontWeight 600,secondary 400 · destructive 按钮用 R.alert 红色文字 ============================================================ */ /* FaultAlertModal — 故障 modal 通用容器。dim backdrop + 居中 alert card。`tone` 决定 icon 圆背景色调;`destructive` 让 primary 按钮 变红色("断开连接"这种需要警告的破坏性动作)。 */ function FaultAlertModal({ tone = 'neutral', icon, title, body, primary = '知道了', secondary, destructive = false, }) { /* tone → icon 圆背景 / 前景色映射 */ const toneMap = { alert: { bg: 'color-mix(in srgb, oklch(60% 0.21 24) 14%, transparent)', fg: R.alert }, gold: { bg: 'color-mix(in srgb, var(--r-gold) 16%, transparent)', fg: R.gold }, data: { bg: 'color-mix(in srgb, var(--r-data) 12%, transparent)', fg: R.data }, neutral: { bg: R.systemFill, fg: R.ink2 }, }; const t = toneMap[tone] || toneMap.neutral; const btnBase = { flex: 1, padding: '14px 0', background: 'transparent', border: 'none', cursor: 'pointer', ...T.body, }; return (
{/* content */}
{icon}

{title}

{body}

{/* buttons */}
{secondary && ( <>
)}
); } /* ── Icon glyphs ─ */ /* ── Icon glyphs ─ 统一 24×24 视口、1.7 线宽、圆角圆头,SF-Symbols 风。slash 只给 "断开 / 被拒"两种 off 态(bluetooth / location),电量低 / GPS 弱 是 low/weak 不是 off,所以不带 slash——slash 本身承担语义。 */ function BluetoothSlashGlyph() { return ( {/* bluetooth bind-rune outline */} ); } function BatteryLowGlyph() { return ( {/* low charge fill — 左侧一小段 = 所剩无几 */} {/* 正极 nub */} ); } function GpsWeakGlyph() { return ( {/* signal bars — 只有最近一格满,越远越淡 = 弱 */} ); } function LocationOffGlyph() { return ( {/* map pin — 尖底 + 中心实心点 = 位置 */} ); } /* ── 4 个 fault alert 实例 ─ */ function FaultSensorDisconnected() { return ( } title="传感器断开了" body="右脚传感器跟手机失去连接 — 这趟的滑行数据会暂停记录。" primary="重新连接" secondary="稍后处理" /> ); } function FaultSensorLowBattery() { return ( } title="传感器电量不足" body="左脚 12%、右脚 8% — 大约还能滑 30 分钟,建议这趟下来后充电。" primary="知道了" /> ); } function FaultGpsWeak() { return ( } title="GPS 信号弱" body="缆车厢里收不到 GPS 是正常的 — 出隧道后会自动恢复,路线会接上。" primary="继续滑行" /> ); } function FaultLocationDenied() { return ( } title="需要打开位置权限" body="ROOY 用 GPS 记录路线和坡度。打开「设置 › 隐私 › 位置」并勾选「使用期间」。" primary="打开设置" secondary="暂不" /> ); } Object.assign(window, { FaultAlertModal, FaultSensorDisconnected, FaultSensorLowBattery, FaultGpsWeak, FaultLocationDenied, BluetoothSlashGlyph, BatteryLowGlyph, GpsWeakGlyph, LocationOffGlyph, });