159 lines
3.7 KiB
Vue
159 lines
3.7 KiB
Vue
<script setup lang="ts">
|
|
import { computed, onMounted, ref, watch } from "vue";
|
|
import { RouterLink, RouterView, useRoute } from "vue-router";
|
|
import { useAuthStore } from "./stores/auth";
|
|
import { useStatsStore } from "./stores/stats";
|
|
|
|
const auth = useAuthStore();
|
|
const stats = useStatsStore();
|
|
const route = useRoute();
|
|
const crt = ref(false);
|
|
const LOW_HASHRATE_THS = 10;
|
|
const TOP_HASHRATE_THS = 70;
|
|
const heatLevel = computed(() => {
|
|
const total = stats.snapshot?.pool.combinedHashrateThs ?? 0;
|
|
return Math.max(0, Math.min(1, (total - LOW_HASHRATE_THS) / (TOP_HASHRATE_THS - LOW_HASHRATE_THS)));
|
|
});
|
|
|
|
onMounted(() => {
|
|
const stored = localStorage.getItem("gashboard.crt") === "1";
|
|
crt.value = stored;
|
|
if (stored) document.body.classList.add("crt");
|
|
});
|
|
|
|
function toggleCrt(): void {
|
|
crt.value = !crt.value;
|
|
document.body.classList.toggle("crt", crt.value);
|
|
localStorage.setItem("gashboard.crt", crt.value ? "1" : "0");
|
|
}
|
|
|
|
function shortNpub(n: string): string {
|
|
return n.length > 16 ? `${n.slice(0, 8)}…${n.slice(-4)}` : n;
|
|
}
|
|
|
|
watch(
|
|
heatLevel,
|
|
(level) => {
|
|
document.documentElement.style.setProperty("--heat", level.toFixed(3));
|
|
document.documentElement.style.setProperty("--heat-color", level > 0.75 ? "#ff4f78" : "#ff3df0");
|
|
document.body.classList.toggle("hot-hash", level > 0.75);
|
|
},
|
|
{ immediate: true },
|
|
);
|
|
</script>
|
|
|
|
<template>
|
|
<header class="topbar">
|
|
<div class="brand glow-magenta flicker">
|
|
⌬ GASHBOARD
|
|
<span class="muted">// solo lottery dashboard</span>
|
|
</div>
|
|
<nav v-if="auth.isLoggedIn" class="nav">
|
|
<RouterLink to="/">status</RouterLink>
|
|
<RouterLink to="/graphs">graphs</RouterLink>
|
|
</nav>
|
|
<div class="actions">
|
|
<button class="thin" @click="toggleCrt">CRT {{ crt ? "on" : "off" }}</button>
|
|
<template v-if="auth.isLoggedIn">
|
|
<span class="muted">{{ auth.npub ? shortNpub(auth.npub) : "" }}</span>
|
|
<button class="thin" @click="auth.logout()">logout</button>
|
|
</template>
|
|
</div>
|
|
</header>
|
|
|
|
<main :class="{ shellLogin: route.name === 'login' }">
|
|
<RouterView />
|
|
</main>
|
|
|
|
<footer class="footbar muted">
|
|
P(block) is a lifestyle, not a number · gashboard v0.1 · {{ new Date().getFullYear() }}
|
|
</footer>
|
|
</template>
|
|
|
|
<style scoped>
|
|
.topbar {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
padding: 12px 24px;
|
|
border-bottom: 1px solid var(--line);
|
|
background: rgba(7, 9, 15, 0.85);
|
|
backdrop-filter: blur(6px);
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 10;
|
|
}
|
|
.brand {
|
|
font-weight: 700;
|
|
letter-spacing: 0.18em;
|
|
font-size: 15px;
|
|
}
|
|
.brand .muted {
|
|
margin-left: 12px;
|
|
letter-spacing: 0.04em;
|
|
font-weight: 400;
|
|
font-size: 11px;
|
|
}
|
|
.nav {
|
|
display: flex;
|
|
gap: 8px;
|
|
align-items: center;
|
|
margin-left: auto;
|
|
margin-right: 16px;
|
|
}
|
|
.nav a {
|
|
color: var(--fg-1);
|
|
border: 1px solid var(--line);
|
|
padding: 4px 10px;
|
|
font-size: 11px;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
.nav a:hover,
|
|
.nav a.router-link-active {
|
|
border-color: var(--neon-cyan);
|
|
color: var(--neon-cyan);
|
|
box-shadow: var(--shadow-cyan);
|
|
}
|
|
.actions {
|
|
display: flex;
|
|
gap: 12px;
|
|
align-items: center;
|
|
}
|
|
button.thin {
|
|
padding: 4px 10px;
|
|
font-size: 11px;
|
|
}
|
|
main {
|
|
padding: 24px;
|
|
max-width: 1400px;
|
|
margin: 0 auto;
|
|
}
|
|
main.shellLogin {
|
|
display: grid;
|
|
place-items: center;
|
|
min-height: calc(100vh - 110px);
|
|
}
|
|
.footbar {
|
|
padding: 14px 24px;
|
|
border-top: 1px solid var(--line);
|
|
font-size: 11px;
|
|
text-align: center;
|
|
letter-spacing: 0.08em;
|
|
}
|
|
@media (max-width: 760px) {
|
|
.topbar {
|
|
align-items: flex-start;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
.nav {
|
|
margin: 0;
|
|
}
|
|
.actions {
|
|
width: 100%;
|
|
justify-content: space-between;
|
|
}
|
|
}
|
|
</style>
|