diff --git a/3DTank/docs/ShopStyleTable.md b/3DTank/docs/ShopStyleTable.md index 5390667..b94745e 100644 --- a/3DTank/docs/ShopStyleTable.md +++ b/3DTank/docs/ShopStyleTable.md @@ -206,6 +206,36 @@ position:absolute; top:1em; z-index:3; 离线实现:没有 `dsw_1` 分类美术 URL,保留官方 `z1so` 的背景色/尺寸/圆角,图像区域用 `stub.shop.card.art` 虚线占位;标题仍按 `b1sp` 定位。 +#### 两种列布局 + +原版 `MGi.tap` 先渲染 `specialCategories`,再渲染 `generalCategories`: + +| 类型 | 列样式 | 卡片样式 | 高度 | +|---|---|---|---| +| 普通分类(一排两行) | `w1hz`:`flex column; justify-content:space-between` | `commonCard` | `calc(100% / 2 - 1.2em)` | +| 特殊/限时(一排一行) | `f1if`:`flex column; justify-content:flex-start` | `limitedOffersCommon` | `calc(100% - .5em)` | + +两种列都是 `width:22.5em; flex:0 0 22.5em; height:calc(100% - .5em); margin-right:1em`。 + +离线数据没有原版 `esw_1` 特殊分类标记;当前用 `KIT(套件)` 作为特殊分类演示全高卡(代码内有 `STUB` 注释)。 + +#### Hover 细节 + +| 目标 | 原版 | CSS | +|---|---|---| +| 分类卡图像 | `VGi` / `KGi` | `transform:scale(1.05,1.05); transition:all .2s ease-in-out` | +| 分类菜单项(未激活) | `pHi` / `wHi` | `box-shadow:0 0 0 2px rgb(255,255,255); border-radius:.5em; margin-left:1.25em; transition:all .1s ease-in-out` | +| 分类菜单项(激活) | `aHi` / `cHi` | `box-shadow:none` | +| 物品卡 | `TYi` | `box-shadow:0 0 0 2px rgb(255,255,255)` | + +这些都已用无头探针确认生成的 CSS,例如: + +```css +.css-1vz2w25:hover { transform: scale(1.05, 1.05); transition: 0.2s ease-in-out; } +.css-1owq6vw:hover { box-shadow: rgb(255, 255, 255) 0em 0em 0em 2px; } +.css-1vberae:hover { box-shadow: none; } +``` + ## 3. 资源 | 用途 | 资源 | diff --git a/3DTank/docs/UiTranslationStatus.md b/3DTank/docs/UiTranslationStatus.md index 5c91c05..ead9b3e 100644 --- a/3DTank/docs/UiTranslationStatus.md +++ b/3DTank/docs/UiTranslationStatus.md @@ -11,7 +11,7 @@ | 子页面外壳 | `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` | 先进入 `ShopSelectedSectionComponentStyle-commonBlock` 分类卡(22.5em,每列 2 张,横向滚动;官方 `z1so` 背景色/尺寸保留,`dsw_1` 分类美术用 `stub.shop.card.art` 虚线占位),点卡后进入 `NewShopCommonComponentStyle-commonContainer`:左侧 25em 分类菜单 + 右侧 37.25em 物品区(分类标题/描述 + 2 列 17.5em 物品卡)+ 底部 3.125em 报告栏;详见 `ShopStyleTable.md` | +| 商店 | `app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt` | 先进入 `ShopSelectedSectionComponentStyle-commonBlock` 分类卡(22.5em;特殊分类一排一行 `limitedOffersCommon`,普通分类一排两行 `commonCard`;卡内 `z1so` 背景色保留,`dsw_1` 分类美术用 `stub.shop.card.art` 虚线占位;已接图像 hover 放大、菜单项 hover 白边/位移、物品卡 hover 白边),点卡后进入 `NewShopCommonComponentStyle-commonContainer`:左侧 25em 分类菜单 + 右侧 37.25em 物品区(分类标题/描述 + 2 列 17.5em 物品卡)+ 底部 3.125em 报告栏;详见 `ShopStyleTable.md` | | 幸运礼盒 | 同上 | 4 种礼盒美术、钥匙图标/数量、可能奖励稀有度、右侧详情/开启确认 | | 战斗选择 | 同上 | 模式筛选(DM/TDM/CTF/CP/AS/JGR/SGE/RUGBY 图标)+ 战场列表 + 右侧详情/加入/创建入口 | | 任务 | 同上 | 分类侧栏(主要/每日/每周/星级挑战/合约)+ 任务卡(进度、奖励图标、领取/去完成)+ 星级挑战横幅 + 离线示例 | 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 272efe1..8bf5989 100644 --- a/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt +++ b/TankiOffline/app/src/jsMain/kotlin/App/Browser/Ui/LobbyScreens.kt @@ -659,11 +659,36 @@ private fun RarityColor(Rarity: Int): String = when (Rarity) { else -> "rgba(232, 242, 255, 0.78)" } -/** 原版 `commonBlockMenuShop`。 */ // ---------------------------------------------------------------- 商店(NewShopCommonComponentStyle) -/** 原版 `commonBlockMenuShop`。 */ -// ---------------------------------------------------------------- 商店(NewShopCommonComponentStyle) +/** 原版 `wHi`:菜单项 hover。 */ +private fun csstype.PropertiesBuilder.ShopMenuItemHover(Active: Boolean) { + val Style: dynamic = js("({})") + if (Active) { + Style["boxShadow"] = "none" + } else { + Style["boxShadow"] = "0em 0em 0em 2px rgb(255, 255, 255)" + Style["borderRadius"] = "0.5em" + Style["marginLeft"] = "1.25em" + Style["transition"] = "all 0.1s ease-in-out" + } + asDynamic()["&:hover"] = Style +} + +/** 原版 `TYi`:物品卡 hover。 */ +private fun csstype.PropertiesBuilder.ShopItemHover() { + val Style: dynamic = js("({})") + Style["boxShadow"] = "0em 0em 0em 2px rgb(255, 255, 255)" + asDynamic()["&:hover"] = Style +} + +/** 原版 `VGi` / `KGi`:分类卡图像 hover 放大。 */ +private fun csstype.PropertiesBuilder.ShopCardImageHover() { + val Style: dynamic = js("({})") + Style["transform"] = "scale(1.05, 1.05)" + Style["transition"] = "all 0.2s ease-in-out" + asDynamic()["&:hover"] = Style +} /** 原版 `commonBlockMenuShop`。 */ private val ShopMenu: csstype.PropertiesBuilder.() -> Unit = { @@ -707,6 +732,7 @@ private fun ShopMenuItemStyle(Active: Boolean): csstype.PropertiesBuilder.() -> Style("background-color", "rgba(255, 204, 0, 0.25)") Style("border-radius", "0.5rem") } + ShopMenuItemHover(Active) } private fun ShopMenuIconStyle(Active: Boolean, Icon: String): csstype.PropertiesBuilder.() -> Unit = { @@ -822,6 +848,7 @@ private fun ShopItemStyle(Selected: Boolean): csstype.PropertiesBuilder.() -> Un "box-shadow", if (Selected) "0em 0em 0em 1px rgb(255, 204, 0)" else "0em 0em 0em 1px rgba(255, 255, 255, 0.3)", ) + ShopItemHover() } private val ShopItemHeader: csstype.PropertiesBuilder.() -> Unit = { @@ -931,6 +958,18 @@ private val ShopCardsColumn: csstype.PropertiesBuilder.() -> Unit = { Style("flex", "0 0 22.5em") } +/** 原版 `TGi` / `f1if`:一列 1 张全高卡(special / limited)。 */ +private val ShopCardsColumnFull: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("flex-direction", "column") + Style("justify-content", "flex-start") + Style("align-items", "stretch") + Style("height", "calc(100% - 0.5em)") + Style("margin-right", "1em") + Style("width", "22.5em") + Style("flex", "0 0 22.5em") +} + /** 原版 `commonCard`。 */ private val ShopCard: csstype.PropertiesBuilder.() -> Unit = { Style("display", "flex") @@ -947,6 +986,22 @@ private val ShopCard: csstype.PropertiesBuilder.() -> Unit = { Style("background", "transparent") } +/** 原版 `limitedOffersCommon`:全高单卡。 */ +private val ShopCardFull: csstype.PropertiesBuilder.() -> Unit = { + Style("display", "flex") + Style("align-items", "center") + Style("justify-content", "center") + Style("border-radius", "0.625em") + Style("cursor", "pointer") + Style("height", "calc(100% - 0.5em)") + Style("overflow", "hidden") + Style("padding", "0.4em") + Style("position", "relative") + Style("width", "22.5em") + Style("border", "0") + Style("background", "transparent") +} + /** 原版 `z1so`:分类卡背景图。 * STUB: 原版 `dsw_1` 分类美术 URL 未接入,只保留官方背景色与尺寸。 */ private val ShopCardImage: csstype.PropertiesBuilder.() -> Unit = { @@ -957,6 +1012,7 @@ private val ShopCardImage: csstype.PropertiesBuilder.() -> Unit = { Style("position", "relative") Style("overflow", "hidden") Style("z-index", "1") + ShopCardImageHover() } /** STUB: 分类美术占位。 */ @@ -974,6 +1030,28 @@ private val ShopCardStub: csstype.PropertiesBuilder.() -> Unit = { Style("border-radius", "0.625em") } +/** 原版 `$Gi`:分类卡。`Full=true` 使用 `limitedOffersCommon`。 */ +private fun react.ChildrenBuilder.ShopCategoryCard( + C: GarageCategory, + Full: Boolean, + OnPick: () -> Unit, +) { + div { + css(if (Full) ShopCardFull else ShopCard) + Ui("shop.card.${C.Id}") + onClick = { OnPick() } + div { + css(ShopCardImage) + div { + css(ShopCardStub) + Ui("stub.shop.card.art") + +"分类美术" + } + } + span { css(ShopCardTitle); +C.Label } + } +} + /** 原版 `b1sp`:分类卡标题。 */ private val ShopCardTitle: csstype.PropertiesBuilder.() -> Unit = { Style("font-family", "BaseFontMedium, FallbackFontMedium") @@ -1069,6 +1147,9 @@ private val ShopScreen = FC { var Category by useState("all") val Items = UiCatalog.Items.filter { it.Buyable && it.Name.isNotBlank() } val Categories = GarageCategories.filter { C -> Items.any { it.Category == C.Id } } + // STUB: 离线数据没有原版 esw_1 特殊分类标记;用 KIT(套件) 演示 limitedOffersCommon 全高卡。 + val SpecialCategories = Categories.filter { it.Id == 5 } + val GeneralCategories = Categories.filter { it.Id != 5 } val Shown = (if (Category == "all") Items else Items.filter { it.Category.toString() == Category }) .distinctBy { it.Name } val ActiveLabel = Categories.firstOrNull { it.Id.toString() == Category }?.Label ?: "全部" @@ -1085,27 +1166,24 @@ private val ShopScreen = FC { div { css(ShopCardsScroll) Ui("shop.cards.scroll") - for (Pair in Categories.chunked(2)) { + for (C in SpecialCategories) { + div { + css(ShopCardsColumnFull) + Ui("shop.cards.column.full") + ShopCategoryCard(C, true) { + Category = C.Id.toString() + Entered = true + } + } + } + for (Pair in GeneralCategories.chunked(2)) { div { css(ShopCardsColumn) Ui("shop.cards.column") for (C in Pair) { - div { - css(ShopCard) - Ui("shop.card.${C.Id}") - onClick = { - Category = C.Id.toString() - Entered = true - } - div { - css(ShopCardImage) - div { - css(ShopCardStub) - Ui("stub.shop.card.art") - +"分类美术" - } - } - span { css(ShopCardTitle); +C.Label } + ShopCategoryCard(C, false) { + Category = C.Id.toString() + Entered = true } } }