126 lines
2.7 KiB
Svelte
126 lines
2.7 KiB
Svelte
<script lang="ts">
|
||
import { onMount } from 'svelte';
|
||
import { mountWorld, type WorldHandle } from '@idle/render';
|
||
import { connectWorld, type ConnectHandlers, type NetStatus } from './net';
|
||
|
||
const STATUS_TEXT: Record<NetStatus, string> = {
|
||
connecting: 'подключение…',
|
||
online: 'онлайн',
|
||
reconnecting: 'переподключение…',
|
||
};
|
||
|
||
let host = $state<HTMLDivElement | undefined>(undefined);
|
||
let status = $state<NetStatus>('connecting');
|
||
|
||
onMount(() => {
|
||
let world: WorldHandle | undefined;
|
||
let disposeNet: (() => void) | undefined;
|
||
|
||
if (host) {
|
||
void mountWorld(host, { background: 0x1d232d }).then((w) => {
|
||
world = w;
|
||
const handlers: ConnectHandlers = {
|
||
onStatus: (s) => {
|
||
status = s;
|
||
},
|
||
onSnapshot: (snap) => world?.applySnapshot(snap.avatars),
|
||
};
|
||
disposeNet = connectWorld(handlers);
|
||
});
|
||
}
|
||
|
||
return () => {
|
||
disposeNet?.();
|
||
world?.destroy();
|
||
};
|
||
});
|
||
</script>
|
||
|
||
<div class="root">
|
||
<header>
|
||
<span class="title">Idle XBOCT</span>
|
||
<span class="badge" data-status={status}>{STATUS_TEXT[status]}</span>
|
||
</header>
|
||
|
||
<div class="main">
|
||
<div class="world" bind:this={host}></div>
|
||
|
||
<aside>
|
||
<section>
|
||
<h2>Инвентарь</h2>
|
||
<p class="muted">свой инвентарь по нику — с M2</p>
|
||
</section>
|
||
<section>
|
||
<h2>Рецепты</h2>
|
||
<p class="muted">дерево рецептов — с M2</p>
|
||
</section>
|
||
<section>
|
||
<h2>Лидерборды</h2>
|
||
<p class="muted">топ зрителей — с M5</p>
|
||
</section>
|
||
<section>
|
||
<h2>Костёр</h2>
|
||
<p class="muted">общий прогресс — с M4</p>
|
||
</section>
|
||
</aside>
|
||
</div>
|
||
</div>
|
||
|
||
<style>
|
||
.root {
|
||
display: flex;
|
||
flex-direction: column;
|
||
height: 100%;
|
||
}
|
||
header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 12px;
|
||
padding: 10px 16px;
|
||
border-bottom: 1px solid #2a3242;
|
||
}
|
||
.title {
|
||
font-weight: 700;
|
||
letter-spacing: 0.04em;
|
||
}
|
||
.badge {
|
||
margin-left: auto;
|
||
font: 12px/1.4 monospace;
|
||
color: #9fb0c3;
|
||
background: #232b38;
|
||
padding: 2px 8px;
|
||
border-radius: 8px;
|
||
}
|
||
.badge[data-status='online'] {
|
||
color: #7ee2a8;
|
||
}
|
||
.main {
|
||
display: flex;
|
||
flex: 1;
|
||
min-height: 0;
|
||
}
|
||
.world {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
aside {
|
||
width: 300px;
|
||
border-left: 1px solid #2a3242;
|
||
padding: 12px 16px;
|
||
overflow: auto;
|
||
}
|
||
section {
|
||
margin-bottom: 18px;
|
||
}
|
||
h2 {
|
||
font-size: 14px;
|
||
margin: 0 0 4px;
|
||
color: #b7c4d4;
|
||
}
|
||
.muted {
|
||
font-size: 12px;
|
||
color: #6d7d90;
|
||
margin: 0;
|
||
}
|
||
</style>
|