Refactor BottomNav and CommunityPage to enhance community forum integration; update navigation logic for mobile and desktop views. Add new localized strings for community page and system settings, including device information and user prompts.

This commit is contained in:
yangy
2026-05-13 17:42:26 +08:00
parent 5285b886ea
commit b3d3f0edcb
9 changed files with 372 additions and 92 deletions
+55 -15
View File
@@ -17,17 +17,40 @@ import localeZh from "@/locales/data-zh.json";
import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
function CyanSlider({ value, min, max, step = 1, onChange }: {
value: number; min: number; max: number; step?: number; onChange: (v: number) => void;
function CyanSlider({ value, min, max, step = 1, onChange, onRelease }: {
value: number;
min: number;
max: number;
step?: number;
onChange: (v: number) => void;
/** Commit to device once when the user releases the thumb (pointer / keyboard). */
onRelease?: (v: number) => void;
}) {
const fillPct = ((value - min) / (max - min)) * 100;
const emitRelease = (el: EventTarget | null) => {
if (!(el instanceof HTMLInputElement)) return;
onRelease?.(Number(el.value));
};
return (
<div className="relative flex items-center w-full mt-2">
<div className="absolute left-0 h-[4px] rounded-full pointer-events-none"
style={{ width: `${fillPct}%`, background: "#00FFF6", boxShadow: "0 0 6px rgba(0,255,246,0.5)" }} />
<input type="range" min={min} max={max} step={step} value={value}
<input
type="range"
min={min}
max={max}
step={step}
value={value}
className="cyan-slider relative z-10"
onChange={(e) => onChange(Number(e.target.value))} />
onChange={(e) => onChange(Number(e.target.value))}
onPointerUp={(e) => emitRelease(e.currentTarget)}
onPointerCancel={(e) => emitRelease(e.currentTarget)}
onKeyUp={(e) => {
if (["ArrowLeft", "ArrowRight", "ArrowUp", "ArrowDown", "Home", "End", "PageUp", "PageDown"].includes(e.key)) {
emitRelease(e.currentTarget);
}
}}
/>
</div>
);
}
@@ -52,6 +75,9 @@ function ListRow({ label, value, onClick }: { label: string; value: string; onCl
type LocaleDac = {
dac?: {
sectionBalanceSensitivity?: string;
sectionOutput?: string;
sectionSystemInterface?: string;
balance?: string;
sensitivity?: string;
filters?: { label?: string; options?: Array<{ index: number; label: string }> };
@@ -137,7 +163,7 @@ export default function AudioPage() {
<div className="px-4 pb-32">
{/* ── 平衡与灵敏度 ── */}
<SectionHeader title="平衡与灵敏度" />
<SectionHeader title={dacText.sectionBalanceSensitivity ?? "平衡与灵敏度"} />
<div className="ios-list-group px-4 py-3 space-y-4">
<div>
<div className="flex items-center justify-between">
@@ -146,14 +172,21 @@ export default function AudioPage() {
{localBalance >= 0 ? `+${localBalance.toFixed(1)}` : localBalance.toFixed(1)} dB
</span>
</div>
<CyanSlider value={localBalance} min={-15} max={15} step={0.1}
<CyanSlider
value={localBalance}
min={-15}
max={15}
step={0.1}
onChange={(v) => {
isBalanceDragging.current = true;
setLocalBalance(v);
// 实时调用接口(例如:-13.0 → -130)
}}
onRelease={(v) => {
isBalanceDragging.current = false;
setLocalBalance(v);
updateSetting({ balance: Math.round(v * 10) });
setTimeout(() => { isBalanceDragging.current = false; }, 500);
}} />
}}
/>
</div>
<div className="border-t border-white/[0.06]" />
<div>
@@ -163,19 +196,26 @@ export default function AudioPage() {
{localVuSens >= 0 ? `+${localVuSens}` : localVuSens} dB
</span>
</div>
<CyanSlider value={localVuSens} min={-20} max={20}
<CyanSlider
value={localVuSens}
min={-20}
max={20}
step={1}
onChange={(v) => {
isVuDragging.current = true;
setLocalVuSens(v);
// 接口参数需要将显示值乘以 2(例如:10dB -> vuSensor=20
}}
onRelease={(v) => {
isVuDragging.current = false;
setLocalVuSens(v);
updateSetting({ vuSensor: Math.round(v * 2) });
setTimeout(() => { isVuDragging.current = false; }, 500);
}} />
}}
/>
</div>
</div>
{/* ── 输出特性 ── */}
<SectionHeader title="输出特性" />
<SectionHeader title={dacText.sectionOutput ?? "输出特性"} />
<div className="ios-list-group">
<ListRow
label={dacText.filters?.label ?? "滤波特性"}
@@ -195,7 +235,7 @@ export default function AudioPage() {
</div>
{/* ── 系统与接口 ── */}
<SectionHeader title="系统与接口" />
<SectionHeader title={dacText.sectionSystemInterface ?? "系统与接口"} />
<div className="ios-list-group">
<ListRow
label={dacText.xlr?.label ?? "XLR 端口极性"}
+106 -14
View File
@@ -1,22 +1,114 @@
import BottomNav from "@/components/BottomNav";
import { useDevice } from "@/contexts/DeviceContext";
import { useIsLgUp } from "@/hooks/useIsLgUp";
import localeZh from "@/locales/data-zh.json";
import localeZhHK from "@/locales/data-zh-HK.json";
import localeEn from "@/locales/data-en.json";
import { ChevronLeft, Copy } from "lucide-react";
import { useMemo, useCallback } from "react";
import { useLocation } from "wouter";
import { toast } from "sonner";
const FORUM_URL = "https://forum.zidoo.tv";
const FORUM_URL = "https://forum.zidoo.tv/index.php";
type CommunityPageLocale = NonNullable<(typeof localeZh)["communityPage"]>;
export default function CommunityPage() {
const isLgUp = useIsLgUp();
const [, setLocation] = useLocation();
const { deviceState } = useDevice();
const text = useMemo((): CommunityPageLocale => {
const lang = deviceState?.language;
const pack = lang === 0 ? localeEn : lang === 1 ? localeZhHK : localeZh;
return (pack.communityPage ?? localeZh.communityPage) as CommunityPageLocale;
}, [deviceState?.language]);
const copyForumUrl = useCallback(async () => {
try {
await navigator.clipboard.writeText(FORUM_URL);
toast.success(text.toastCopied);
} catch {
try {
const ta = document.createElement("textarea");
ta.value = FORUM_URL;
ta.style.position = "fixed";
ta.style.left = "-9999px";
document.body.appendChild(ta);
ta.focus();
ta.select();
const ok = document.execCommand("copy");
document.body.removeChild(ta);
if (ok) toast.success(text.toastCopied);
else toast.error(text.toastCopyFailed);
} catch {
toast.error(text.toastCopyFailed);
}
}
}, [text.toastCopied, text.toastCopyFailed]);
if (isLgUp) {
return (
<div
className="h-[100dvh] flex flex-col pb-[calc(env(safe-area-inset-bottom,0px)+56px)]"
style={{ background: "#04060d" }}
>
<iframe
className="flex-1 w-full min-h-0 border-0 bg-white"
src={FORUM_URL}
title="Zidoo forum"
allow="fullscreen; clipboard-read; clipboard-write"
referrerPolicy="strict-origin-when-cross-origin"
/>
<BottomNav />
</div>
);
}
return (
<div
className="h-[100dvh] flex flex-col pb-[calc(env(safe-area-inset-bottom,0px)+56px)]"
style={{
background: "#04060d",
}}
>
<iframe
className="flex-1 w-full min-h-0 border-0 bg-white"
src={FORUM_URL}
title="Zidoo forum"
allow="fullscreen; clipboard-read; clipboard-write"
referrerPolicy="strict-origin-when-cross-origin"
/>
<div className="min-h-[100dvh] bg-black flex flex-col pb-[calc(env(safe-area-inset-bottom,0px)+56px)]">
<div className="page-header">
<button
type="button"
onClick={() => setLocation("/")}
className="mr-4 text-white/60 active:text-white transition-colors"
aria-label="Back"
>
<ChevronLeft size={24} />
</button>
<h1 className="flex-1 text-center text-[17px] font-semibold text-white">{text.title}</h1>
<div className="w-8" />
</div>
<div className="flex-1 px-4 pt-4">
<div
className="ios-list-group p-4 space-y-4"
style={{
background: "linear-gradient(180deg, rgba(34,36,40,0.95) 0%, rgba(24,26,30,0.95) 100%)",
border: "1px solid rgba(255,255,255,0.1)",
borderRadius: 14,
boxShadow: "0 8px 32px rgba(0,0,0,0.35)",
}}
>
<p className="text-[15px] leading-relaxed text-white/75">{text.intro}</p>
<div
className="rounded-[10px] border border-white/12 bg-[#15171b] px-3 py-3 font-mono text-[12px] leading-snug break-all text-[#00FFF6]/90 select-all"
>
{FORUM_URL}
</div>
<button
type="button"
onClick={copyForumUrl}
className="flex w-full items-center justify-center gap-2 rounded-full py-3 text-[15px] font-semibold text-black transition-all active:scale-[0.98] bg-[#00FFF6] hover:brightness-95"
>
<Copy size={18} strokeWidth={2.25} />
{text.copyLink}
</button>
</div>
</div>
<BottomNav />
</div>
);
+45 -28
View File
@@ -6,7 +6,7 @@
2. Two action cards: 批量编辑 / 添加耳机型号
3. Headphone model row: [Name ▼] [] [+]
4. Freq response chart: legend + A/B + 复制到B + DIFF + SVG curve
5. Band grid: 10 pills (2 rows × 5)
5. Band grid: below lg breakpoint 2×5 pills; lg+ single row ×10
6. Band detail card: 滤波器 / FREQ / GAIN / Q值
7. Total gain card: 总增益 value + AUTO toggle + slider
============================================================ */
@@ -998,11 +998,11 @@ export default function EQPage() {
const handleSaveAddPreset = async () => {
const nextName = addPresetMode === "copy" ? copyPresetName.trim() : flatPresetName.trim();
if (!nextName) {
toast.error("名称不能为空");
toast.error(eqUi.addPresetNameEmpty);
return;
}
if (headphoneModels.includes(nextName)) {
toast.error("名称已存在,请修改后再保存");
toast.error(eqUi.addPresetNameExists);
return;
}
@@ -1056,9 +1056,9 @@ export default function EQPage() {
setHeadphoneIdx(headphoneModels.length);
}
setIsAddPresetDialogOpen(false);
toast.success("已新增预设");
toast.success(eqUi.toastAddPresetOk);
} catch {
toast.error("新增预设失败");
toast.error(eqUi.toastAddPresetFail);
}
};
@@ -1545,13 +1545,13 @@ export default function EQPage() {
eqUi={eqUi}
/>
{/* ── Band grid (2 rows × 5) ── */}
{/* ── Band grid: 2×5 below lg, 1×10 on lg+ (typical desktop) ── */}
<div className="ios-list-group p-3">
<div className="grid grid-cols-5 gap-1.5">
<div className="grid grid-cols-5 gap-1.5 lg:grid-cols-10 lg:gap-1">
{bands.map((b, i) => (
<button key={i}
className={cn(
"flex flex-col items-center py-2.5 px-1 rounded-[10px] transition-all duration-150 active:scale-95",
"flex flex-col items-center rounded-[10px] transition-all duration-150 active:scale-95 py-2.5 px-1 lg:py-2 lg:px-0.5",
selectedBand === i ? "text-black" : "text-white/55"
)}
style={selectedBand === i ? {
@@ -1561,8 +1561,8 @@ export default function EQPage() {
background: "rgba(44,44,46,0.65)",
}}
onClick={() => setSelectedBand(i)}>
<span className="text-[12px] font-bold leading-tight">{freqLabel(b.freq)}</span>
<span className="text-[9px] mt-0.5 opacity-70 leading-tight">{getFilterShortName(b.type)}</span>
<span className="text-[12px] font-bold leading-tight lg:text-[11px]">{freqLabel(b.freq)}</span>
<span className="text-[9px] mt-0.5 opacity-70 leading-tight lg:text-[8px] lg:mt-0">{getFilterShortName(b.type)}</span>
</button>
))}
</div>
@@ -1697,69 +1697,86 @@ export default function EQPage() {
</div>
{isAddPresetDialogOpen && (
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-black/60 px-4">
<div className="fixed inset-0 z-[120] flex items-center justify-center bg-black/65 px-4">
<div
className="w-full max-w-[420px] rounded-[14px] p-5"
style={{
background: "rgba(236,236,238,0.97)",
border: "1px solid rgba(255,255,255,0.35)",
background: "linear-gradient(180deg, rgba(34,36,40,0.98) 0%, rgba(24,26,30,0.98) 100%)",
border: "1px solid rgba(255,255,255,0.12)",
boxShadow: "0 18px 48px rgba(0,0,0,0.55)",
}}
onClick={(e) => e.stopPropagation()}
>
<div className="flex items-center justify-between mb-5">
<h3 className="text-[31px] text-black/75 font-semibold"></h3>
<div className="flex items-center justify-between mb-4">
<h3 className="text-[18px] font-semibold leading-tight text-white/90">{eqUi.addPresetTitle}</h3>
<button
type="button"
className="text-black/45 hover:text-black/70 transition-colors"
className="rounded-full p-1.5 text-white/45 transition-colors hover:bg-white/10 hover:text-white/80"
onClick={() => setIsAddPresetDialogOpen(false)}
aria-label={eqUi.closeDrawer}
>
<X size={20} />
</button>
</div>
<div className="space-y-4">
<label className="flex items-center gap-3">
<div className="space-y-3">
<label
className={cn(
"flex cursor-pointer items-center gap-3 rounded-[12px] border border-white/10 p-3 transition-colors",
addPresetMode === "copy" ? "bg-white/[0.06]" : "bg-white/[0.02] hover:bg-white/[0.04]",
)}
>
<input
type="radio"
name="add-preset-mode"
checked={addPresetMode === "copy"}
onChange={() => setAddPresetMode("copy")}
className="w-4 h-4"
className="h-4 w-4 shrink-0 accent-[#00FFF6]"
/>
<input
value={copyPresetName}
onChange={(e) => setCopyPresetName(e.target.value)}
className="flex-1 h-10 px-3 rounded-[5px] text-[15px] text-black/70 bg-[#f0f0f2] border border-black/15 outline-none"
onClick={(e) => e.stopPropagation()}
className="h-10 min-w-0 flex-1 rounded-[10px] border border-white/12 bg-[#15171b] px-3 text-[15px] text-white/90 outline-none ring-0 ring-offset-0 placeholder:text-white/30 focus:border-white/15 focus:ring-0 focus-visible:ring-0"
/>
</label>
<label className="flex items-center gap-3">
<label
className={cn(
"flex cursor-pointer items-center gap-3 rounded-[12px] border border-white/10 p-3 transition-colors",
addPresetMode === "flat" ? "bg-white/[0.06]" : "bg-white/[0.02] hover:bg-white/[0.04]",
)}
>
<input
type="radio"
name="add-preset-mode"
checked={addPresetMode === "flat"}
onChange={() => setAddPresetMode("flat")}
className="w-4 h-4"
className="h-4 w-4 shrink-0 accent-[#00FFF6]"
/>
<input
value={flatPresetName}
onChange={(e) => setFlatPresetName(e.target.value)}
className="flex-1 h-10 px-3 rounded-[5px] text-[15px] text-black/70 bg-[#f0f0f2] border border-black/15 outline-none"
onClick={(e) => e.stopPropagation()}
className="h-10 min-w-0 flex-1 rounded-[10px] border border-white/12 bg-[#15171b] px-3 text-[15px] text-white/90 outline-none ring-0 ring-offset-0 placeholder:text-white/30 focus:border-white/15 focus:ring-0 focus-visible:ring-0"
/>
</label>
</div>
<div className="mt-6 flex items-center justify-center gap-16">
<div className="mt-6 flex items-center justify-center gap-4 sm:gap-8">
<button
type="button"
className="px-5 py-2 rounded-full text-[26px] text-black/80 bg-[#d2d2d5] hover:bg-[#c7c7ca] transition-colors"
className="min-w-[112px] rounded-full px-5 py-2.5 text-[15px] font-medium text-white/85 transition-colors bg-[#3f4349] hover:bg-[#4a4f56] active:scale-[0.98]"
onClick={() => setIsAddPresetDialogOpen(false)}
>
{eqUi.cancel}
</button>
<button
type="button"
className="px-5 py-2 rounded-full text-[26px] text-black bg-[#00FFF6] hover:brightness-95 transition-all"
className="min-w-[112px] rounded-full px-5 py-2.5 text-[15px] font-semibold text-black transition-all bg-[#00FFF6] hover:brightness-95 active:scale-[0.98]"
onClick={handleSaveAddPreset}
>
{eqUi.save}
</button>
</div>
</div>
+12 -2
View File
@@ -574,7 +574,12 @@ export default function Home() {
>
<Sliders size={22} className={(!bypassOn && ds.peqEnable === 1) ? "text-[#00FFF6]" : "text-white/60"} />
</button>
<span className={cn("text-[11px]", (!bypassOn && ds.peqEnable === 1) ? "text-[#00FFF6]" : "text-white/35")}>HP-EQ</span>
<div className="flex flex-col items-center gap-0.5">
<span className={cn("text-[11px]", (!bypassOn && ds.peqEnable === 1) ? "text-[#00FFF6]" : "text-white/35")}>HP-EQ</span>
<span className="text-[9px] text-white/25 text-center leading-snug max-w-[76px]">
{homeText.doubleClickHint ?? "Double-click to enter"}
</span>
</div>
</div>
{/* Effect */}
@@ -586,7 +591,12 @@ export default function Home() {
>
<Activity size={22} className={(!bypassOn && effectOn) ? "text-[#00FFF6]" : "text-white/60"} />
</button>
<span className={cn("text-[11px]", (!bypassOn && effectOn) ? "text-[#00FFF6]" : "text-white/35")}>Effect</span>
<div className="flex flex-col items-center gap-0.5">
<span className={cn("text-[11px]", (!bypassOn && effectOn) ? "text-[#00FFF6]" : "text-white/35")}>Effect</span>
<span className="text-[9px] text-white/25 text-center leading-snug max-w-[76px]">
{homeText.doubleClickHint ?? "Double-click to enter"}
</span>
</div>
</div>
{/* Bypass (DSP) */}
+39 -15
View File
@@ -67,6 +67,7 @@ function ListRow({ label, description, value, onClick, toggle, checked, onToggle
type LocaleSettings = {
settings: {
vu: string;
vuMeter?: string;
screenBrightness: { label: string; options: Array<{ index: number; label: string }> };
turnOffScreen: { label: string; options: Array<{ index: number; label: string }> };
knobBrightness: { label: string; options: Array<{ index: number; label: string }> };
@@ -75,6 +76,22 @@ type LocaleSettings = {
Automatic: { label: string; options: Array<{ index: number; label: string }> };
sleepTime?: { label: string; options: Array<{ index: number; label: string }> };
sleep?: { label: string; options: Array<{ index: number; label: string }> };
systemPage: {
sectionDisplay: string;
sectionGeneral: string;
sectionAbout: string;
screenBrightnessDesc: string;
turnOffScreenDesc: string;
knobBrightnessDesc: string;
buttonLightDesc: string;
languageDesc: string;
automaticDesc: string;
sleepDesc: string;
deviceName: string;
firmwareVersion: string;
macAddress: string;
disconnect: string;
};
};
};
@@ -111,6 +128,7 @@ export default function SystemPage() {
const knobBreathLightOn = (ds?.knob_breathlight ?? 1) === 0;
const localeData = LOCALE_BY_LANG[langIdx] ?? localeZh;
const settingsText = localeData.settings;
const ui = settingsText.systemPage;
const SCREEN_LIGHT_OPTIONS = optionLabels(settingsText.screenBrightness?.options, ["亮", "中等", "暗"]);
const KNOB_LIGHT_OPTIONS = optionLabels(settingsText.knobBrightness?.options, ["关闭", "亮", "中等", "暗"]);
const SCREEN_OFF_OPTIONS = optionLabels(settingsText.turnOffScreen?.options, ["常亮", "30秒", "1分钟", "3分钟", "5分钟"]);
@@ -118,6 +136,12 @@ export default function SystemPage() {
const AUTO_HOME_OPTIONS = optionLabels(settingsText.Automatic?.options, ["关闭", "20秒", "40秒", "60秒"]);
const LANG_OPTIONS = optionLabels(settingsText.language?.options, ["English", "繁體中文", "简体中文"]);
const sleepRowLabel =
settingsText.sleepTime?.label ??
settingsText.sleep?.label ??
(localeEn as LocaleSettings).settings.sleep?.label ??
"Sleep";
const goSelect = (title: string, options: string[], selected: number, key: string) => {
navigateToSelect(title, options, selected, "/system", key);
setLocation("/select");
@@ -135,49 +159,49 @@ export default function SystemPage() {
<div className="px-4 pb-32">
{/* ── 显示 ── */}
<SectionHeader title="显示" />
<SectionHeader title={ui.sectionDisplay} />
<div className="ios-list-group">
<ListRow label={settingsText.screenBrightness.label} description="调节屏幕的整体亮度"
<ListRow label={settingsText.screenBrightness.label} description={ui.screenBrightnessDesc}
value={SCREEN_LIGHT_OPTIONS[screenLightIdx]}
onClick={() => goSelect(settingsText.screenBrightness.label, SCREEN_LIGHT_OPTIONS, screenLightIdx, "screenLight")} />
<ListRow label={settingsText.turnOffScreen.label} description="设备无操作后自动关闭屏幕的时间"
<ListRow label={settingsText.turnOffScreen.label} description={ui.turnOffScreenDesc}
value={SCREEN_OFF_OPTIONS[screenOffIdx]}
onClick={() => goSelect(settingsText.turnOffScreen.label, SCREEN_OFF_OPTIONS, screenOffIdx, "screenOff")} />
<ListRow label={settingsText.knobBrightness.label} description="调节旋钮指示灯的亮度"
<ListRow label={settingsText.knobBrightness.label} description={ui.knobBrightnessDesc}
value={KNOB_LIGHT_OPTIONS[buttonLightIdx]}
onClick={() => goSelect(settingsText.knobBrightness.label, KNOB_LIGHT_OPTIONS, buttonLightIdx, "buttonLight")} />
<ListRow label={settingsText.buttonLight.label} description="锁屏后旋钮是否显示呼吸灯效果"
<ListRow label={settingsText.buttonLight.label} description={ui.buttonLightDesc}
toggle checked={knobBreathLightOn}
onToggle={(v) => updateSetting({ knob_breathlight: v ? 0 : 1 })} />
</div>
{/* ── 通用 ── */}
<SectionHeader title="通用" />
<SectionHeader title={ui.sectionGeneral} />
<div className="ios-list-group">
<ListRow label={settingsText.language.label} description="设置系统显示语言"
<ListRow label={settingsText.language.label} description={ui.languageDesc}
value={LANG_OPTIONS[langIdx]}
onClick={() => goSelect(settingsText.language.label, LANG_OPTIONS, langIdx, "language")} />
<ListRow label={settingsText.Automatic.label} description="闲置一段时间后自动回到主界面"
<ListRow label={settingsText.Automatic.label} description={ui.automaticDesc}
value={AUTO_HOME_OPTIONS[autoHomeIdx]}
onClick={() => goSelect(settingsText.Automatic.label, AUTO_HOME_OPTIONS, autoHomeIdx, "autoHome")} />
<ListRow label={settingsText.sleepTime?.label ?? settingsText.sleep?.label ?? "休眠"} description="设置设备进入低功耗模式的时间"
<ListRow label={sleepRowLabel} description={ui.sleepDesc}
value={SLEEP_OPTIONS[sleepIdx]}
onClick={() => goSelect(settingsText.sleepTime?.label ?? settingsText.sleep?.label ?? "休眠", SLEEP_OPTIONS, sleepIdx, "sleep")} />
onClick={() => goSelect(sleepRowLabel, SLEEP_OPTIONS, sleepIdx, "sleep")} />
</div>
{/* ── 关于 ── */}
<SectionHeader title="关于" />
<SectionHeader title={ui.sectionAbout} />
<div className="ios-list-group">
<div className="ios-list-row">
<span className="flex-1 text-[16px] text-white"></span>
<span className="flex-1 text-[16px] text-white">{ui.deviceName}</span>
<span className="ios-row-value">{ds?.device ?? "Luxsin X9"}</span>
</div>
<div className="ios-list-row">
<span className="flex-1 text-[16px] text-white"></span>
<span className="flex-1 text-[16px] text-white">{ui.firmwareVersion}</span>
<span className="ios-row-value">{ds?.version ?? "—"}</span>
</div>
<div className="ios-list-row">
<span className="flex-1 text-[16px] text-white">MAC </span>
<span className="flex-1 text-[16px] text-white">{ui.macAddress}</span>
<span className="ios-row-value text-[12px]">{ds?.mac ?? "—"}</span>
</div>
</div>
@@ -188,7 +212,7 @@ export default function SystemPage() {
className="w-full py-4 rounded-[14px] text-[16px] font-medium transition-all duration-150 active:scale-[0.98]"
style={{ background: "rgba(239,68,68,0.1)", border: "1px solid rgba(239,68,68,0.2)", color: "#ef4444" }}
onClick={() => { disconnect(); setLocation("/"); }}>
{ui.disconnect}
</button>
</div>
</div>