From 09bc41362da0034eb1b57eff333be9876a333647 Mon Sep 17 00:00:00 2001 From: TermiNexus Date: Mon, 26 Oct 2026 18:50:30 +0800 Subject: [PATCH] =?UTF-8?q?fix(ui):=20=E5=A4=8D=E7=94=A8=20HEAD=E3=80=81?= =?UTF-8?q?=E5=90=AF=E7=94=A8=20i18n=20=E5=B9=B6=E6=A0=A1=E5=87=86?= =?UTF-8?q?=E8=AE=BE=E7=BD=AE=E9=A1=B5=E9=85=8D=E8=89=B2=E4=B8=8E=E5=86=85?= =?UTF-8?q?=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3DTank/docs/ColorPalette.md | 66 +++ 3DTank/docs/SettingsStyleTable.md | 8 + 3DTank/docs/UiTranslationStatus.md | 8 +- .../jsMain/kotlin/App/Browser/LobbyView.kt | 2 +- .../jsMain/kotlin/App/Browser/Translations.kt | 60 ++- .../kotlin/App/Browser/Ui/LobbyScreens.kt | 445 +++++++++--------- .../jsMain/kotlin/App/Browser/Ui/Palette.kt | 65 +++ .../jsMain/kotlin/App/Browser/Ui/UiTheme.kt | 30 +- .../app/src/jsMain/resources/lobby3.css | 6 +- 9 files changed, 435 insertions(+), 255 deletions(-) create mode 100644 3DTank/docs/ColorPalette.md create mode 100644 TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/Palette.kt diff --git a/3DTank/docs/ColorPalette.md b/3DTank/docs/ColorPalette.md new file mode 100644 index 0000000..41f5257 --- /dev/null +++ b/3DTank/docs/ColorPalette.md @@ -0,0 +1,66 @@ +# Color Palette(原版配色表) + +> 来源:`module_82347` 的 `C9i`(`E9i()` 单例)构造器。 +> 这些值直接来自原版主题对象,不是从截图取色。 +> Kotlin 侧对应:`TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/Palette.kt` + +## 基础 + +| 原字段 | 值 | 用途 | +|---|---|---| +| `z1hz_1` | `rgb(255,255,255)` | 主文字 | +| `a1i0_1` | `rgb(0,0,0)` | 黑色 | +| `b1i0_1` | `rgba(255,255,255,0.05)` | 极淡面板 | +| `c1i0_1` | `rgba(255,255,255,0.1)` | 面板背景 | +| `d1i0_1` | `rgba(255,255,255,0.15)` | 分隔线 | +| `e1i0_1` | `rgba(255,255,255,0.2)` | 控件边框 | +| `f1i0_1` | `rgba(255,255,255,0.25)` | HEAD 分隔线 | +| `g1i0_1` | `rgba(255,255,255,0.3)` | 次级文字/边框 | +| `h1i0_1` | `rgba(255,255,255,0.4)` | 弱文字 | +| `i1i0_1` | `rgba(255,255,255,0.5)` | 说明文字 | +| `j1i0_1` | `rgba(255,255,255,0.6)` | 菜单未激活 | +| `k1i0_1` | `rgba(255,255,255,0.7)` | 较强文字 | +| `l1i0_1` | `rgba(255,255,255,0.9)` | 标签文字 | +| `m1i0_1` | `rgba(255,255,255,0.95)` | 高亮文字 | + +## 主色 + +| 原字段 | 值 | 用途 | +|---|---|---| +| `b1i1_1` | `rgb(255,188,9)` | 主黄色 / 菜单激活线 | +| `d1i1_1` | `rgb(255,214,48)` | 黄色高亮 | +| `e1i1_1` | `rgb(0,215,255)` | 青色 | +| `f1i1_1` | `rgb(0,212,255)` | 青色(主) | +| `i1i1_1` | `rgb(116,186,61)` | 绿色 | +| `j1i1_1` | `rgb(232,139,0)` | 橙色 | +| `h1i1_1` | `rgb(191,213,255)` | 浅蓝 | +| `v1i0_1` | `rgb(255,124,124)` | 红色 | +| `z1i0_1` | `rgb(235,87,87)` | 深红 | +| `l1i1_1` | `rgb(197,197,197)` | 灰色 | +| `x1i0_1` | `rgb(78,80,95)` | 深灰 | +| `y1i0_1` | `rgb(54,58,66)` | 更深灰 | + +## 渐变 + +| 原字段 | 值 | 用途 | +|---|---|---| +| `d1i2_1` | `radial-gradient(#203040 0%, #03080D 100%)` | 子页面根背景 | +| `e1i2_1` | `linear-gradient(to top right, #4A4A5B 0%, #32363E 100%)` | 顶部渐变 | +| `f1i2_1` | `linear-gradient(to bottom, #2E3235 0%, #2E3235 0.01%, rgba(46,50,53,0.1) 83.57%, rgba(46,50,53,0) 100%)` | 菜单纵向渐变 | +| `q1i1_1` | `linear-gradient(135deg, #FF5309 0%, rgba(255,83,9,0) 71.43%)` | 橙红渐变 | +| `r1i1_1` | `linear-gradient(135deg, #FFBC09 0%, rgba(255,188,9,0) 66.67%)` | 黄色渐变 | +| `s1i1_1` | `linear-gradient(135deg, #AD87FF 0%, rgba(173,135,255,0) 68.75%)` | 紫色渐变 | +| `t1i1_1` | `linear-gradient(135deg, #16B9FF 0%, rgba(22,185,255,0) 84.37%)` | 蓝色渐变 | +| `u1i1_1` | `linear-gradient(225deg, #BFFF56 0%, rgba(191,255,86,0) 59.26%)` | 绿色渐变 | + +## 使用规则 + +- 子页面根背景:`Palette.PageGradient` +- 面板:`Palette.Panel`(`rgba(255,255,255,0.1)`) +- 面板边框:`Palette.PanelBorder`(`rgba(255,255,255,0.2)`) +- 分隔线:`Palette.Divider`(`rgba(255,255,255,0.25)`) +- 主文字:`Palette.White` +- 说明文字:`Palette.White50` +- 菜单未激活:`Palette.White60` +- 菜单激活线/主强调:`Palette.Yellow` +- HEAD 背景:`Palette.TopGradient` diff --git a/3DTank/docs/SettingsStyleTable.md b/3DTank/docs/SettingsStyleTable.md index 67baafa..369b971 100644 --- a/3DTank/docs/SettingsStyleTable.md +++ b/3DTank/docs/SettingsStyleTable.md @@ -2,6 +2,14 @@ > 来源:`3dtank.com/play/static/js/unpacked/module_82347.js` 的 `SettingsComponentStyle` / `SettingsMenuComponentStyle` / `SettingsButtonsComponentStyle` / `GameSettingsStyle` / `DropDownStyle` / `CheckBoxComponentStyle` / `InputRangeComponentStyle`。 > 方法:在无头页面中加载原包模块,patch `module_37036` 的 `ld` 捕获样式类,实例化样式类后逐个执行 `duv_1[0]` 样式函数,并用 recording style object 记录 `rpf` / `gp8` 调用。 +> 配色:`ColorPalette.md` / `Palette.kt`,直接来自 `C9i`(`E9i()`)构造器。 + +## 0. 页面外壳 + +- 子页面**复用主页 HEAD**,只把左侧 VIP 区域替换成退出键;组队、等级、昵称/经验、Logo、货币、F11、退出都不变。 +- 不再使用独立 Breadcrumbs 顶栏。 +- 设置菜单顺序:游戏显示 / 战斗 / 画面 / 音效 / 操控 / 账户 / 安全。 +- 文案全部走 `Translations` i18n,语言下拉切换 `Translations.LoadLocale` 并重绘。 ## 1. 页面骨架 diff --git a/3DTank/docs/UiTranslationStatus.md b/3DTank/docs/UiTranslationStatus.md index a292a42..b9d94a0 100644 --- a/3DTank/docs/UiTranslationStatus.md +++ b/3DTank/docs/UiTranslationStatus.md @@ -7,15 +7,15 @@ | 屏幕 / 组件 | 文件 | 说明 | |---|---|---| -| HEAD 顶栏 | `app/src/jsMain/kotlin/App/Browser/Ui/HeadScreen.kt` | Emotion;VIP→子页面退出键、组队两段、内部分隔线、货币/F11/退出 | -| 子页面 Breadcrumbs | `LobbyScreens.kt` | 6em 高;返回 / 标题 / 红宝石+水晶 / F11 / 退出 | +| HEAD 顶栏 | `app/src/jsMain/kotlin/App/Browser/Ui/HeadScreen.kt` | Emotion;所有页面复用;子页面只把 VIP 替换为退出键,其余组队/等级/昵称/货币/F11/退出不变 | +| 子页面外壳 | `LobbyView.kt` + `lobby3.css` | `ms-top` 始终显示;`#lobby-content` 从 6em 开始;不再使用独立 Breadcrumbs 顶栏 | | 大厅首页 | `app/src/jsMain/kotlin/App/Browser/Ui/HomeScreen.kt` | Emotion;Play / 主菜单 / Footer / BattlePass / 公告 | | 车库 | `app/src/jsMain/kotlin/App/Browser/Ui/GarageScreen.kt` | React;默认「总览」+ 右侧 4 个已装配件;分类栏、底盘/炮塔选择条、迷彩网格、左右面板、底部选择条 | | 商店 | `app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt` | 分类页签 + 物品卡片(分类/名称图标、稀有度、VIP、货币价格)+ 右侧详情/购买确认 | | 幸运礼盒 | 同上 | 4 种礼盒美术、钥匙图标/数量、可能奖励稀有度、右侧详情/开启确认 | | 战斗选择 | 同上 | 模式筛选(DM/TDM/CTF/CP/AS/JGR/SGE/RUGBY 图标)+ 战场列表 + 右侧详情/加入/创建入口 | | 任务 | 同上 | 分类侧栏(主要/每日/每周/星级挑战/合约)+ 任务卡(进度、奖励图标、领取/去完成)+ 星级挑战横幅 + 离线示例 | -| 设置 | 同上 | 8 个分类侧栏;开关/滑杆/下拉/按键表;账户、安全、支持分组;全部重置。**已按原包 `SettingsComponentStyle` 展开值校准**(72.25em 内容区、18em 菜单、46.875em 面板、1.875em 内边距、黄色滑动条/左侧线),详见 `3DTank/docs/SettingsStyleTable.md` | +| 设置 | 同上 | 7 个分类:游戏显示/战斗/画面/音效/操控/账户/安全;开关/滑杆/下拉/按键表;文案走 i18n;样式与配色按原包校准 | | 聊天 | 同上 | 频道侧栏(公共/战斗/军团/私聊)+ 消息行(头像/作者/时间)+ 发送;军团频道读 `ClanChatState` | | 新闻 | 同上 | 左列表 + 右详情;真实图片/日期/正文;无数据时显示带标记的离线示例 | | 军团 | 同上 | 未加入:军团美术、创建/搜索、许可证;已加入:军团头图、信息/成员/请求/邀请页签与成员表 | @@ -32,6 +32,8 @@ | 文件 | 内容 | |---|---| | `UiTheme.kt` | 颜色/字体/间距 token、Panel/Button/Tab/Card/Grid/Empty 等通用 Emotion 样式 | +| `Palette.kt` | 原版 `C9i` 配色表;所有颜色从 `Palette` 取值,见 `3DTank/docs/ColorPalette.md` | +| `Translations.kt` | 原版翻译加载;支持 zh-CN/en-US/ru/de/fr/es/pl/pt-BR/tr/ja 切换 | | `HeadScreen.kt` | `HeadContext` / `HeadMount` | | `HomeScreen.kt` | `HomeContext` / `HomeMount` | | `LobbyScreens.kt` | `ScreenContext` / `ScreenMount` / 子页面路由 | diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Browser/LobbyView.kt b/TankiOffline/app/src/jsMain/kotlin/App/Browser/LobbyView.kt index f803a79..e8fbd32 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/LobbyView.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/LobbyView.kt @@ -105,7 +105,7 @@ class LobbyView(private val Client: LobbyClient) { LDoc.getElementById("lobby-content").classList.toggle("garage", Nav == "garage") LDoc.getElementById("ms-home").classList.toggle("hidden", Nav != "home") // 车库复用主页顶栏(HEAD);其余子页面暂时隐藏,保留返回按钮。 - LDoc.getElementById("ms-top").classList.toggle("hidden", Nav != "home" && Nav != "garage") + LDoc.getElementById("ms-top").classList.toggle("hidden", false) LDoc.getElementById("ms-back").classList.add("hidden") if (Nav == "home") { diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Translations.kt b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Translations.kt index 60ae411..63d6e71 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Translations.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Translations.kt @@ -1,16 +1,48 @@ -// Translations.kt —— 真实 UI 文案(运行时拉取游戏自带的 zh-CN 翻译) +// Translations.kt —— 真实 UI 文案(运行时拉取游戏自带的翻译) // // 资源路径来自 3DTank/src/resources.json 的 resourceMap(id 19144)。 -// 拉取失败时回退到调用点给的默认文案,UI 不会因网络问题变空白。 +// 支持运行时切换语言;拉取失败时保留当前语言,调用点可给默认文案。 package App.Browser object Translations { private var Root: dynamic = null private var Loading = false + private val Cache = mutableMapOf() + + var Current: String = "zh-CN" + private set val Loaded: Boolean get() = Root != null + /** 原版可用的语言文件;键为 BCP-47,值为本地资源路径。 */ + val Languages: List> = listOf( + "zh-CN" to "简体中文", + "en-US" to "English", + "ru" to "Русский", + "de" to "Deutsch", + "fr" to "Français", + "es-ES" to "Español", + "pl" to "Polski", + "pt-BR" to "Português", + "tr" to "Türkçe", + "ja" to "日本語", + ) + + private fun PathOf(Code: String): String = when (Code) { + "zh-CN" -> "/play/static/translations/zh-CN.61d6ab60.json" + "en-US" -> "/play/static/translations/en-US.0f876fd1.json" + "ru" -> "/play/static/translations/ru.d6b949df.json" + "de" -> "/play/static/translations/de.6e129d94.json" + "fr" -> "/play/static/translations/fr.58d6d8d9.json" + "es-ES" -> "/play/static/translations/es-ES.93f63d79.json" + "pl" -> "/play/static/translations/pl.7aeeed9f.json" + "pt-BR" -> "/play/static/translations/pt-BR.053ff7af.json" + "tr" -> "/play/static/translations/tr.16473698.json" + "ja" -> "/play/static/translations/ja.1948901a.json" + else -> "/play/static/translations/zh-CN.61d6ab60.json" + } + private val FetchJson: (String, (dynamic) -> Unit) -> Unit = js( """ (function (Url, Cb) { @@ -23,16 +55,32 @@ object Translations { """ ) - /** 拉取 zh-CN 翻译;完成后调用 OnReady(用于重绘)。 */ + /** 拉取当前语言;完成后调用 OnReady(用于重绘)。 */ fun Load(OnReady: () -> Unit) { - if (Loaded || Loading) { + LoadLocale(Current, OnReady) + } + + /** 切换语言并拉取翻译;失败时保留当前语言。 */ + fun LoadLocale(Code: String, OnReady: () -> Unit) { + val Cached = Cache[Code] + if (Cached != null) { + Root = Cached + Current = Code + OnReady() + return + } + if (Loading) { OnReady() return } Loading = true - FetchJson("/play/static/translations/zh-CN.61d6ab60.json") { Json -> - Root = Json + FetchJson(PathOf(Code)) { Json -> Loading = false + if (Json != null) { + Cache[Code] = Json + Root = Json + Current = Code + } OnReady() } } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt index 7695888..b4891f0 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt @@ -5,6 +5,7 @@ package App.Browser.Ui import App.Browser.LobbyClient +import App.Browser.Translations import App.State.MainQuestInfo import App.State.NewsItemData import App.State.RootState @@ -152,61 +153,6 @@ private fun ChildrenBuilder.ScreenShell( div { css(ScreenRoot) Ui("screen." + Title) - div { - css(BreadcrumbsHeader) - button { - css(BreadcrumbsBack) - Ui("screen.back") - onClick = { ScreenContext.OnBack() } - +"←" - } - h2 { - css(BreadcrumbsTitle) - +Title - if (Subtitle.isNotEmpty()) { - span { css(ScreenSubtitle); +Subtitle } - } - } - div { - css(BreadcrumbsRight) - span { - css(BreadcrumbsCurrency) - img { - src = Asset("static/images/ruby.ce62efbc.svg") - css { Style("width", "1em"); Style("height", "1em") } - } - +"${HeadContext.Rubies}" - img { - src = Asset("static/images/addRuby.ff47c8be.svg") - css { Style("width", "1em"); Style("height", "1em"); Style("margin-left", "0.3em"); Style("opacity", "0.85") } - } - } - span { - css(BreadcrumbsCurrency) - img { - src = Asset("static/images/crystalSmall.242e6c15.svg") - css { Style("width", "1em"); Style("height", "1em") } - } - +"${HeadContext.Crystals}" - img { - src = Asset("static/images/addCrystal.af9a397c.svg") - css { Style("width", "1em"); Style("height", "1em"); Style("margin-left", "0.3em"); Style("opacity", "0.85") } - } - } - div { - css(BreadcrumbsFullscreen) - onClick = { HeadContext.OnFullscreen() } - i { css(BreadcrumbsFullscreenIcon) } - b { css(BreadcrumbsFullscreenText); +"F11" } - } - button { - css(BreadcrumbsExit) - Ui("screen.exit") - onClick = { HeadContext.OnLogout() } - +"⏻" - } - } - } div { if (FullBleed) css(ScreenBodyFull) else css(ScreenBody) Body() @@ -1891,8 +1837,6 @@ private val QuestsScreen = FC { // ---------------------------------------------------------------- 设置 -private val SettingsTabs = listOf("游戏显示", "画面", "音效", "操控", "账户", "战斗", "安全", "支持") - private sealed class SettingEntry { data class Header(val Text: String) : SettingEntry() data class Toggle( @@ -1928,200 +1872,227 @@ private sealed class SettingEntry { data class Keys(val Rows: List>) : SettingEntry() } +private val SettingsTabKeys = listOf("GAME", "BATTLE", "GRAPHIC", "SOUND", "CONTROL", "ACCOUNT", "SECURITY") +private val SettingsTabFallbacks = listOf("游戏显示", "战斗", "画面", "音效", "操控", "账户", "安全") + +private fun SettingsTabLabels(): List = + SettingsTabKeys.mapIndexed { Index, Key -> + Translations.T("lobby.settings.menu.$Key", SettingsTabFallbacks[Index]) + } + +private fun ST(Key: String, Default: String): String = Translations.T("lobby.settings.$Key", Default) + private fun SettingsEntries(Tab: Int, UserName: String): List = when (Tab) { 0 -> listOf( - SettingEntry.Header("常规"), SettingEntry.Choice( - "language", "语言", "允许您将游戏语言更改为可用选项之一。", - listOf("简体中文", "English", "Русский", "Deutsch", "Français", "Español"), 0, + "language", + ST("game.LANGUAGE", "语言"), + ST("game.LANGUAGE_DESCRIPTION", "允许您将游戏语言更改为可用选项之一。"), + Translations.Languages.map { it.second }, + Translations.Languages.indexOfFirst { it.first == Translations.Current }.coerceAtLeast(0), ), - SettingEntry.Toggle("showDropZones", "显示空投区", "显示道具箱在地图掉落的位置", true), - SettingEntry.Toggle("alternateCamera", "备用视角", "开启备用视角功能", false), - SettingEntry.Toggle("showYourDrone", "显示您的无人机", "在屏幕上显示您的无人机", true), - SettingEntry.Toggle("showOtherDrone", "显示其他无人机", "显示其他无人机", true), - SettingEntry.Toggle("showChat", "显示聊天窗口", "显示聊天窗口", true), - SettingEntry.Toggle("showNotifications", "显示通知", "在屏幕上显示您的通知", true), - SettingEntry.Toggle("receivePresents", "接收礼物", "允许您接收其他玩家的礼物", true), - SettingEntry.Header("音频与通知"), - SettingEntry.Toggle("backgroundSound", "背景声音", "启用背景音效", true), - SettingEntry.Toggle("blurMute", "后台自动静音", "标签页失焦时静音战斗", true), - SettingEntry.Toggle("disableBattleInvites", "屏蔽战斗邀请", "屏蔽好友组队邀请", false), - SettingEntry.Info("浏览器通知", "已启用", "当游戏不是活动窗口时从其他玩家那里获取信息"), + SettingEntry.Toggle("showDropZones", ST("game.SHOW_DROP_ZONES", "显示空投区"), ST("game.SHOW_DROP_ZONES_DESCRIPTION", "显示道具箱在地图掉落的位置"), true), + SettingEntry.Toggle("alternateCamera", ST("game.ALTERNATE_CAMERA", "备用视角"), ST("game.ALTERNATE_CAMERA_DESCRIPTION", "开启备用视角功能"), false), + SettingEntry.Toggle("showYourDrone", ST("game.SHOW_YOUR_DRONE", "显示您的无人机"), ST("game.SHOW_YOUR_DRONE_DESCRIPTION", "在屏幕上显示您的无人机"), true), + SettingEntry.Toggle("showOtherDrone", ST("game.SHOW_OTHER_DRONE", "显示其他无人机"), ST("game.SHOW_OTHER_DRONE_DESCRIPTION", "显示其他无人机"), true), + SettingEntry.Toggle("showChat", ST("game.SHOW_CHAT", "显示聊天窗口"), ST("game.SHOW_CHAT_DESCRIPTION", "显示聊天窗口"), true), + SettingEntry.Toggle("showNotifications", ST("game.SHOW_NOTIFICATIONS", "显示通知"), ST("game.SHOW_NOTIFICATIONS_DESCRIPTION", "在屏幕上显示您的通知"), true), + SettingEntry.Toggle("receivePresents", ST("game.RECEIVE_PRESENTS_ENABLED", "接收礼物"), ST("game.RECEIVE_PRESENTS_ENABLED_DESCRIPTION", "允许您接收其他玩家的礼物"), true), + SettingEntry.Toggle("backgroundSound", ST("game.BACKGROUND_SOUND", "背景声音"), ST("game.BACKGROUND_SOUND_DESCRIPTION", "启用背景音效"), true), + SettingEntry.Toggle("blurMute", ST("game.BLUR_MUTE", "后台自动静音"), ST("game.BLUR_MUTE_DESCRIPTION", "标签页失焦时静音战斗"), true), + SettingEntry.Toggle("disableBattleInvites", ST("game.DISABLE_SHOWING_BATTLE_INVITES", "屏蔽战斗邀请"), ST("game.DISABLE_SHOWING_BATTLE_INVITES_DESCRIPTION", "屏蔽好友组队邀请"), false), SettingEntry.Action( - "激活兑换码", - "兑换码可让您获得水晶、VIP 天数、金箱子、迷彩和车库中的一些物品。", - "激活", + ST("game.ACTIVATE_PROMOCODE", "激活兑换码"), + ST("game.ACTIVATE_PROMOCODE_DESCRIPTION", "兑换码可让您获得水晶、VIP 天数、金箱子、迷彩和车库中的一些物品。"), + ST("game.ACTIVATE_PROMOCODE", "激活"), "兑换码功能将在账户协议接入后开放。", ), ) 1 -> listOf( - SettingEntry.Header("图形质量"), + SettingEntry.Toggle("aimDot", ST("battle.SHOW_AIM_HELPER_DOT", "在屏幕中间显示一个点"), ST("battle.SHOW_AIM_HELPER_DOT_DESCRIPTION", "显示一个点状光标,以便于镜头定位"), false), + SettingEntry.Toggle("showDamage", ST("battle.SHOW_DAMAGE", "显示伤害"), ST("battle.SHOW_DAMAGE_DESCRIPTION", "显示您对敌人造成的伤害生命值"), true), + SettingEntry.Toggle("autoSuicide", ST("battle.AUTO_SUICIDE_ON_OVERTURN", "自动自毁"), ST("battle.AUTO_SUICIDE_ON_OVERTURN_DESCRIPTION", "如果您的坦克翻车或者侧翻,将会开启自动自毁。"), true), + SettingEntry.Toggle("showStatusName", ST("battle.SHOW_STATUS_NAME", "状态效果文字提示"), ST("battle.SHOW_STATUS_NAME_DESCRIPTION", "显示状态效果的指示文字"), true), + SettingEntry.Toggle("centeredLayout", ST("battle.IS_CENTERED_LAYOUT", "屏幕中央的战斗界面"), ST("battle.IS_CENTERED_LAYOUT_DESCRIPTION", "在屏幕底部的中央显示战斗界面的信息。"), false), + SettingEntry.Toggle("showGoldBoxCreator", ST("battle.SHOW_GOLD_BOX_CREATOR", "显示谁投放了金箱子"), ST("battle.SHOW_GOLD_BOX_CREATOR_DESCRIPTION", "在掉落金箱子时显示投放者昵称"), true), + SettingEntry.Toggle("showTankPointer", ST("battle.SHOW_TANK_POINTER", "显示伤害方向指示器"), ST("battle.SHOW_TANK_POINTER_DESCRIPTION", "受到伤害时显示指向伤害来源的指示器"), true), + SettingEntry.Toggle("autoSupply", ST("battle.AUTO_SUPPLY", "战斗中自动激活道具"), ST("battle.AUTO_SUPPLY", "在战斗中自动使用已装备的道具"), false), + SettingEntry.Toggle("showButtonLabels", ST("battle.SHOW_BUTTON_LABELS", "按钮上显示快捷键"), ST("battle.SHOW_BUTTON_LABELS_DESCRIPTION", "显示战斗界面按钮所绑定的快捷键"), true), + SettingEntry.Slider("battleUiScale", ST("battle.BATTLE_UI_SCALE", "战斗画面尺寸"), ST("battle.BATTLE_UI_SCALE_DESCRIPTION", "调整战斗界面的大小"), 100), + SettingEntry.Slider("aimOpacity", ST("battle.AIM_OPACITY", "镜头中心点不透明度"), ST("battle.AIM_OPACITY", "调整镜头中心点的透明度"), 80), SettingEntry.Choice( - "quality", "图形质量", "允许您在精美的图形和快速的性能之间做出最佳选择。", - listOf("最低", "低", "中", "高", "最高", "自定义"), 3, + "aimScale", ST("battle.AIM_SCALE", "镜头中心点大小"), ST("battle.AIM_SCALE", "调整镜头中心点的大小。"), + listOf( + ST("battle.AIM_SCALE_MINIMAL", "最小"), + ST("battle.AIM_SCALE_SMALL", "中等"), + ST("battle.AIM_SCALE_MEDIUM", "巨大"), + ST("battle.AIM_SCALE_LARGE", "特大"), + ST("battle.AIM_SCALE_MAXIMAL", "最大"), + ), + 1, ), - SettingEntry.Toggle("showFps", "显示帧数和延迟", "显示您的帧数和延迟", false), - SettingEntry.Toggle("skybox", "显示天空", "显示地图天空", true), - SettingEntry.Toggle("autoQuality", "自动图形质量", "自动设置图形质量", true), - SettingEntry.Header("阴影与光照"), - SettingEntry.Toggle("dynamicShadows", "动态阴影", "启用动态阴影", true), - SettingEntry.Toggle("dynamicLighting", "动态照明", "启用动态照明", true), - SettingEntry.Toggle("shadows", "阴影", "启用阴影", true), - SettingEntry.Toggle("softShadows", "软阴影", "启用软阴影", true), - SettingEntry.Toggle("softParticles", "软粒子", "启用软粒子", true), - SettingEntry.Toggle("ssao", "环境光遮蔽", "启用环境光遮蔽", true), - SettingEntry.Toggle("fog", "雾效", "启用雾效", true), - SettingEntry.Header("效果与分辨率"), - SettingEntry.Choice("decals", "射弹孔与爆炸痕迹", "更改战斗中地图上显示的射击和爆炸痕迹。", listOf("关闭", "低", "中", "高", "最大"), 2), - SettingEntry.Choice("trees", "树木图形质量", "更改灌木和树木的渲染质量。", listOf("精简", "低", "中", "高"), 2), - SettingEntry.Choice("resolution", "图像分辨率", "在战斗中更改游戏屏幕分辨率,这会明显影响性能。", listOf("最低", "非常低", "低", "最高"), 3), - SettingEntry.Choice("fpsLimit", "帧数限制", "限制游戏的最大帧数。", listOf("30", "60", "120", "最高"), 1), - SettingEntry.Toggle("vsync", "垂直同步", "启用垂直同步", false), - SettingEntry.Toggle("antialiasing", "抗锯齿", "启用抗锯齿", true), - SettingEntry.Toggle("dust", "弹壳与扬尘", "显示移动轨迹下的弹壳与扬尘", true), - SettingEntry.Toggle("anisotropic", "各向异性过滤", "更改远处纹理的图像质量", true), ) 2 -> listOf( - SettingEntry.Header("音量"), - SettingEntry.Slider("masterVolume", "主音量", "允许您更改主音量", 80), - SettingEntry.Slider("musicVolume", "音乐音量", "允许您更改大厅音乐的音量", 60), - SettingEntry.Slider("ambientVolume", "环境音量", "允许您更改游戏中的背景声音", 50), - SettingEntry.Slider("sfxVolume", "音效音量", "允许您更改游戏中的战斗声音(射击、爆炸声)", 90), - SettingEntry.Slider("uiVolume", "界面音量", "允许您更改界面的音量", 70), - SettingEntry.Header("命中提示"), - SettingEntry.Slider("hitmarkPlayer", "受到敌方伤害时的音量", "允许您更改敌人射击您坦克的音量", 60), - SettingEntry.Slider("hitmarkEnemy", "对敌方造成伤害时的音量", "允许您更改您自己射击敌方坦克的音量", 60), - SettingEntry.Toggle("backgroundSound", "背景声音", "启用背景音效", true), - SettingEntry.Toggle("blurMute", "后台自动静音", "标签页失焦时静音战斗", true), + SettingEntry.Choice( + "quality", ST("graphics.QUALITY_BRACKET_TITLE_LABEL", "图形质量"), ST("graphics.QUALITY_BRACKETS_DESCRIPTION", "允许您在精美的图形和快速的性能之间做出最佳选择。"), + listOf( + ST("graphics.QUALITY_BRACKET_MINIMUM", "最低"), + ST("graphics.QUALITY_BRACKET_LOW", "低"), + ST("graphics.QUALITY_BRACKET_MEDIUM", "中"), + ST("graphics.QUALITY_BRACKET_HIGH", "高"), + ST("graphics.QUALITY_BRACKET_MAXIMUM", "最高"), + ST("graphics.QUALITY_BRACKET_OTHER", "自定义"), + ), + 3, + ), + SettingEntry.Toggle("showFps", ST("graphics.SHOW_FPS_AND_PING", "显示帧数和延迟"), ST("graphics.SHOW_FPS_AND_PING_DESCRIPTION", "显示您的帧数和延迟"), false), + SettingEntry.Toggle("skybox", ST("graphics.SHOW_SKYBOX", "显示天空"), ST("graphics.SHOW_SKYBOX_DESCRIPTION", "显示地图天空"), true), + SettingEntry.Toggle("autoQuality", ST("graphics.GRAPHICS_AUTO_QUALITY", "自动图形质量"), ST("graphics.GRAPHICS_AUTO_QUALITY_DESCRIPTION", "自动设置图形质量"), true), + SettingEntry.Toggle("dynamicShadows", ST("graphics.DYNAMIC_SHADOWS", "动态阴影"), ST("graphics.DYNAMIC_SHADOWS_DESCRIPTION", "启用动态阴影"), true), + SettingEntry.Toggle("dynamicLighting", ST("graphics.DYNAMIC_LIGHTING", "动态照明"), ST("graphics.DYNAMIC_LIGHTING_DESCRIPTION", "启用动态照明"), true), + SettingEntry.Toggle("shadows", ST("graphics.SHADOWS", "阴影"), ST("graphics.SHADOWS_DESCRIPTION", "启用阴影"), true), + SettingEntry.Toggle("softShadows", ST("graphics.SOFT_SHADOWS", "软阴影"), ST("graphics.SOFT_SHADOWS_DESCRIPTION", "启用软阴影"), true), + SettingEntry.Toggle("softParticles", ST("graphics.SOFT_PARTICLES", "软粒子"), ST("graphics.SOFT_PARTICLES_DESCRIPTION", "启用软粒子"), true), + SettingEntry.Toggle("ssao", ST("graphics.SSAO", "环境光遮蔽"), ST("graphics.SSAO_DESCRIPTION", "启用环境光遮蔽"), true), + SettingEntry.Toggle("fog", ST("graphics.FOG", "雾效"), ST("graphics.FOG_DESCRIPTION", "启用雾效"), true), + SettingEntry.Choice( + "decals", ST("graphics.DECALS", "射弹孔与爆炸痕迹"), ST("graphics.DECALS_DESCRIPTION", "更改战斗中地图上显示的射击和爆炸痕迹。"), + listOf( + ST("graphics.DECALS_KEY_OFF", "关闭"), + ST("graphics.DECALS_KEY_FEW", "低"), + ST("graphics.DECALS_KEY_MANY", "中"), + ST("graphics.DECALS_KEY_TOO_MANY", "高"), + ST("graphics.DECALS_KEY_MAXIMUM", "最大"), + ), + 2, + ), + SettingEntry.Choice( + "trees", ST("graphics.TREES_QUALITY", "树木图形质量"), ST("graphics.TREES_QUALITY_DESCRIPTION", "更改灌木和树木的渲染质量。"), + listOf( + ST("graphics.TREES_QUALITY_SPRITES", "精简"), + ST("graphics.TREES_QUALITY_LOW", "低"), + ST("graphics.TREES_QUALITY_MEDIUM", "中"), + ST("graphics.TREES_QUALITY_HIGH", "高"), + ), + 2, + ), + SettingEntry.Choice( + "resolution", ST("graphics.GRAPHICS_RESOLUTION", "图像分辨率"), ST("graphics.GRAPHICS_RESOLUTION_DESCRIPTION", "在战斗中更改游戏屏幕分辨率,这会明显影响性能。"), + listOf( + ST("graphics.GRAPHICS_RESOLUTION_MINIMAL", "最低"), + ST("graphics.GRAPHICS_RESOLUTION_VERY_LOW", "非常低"), + ST("graphics.GRAPHICS_RESOLUTION_LOW", "低"), + ST("graphics.GRAPHICS_RESOLUTION_MAXIMAL", "最高"), + ), + 3, + ), + SettingEntry.Choice("fpsLimit", ST("graphics.FPS_LIMIT", "帧数限制"), ST("graphics.FPS_LIMIT", "限制游戏的最大帧数。"), listOf("30", "60", "120", ST("graphics.FPS_LIMIT_MAX", "最高")), 1), + SettingEntry.Toggle("vsync", ST("graphics.VSYNC", "垂直同步"), ST("graphics.VSYNC_DESCRIPTION", "启用垂直同步"), false), + SettingEntry.Toggle("antialiasing", ST("graphics.ANTIALIASING", "抗锯齿"), ST("graphics.ANTIALIASING_DESCRIPTION", "启用抗锯齿"), true), + SettingEntry.Toggle("dust", ST("graphics.DUST", "弹壳与扬尘"), ST("graphics.DUST_DESCRIPTION", "显示移动轨迹下的弹壳与扬尘"), true), + SettingEntry.Toggle("anisotropic", ST("graphics.ANISOTROPIC_FILTERING", "各向异性过滤"), ST("graphics.ANISOTROPIC_FILTERING_DESCRIPTION", "更改远处纹理的图像质量"), true), ) 3 -> listOf( - SettingEntry.Header("输入设备"), - SettingEntry.Choice("inputDevice", "输入设备", "选择用于控制坦克的输入设备。", listOf("鼠标 + 键盘", "键盘", "游戏手柄", "触摸屏"), 0), - SettingEntry.Slider("sensitivity", "镜头灵敏度", "调整鼠标移动时的镜头灵敏度。", 50), - SettingEntry.Toggle("invertShaft", "反转垂直轴方向", "反转狙击模式垂直轴方向", false), - SettingEntry.Toggle("inversedBackward", "倒车时倒转转向", "倒车时反转转向", false), - SettingEntry.Toggle("inversedCamera", "反转镜头控制", "垂直移动瞄准摇杆时反转镜头方向", false), - SettingEntry.Toggle("mouseAcceleration", "系统鼠标加速", "如果鼠标操作时镜头经常突然跳动,请禁用此选项。", false), - SettingEntry.Header("按键绑定"), + SettingEntry.Slider("masterVolume", ST("game.MASTER_VOLUME", "主音量"), ST("game.MASTER_VOLUME_DESCRIPTION", "允许您更改主音量"), 80), + SettingEntry.Slider("musicVolume", ST("game.MUSIC_VOLUME", "音乐音量"), ST("game.MUSIC_VOLUME_DESCRIPTION", "允许您更改大厅音乐的音量"), 60), + SettingEntry.Slider("ambientVolume", ST("game.AMBIENT_VOLUME", "环境音量"), ST("game.AMBIENT_VOLUME_DESCRIPTION", "允许您更改游戏中的背景声音"), 50), + SettingEntry.Slider("sfxVolume", ST("game.SFX_VOLUME", "音效音量"), ST("game.SFX_VOLUME_DESCRIPTION", "允许您更改游戏中的战斗声音(射击、爆炸声)"), 90), + SettingEntry.Slider("uiVolume", ST("game.UI_VOLUME", "界面音量"), ST("game.UI_VOLUME_DESCRIPTION", "允许您更改界面的音量"), 70), + SettingEntry.Slider("hitmarkPlayer", ST("game.HITMARK_PLAYER_VOLUME", "受到敌方伤害时的音量"), ST("game.HITMARK_PLAYER_VOLUME_DESCRIPTION", "允许您更改敌人射击您坦克的音量"), 60), + SettingEntry.Slider("hitmarkEnemy", ST("game.HITMARK_ENEMY_VOLUME", "对敌方造成伤害时的音量"), ST("game.HITMARK_ENEMY_VOLUME_DESCRIPTION", "允许您更改您自己射击敌方坦克的音量"), 60), + ) + 4 -> listOf( + SettingEntry.Choice( + "inputDevice", ST("controls.inputDevice.INPUT_DEVICE", "输入设备"), ST("controls.inputDevice.INPUT_DEVICE", "选择用于控制坦克的输入设备。"), + listOf( + ST("controls.inputDevice.MOUSE_KEYBOARD", "鼠标 + 键盘"), + ST("controls.inputDevice.KEYBOARD", "键盘"), + ST("controls.inputDevice.GAMEPAD", "游戏手柄"), + ST("controls.inputDevice.TOUCHSCREEN", "触摸屏"), + ), + 0, + ), + SettingEntry.Slider("sensitivity", ST("controls.inputDevice.mouse.CAMERA_SENSITIVITY", "镜头灵敏度"), ST("controls.inputDevice.mouse.CAMERA_SENSITIVITY", "调整鼠标移动时的镜头灵敏度。"), 50), + SettingEntry.Toggle("invertShaft", ST("controls.INVERT_SHAFT_VERTICAL_AXIS", "反转垂直轴方向"), ST("controls.INVERT_SHAFT_VERTICAL_AXIS_DESCRIPTION", "反转狙击模式垂直轴方向"), false), + SettingEntry.Toggle("inversedBackward", ST("controls.INVERSED_BACKWARD_MOVING", "倒车时倒转转向"), ST("controls.INVERSED_BACKWARD_MOVING_DESCRIPTION", "倒车时反转转向"), false), + SettingEntry.Toggle("inversedCamera", ST("controls.INVERSED_CAMERA_HEIGHT", "反转镜头控制"), ST("controls.INVERSED_CAMERA_HEIGHT_DESCRIPTION", "垂直移动瞄准摇杆时反转镜头方向"), false), + SettingEntry.Toggle("mouseAcceleration", ST("controls.inputDevice.mouse.ENABLE_ACCELERATION", "系统鼠标加速"), ST("controls.inputDevice.mouse.ENABLE_ACCELERATION_DESCRIPTION", "如果鼠标操作时镜头经常突然跳动,请禁用此选项。"), false), SettingEntry.Keys( listOf( - "坦克前进" to "W", - "坦克后退" to "S", - "坦克左转" to "A", - "坦克右转" to "D", - "炮塔左转" to "←", - "炮塔右转" to "→", - "射击" to "空格", - "使用维修工具" to "1", - "使用护甲提升" to "2", - "使用伤害提升" to "3", - "使用速度提升" to "4", - "布设地雷" to "5", - "投掷手榴弹" to "G", - "终极技能" to "F", - "暂停" to "Esc", - "战绩统计" to "Tab", - "快速进入车库" to "L", + ST("keyboard.MOVE_TANK_FORWARD", "坦克前进") to "W", + ST("keyboard.MOVE_TANK_BACKWARDS", "坦克后退") to "S", + ST("keyboard.TURN_TANK_LEFT", "坦克左转") to "A", + ST("keyboard.TURN_TANK_RIGHT", "坦克右转") to "D", + ST("keyboard.ROTATE_TURRET_LEFT", "炮塔左转") to "←", + ST("keyboard.ROTATE_TURRET_RIGHT", "炮塔右转") to "→", + ST("keyboard.SHOOT", "射击") to "空格", + ST("keyboard.USE_FIRST_AID_SUPPLY", "使用维修工具") to "1", + ST("keyboard.USE_DOUBLE_ARMOR_SUPPLY", "使用护甲提升") to "2", + ST("keyboard.USE_DOUBLE_DAMAGE_SUPPLY", "使用伤害提升") to "3", + ST("keyboard.USE_SPEED_BOOST_SUPPLY", "使用速度提升") to "4", + ST("keyboard.DROP_MINE", "布设地雷") to "5", + ST("keyboard.DROP_GRENADE", "投掷手榴弹") to "G", + ST("keyboard.OVERDRIVES", "终极技能") to "F", + ST("keyboard.PAUSE", "暂停") to "Esc", + ST("keyboard.STATISTICS", "战绩统计") to "Tab", + ST("keyboard.QUICK_GARAGE", "快速进入车库") to "L", ), ), ) - 4 -> listOf( - SettingEntry.Header("账户信息"), - SettingEntry.Info("昵称", UserName.ifBlank { "未登录" }), - SettingEntry.Info("您的位置", "中国"), - SettingEntry.Info("绑定邮箱", "未绑定", "绑定邮箱后可以更安全地找回账号。"), - SettingEntry.Header("账户安全"), + 5 -> listOf( + SettingEntry.Info(ST("account.NEW_UID", "昵称"), UserName.ifBlank { "未登录" }), + SettingEntry.Info(ST("account.YOUR_LOCATION", "您的位置"), "中国"), + SettingEntry.Info(ST("account.EMAIL_INPUT_LABEL", "绑定邮箱"), "未绑定", ST("account.CONNECT_EMAIL_TITLE", "绑定邮箱后可以更安全地找回账号。")), SettingEntry.Action( - "更改密码和电子邮件", - "通过验证当前密码来更新登录邮箱和密码。", - "更改", + ST("account.CHANGE_PASSWORD_AND_EMAIL", "更改密码和电子邮件"), + ST("account.CHANGE_PASSWORD_AND_EMAIL", "通过验证当前密码来更新登录邮箱和密码。"), + ST("account.CHANGE_BUTTON", "更改"), "更改密码和电子邮件将在账户协议接入后开放。", ), SettingEntry.Action( - "绑定邮箱", - "绑定您的邮箱以保护您的账号!", - "连接", + ST("account.CONNECT_EMAIL_TITLE", "绑定邮箱"), + ST("account.CONNECT_EMAIL_TITLE", "绑定您的邮箱以保护您的账号!"), + ST("account.CONNECT_BUTTON", "连接"), "邮箱绑定功能将在账户协议接入后开放。", ), SettingEntry.Action( - "更改昵称", - "首次更改昵称免费。", - "更改昵称", + ST("account.USER_RENAME_TITLE", "更改昵称"), + ST("account.UID_RENAME_FREE_HINT", "首次更改昵称免费。"), + ST("account.USER_RENAME_TITLE", "更改昵称"), "昵称修改功能将在账户协议接入后开放。", ), - SettingEntry.Header("账号管理"), SettingEntry.Action( - "账号删除", - "提交申请后,您将有 30 天时间取消此决定;超过该期限后,账号将无法恢复。", - "删除账号", + ST("account.ACCOUNT_REMOVAL_BUTTON", "删除账号"), + ST("account.ACCOUNT_REMOVAL_DESCRIPTION_SHORT", "提交申请后,您将有 30 天时间取消此决定。"), + ST("account.ACCOUNT_REMOVAL_BUTTON", "删除账号"), "账号删除功能将在账户协议接入后开放。", ), ) - 5 -> listOf( - SettingEntry.Header("界面"), - SettingEntry.Toggle("aimDot", "在屏幕中间显示一个点", "显示一个点状光标,以便于镜头定位", false), - SettingEntry.Toggle("showDamage", "显示伤害", "显示您对敌人造成的伤害生命值", true), - SettingEntry.Toggle("autoSuicide", "自动自毁", "如果您的坦克翻车或者侧翻,将会开启自动自毁。", true), - SettingEntry.Toggle("showStatusName", "状态效果文字提示", "显示状态效果的指示文字", true), - SettingEntry.Toggle("centeredLayout", "屏幕中央的战斗界面", "在屏幕底部的中央显示战斗界面的信息。", false), - SettingEntry.Toggle("showGoldBoxCreator", "显示谁投放了金箱子", "在掉落金箱子时显示投放者昵称", true), - SettingEntry.Toggle("showTankPointer", "显示伤害方向指示器", "受到伤害时显示指向伤害来源的指示器", true), - SettingEntry.Toggle("autoSupply", "战斗中自动激活道具", "在战斗中自动使用已装备的道具", false), - SettingEntry.Toggle("showButtonLabels", "按钮上显示快捷键", "显示战斗界面按钮所绑定的快捷键", true), - SettingEntry.Header("瞄准与缩放"), - SettingEntry.Slider("battleUiScale", "战斗画面尺寸", "调整战斗界面的大小", 100), - SettingEntry.Slider("aimOpacity", "镜头中心点不透明度", "调整镜头中心点的透明度", 80), - SettingEntry.Choice("aimScale", "镜头中心点大小", "调整镜头中心点的大小。", listOf("最小", "中等", "巨大", "特大", "最大"), 1), - ) - 6 -> listOf( - SettingEntry.Header("双重身份验证"), - SettingEntry.Info("状态", "未启用", "双重身份验证可提高您账号的安全性。"), + else -> listOf( + SettingEntry.Info(ST("security.TITLE", "双重身份验证"), "未启用", ST("security.DESCRIPTION", "双重身份验证可提高您账号的安全性。")), SettingEntry.Action( - "启用双重身份验证", - "完成 3 个简单步骤,让您的账号更安全:安装验证程序、扫描二维码、输入安全码。", - "启用", + ST("security.TITLE", "启用双重身份验证"), + ST("security.INTRO", "完成 3 个简单步骤,让您的账号更安全。"), + ST("security.ENABLE_BUTTON", "启用"), "双重身份验证将在账户协议接入后开放。", ), - SettingEntry.Header("隐私"), - SettingEntry.Toggle("hideProfile", "隐藏您的个人资料", "在公共应用编程接口中隐藏您的游戏统计信息和个人档案信息", false), - SettingEntry.Header("会话"), - SettingEntry.Info("登录记录", "—"), - SettingEntry.Info("会话过期时间", "7 天"), + SettingEntry.Toggle("hideProfile", ST("security.HIDE_PROFILE_TITLE", "隐藏您的个人资料"), ST("security.HIDE_PROFILE_DESCRIPTION", "在公共应用编程接口中隐藏您的游戏统计信息和个人档案信息"), false), SettingEntry.Action( - "退出所有设备", - "让所有已登录设备上的会话立即失效。", - "退出", - "会话管理将在账户协议接入后开放。", - ), - ) - else -> listOf( - SettingEntry.Header("客户端"), - SettingEntry.Info("客户端版本", "Kotlin/JS"), - SettingEntry.Info("协议版本", "S3"), - SettingEntry.Info("渲染模式", "WebGL"), - SettingEntry.Header("帮助与政策"), - SettingEntry.Action( - "技术支持", - "请使用另一台设备的摄像头扫描支持门户二维码,或在浏览器中打开支持网站。", - "打开支持网站", - "支持网站将在外部浏览器打开。", - ), - SettingEntry.Action( - "使用许可及服务协议", - "查看游戏的使用许可及服务协议。", - "打开", + ST("policy.TERMS_AND_CONDITIONS", "使用许可及服务协议"), + ST("policy.TERMS_AND_CONDITIONS", "查看游戏的使用许可及服务协议。"), + ST("policy.TERMS_AND_CONDITIONS", "打开"), "政策页面将在外部浏览器打开。", ), SettingEntry.Action( - "隐私政策", - "查看游戏的隐私政策。", - "打开", + ST("policy.PRIVACY_POLICY", "隐私政策"), + ST("policy.PRIVACY_POLICY", "查看游戏的隐私政策。"), + ST("policy.PRIVACY_POLICY", "打开"), "政策页面将在外部浏览器打开。", ), SettingEntry.Action( - "第三方共享个人信息清单", - "查看第三方共享个人信息清单。", - "打开", + ST("policy.SHARED_INFO", "第三方共享个人信息清单"), + ST("policy.SHARED_INFO", "查看第三方共享个人信息清单。"), + ST("policy.SHARED_INFO", "打开"), "政策页面将在外部浏览器打开。", ), ) @@ -2160,8 +2131,8 @@ private val SettingsMenuHighlight: csstype.PropertiesBuilder.() -> Unit = { Style("left", "0") Style("width", "18em") Style("height", "2.5em") - Style("background", "linear-gradient(to right, #2E3235, rgba(46, 50, 53, 0))") - Style("color", "rgb(255, 188, 9)") + Style("background", Palette.MenuGradient) + Style("color", Palette.Yellow) Style("z-index", "1") Style("transition", "all 0.1s ease-in-out") Style("pointer-events", "none") @@ -2172,7 +2143,7 @@ private val SettingsMenuLine: csstype.PropertiesBuilder.() -> Unit = { Style("left", "0") Style("width", "0.313em") Style("height", "2.5em") - Style("background-color", "rgb(255, 188, 9)") + Style("background-color", Palette.Yellow) Style("z-index", "3") Style("transition", "all 0.1s ease-in-out") Style("pointer-events", "none") @@ -2227,7 +2198,7 @@ private val SettingsPanel: csstype.PropertiesBuilder.() -> Unit = { Style("padding", "1.875em") Style("padding-bottom", "0") Style("position", "relative") - Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("background-color", Palette.Panel) Style("box-sizing", "border-box") Style("overflow-x", "hidden") Style("overflow-y", "auto") @@ -2237,7 +2208,7 @@ private val SettingsPanel: csstype.PropertiesBuilder.() -> Unit = { private val SettingsDivider: csstype.PropertiesBuilder.() -> Unit = { Style("flex", "0 0 auto") - Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("background-color", Palette.Panel) Style("width", "49.125em") Style("height", "0.063em") Style("margin-bottom", "1.875em") @@ -2258,6 +2229,7 @@ private val SettingsHeadline: csstype.PropertiesBuilder.() -> Unit = { private val SettingsRow: csstype.PropertiesBuilder.() -> Unit = { Style("display", "flex") Style("flex-direction", "row") + Style("flex-wrap", "wrap") Style("justify-content", "space-between") Style("align-items", "center") Style("width", "100%") @@ -2273,14 +2245,14 @@ private val SettingsLabel: csstype.PropertiesBuilder.() -> Unit = { Style("font-weight", "normal") Style("font-size", "1em") Style("line-height", "normal") - Style("color", "rgba(255, 255, 255, 0.9)") + Style("color", Palette.White90) } private val SettingsDesc: csstype.PropertiesBuilder.() -> Unit = { Style("font-family", "BaseFontRegular, FallbackFontRegular") Style("font-size", "0.85em") Style("line-height", "1.35") - Style("color", "rgba(255, 255, 255, 0.5)") + Style("color", Palette.White50) Style("margin-top", "0.2em") } @@ -2297,7 +2269,7 @@ private val SettingsDropTitle: csstype.PropertiesBuilder.() -> Unit = { Style("font-size", "1em") Style("line-height", "normal") Style("margin", "0 0 1em") - Style("color", "rgba(255, 255, 255, 0.9)") + Style("color", Palette.White90) } private val SettingsSelect: csstype.PropertiesBuilder.() -> Unit = { @@ -2307,9 +2279,9 @@ private val SettingsSelect: csstype.PropertiesBuilder.() -> Unit = { Style("min-width", "4.375em") Style("width", "100%") Style("height", "3.125em") - Style("border", "0.063em solid rgba(255, 255, 255, 0.2)") + Style("border", "0.063em solid " + Palette.PanelBorder) Style("box-sizing", "border-box") - Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("background-color", Palette.Panel) Style("padding-left", "0.875em") Style("padding-right", "0.875em") Style("color", "white") @@ -2346,7 +2318,7 @@ private val SettingsRange: csstype.PropertiesBuilder.() -> Unit = { Style("appearance", "none") Style("width", "100%") Style("height", "0.563em") - Style("background-color", "rgba(255, 255, 255, 0.2)") + Style("background-color", Palette.White20) Style("outline", "none") Style("accent-color", "rgb(255, 188, 9)") } @@ -2367,7 +2339,7 @@ private val SettingsButtons: csstype.PropertiesBuilder.() -> Unit = { private val SettingsButton: csstype.PropertiesBuilder.() -> Unit = { Style("width", "15.625em") Style("height", "3.125em") - Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("background-color", Palette.Panel) Style("border", "0") Style("color", "white") Style("font-family", "BaseFontBold, FallbackFontBold") @@ -2382,6 +2354,13 @@ private val SettingsButtonWide: csstype.PropertiesBuilder.() -> Unit = { Style("width", "18.125em") } +private val SettingsActionButton: csstype.PropertiesBuilder.() -> Unit = { + Apply(SettingsButton) + Style("width", "10em") + Style("min-width", "0") + Style("padding", "0 1em") +} + private val SettingsCheckboxRow: csstype.PropertiesBuilder.() -> Unit = { Style("display", "flex") Style("align-items", "center") @@ -2431,7 +2410,7 @@ private val SettingsKeyRow: csstype.PropertiesBuilder.() -> Unit = { Style("align-items", "center") Style("justify-content", "space-between") Style("padding", "0.35em 0") - Style("border-bottom", "0.063em solid rgba(255, 255, 255, 0.1)") + Style("border-bottom", "0.063em solid " + Palette.White10) } private val SettingsScreen = FC { @@ -2440,6 +2419,7 @@ private val SettingsScreen = FC { var Sliders by useState>(emptyMap()) var Choices by useState>(emptyMap()) val UserName = StateOrNull()?.User?.Uid ?: "" + val Tabs = SettingsTabLabels() val Entries = SettingsEntries(Tab, UserName) fun ResetAll() { @@ -2449,7 +2429,7 @@ private val SettingsScreen = FC { ToastContext.Show("已重置当前设置(离线模式)") } - ScreenShell("设置", SettingsTabs.getOrElse(Tab) { "" }, FullBleed = true) { + ScreenShell("设置", Tabs.getOrElse(Tab) { "" }, FullBleed = true) { div { css(SettingsPage) div { @@ -2468,7 +2448,7 @@ private val SettingsScreen = FC { Style("top", "calc(${Tab * 5}em + 1.25em)") } } - for ((Index, Name) in SettingsTabs.withIndex()) { + for ((Index, Name) in Tabs.withIndex()) { button { css(SettingsMenuItem) onClick = { Tab = Index } @@ -2484,6 +2464,7 @@ private val SettingsScreen = FC { css(SettingsContainer) div { css(SettingsPanel) + div { css(SettingsHeadline); +Tabs.getOrElse(Tab) { "" } } var HasHeader = false for (Entry in Entries) { when (Entry) { @@ -2540,7 +2521,17 @@ private val SettingsScreen = FC { value = Entry.Options.getOrElse(Index) { Entry.Options.firstOrNull() ?: "" } onChange = { E -> val Found = Entry.Options.indexOf(E.target.value) - if (Found >= 0) Choices = Choices + (Entry.Id to Found) + if (Found >= 0) { + Choices = Choices + (Entry.Id to Found) + if (Entry.Id == "language") { + val Code = Translations.Languages.getOrNull(Found)?.first ?: "zh-CN" + Translations.LoadLocale(Code) { + ScreenMount.Render() + HeadMount.Render() + HomeMount.Render() + } + } + } } for (Option in Entry.Options) option { value = Option; +Option } } @@ -2556,7 +2547,7 @@ private val SettingsScreen = FC { if (Entry.Desc.isNotEmpty()) div { css(SettingsDesc); +Entry.Desc } } button { - css(SettingsButton) + css(SettingsActionButton) onClick = { ToastContext.Show(Entry.Toast) } +Entry.Button } @@ -2590,8 +2581,8 @@ private val SettingsScreen = FC { css { Style("min-width", "2.2em") Style("padding", "0.2em 0.45em") - Style("border", "0.063em solid rgba(255, 255, 255, 0.2)") - Style("background-color", "rgba(255, 255, 255, 0.1)") + Style("border", "0.063em solid " + Palette.PanelBorder) + Style("background-color", Palette.Panel) Style("text-align", "center") Style("font", "500 0.8em/1.3 ${Ui.FontMedium}") } diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/Palette.kt b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/Palette.kt new file mode 100644 index 0000000..3667ea0 --- /dev/null +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/Palette.kt @@ -0,0 +1,65 @@ +// Palette.kt —— 原版配色表 +// +// 来源:module_82347 的 C9i(E9i() 单例)构造器。 +// 这些是原版 Kotlin/JS 主题对象的字段值,不是从截图上取的近似色。 +package App.Browser.Ui + +object Palette { + // 基础 + const val Transparent = "transparent" + const val Black = "rgb(0, 0, 0)" + const val Black20 = "rgba(0, 0, 0, 0.2)" + const val Black30 = "rgba(0, 0, 0, 0.3)" + const val Black50 = "rgba(0, 0, 0, 0.5)" + const val White = "rgb(255, 255, 255)" + const val White05 = "rgba(255, 255, 255, 0.05)" + const val White10 = "rgba(255, 255, 255, 0.1)" + const val White15 = "rgba(255, 255, 255, 0.15)" + const val White20 = "rgba(255, 255, 255, 0.2)" + const val White25 = "rgba(255, 255, 255, 0.25)" + const val White30 = "rgba(255, 255, 255, 0.3)" + const val White40 = "rgba(255, 255, 255, 0.4)" + const val White50 = "rgba(255, 255, 255, 0.5)" + const val White60 = "rgba(255, 255, 255, 0.6)" + const val White70 = "rgba(255, 255, 255, 0.7)" + const val White90 = "rgba(255, 255, 255, 0.9)" + const val White95 = "rgba(255, 255, 255, 0.95)" + + // 主色 + const val Yellow = "rgb(255, 188, 9)" + const val Yellow30 = "rgba(255, 188, 9, 0.3)" + const val Yellow20 = "rgba(255, 188, 9, 0.2)" + const val YellowSoft = "rgb(255, 214, 48)" + const val Cyan = "rgb(0, 212, 255)" + const val CyanBright = "rgb(0, 215, 255)" + const val Green = "rgb(118, 255, 51)" + const val Green70 = "rgba(118, 255, 51, 0.7)" + const val Green30 = "rgba(118, 255, 51, 0.3)" + const val BlueLight = "rgb(191, 213, 255)" + const val Blue = "rgb(80, 182, 255)" + const val BlueDark = "rgb(61, 111, 186)" + const val Red = "rgb(255, 124, 124)" + const val RedSoft = "rgb(255, 102, 102)" + const val RedStrong = "rgb(235, 87, 87)" + const val Orange = "rgb(232, 139, 0)" + const val Purple = "rgb(175, 115, 225)" + const val Teal = "rgb(48, 213, 200)" + + // 面板 / 边框 / 文本 + const val Panel = White10 + const val PanelSoft = White05 + const val PanelBorder = White20 + const val Divider = White25 + const val DividerSoft = White15 + const val Text = White + const val TextMuted = White60 + const val TextDim = White50 + const val TextFaint = White40 + + // 原版渐变 + const val PageGradient = "radial-gradient(#203040 0%, #03080D 100%)" + const val PageGradientTop = "linear-gradient(to top right, #4A4A5B 0%, #32363E 100%)" + const val MenuGradient = "linear-gradient(to right, #2E3235, rgba(46, 50, 53, 0))" + const val MenuGradientDown = "linear-gradient(to bottom, #2E3235 0%, #2E3235 0.01%, rgba(46, 50, 53, 0.1) 83.57%, rgba(46, 50, 53, 0) 100%)" + const val TopGradient = "linear-gradient(180deg, rgba(0, 25, 38, 0.98), rgba(0, 16, 24, 0.94))" +} diff --git a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt index 200a9e6..84e88b8 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/UiTheme.kt @@ -8,20 +8,20 @@ import csstype.PropertiesBuilder import react.dom.html.HTMLAttributes internal object Ui { - // 颜色 + // 颜色(全部来自 Palette.kt 的原版 C9i 配色表) const val Bg = "#001926" - const val BgDeep = "#000b12" - const val Panel = "rgba(0, 18, 28, 0.82)" - const val PanelSoft = "rgba(0, 18, 28, 0.55)" - const val Divider = "rgba(255, 255, 255, 0.25)" - const val DividerSoft = "rgba(191, 213, 255, 0.12)" - const val Text = "#ffffff" - const val Muted = "rgba(255, 255, 255, 0.55)" - const val Accent = "#76ff33" - const val Cyan = "#00d4ff" - const val Ruby = "#ff4a6a" - const val Crystal = "#7fd4ff" - const val Danger = "#ff5a5a" + const val BgDeep = "#03080D" + const val Panel = Palette.Panel + const val PanelSoft = Palette.PanelSoft + const val Divider = Palette.Divider + const val DividerSoft = Palette.DividerSoft + const val Text = Palette.Text + const val Muted = Palette.TextMuted + const val Accent = Palette.Green + const val Cyan = Palette.Cyan + const val Ruby = Palette.Red + const val Crystal = Palette.BlueLight + const val Danger = Palette.RedStrong // 字体 const val FontRegular = "BaseFontRegular, \"Segoe UI\", sans-serif" @@ -50,8 +50,8 @@ internal val ScreenRoot: PropertiesBuilder.() -> Unit = { Style("display", "flex") Style("flex-direction", "column") Style("min-height", "0") - Style("background", "radial-gradient(#203040 0%, #03080D 100%)") - Style("background-color", "#03080D") + Style("background", Palette.PageGradient) + Style("background-color", Ui.BgDeep) Style("color", Ui.Text) Style("font-family", Ui.FontRegular) Style("overflow", "hidden") diff --git a/TankiOffline/app/src/jsMain/resources/lobby3.css b/TankiOffline/app/src/jsMain/resources/lobby3.css index 51d787e..dee95c5 100644 --- a/TankiOffline/app/src/jsMain/resources/lobby3.css +++ b/TankiOffline/app/src/jsMain/resources/lobby3.css @@ -241,9 +241,9 @@ body { margin: 0; overflow: hidden; background: #001926; color: #fff; } /* ---------------- 屏幕 / 返回 / 底部信息 ---------------- */ .ms-content { - position: absolute; inset: 0; z-index: 6; - padding: 7em 3em 2.5em; box-sizing: border-box; overflow: auto; - background: linear-gradient(180deg, rgba(0, 12, 20, 0.86), rgba(0, 8, 14, 0.94)); + position: absolute; inset: 6em 0 0 0; z-index: 1; + padding: 0; box-sizing: border-box; overflow: hidden; + background: none; } .hidden { display: none !important; } .ms-back {