feat(ui): 接入 Three.js 车库 3D 场景与顶栏 VIP/组队细节

This commit is contained in:
TermiNexus committed 2026-10-25 20:13:26 +08:00
1 parent 947e0f35e3
commit a67e1225a5
14 files changed
+55862 -12

No files matched your search

@@ -73,6 +73,12 @@ class LobbyView(private val Client: LobbyClient) {
SetHtml(
"ms-top-left",
"""
<div class="premium-block ${if (U.HasPremium) "on" else "off"}">
<i class="premium-icon"></i>
</div>
<div class="group-block">
<i class="group-icon g1"></i><i class="group-icon g2"></i>
</div>
<div class="rank-icon"><img src="${RankIcon(U.Rank)}" alt=""></div>
<div class="user-progress">
<span class="user-name">${LEsc(U.Uid.ifEmpty { "未登录" })}</span>
@@ -208,6 +214,9 @@ class LobbyView(private val Client: LobbyClient) {
""".trimIndent()
private fun Bind() {
LDoc.querySelector(".ms-fullscreen")?.addEventListener("click", {
js("document.documentElement.requestFullscreen && document.documentElement.requestFullscreen()")
})
LDoc.querySelectorAll("[data-go]").forEach { El: dynamic ->
El.addEventListener("click", {
val Target = El.getAttribute("data-go") as String
@@ -0,0 +1,25 @@
// Garage3D.kt —— 车库 3D 桥(实际渲染在 resources/Garage3D.js)
//
// 为什么放在普通 JS 模块里:Three.js 的 ESM 依赖用 index.html 的 import map 解析,
// Kotlin/JS 侧只负责在 React 挂载后调用 window.Garage3D.mount(canvasId)。
package App.Browser.Ui
private val MountImpl: (String) -> Unit = js(
"(function (Cid) {" +
" var N = 0;" +
" var T = setInterval(function () {" +
" if (window.Garage3D) { clearInterval(T); window.Garage3D.mount(Cid); }" +
" else if (++N > 600) { clearInterval(T); }" +
" }, 100);" +
"})"
)
object Garage3D {
fun Mount(CanvasId: String = "garage-3d") {
MountImpl(CanvasId)
}
fun Dispose() {
js("window.Garage3D && window.Garage3D.dispose()")
}
}
@@ -19,6 +19,7 @@ import react.dom.html.HTMLAttributes
import react.dom.html.ReactHTML.aside
import react.dom.html.ReactHTML.b
import react.dom.html.ReactHTML.button
import react.dom.html.ReactHTML.canvas
import react.dom.html.ReactHTML.div
import react.dom.html.ReactHTML.header
import react.dom.html.ReactHTML.i
@@ -112,14 +113,14 @@ private val GarageScreen = FC<Props> {
div {
Css("g2-scene")
div {
Css("g2-scene-art")
+((Selected ?: Turret ?: Hull)?.Name?.take(1) ?: "T")
canvas {
id = "garage-3d"
Css("g2-canvas")
}
div {
Css("g2-scene-caption")
+((Selected ?: Turret ?: Hull)?.Name ?: "3D 坦克预览")
span { +"3D 加载器接入中" }
span { +"官方 3DS 车库场景 · 坦克模型接入中" }
}
button {
Css("g2-drawer-toggle")
@@ -232,9 +233,12 @@ object GarageMount {
if (Root != null && !Force) return
if (Root == null) Root = createRoot(El)
Root.render(GarageScreen.create())
val Start: () -> Unit = { Garage3D.Mount("garage-3d") }
js("(function (Fn) { setTimeout(Fn, 0); })")(Start)
}
fun Unmount() {
Garage3D.Dispose()
try {
Root?.unmount()
} catch (_: Throwable) {
@@ -18,6 +18,8 @@ data class CatalogItem(
val Buyable: Boolean,
val Price: Int,
val Currency: Int,
val Preview: Long,
val Resource3D: Long,
)
object UiCatalog {
@@ -47,6 +49,8 @@ object UiCatalog {
Buyable = Item.buyable,
Price = Live.Prices[Id] ?: 0,
Currency = Live.Currencies[Id] ?: 0,
Preview = Live.Previews[Id] ?: 0L,
Resource3D = Live.Resource3D[Id] ?: 0L,
)
}
Loaded = true
@@ -60,14 +64,21 @@ object UiCatalog {
Waiters.add(OnReady)
if (Loading) return
Loading = true
FetchFixture { Text ->
if (Text != null) Items = Parse(Text)
Loaded = true
Loading = false
val Pending = Waiters
Waiters = ArrayList()
for (F in Pending) F()
// fixture 只有 200KB,同步读取避免回调链在开发服务器/HMR 下偶发不触发。
try {
val Xhr = js("new XMLHttpRequest()")
Xhr.open("GET", "/fixtures/catalog.json", false)
Xhr.send()
if ((Xhr.status as Int) == 200) {
Items = Parse(Xhr.responseText as String)
}
} catch (_: Throwable) {
}
Loaded = true
Loading = false
val Pending = Waiters
Waiters = ArrayList()
for (F in Pending) F()
}
private val FetchFixture: ((String?) -> Unit) -> Unit = { Callback ->
@@ -101,6 +112,8 @@ object UiCatalog {
Buyable = R.buyable as Boolean,
Price = (R.price as Number).toInt(),
Currency = (R.currency as Number).toInt(),
Preview = ((R.preview as? Number)?.toLong()) ?: 0L,
Resource3D = ((R.resource3d as? Number)?.toLong()) ?: 0L,
)
)
}
@@ -55,6 +55,7 @@ import Net.Generated.GarageKitCC
import Net.Generated.GroupedCC
import Net.Generated.ItemCategoryCC
import Net.Generated.ItemDevicesCC
import Net.Generated.Item3DCC
import Net.Generated.ItemModelCC
import Net.Generated.ItemPropertiesCC
import Net.Generated.ItemRarityCC
@@ -78,6 +79,7 @@ object CatalogModelIds {
const val Buyable = 2769268442698022653L
const val TimePeriod = 8032192927455575509L
const val ItemModel = 3438505192580052999L
const val Item3D = 7472019549033825785L
const val ItemRarity = 658797348514407988L
const val DescriptionModel = 3935107155251354407L
const val ItemCategory = 2114179554921095892L
@@ -123,6 +125,8 @@ class ItemCatalog {
/** 物品价格 / 货币(BuyableCC 的两个字段;GarageItemObject 没地方放,单独存)。 */
val Prices: MutableMap<Long, Int> = LinkedHashMap()
val Currencies: MutableMap<Long, Int> = LinkedHashMap()
val Previews: MutableMap<Long, Long> = LinkedHashMap()
val Resource3D: MutableMap<Long, Long> = LinkedHashMap()
/** 被识别并吸收的 Pt 项数。 */
var ConsumedItems: Int = 0
@@ -177,6 +181,12 @@ class ItemCatalog {
Prices[ObjectId] = it.Field3
Currencies[ObjectId] = it.CurrencyId
}
(Models[CatalogModelIds.ItemModel] as? ItemModelCC)?.let {
Previews[ObjectId] = it.Preview
}
(Models[CatalogModelIds.Item3D] as? Item3DCC)?.let { Cc ->
Cc.Resource?.let { Resource3D[ObjectId] = it }
}
Mark(Consumed, Begin, End)
Groups++
Any = true
@@ -245,6 +245,8 @@ object SessionCheck {
B.append(",\"buyable\":").append(if (Item.buyable) "true" else "false")
B.append(",\"price\":").append(Router.Catalog.Prices[Id] ?: 0)
B.append(",\"currency\":").append(Router.Catalog.Currencies[Id] ?: 0)
B.append(",\"preview\":").append(Router.Catalog.Previews[Id] ?: 0L)
B.append(",\"resource3d\":").append(Router.Catalog.Resource3D[Id] ?: 0L)
B.append('}')
}
B.append("]}")
@@ -0,0 +1,168 @@
// Garage3D.js —— 车库 3D(Three.js 加载官方 3DS 场景 + 占位坦克)
// 由 index.html 以 <script type="module"> 加载;import map 把 'three' 指到本地镜像。
import * as THREE from 'three';
import { TDSLoader } from '/vendor/three/TDSLoader.js';
import { OrbitControls } from '/vendor/three/OrbitControls.js';
const MapUrl = 'https://res.3dtank.com/613/2501/252/46/32047724143075/object.3ds';
let Current = null;
function PlaceholderTank() {
const G = new THREE.Group();
const HullMat = new THREE.MeshStandardMaterial({ color: 0x4c5a3a, roughness: 0.65, metalness: 0.35 });
const DarkMat = new THREE.MeshStandardMaterial({ color: 0x1a1d20, roughness: 0.9 });
const Hull = new THREE.Mesh(new THREE.BoxGeometry(2.1, 0.55, 1.25), HullMat);
Hull.position.y = 0.55;
Hull.castShadow = true;
G.add(Hull);
const Turret = new THREE.Mesh(new THREE.CylinderGeometry(0.48, 0.55, 0.34, 24), HullMat);
Turret.position.set(0, 0.98, 0.05);
Turret.castShadow = true;
G.add(Turret);
const Barrel = new THREE.Mesh(new THREE.CylinderGeometry(0.07, 0.07, 1.55, 12), DarkMat);
Barrel.rotation.x = Math.PI / 2;
Barrel.position.set(0, 0.98, 0.9);
G.add(Barrel);
for (const X of [-1.02, 1.02]) {
const Track = new THREE.Mesh(new THREE.BoxGeometry(0.36, 0.52, 1.55), DarkMat);
Track.position.set(X, 0.36, 0);
Track.castShadow = true;
G.add(Track);
}
return G;
}
function Mount(CanvasId) {
if (Current && Current.scene) return;
const canvas = document.getElementById(CanvasId);
if (!canvas) {
if (Mount.Retry === undefined) Mount.Retry = 0;
if (Mount.Retry++ < 100) setTimeout(() => Mount(CanvasId), 100);
return;
}
Mount.Retry = 0;
const renderer = new THREE.WebGLRenderer({ canvas, antialias: true, alpha: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.shadowMap.enabled = true;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(38, 1, 0.1, 100);
camera.position.set(4.2, 2.6, 5.2);
const controls = new OrbitControls(camera, canvas);
controls.target.set(0, 1.0, 0);
controls.enablePan = false;
controls.enableZoom = true;
controls.minDistance = 3;
controls.maxDistance = 12;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.7;
scene.add(new THREE.HemisphereLight(0xbfd5ff, 0x0a141e, 2.2));
const Sun = new THREE.DirectionalLight(0xffffff, 2.4);
Sun.position.set(4, 7, 5);
Sun.castShadow = true;
Sun.shadow.mapSize.set(1024, 1024);
scene.add(Sun);
const Rim = new THREE.DirectionalLight(0x3f9fe0, 1.0);
Rim.position.set(-5, 3, -4);
scene.add(Rim);
const Floor = new THREE.Mesh(
new THREE.CircleGeometry(4.2, 64),
new THREE.MeshStandardMaterial({ color: 0x0d1a24, roughness: 0.9, metalness: 0.1 }),
);
Floor.rotation.x = -Math.PI / 2;
Floor.receiveShadow = true;
scene.add(Floor);
const Platform = new THREE.Mesh(
new THREE.CylinderGeometry(2.1, 2.25, 0.18, 64),
new THREE.MeshStandardMaterial({ color: 0x1b2c3a, roughness: 0.6, metalness: 0.5 }),
);
Platform.position.y = 0.09;
Platform.receiveShadow = true;
Platform.castShadow = true;
scene.add(Platform);
const Tank = PlaceholderTank();
Tank.position.y = 0.18;
scene.add(Tank);
new TDSLoader().load(
MapUrl,
(Obj) => {
Obj.traverse((N) => {
if (N.isMesh) {
N.castShadow = true;
N.receiveShadow = true;
if (N.material) {
N.material = new THREE.MeshStandardMaterial({
color: N.material.color ? N.material.color.getHex() : 0x6f7b86,
roughness: 0.8,
metalness: 0.15,
opacity: N.material.opacity === undefined ? 1 : N.material.opacity,
transparent: !!N.material.transparent,
});
}
}
});
const Box = new THREE.Box3().setFromObject(Obj);
const Size = Box.getSize(new THREE.Vector3());
const Center = Box.getCenter(new THREE.Vector3());
const Scale = 6.5 / Math.max(Size.x, Size.y, Size.z);
Obj.scale.setScalar(Scale);
Obj.position.set(-Center.x * Scale, -Box.min.y * Scale, -Center.z * Scale);
scene.add(Obj);
},
undefined,
() => {},
);
function Resize() {
const Rect = (canvas.parentElement || canvas).getBoundingClientRect();
const W = Math.max(1, Math.floor(Rect.width));
const H = Math.max(1, Math.floor(Rect.height));
if (canvas.width !== W || canvas.height !== H) {
renderer.setSize(W, H, false);
camera.aspect = W / H;
camera.updateProjectionMatrix();
}
}
const Observer = new ResizeObserver(Resize);
Observer.observe(canvas.parentElement || canvas);
Resize();
let Alive = true;
function Frame() {
if (!Alive) return;
Resize();
controls.update();
renderer.render(scene, camera);
requestAnimationFrame(Frame);
}
Frame();
Current = {
scene,
camera,
renderer,
dispose() {
Alive = false;
Observer.disconnect();
controls.dispose();
renderer.dispose();
Current = null;
window.__garage3d = null;
},
};
window.__garage3d = Current;
}
function Dispose() {
if (Current) Current.dispose();
}
window.Garage3D = { mount: Mount, dispose: Dispose };
@@ -1 +1 @@
{"items":[{"id":1931009789024,"name":"史诗幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009789028,"name":"普通幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009789026,"name":"传奇幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009789030,"name":"稀有幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":-2147482397,"name":"","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":-2147482396,"name":"","category":1,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":-2147482006,"name":"","category":2,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":-2147482072,"name":"","category":14,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":-2147482393,"name":"","category":17,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":10007274,"name":"维修工具","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":150,"currency":0},{"id":10007268,"name":"护甲提升","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":94,"buyable":true,"price":50,"currency":0},{"id":920004863750,"name":"金箱子","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":150,"currency":2},{"id":10007271,"name":"伤害提升","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":50,"currency":0},{"id":10007277,"name":"地雷","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":50,"currency":0},{"id":921009756684,"name":"核能","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780088,"name":"耀斑","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780012,"name":"炸弹","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":38,"buyable":true,"price":1250,"currency":0},{"id":1931009780146,"name":"沙皇","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780084,"name":"医护兵","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":true,"price":1250,"currency":0},{"id":1931009780145,"name":"雪球","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780175,"name":"迫击炮","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":true,"price":1250,"currency":0},{"id":1931009780154,"name":"礼炮","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":true,"price":1800,"currency":0},{"id":1931009780221,"name":"蛋","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780087,"name":"磁铁","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780085,"name":"烟雾","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780086,"name":"星辰","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780083,"name":"泡沫","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009780126,"name":"南瓜","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":10007280,"name":"速度提升","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":50,"currency":0},{"id":1931009781093,"name":"精英通行证","category":4,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":920009600251,"name":"解锁星级挑战奖励加成","category":4,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":920006040680,"name":"军团许可证","category":12,"viewCategory":5,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009782002,"name":"活动通行证","category":4,"viewCategory":5,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":1931009801939,"name":"狂乱","category":2,"viewCategory":2,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0},{"id":920009180238,"name":"协同","catLine truncated
{"items":[{"id":1931009789024,"name":"史诗幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":0},{"id":1931009789028,"name":"普通幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":0},{"id":1931009789026,"name":"传奇幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":0},{"id":1931009789030,"name":"稀有幸运礼盒","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":0},{"id":-2147482397,"name":"","category":0,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":1612936741458},{"id":-2147482396,"name":"","category":1,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":1614588292448},{"id":-2147482006,"name":"","category":2,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":0},{"id":-2147482072,"name":"","category":14,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":1529404582920},{"id":-2147482393,"name":"","category":17,"viewCategory":0,"rarity":0,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":0,"resource3d":1691064941947},{"id":10007274,"name":"维修工具","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":150,"currency":0,"preview":59056,"resource3d":0},{"id":10007268,"name":"护甲提升","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":94,"buyable":true,"price":50,"currency":0,"preview":59059,"resource3d":0},{"id":920004863750,"name":"金箱子","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":150,"currency":2,"preview":500363500,"resource3d":0},{"id":10007271,"name":"伤害提升","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":50,"currency":0,"preview":59062,"resource3d":0},{"id":10007277,"name":"地雷","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":true,"price":50,"currency":0,"preview":5002675,"resource3d":0},{"id":921009756684,"name":"核能","category":3,"viewCategory":3,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1632470139230,"resource3d":0},{"id":1931009780088,"name":"耀斑","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1701691678641,"resource3d":0},{"id":1931009780012,"name":"炸弹","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":38,"buyable":true,"price":1250,"currency":0,"preview":1725957820352,"resource3d":0},{"id":1931009780146,"name":"沙皇","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1739272626174,"resource3d":0},{"id":1931009780084,"name":"医护兵","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":true,"price":1250,"currency":0,"preview":1701948585354,"resource3d":0},{"id":1931009780145,"name":"雪球","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1734518281269,"resource3d":0},{"id":1931009780175,"name":"迫击炮","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":true,"price":1250,"currency":0,"preview":1738684872029,"resource3d":0},{"id":1931009780154,"name":"礼炮","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":true,"price":1800,"currency":0,"preview":1734521257801,"resource3d":0},{"id":1931009780221,"name":"蛋","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1725957820352,"resource3d":0},{"id":1931009780087,"name":"磁铁","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1701691678641,"resource3d":0},{"id":1931009780085,"name":"烟雾","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1701691678641,"resource3d":0},{"id":1931009780086,"name":"星辰","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1701691678641,"resource3d":0},{"id":1931009780083,"name":"泡沫","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"currency":0,"preview":1701691678641,"resource3d":0},{"id":1931009780126,"name":"南瓜","category":18,"viewCategory":9,"rarity":5,"premium":false,"count":0,"buyable":false,"price":0,"cuLine truncated
@@ -6,6 +6,10 @@
<title>TankiOffline · 大厅</title>
<link rel="stylesheet" href="entrance.css" />
<link rel="stylesheet" href="lobby.css" />
<script type="importmap">
{ "imports": { "three": "/vendor/three/three.module.js" } }
</script>
<script type="module" src="/Garage3D.js"></script>
</head>
<body>
<div id="app"></div>
@@ -499,3 +499,28 @@ body { margin: 0; overflow: hidden; background: #001926; color: #fff; }
.g2-supply { width: auto; padding: 0 0.5em; }
.g2-supply span { display: none; }
}
/* 顶栏:VIP / 组队图标(对照线上 .ksc-38 / .ksc-40) */
.premium-block {
position: relative; width: 6em; height: 6em;
display: flex; align-items: center; justify-content: center;
background: radial-gradient(100% 100% at 0% 0%, rgba(255, 204, 0, 0.75) 0%, rgba(255, 38, 74, 0.5) 50%, rgba(255, 38, 74, 0) 100%);
}
.premium-block.off { opacity: 0.35; filter: grayscale(0.6); }
.premium-icon {
width: 2em; height: 2em;
background: url("/play/static/images/iconButtonPremium.bc17a592.svg") center / contain no-repeat;
}
.group-block {
position: relative; width: 6em; height: 6em;
display: flex; align-items: center; justify-content: center;
}
.group-icon {
position: absolute; width: 1.5em; height: 1.5em;
background: rgba(191, 212, 255, 0.5);
mask: url("/play/static/images/groupUser.67c9ec61.svg") center / contain no-repeat;
-webkit-mask: url("/play/static/images/groupUser.67c9ec61.svg") center / contain no-repeat;
}
.group-icon.g1 { transform: translateX(-0.5em); }
.group-icon.g2 { transform: translateX(0.5em); }
.premium-block + .group-block + .rank-icon { border-left: 1px solid rgba(255, 255, 255, 0.25); }
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+5
View File
@@ -5622,14 +5622,17 @@
}
},
"packages_imported/kotlin-cssom-core/2025.5.0": {
"name": "kotlin-cssom-core",
"version": "2025.5.0",
"devDependencies": {}
},
"packages_imported/kotlin-csstype/2025.5.0-3.1.3": {
"name": "kotlin-csstype",
"version": "2025.5.0-3.1.3",
"devDependencies": {}
},
"packages_imported/kotlin-emotion/2025.3.26": {
"name": "kotlin-emotion",
"version": "2025.3.26",
"dependencies": {
"@emotion/cache": "^11.14.0",
@@ -5641,6 +5644,7 @@
"devDependencies": {}
},
"packages_imported/kotlin-react-core/2025.5.0-19.1.0": {
"name": "kotlin-react-core",
"version": "2025.5.0-19.1.0",
"dependencies": {
"react": "^19.1.0"
@@ -5648,6 +5652,7 @@
"devDependencies": {}
},
"packages_imported/kotlin-react-dom/2025.5.0-19.1.0": {
"name": "kotlin-react-dom",
"version": "2025.5.0-19.1.0",
"dependencies": {
"react": "^19.1.0",