simpleasm/frontend/src/views/WelcomeView.vue
Fam Zheng e465b1cf71 Initial commit: Simple ASM - ARM assembly learning game
10-level progressive game teaching ARM assembly basics:
registers, arithmetic, bitwise ops, memory, branching, loops.
Vue 3 + FastAPI + SQLite with K8s deployment.
2026-04-07 10:17:15 +01:00

159 lines
5.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="welcome">
<div class="binary-rain" ref="rainEl"></div>
<div class="welcome-card">
<div class="logo">
<span class="logo-icon">&#9889;</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()">开始冒险 &rarr;</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">继续冒险 &rarr;</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>