10-level progressive game teaching ARM assembly basics: registers, arithmetic, bitwise ops, memory, branching, loops. Vue 3 + FastAPI + SQLite with K8s deployment.
159 lines
5.0 KiB
Vue
159 lines
5.0 KiB
Vue
<template>
|
||
<div class="welcome">
|
||
<div class="binary-rain" ref="rainEl"></div>
|
||
<div class="welcome-card">
|
||
<div class="logo">
|
||
<span class="logo-icon">⚡</span>
|
||
<h1>Simple ASM</h1>
|
||
<p class="subtitle">汇编探险家</p>
|
||
</div>
|
||
<p class="intro">准备好探索计算机的内心世界了吗?<br>输入你的名字,开始冒险吧!</p>
|
||
<form @submit.prevent="start" v-if="!store.isLoggedIn">
|
||
<input v-model="name" type="text" placeholder="输入你的名字..." autofocus maxlength="20" class="name-input" />
|
||
<button type="submit" class="start-btn" :disabled="!name.trim()">开始冒险 →</button>
|
||
</form>
|
||
<div v-else class="returning">
|
||
<p>欢迎回来,<strong>{{ store.playerName }}</strong>!</p>
|
||
<p class="stat-line">已获得 <span class="star-count">{{ store.totalStars }}</span> 颗星星</p>
|
||
<router-link to="/levels" class="continue-btn">继续冒险 →</router-link>
|
||
<button class="logout-btn" @click="store.logout()">换个名字</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<script setup>
|
||
import { ref, onMounted } from 'vue'
|
||
import { useRouter } from 'vue-router'
|
||
import { useGameStore } from '../stores/game.js'
|
||
|
||
const router = useRouter()
|
||
const store = useGameStore()
|
||
const name = ref('')
|
||
const rainEl = ref(null)
|
||
|
||
async function start() {
|
||
const n = name.value.trim()
|
||
if (!n) return
|
||
await store.login(n)
|
||
router.push('/levels')
|
||
}
|
||
|
||
onMounted(() => {
|
||
if (!rainEl.value) return
|
||
for (let i = 0; i < 25; i++) {
|
||
const col = document.createElement('div')
|
||
col.className = 'rain-col'
|
||
col.style.left = `${(i / 25) * 100 + Math.random() * 4}%`
|
||
col.style.animationDuration = `${5 + Math.random() * 8}s`
|
||
col.style.animationDelay = `${-Math.random() * 10}s`
|
||
col.style.opacity = String(0.08 + Math.random() * 0.12)
|
||
col.textContent = Array.from({ length: 50 }, () => Math.random() > 0.5 ? '1' : '0').join('\n')
|
||
rainEl.value.appendChild(col)
|
||
}
|
||
})
|
||
</script>
|
||
|
||
<style scoped>
|
||
.welcome {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
min-height: 100vh;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.binary-rain { position: absolute; inset: 0; pointer-events: none; }
|
||
.rain-col {
|
||
position: absolute;
|
||
top: -60%;
|
||
font-family: var(--font-mono);
|
||
font-size: 14px;
|
||
line-height: 1.4;
|
||
color: var(--accent-blue);
|
||
white-space: pre;
|
||
animation: rain linear infinite;
|
||
user-select: none;
|
||
}
|
||
@keyframes rain {
|
||
from { transform: translateY(-40%); }
|
||
to { transform: translateY(110vh); }
|
||
}
|
||
.welcome-card {
|
||
position: relative;
|
||
z-index: 1;
|
||
background: var(--bg-card);
|
||
border: 1px solid var(--border);
|
||
border-radius: var(--radius-lg);
|
||
padding: 48px 40px;
|
||
max-width: 460px;
|
||
width: 92%;
|
||
text-align: center;
|
||
box-shadow: 0 0 80px rgba(59,130,246,0.08);
|
||
}
|
||
.logo { margin-bottom: 20px; }
|
||
.logo-icon { font-size: 48px; display: block; margin-bottom: 4px; }
|
||
.logo h1 {
|
||
font-family: var(--font-mono);
|
||
font-size: 36px;
|
||
font-weight: 700;
|
||
background: linear-gradient(135deg, var(--accent-cyan), var(--accent-blue));
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
.subtitle { font-size: 16px; color: var(--text-secondary); margin-top: 4px; }
|
||
.intro { color: var(--text-secondary); line-height: 1.6; margin-bottom: 28px; font-size: 15px; }
|
||
.name-input {
|
||
width: 100%;
|
||
padding: 14px 18px;
|
||
background: var(--bg-surface);
|
||
border: 2px solid var(--border);
|
||
border-radius: var(--radius);
|
||
color: var(--text-primary);
|
||
font-size: 16px;
|
||
font-family: var(--font-sans);
|
||
outline: none;
|
||
transition: border-color 0.3s;
|
||
margin-bottom: 16px;
|
||
}
|
||
.name-input:focus { border-color: var(--accent-blue); box-shadow: 0 0 0 3px rgba(59,130,246,0.2); }
|
||
.name-input::placeholder { color: var(--text-muted); }
|
||
.start-btn {
|
||
width: 100%;
|
||
padding: 14px;
|
||
background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan));
|
||
color: white;
|
||
font-size: 18px;
|
||
font-weight: 600;
|
||
}
|
||
.start-btn:hover:not(:disabled) { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(59,130,246,0.4); }
|
||
.start-btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
||
.returning { margin-top: 8px; }
|
||
.returning p { color: var(--text-secondary); margin-bottom: 8px; }
|
||
.returning strong { color: var(--accent-cyan); }
|
||
.stat-line { margin-bottom: 20px !important; }
|
||
.star-count { color: var(--accent-yellow); font-weight: 700; font-size: 18px; }
|
||
.continue-btn {
|
||
display: inline-block;
|
||
padding: 14px 32px;
|
||
background: linear-gradient(135deg, var(--accent-blue), var(--accent-cyan));
|
||
border-radius: var(--radius);
|
||
color: white;
|
||
font-weight: 600;
|
||
font-size: 16px;
|
||
transition: all 0.3s;
|
||
margin-bottom: 12px;
|
||
}
|
||
.continue-btn:hover { transform: translateY(-2px); box-shadow: 0 6px 20px rgba(59,130,246,0.4); text-decoration: none; }
|
||
.logout-btn {
|
||
display: block;
|
||
margin: 0 auto;
|
||
background: transparent;
|
||
color: var(--text-muted);
|
||
font-size: 13px;
|
||
padding: 4px 12px;
|
||
}
|
||
.logout-btn:hover { color: var(--text-secondary); }
|
||
</style>
|