pet_data/app/components/pixel/GodSystemOverlay.vue

323 lines
14 KiB
Vue

<template>
<div class="flex flex-col h-full gap-4">
<!-- Header: Deity System Title -->
<div class="text-xl font-bold tracking-widest text-[#2ce8f4] border-b-2 border-[#4a3b5e] pb-2">
[GOD SYSTEM] 神明系統
</div>
<!-- Top Action Buttons Grid -->
<div class="grid grid-cols-2 md:grid-cols-4 gap-2">
<PixelButton
v-for="action in TAB_ACTIONS"
:key="action.id"
@click="activeTab = action.id"
:variant="activeTab === action.id ? 'primary' : 'secondary'"
class="text-xs md:text-sm flex items-center justify-center gap-2"
>
<component :is="action.icon" :size="16" />
{{ action.label }}
</PixelButton>
<PixelButton :variant="activeTab === 'LIST' ? 'primary' : 'secondary'" @click="activeTab = 'LIST'" class="text-xs md:text-sm">
[LIST] 神明列表
</PixelButton>
</div>
<!-- Main Content Area -->
<div class="flex-grow bg-[#150c1f] border border-[#4a3b5e] p-4 relative overflow-hidden">
<!-- Background Decor -->
<div class="absolute inset-0 opacity-20 pointer-events-none flex items-center justify-center">
<div class="w-64 h-64 border-[20px] border-[#2b193f] rounded-full"></div>
</div>
<!-- --- VIEW: PRAY --- -->
<div v-if="activeTab === 'PRAY'" class="flex flex-col items-center justify-center h-full gap-6">
<div class="transform scale-150 mb-4">
<PixelAvatar :deityId="currentDeity" />
</div>
<h2 class="text-2xl text-[#f6b26b] font-bold">{{ activeDeity.title }} {{ activeDeity.name }}</h2>
<div class="w-full max-w-md">
<div class="flex justify-between text-xs text-[#8f80a0] mb-1">
<span>FAVOR (好感度)</span>
<span>{{ activeDeity.favor }}/{{ activeDeity.maxFavor }}</span>
</div>
<div class="h-4 bg-[#2b193f] border border-[#4a3b5e] rounded-full overflow-hidden relative">
<div
class="h-full bg-[#d95763] transition-all duration-500"
:style="{ width: `${(activeDeity.favor / activeDeity.maxFavor) * 100}%` }"
></div>
</div>
</div>
<PixelButton class="w-48 py-4 text-lg animate-pulse" @click="$emit('addFavor', 10)">
🙏 PRAY (祈福)
</PixelButton>
<p class="text-xs text-[#8f80a0]">Increases favor with {{ activeDeity.name }}</p>
</div>
<!-- --- VIEW: JIAOBEI (Free Toss) --- -->
<div v-else-if="activeTab === 'JIAOBEI'" class="flex flex-col items-center justify-center h-full gap-8">
<h3 class="text-[#f6b26b] text-lg uppercase tracking-widest">Moon Block Divination</h3>
<JiaobeiBlocks :result="lastResult" :isTossing="isTossing" />
<div class="mt-8">
<PixelButton @click="handleToss(false)" :disabled="isTossing" class="w-40">
{{ isTossing ? 'TOSSING...' : 'TOSS BLOCKS' }}
</PixelButton>
</div>
</div>
<!-- --- VIEW: LOT (Draw & Verify) --- -->
<div v-else-if="activeTab === 'LOT'" class="flex flex-col items-center justify-center h-full gap-4 text-center w-full">
<!-- Phase: Idle -->
<template v-if="lotPhase === LotPhase.Idle">
<Scroll :size="64" class="text-[#ffe762] mb-4" />
<h3 class="text-xl text-[#e0d8f0] mb-2">Draw a Fortune Lot</h3>
<p class="text-sm text-[#8f80a0] max-w-xs mb-6">
Shake the container to draw a stick, then verify it with 3 consecutive Saint Cups.
</p>
<PixelButton @click="handleDrawLot" class="w-48">
DRAW LOT
</PixelButton>
</template>
<!-- Phase: Drawing (Animation) -->
<div v-else-if="lotPhase === LotPhase.Drawing" class="animate-bounce">
<div class="w-16 h-24 bg-[#4a2e18] border-2 border-[#f6b26b] mx-auto mb-4 relative rounded-sm">
<div class="absolute top-0 left-0 w-full h-full flex items-center justify-center text-[#f6b26b] font-bold">
...
</div>
</div>
<span class="text-[#f6b26b] tracking-widest">SHAKING...</span>
</div>
<!-- Phase: Verify -->
<template v-else-if="lotPhase === LotPhase.PendingVerify || lotPhase === LotPhase.Verifying">
<div class="text-2xl font-bold text-[#e0d8f0] border-2 border-[#f6b26b] px-4 py-2 mb-4 bg-[#2b193f]">
LOT #{{ drawnLotNumber }}
</div>
<p class="text-sm text-[#8f80a0] mb-4">
Verify with 3 Consecutive Saint Cups
</p>
<div class="flex gap-2 mb-6 justify-center">
<div
v-for="i in 3"
:key="i"
class="w-4 h-4 rounded-full border border-[#4a3b5e]"
:class="i <= saintCupCount ? 'bg-[#d95763] shadow-[0_0_10px_#d95763]' : 'bg-[#1b1026]'"
/>
</div>
<JiaobeiBlocks :result="lastResult" :isTossing="isTossing" />
<div class="mt-8">
<PixelButton @click="handleToss(true)" :disabled="isTossing" class="w-40">
VERIFY ({{ saintCupCount }}/3)
</PixelButton>
</div>
</template>
<!-- Phase: Failed -->
<template v-else-if="lotPhase === LotPhase.Failed">
<div class="text-[#d95763] text-4xl mb-4">✖</div>
<h3 class="text-lg text-[#d95763] mb-2">Not a Saint Cup</h3>
<p class="text-sm text-[#8f80a0] mb-6">
The deity indicates this is not the right lot.<br/>Please draw again.
</p>
<PixelButton @click="resetLot" variant="danger">
TRY AGAIN
</PixelButton>
</template>
<!-- Phase: Success - Detailed Result -->
<div v-else-if="lotPhase === LotPhase.Success" class="w-full h-full overflow-y-auto custom-scrollbar p-2">
<PixelFrame class="bg-[#1b1026] border-4 border-[#f6b26b] relative shadow-[0_0_20px_rgba(246,178,107,0.3)]">
<!-- Header -->
<div class="text-center border-b-2 border-[#4a3b5e] pb-3 mb-3 bg-[#231533] p-2">
<div class="text-[#99e550] text-sm md:text-lg font-bold mb-1 flex items-center justify-center gap-2 animate-pulse">
<Sparkles :size="16" />
<span>三聖筊!{{ activeDeity.name }}允准解籤</span>
<Sparkles :size="16" />
</div>
<div class="text-[#f6b26b] text-xl md:text-3xl font-bold tracking-widest mt-2 font-serif">
【{{ LOT_RESULT_DATA.number }}】 {{ LOT_RESULT_DATA.level }}
</div>
</div>
<!-- Poem (Block) -->
<div class="bg-[#2b193f] p-4 text-center mb-4 border-l-4 border-[#f6b26b] mx-2 shadow-inner">
<div v-for="(line, i) in LOT_RESULT_DATA.poem" :key="i" class="text-lg md:text-xl text-[#e0d8f0] tracking-[0.2em] leading-loose font-serif drop-shadow-md">
{{ line }}
</div>
</div>
<!-- Details Grid -->
<div class="flex flex-col gap-4 text-left px-2">
<!-- Meaning -->
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
<span class="text-[#f6b26b] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
【解曰】 Meaning
</span>
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ LOT_RESULT_DATA.meaning }}</p>
</div>
<!-- Interpretation -->
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
<span class="text-[#99e550] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
【解籤】 Interpretation
</span>
<p class="text-[#e0d8f0] text-sm leading-relaxed mt-1">{{ LOT_RESULT_DATA.interpretation }}</p>
</div>
<!-- Story -->
<div class="bg-[#0f0816] p-2 border border-[#4a3b5e]">
<span class="text-[#2ce8f4] font-bold text-sm block mb-1 border-b border-[#4a3b5e] pb-1 w-full">
【典故】 Story: {{ LOT_RESULT_DATA.storyTitle }}
</span>
<div class="text-[#8f80a0] text-xs leading-relaxed mt-1">
{{ LOT_RESULT_DATA.story }}
</div>
</div>
</div>
<div class="mt-6 flex justify-center mb-2">
<PixelButton @click="resetLot" class="w-full md:w-auto px-8 py-3">
收入背包 (KEEP LOT)
</PixelButton>
</div>
</PixelFrame>
</div>
</div>
<!-- --- VIEW: LIST/SWITCH --- -->
<div v-else-if="activeTab === 'LIST' || activeTab === 'VERIFY'" class="flex flex-col gap-4">
<div class="text-xs text-[#8f80a0] uppercase mb-2">▼ [SWITCH] 切換神明</div>
<div class="grid grid-cols-2 gap-4">
<button
v-for="deity in Object.values(deities)"
:key="deity.id"
@click="$emit('switchDeity', deity.id)"
class="border-2 p-3 flex items-center gap-3 transition-all relative"
:class="currentDeity === deity.id ? 'border-[#99e550] bg-[#2b193f]' : 'border-[#4a3b5e] bg-[#0f0816] hover:bg-[#150c1f]'"
>
<div class="w-10 h-10 relative">
<PixelAvatar :deityId="deity.id" />
</div>
<div class="flex flex-col items-start">
<span class="font-bold" :class="currentDeity === deity.id ? 'text-[#99e550]' : 'text-[#e0d8f0]'">
{{ deity.name }}
</span>
<span class="text-[10px] text-[#8f80a0]">{{ deity.title }}</span>
</div>
<div v-if="currentDeity === deity.id" class="absolute top-2 right-2 w-2 h-2 bg-[#99e550] rounded-full shadow-[0_0_5px_#99e550]"></div>
</button>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue';
import { Heart, Sparkles, Scroll, Repeat, CheckCircle2 } from 'lucide-vue-next';
import PixelButton from './PixelButton.vue';
import PixelAvatar from './PixelAvatar.vue';
import PixelFrame from './PixelFrame.vue';
import JiaobeiBlocks from './JiaobeiBlocks.vue';
import { DeityId, JiaobeiResult, LotPhase } from '~/types/pixel';
import type { Deity } from '~/types/pixel';
interface Props {
currentDeity: DeityId;
deities: Record<DeityId, Deity>;
}
const props = defineProps<Props>();
defineEmits(['switchDeity', 'addFavor']);
const activeTab = ref('PRAY');
const isTossing = ref(false);
const lastResult = ref<JiaobeiResult | null>(null);
const lotPhase = ref<LotPhase>(LotPhase.Idle);
const drawnLotNumber = ref<number | null>(null);
const saintCupCount = ref(0);
const TAB_ACTIONS = [
{ id: 'PRAY', label: 'PRAY (祈福)', icon: Sparkles },
{ id: 'LOT', label: 'LOT (求籤)', icon: Scroll },
{ id: 'VERIFY', label: 'VERIFY (驗證)', icon: CheckCircle2 },
{ id: 'JIAOBEI', label: 'JIAOBEI (擲筊)', icon: Repeat },
];
const LOT_RESULT_DATA = {
number: "第八十六籤",
level: "上籤",
poem: [
"春來花發映陽臺",
"萬里舟行進寶來",
"躍過禹門三級浪",
"恰如平地一聲雷"
],
meaning: "此卦上朝見帝之象。凡事太吉大利也。",
interpretation: "朝帝受職。如貧得寶。謀望從心。卦中第一。此籤從心所欲。諸事皆吉。",
storyTitle: "商絡中三元",
story: "三元記。明朝。商絡。浙江人。父早亡。商絡三元及第。喻步步高升也。(三元即三級試。鄉試解元。省試會元。殿試狀元)"
};
const activeDeity = computed(() => props.deities[props.currentDeity]);
const calculateToss = (): JiaobeiResult => {
const rand = Math.random();
if (rand < 0.5) return JiaobeiResult.Saint;
if (rand < 0.75) return JiaobeiResult.Smile;
return JiaobeiResult.Cry;
};
const handleToss = (isLotVerify = false) => {
if (isTossing.value) return;
isTossing.value = true;
lastResult.value = null;
setTimeout(() => {
const result = calculateToss();
isTossing.value = false;
lastResult.value = result;
if (isLotVerify) {
if (result === JiaobeiResult.Saint) {
saintCupCount.value++;
if (saintCupCount.value >= 3) {
lotPhase.value = LotPhase.Success;
}
} else {
lotPhase.value = LotPhase.Failed;
}
}
}, 1500);
};
const handleDrawLot = () => {
lotPhase.value = LotPhase.Drawing;
setTimeout(() => {
drawnLotNumber.value = Math.floor(Math.random() * 60) + 1;
lotPhase.value = LotPhase.PendingVerify;
saintCupCount.value = 0;
lastResult.value = null;
}, 2000);
};
const resetLot = () => {
lotPhase.value = LotPhase.Idle;
drawnLotNumber.value = null;
saintCupCount.value = 0;
lastResult.value = null;
};
</script>