feat(ui): 接入 Three.js 车库 3D 场景与顶栏 VIP/组队细节
This commit is contained in:
1 parent
947e0f35e3
commit
a67e1225a5
14 files changed
+55858
-8
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,15 +64,22 @@ object UiCatalog {
|
||||
Waiters.add(OnReady)
|
||||
if (Loading) return
|
||||
Loading = true
|
||||
FetchFixture { Text ->
|
||||
if (Text != null) Items = Parse(Text)
|
||||
// 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 ->
|
||||
js(
|
||||
@@ -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
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user