Files
infrapuzzle/projects/sasch40/frontend/src/components/TavernView.vue
T
Moritz Graf bf15c2e9cb feat(sasch40): deliver schankraum v6 and zero-occlusion hotspot positions
- Replace schankraum with v6: exactly one Queen Verena with tiara on upper gallery, Lutz in hot tub, Armend at forge, rustic beer taps, no crowns at patron tables, seamless secret oak trapdoor without text
- Rename Papa Markus button to 'Markus (Vater)' and place above his head in stall window to avoid facial/body occlusion
- Relocate character hotspot badges across all tavern scenes (Verena, Lutz, Armend, Amy, Mane, Viktor, Pü) onto railings, table rims, and lower body levels to guarantee zero occlusion of faces or bodies
2026-09-25 09:46:45 +02:00

2432 lines
71 KiB
Vue
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.
<script setup lang="ts">
import { ref } from 'vue'
import { useGameStore } from '../stores/gameStore'
import { CHARACTERS } from '../config/characters'
import { GSTANZL_DUEL } from '../config/dialogues'
import { sound } from '../audio/synthAudio'
const store = useGameStore()
type TavernRoom = 'schankraum' | 'nebenraum' | 'marktplatz' | 'hinterland' | 'hinterzimmer' | 'hinter_den_bergen'
const currentRoom = ref<TavernRoom>('schankraum')
interface ActiveDialog {
characterId: string
name: string
title: string
portrait: string
text: string
actionLabel?: string
actionFn?: () => void
}
const activeDialog = ref<ActiveDialog | null>(null)
const duelActive = ref(false)
const duelStep = ref(0)
const duelWon = ref(false)
const duelIntroOpen = ref(false)
const zulumanMirrorActive = ref(false)
const trapdoorDiscovered = ref(store.unlockedEasterEggs.includes('hinterzimmer'))
let lastTrapTap = 0
function switchRoom(room: TavernRoom) {
sound.playDialogueBlip()
currentRoom.value = room
}
function handleTrapdoorClick() {
if (trapdoorDiscovered.value || store.unlockedEasterEggs.includes('hinterzimmer')) {
sound.playFanfare()
currentRoom.value = 'hinterzimmer'
return
}
const now = Date.now()
if (now - lastTrapTap < 450) {
// Double tap / double click
triggerTrapdoor()
} else {
lastTrapTap = now
sound.playDialogueBlip()
}
}
function triggerTrapdoor() {
sound.playFanfare()
trapdoorDiscovered.value = true
store.unlockEasterEgg('hinterzimmer')
currentRoom.value = 'hinterzimmer'
}
function enterPartyBunker() {
sound.playFanfare()
store.unlockEasterEgg('hinterzimmer')
currentRoom.value = 'hinterzimmer'
}
// Wildschwein Schnick-Schnack-Schnuck Minigame with NAT
type WildBoarChoice = 'keiler' | 'dickicht' | 'lanze'
const wildBoarIntroOpen = ref(false)
const wildBoarDuelActive = ref(false)
const wildBoarPlayerScore = ref(0)
const wildBoarNatScore = ref(0)
const wildBoarRound = ref(1)
const wildBoarResultMsg = ref('')
const wildBoarLastPlayerMove = ref<WildBoarChoice | null>(null)
const wildBoarLastNatMove = ref<WildBoarChoice | null>(null)
const wildBoarGameOver = ref(false)
const wildBoarPlayerWon = ref(false)
function openWildBoarIntro() {
sound.playDialogueBlip()
wildBoarIntroOpen.value = true
}
function startWildBoarDuel() {
sound.playDialogueBlip()
wildBoarIntroOpen.value = false
wildBoarDuelActive.value = true
wildBoarPlayerScore.value = 0
wildBoarNatScore.value = 0
wildBoarRound.value = 1
wildBoarResultMsg.value = 'Wähle deine Taktik für Runde 1!'
wildBoarLastPlayerMove.value = null
wildBoarLastNatMove.value = null
wildBoarGameOver.value = false
wildBoarPlayerWon.value = false
}
function formatBoarMove(m: WildBoarChoice): string {
if (m === 'keiler') return '🐗 Keiler'
if (m === 'dickicht') return '🌿 Dickicht'
return '🏹 Lanze'
}
function playWildBoarMove(playerChoice: WildBoarChoice, event?: Event) {
if (event?.currentTarget) {
(event.currentTarget as HTMLElement).blur()
}
if (wildBoarGameOver.value) return
const choices: WildBoarChoice[] = ['keiler', 'dickicht', 'lanze']
const natChoice = choices[Math.floor(Math.random() * choices.length)]
wildBoarLastPlayerMove.value = playerChoice
wildBoarLastNatMove.value = natChoice
if (playerChoice === natChoice) {
sound.playDialogueBlip()
wildBoarResultMsg.value = 'Unentschieden! Zwei Keiler prallen mit den Köpfen zusammen!'
} else if (
(playerChoice === 'keiler' && natChoice === 'dickicht') ||
(playerChoice === 'dickicht' && natChoice === 'lanze') ||
(playerChoice === 'lanze' && natChoice === 'keiler')
) {
sound.playCoin()
wildBoarPlayerScore.value++
const explanation =
playerChoice === 'keiler'
? 'Dein Keiler bricht durch NATs Dickicht!'
: playerChoice === 'dickicht'
? 'Dein Eichen-Dickicht fängt NATs Lanze ab!'
: 'Deine Lanze stoppt NATs Keiler!'
wildBoarResultMsg.value = `Punkt für Sascha! ${explanation}`
} else {
sound.playGulp()
wildBoarNatScore.value++
const explanation =
natChoice === 'keiler'
? 'NATs Keiler bricht durch dein Dickicht!'
: natChoice === 'dickicht'
? 'NATs Dickicht verheddert deine Lanze!'
: 'NATs Lanze stoppt deinen Keiler!'
wildBoarResultMsg.value = `Punkt für NAT! ${explanation}`
}
wildBoarRound.value++
if (wildBoarPlayerScore.value >= 2) {
wildBoarGameOver.value = true
wildBoarPlayerWon.value = true
sound.playFanfare()
store.addKronkorken(10)
store.addBeer()
store.unlockEasterEgg('wildschwein_meister')
} else if (wildBoarNatScore.value >= 2) {
wildBoarGameOver.value = true
wildBoarPlayerWon.value = false
}
}
function openCharacterDialog(charId: string) {
const char = CHARACTERS[charId]
if (!char) return
sound.playDialogueBlip()
if (charId === 'verena') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Grüß Gott mein liebster König Sascha! Alles Gute zu 40 Jahren Regentschaft! Feier so viel du willst, hab die beste Zeit deines Lebens... aber eines merkst du dir: Ich putze nach der Party fei ned!',
actionLabel: 'Verstanden, Königin! (*Bussi geben*)',
actionFn: () => {
store.addBeer()
activeDialog.value = null
}
}
} else if (charId === 'armend') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Habe die Ehre Sascha! Schau dir diesen Prachtbart an. Ich habe gerade 100 deiner verlorenen Kronkorken auf dem Amboss eingeschmolzen und daraus den stabilsten Party-Subwoofer des Balkans geschmiedet! Hier, nimm diese 10 Meister-Korken!',
actionLabel: 'Vergelt\'s Gott, Armend! (+10 Kronkorken)',
actionFn: () => {
store.addKronkorken(10)
activeDialog.value = null
}
}
} else if (charId === 'lutz') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Glück auf von ganz oben, Euer Majestät! Der Kamin von Schloss Haumdaucher ist blitzblank gekehrt. ZTUL garantiert: Die Nebelmaschinen können auf Anschlag laufen!',
actionLabel: 'Bester Mann, Lutz!',
actionFn: () => {
store.unlockEasterEgg('ztul')
activeDialog.value = null
}
}
} else if (charId === 'amy') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Rave the Kingdom! Schau mal, ich male gerade das königliche Thron-Banner in Neonfarben. Ein bisschen Hopfen, ein bisschen Magie und ganz viel Liebe!',
actionLabel: 'Wunderschön, Amy!',
actionFn: () => {
store.addKronkorken(3)
activeDialog.value = null
}
}
} else if (charId === 'mario') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Hoppla die Waldfee! Schau wie die kreischenden Kettensägen und Hopfenkrüge durch die Luft wirbeln! Keine Sorge Majestät, die Ketten sind frisch geölt! Nimm diese 4 Meister-Korken!',
actionLabel: 'Sauber jongliert, Mario! (+4 Kronkorken)',
actionFn: () => {
store.addKronkorken(4)
activeDialog.value = null
}
}
} else if (charId === 'nat') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Servus Sascha! Schau dir meine Fest-Bühne an! Ich erzähle den Leuten gerade epische Geschichten von meinen Girls und meinen Heldentaten im Haumdaucher Dickicht. Bevor du meine Kronkorken kriegst, forder ich dich zum Hof-Duell heraus: Wildschwein-Schnick-Schnack-Schnuck!',
actionLabel: '🐗 Zum Wildschwein-Duell!',
actionFn: () => {
activeDialog.value = null
openWildBoarIntro()
}
}
} else if (charId === 'mane') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Servus beinand! Ich wandere grad über den Haumdaucher Höhenweg. Wander-Tipp: Wer rastet, der rostet – und wer nicht tanzt, kriegt keinen Schaum auf die Halbe! Hier, 4 Kronkorken für den Gipfelsturm!',
actionLabel: 'Danke Mane! Berg heil! (+4 Kronkorken)',
actionFn: () => {
store.addKronkorken(4)
activeDialog.value = null
}
}
} else if (charId === 'hansi') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Habe die Ehre Sascha! Der Abschied naht... ich breche bald auf nach Münchanien. Aber vorher rocken wir heute Abend auf der Schlossbühne noch das gigantischste Abschiedskonzert der Geschichte! Nimm diese 4 Kronkorken als Proviant!',
actionLabel: 'Auf das Konzert, Hansi! (+4 Kronkorken)',
actionFn: () => {
store.addKronkorken(4)
activeDialog.value = null
}
}
} else if (charId === 'pue') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Majestät! Willkommen im innersten VIP-Party-Bunker des Basses! Die magischen Kristalle vibrieren auf 135 BPM und die Lasershow brennt! Hier sind 5 Kronkorken für den Stroboskop-Zauber!',
actionLabel: 'Zünde den Kristall-Zauber, Pü! (+5 Kronkorken)',
actionFn: () => {
store.addKronkorken(5)
activeDialog.value = null
}
}
} else if (charId === 'flo_maier') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Habe die Ehre Sascha! Die Monitore wummern mit 135 BPM! Ich hab dir eine geheime Bass-Platte aufgelegt – hier sind 5 Meister-Kronkorken für den nächsten Drop!',
actionLabel: 'Dreh die Bässe auf, Flo! (+5 Kronkorken)',
actionFn: () => {
store.addKronkorken(5)
activeDialog.value = null
}
}
} else if (charId === 'viktor') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Servus König Sascha! Im Party Bunker fließt das Helle kälter als die Nacht! Hoch die Krüge auf 40 Jahre Regentschaft!',
actionLabel: 'Prost Viktor! (+3 Kronkorken & Helles)',
actionFn: () => {
store.addKronkorken(3)
store.addBeer()
activeDialog.value = null
}
}
} else if (charId === 'markus') {
activeDialog.value = {
characterId: charId,
name: char.name,
title: char.title,
portrait: char.portrait,
text: 'Servus mein Sohn! Schau di an, 40 Jahre und herrschst über ganz Haumdaucher! Meine taiwanischen Freunde und ich trinken 58% Kinmen-Gaoliang und spielen Liar\'s Dice (Chui Niu / 吹牛)! Traust du dich gegen deinen alten Herrn anzutreten? Wer verliert, kippt einen Kurzen!',
actionLabel: '🎲 Auf geht\'s Papa, Liar\'s Dice spielen!',
actionFn: () => {
activeDialog.value = null
openLiarsDiceIntro()
}
}
}
}
// Liar's Dice (Chui Niu / 吹牛) State & Logic
const liarsDiceIntroOpen = ref(false)
const liarsDiceActive = ref(false)
const liarsDiceGameOver = ref(false)
const liarsDicePlayerWon = ref(false)
const liarsDiceRound = ref(1)
const liarsDicePlayerWins = ref(0)
const liarsDiceMarkusWins = ref(0)
const playerDice = ref<number[]>([1, 2, 3, 4, 5])
const markusDice = ref<number[]>([1, 2, 3, 4, 5])
const markusDiceRevealed = ref(false)
const currentBid = ref<{ count: number; value: number } | null>(null)
const lastBidder = ref<'player' | 'markus' | null>(null)
const liarsDiceMsg = ref('')
const selectedBidCount = ref(2)
const selectedBidValue = ref(2)
const isShaking = ref(false)
const shotAnimation = ref<'sascha' | 'markus' | null>(null)
const isRoundEvaluating = ref(false)
function dieIcon(val: number): string {
const icons = ['', '⚀', '⚁', '⚂', '⚃', '⚄', '⚅']
return icons[val] || '🎲'
}
function openLiarsDiceIntro() {
sound.playDialogueBlip()
liarsDiceIntroOpen.value = true
}
function startLiarsDiceMatch() {
sound.playDialogueBlip()
liarsDiceIntroOpen.value = false
liarsDiceActive.value = true
liarsDiceGameOver.value = false
liarsDicePlayerWon.value = false
liarsDicePlayerWins.value = 0
liarsDiceMarkusWins.value = 0
liarsDiceRound.value = 1
startNewDiceRound()
}
function startNewDiceRound() {
sound.playDiceShake()
isShaking.value = true
markusDiceRevealed.value = false
currentBid.value = null
lastBidder.value = null
shotAnimation.value = null
isRoundEvaluating.value = false
selectedBidCount.value = 2
selectedBidValue.value = 2
// Generate 5 random dice each (1 to 6)
playerDice.value = Array.from({ length: 5 }, () => Math.floor(Math.random() * 6) + 1).sort((a, b) => a - b)
markusDice.value = Array.from({ length: 5 }, () => Math.floor(Math.random() * 6) + 1).sort((a, b) => a - b)
setTimeout(() => {
isShaking.value = false
liarsDiceMsg.value = `Runde ${liarsDiceRound.value}: Schüttle die Becher! Gib dein Gebot ab (z.B. mindestens zwei Dreier).`
}, 400)
}
function canMakeBid(count: number, value: number): boolean {
if (!currentBid.value) return count >= 1 && count <= 10
if (count > currentBid.value.count) return true
if (count === currentBid.value.count && value > currentBid.value.value) return true
return false
}
function makePlayerBid() {
if (isRoundEvaluating.value || isShaking.value) return
if (!canMakeBid(selectedBidCount.value, selectedBidValue.value)) {
alert('Dein Gebot muss höher sein als das vorherige (höhere Anzahl oder bei gleicher Anzahl höhere Augenzahl)!')
return
}
sound.playDialogueBlip()
currentBid.value = { count: selectedBidCount.value, value: selectedBidValue.value }
lastBidder.value = 'player'
liarsDiceMsg.value = `Du sagst an: Mindestens ${currentBid.value.count}x die ${dieIcon(currentBid.value.value)}!`
// Next minimum suggestion for controls
if (selectedBidValue.value < 6) {
selectedBidValue.value++
} else {
selectedBidCount.value++
selectedBidValue.value = 2
}
// Trigger Markus AI after a pause
setTimeout(() => {
markusTurn()
}, 1000)
}
function markusTurn() {
if (isRoundEvaluating.value || !currentBid.value) return
const bid = currentBid.value
// Count how many matching dice Markus has (1s are wild unless bid is on 1)
const markusMatches = markusDice.value.filter(d => d === bid.value || (bid.value !== 1 && d === 1)).length
const expectedPlayerMatches = bid.value === 1 ? 0.83 : 1.67
const totalEstimated = markusMatches + expectedPlayerMatches
// Call bluff if the bid is too high or improbable
const bluffThreshold = bid.count >= 6 ? 0.8 : 1.3
if (bid.count > totalEstimated + bluffThreshold) {
callBluff('markus')
return
}
// Otherwise, Markus raises!
const counts: Record<number, number> = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0 }
markusDice.value.forEach(d => counts[d]++)
let bestVal = 2
let maxC = 0
for (let v = 2; v <= 6; v++) {
const effective = counts[v] + counts[1]
if (effective > maxC) {
maxC = effective
bestVal = v
}
}
let nextCount = bid.count
let nextVal = bid.value + 1
if (nextVal > 6) {
nextCount = bid.count + 1
nextVal = Math.min(6, Math.max(2, bestVal))
}
if (nextCount === bid.count && bestVal > bid.value) {
nextVal = bestVal
}
sound.playDialogueBlip()
currentBid.value = { count: nextCount, value: nextVal }
lastBidder.value = 'markus'
liarsDiceMsg.value = `Papa Markus erhöht: Mindestens ${nextCount}x die ${dieIcon(nextVal)}! Was sagst du, mein Sohn?`
// Update player selectors to be just above Markus' bid
if (nextVal < 6) {
selectedBidCount.value = nextCount
selectedBidValue.value = nextVal + 1
} else {
selectedBidCount.value = nextCount + 1
selectedBidValue.value = 2
}
}
function callBluff(caller: 'player' | 'markus') {
if (isRoundEvaluating.value || !currentBid.value) return
isRoundEvaluating.value = true
sound.playCupSlam()
markusDiceRevealed.value = true
const bid = currentBid.value
const allDice = [...playerDice.value, ...markusDice.value]
const totalMatches = allDice.filter(d => d === bid.value || (bid.value !== 1 && d === 1)).length
const bidWasTrue = totalMatches >= bid.count
setTimeout(() => {
sound.playGaoliangShot()
if (caller === 'player') {
if (bidWasTrue) {
// Player called bluff wrongly -> Player loses round!
liarsDiceMarkusWins.value++
shotAnimation.value = 'sascha'
liarsDiceMsg.value = `Aufgedeckt: Es liegen tatsächlich ${totalMatches}x ${dieIcon(bid.value)}! Das Gebot stimmte. Sascha kippt einen Kurzen Gaoliang! 🍶`
} else {
// Player caught Markus bluffing -> Markus loses round!
liarsDicePlayerWins.value++
shotAnimation.value = 'markus'
liarsDiceMsg.value = `Aufgedeckt: Nur ${totalMatches}x ${dieIcon(bid.value)}! Markus hat geblufft und muss einen Gaoliang kippen! 乾杯!`
}
} else {
// Markus called bluff
if (bidWasTrue) {
// Markus called bluff wrongly -> Markus loses round!
liarsDicePlayerWins.value++
shotAnimation.value = 'markus'
liarsDiceMsg.value = `Aufgedeckt: Es liegen ${totalMatches}x ${dieIcon(bid.value)}! Saschas Gebot stimmte! Markus kippt einen Gaoliang!`
} else {
// Markus caught player bluffing -> Player loses round!
liarsDiceMarkusWins.value++
shotAnimation.value = 'sascha'
liarsDiceMsg.value = `Aufgedeckt: Nur ${totalMatches}x ${dieIcon(bid.value)}! Du hast geblufft, Sascha! Trink einen Gaoliang!`
}
}
// Check Match End (Best of 3)
if (liarsDicePlayerWins.value >= 2) {
liarsDiceGameOver.value = true
liarsDicePlayerWon.value = true
sound.playFanfare()
store.addKronkorken(10)
store.addItem('58% Kinmen Gaoliang')
store.unlockEasterEgg('taiwan_dice_champion')
} else if (liarsDiceMarkusWins.value >= 2) {
liarsDiceGameOver.value = true
liarsDicePlayerWon.value = false
}
}, 600)
}
function proceedNextDiceRound() {
liarsDiceRound.value++
startNewDiceRound()
}
function inspectMirror() {
sound.playFanfare()
zulumanMirrorActive.value = true
store.unlockEasterEgg('zuluman')
}
function openGstanzlIntro() {
sound.playDialogueBlip()
duelIntroOpen.value = true
}
function startGstanzlDuel() {
sound.playDialogueBlip()
duelIntroOpen.value = false
duelActive.value = true
duelStep.value = 0
duelWon.value = false
}
function answerGstanzl(isCorrect: boolean) {
sound.playDialogueBlip()
if (isCorrect) {
if (duelStep.value < GSTANZL_DUEL.length - 1) {
duelStep.value++
} else {
duelWon.value = true
store.setGstanzlWon()
}
} else {
alert('Der Ritter lacht dich aus! Versuch einen schlagfertigeren bayerischen Konter!')
}
}
function proceedToExpress() {
store.setAct('party_express')
}
</script>
<template>
<div class="tavern-container">
<div class="scene-viewport">
<!-- Left Medieval Ornament Border -->
<div class="medieval-side-border border-left" aria-hidden="true">
<div class="ornament-column">
<span class="ornament-symbol">⚜️</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">👑</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">🍻</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">⚔️</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">🛡️</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">🌿</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">⚜️</span>
</div>
</div>
<!-- The 9:16 Scene Stage: whole image is 100% visible, zero cropping, hotspots locked to image -->
<div class="scene-stage">
<!-- 4 Golden Corner Ornaments -->
<div class="corner-ornament corner-tl"></div>
<div class="corner-ornament corner-tr"></div>
<div class="corner-ornament corner-bl"></div>
<div class="corner-ornament corner-br"></div>
<!-- ================= 1. GASTSTUBE (SCHANKRAUM) ================= -->
<template v-if="currentRoom === 'schankraum'">
<img src="/assets/scenes/schankraum_v6.jpg?v=6" alt="Gaststube Zum wummernden Ritter" class="tavern-bg" />
<!-- Verena on the wooden gallery -->
<button class="hotspot hotspot-verena" @click="openCharacterDialog('verena')">
<img :src="CHARACTERS.verena.portrait" class="hotspot-thumb" alt="Königin Verena" />
<span class="hotspot-badge">Verena 👑</span>
</button>
<!-- Door to Nebenraum (Kemenate / Atelier) -->
<button class="hotspot door-hotspot hotspot-door-nebenraum" @click="switchRoom('nebenraum')">
<span class="door-icon">🚪</span>
<span class="hotspot-badge glow-door">Zum Nebenraum ▶</span>
</button>
<!-- Armend at the forge/anvil -->
<button class="hotspot hotspot-armend" @click="openCharacterDialog('armend')">
<img :src="CHARACTERS.armend.portrait" class="hotspot-thumb" alt="Armend" />
<span class="hotspot-badge">Armend 🔨</span>
</button>
<!-- Lutz at the fireplace -->
<button class="hotspot hotspot-lutz" @click="openCharacterDialog('lutz')">
<img :src="CHARACTERS.lutz.portrait" class="hotspot-thumb" alt="Lutz" />
<span class="hotspot-badge">Lutz (ZTUL) 🎩</span>
</button>
<!-- Doorway to Marktplatz -->
<button class="hotspot door-hotspot hotspot-door-marktplatz" @click="switchRoom('marktplatz')">
<span class="door-icon">🎪</span>
<span class="hotspot-badge glow-door">Zum Marktplatz ▶</span>
</button>
<!-- Luke zum Party Bunker (100% invisible until double-clicked) -->
<button
class="hotspot trapdoor-hotspot"
:class="{ 'trap-invisible': !trapdoorDiscovered && !store.unlockedEasterEggs.includes('hinterzimmer') }"
@click="handleTrapdoorClick"
@dblclick="triggerTrapdoor"
:title="trapdoorDiscovered || store.unlockedEasterEggs.includes('hinterzimmer') ? 'Luke zum Party Bunker' : undefined"
>
<template v-if="trapdoorDiscovered || store.unlockedEasterEggs.includes('hinterzimmer')">
<span class="trapdoor-icon">🚪</span>
<span class="trapdoor-badge">Luke zum Party Bunker 🪩</span>
</template>
</button>
</template>
<!-- ================= 2. NEBENRAUM (KEMENATE & ATELIER) ================= -->
<template v-else-if="currentRoom === 'nebenraum'">
<img src="/assets/scenes/nebenraum_v2.jpg?v=2" alt="Königlicher Nebenraum & Atelier" class="tavern-bg" />
<!-- Zuluman Magic Mirror on the timber wall -->
<button class="hotspot hotspot-mirror-nebenraum" @click="inspectMirror">
<span class="mirror-icon">🪞</span>
<span class="hotspot-badge pulse">Zuluman-Spiegel</span>
</button>
<!-- Amy at the easel painting the canvas -->
<button class="hotspot hotspot-amy-nebenraum" @click="openCharacterDialog('amy')">
<img :src="CHARACTERS.amy.portrait" class="hotspot-thumb" alt="Amy" />
<span class="hotspot-badge">Amy 🎨</span>
</button>
<!-- Door back to Gaststube (Schankraum) -->
<button class="hotspot door-hotspot hotspot-door-back-gaststube-nr" @click="switchRoom('schankraum')">
<span class="door-icon">🍺</span>
<span class="hotspot-badge glow-door">◀ Zur Gaststube</span>
</button>
</template>
<!-- ================= 3. MARKTPLATZ ================= -->
<template v-else-if="currentRoom === 'marktplatz'">
<img src="/assets/scenes/marktplatz_v2.jpg?v=2" alt="Marktplatz Haumdaucher" class="tavern-bg" />
<!-- NAT on the festival stage telling stories of his girls and heroic deeds -->
<button class="hotspot hotspot-nat" @click="openCharacterDialog('nat')">
<img :src="CHARACTERS.nat.portrait" class="hotspot-thumb" alt="NAT" />
<span class="hotspot-badge">NAT (Josef) 🐗</span>
</button>
<!-- Gaukler Mario juggling chainsaws & hops on the cobblestones -->
<div class="mario-container">
<button class="hotspot hotspot-mario" @click="openCharacterDialog('mario')">
<div class="chainsaw-circus">
<span class="saw saw-left">🪚</span>
<span class="saw saw-right">🪚</span>
<span class="spark spark-1">⚡</span>
<span class="spark spark-2">✨</span>
</div>
<img :src="CHARACTERS.mario.portrait" class="hotspot-thumb" alt="Mario" />
<span class="hotspot-badge">Mario (Kettensägen!) 🤹</span>
</button>
</div>
<!-- Door back to Gaststube -->
<button class="hotspot door-hotspot hotspot-door-back-gaststube" @click="switchRoom('schankraum')">
<span class="door-icon">🍺</span>
<span class="hotspot-badge glow-door">◀ Zur Gaststube</span>
</button>
<!-- Trail to Hinterland -->
<button class="hotspot door-hotspot hotspot-door-hinterland" @click="switchRoom('hinterland')">
<span class="door-icon">🌲</span>
<span class="hotspot-badge glow-door">Zum Hinterland ▶</span>
</button>
</template>
<!-- ================= 4. HINTERLAND ================= -->
<template v-else-if="currentRoom === 'hinterland'">
<img src="/assets/scenes/hinterland.jpg" alt="Hinterland Haumdaucher Alm" class="tavern-bg" />
<!-- Wanderin Mane (Ramona) hiking with animated stride -->
<button class="hotspot hotspot-mane hiking-anim" @click="openCharacterDialog('mane')">
<span class="hiking-stick">🦯</span>
<img :src="CHARACTERS.mane.portrait" class="hotspot-thumb" alt="Mane" />
<span class="hotspot-badge">Mane (Wanderin) 🥾</span>
</button>
<!-- Hansi walking towards Münchanien -->
<button class="hotspot hotspot-hansi" @click="openCharacterDialog('hansi')">
<img :src="CHARACTERS.hansi.portrait" class="hotspot-thumb" alt="Hansi" />
<span class="hotspot-badge">Hansi (Münchanien) 🧔</span>
</button>
<!-- Trail to Hinter den Bergen (Taiwan & Papa Markus) -->
<button class="hotspot door-hotspot hotspot-door-hinter-den-bergen" @click="switchRoom('hinter_den_bergen')">
<span class="door-icon">🏔️</span>
<span class="hotspot-badge glow-door">Hinter den Bergen ▶</span>
</button>
<!-- Trail back to Marktplatz -->
<button class="hotspot door-hotspot hotspot-door-back-marktplatz" @click="switchRoom('marktplatz')">
<span class="door-icon">🎪</span>
<span class="hotspot-badge glow-door">◀ Zum Marktplatz</span>
</button>
</template>
<!-- ================= 5. HINTER DEN BERGEN (TAIWAN & PAPA MARKUS) ================= -->
<template v-else-if="currentRoom === 'hinter_den_bergen'">
<img src="/assets/scenes/hinter_den_bergen.jpg" alt="Hinter den Bergen mit Papa Markus" class="tavern-bg" />
<!-- Markus and Taiwanese companions table -->
<button class="hotspot hotspot-markus" @click="openCharacterDialog('markus')">
<img :src="CHARACTERS.markus.portrait" class="hotspot-thumb" alt="Markus (Vater)" />
<span class="hotspot-badge">Markus (Vater) 🎲🍶</span>
</button>
<!-- Trail back to Hinterland -->
<button class="hotspot door-hotspot hotspot-door-back-hinterland" @click="switchRoom('hinterland')">
<span class="door-icon">🌲</span>
<span class="hotspot-badge glow-door">◀ Zum Hinterland</span>
</button>
</template>
<!-- ================= 6. HINTERZIMMER (SECRET VIP RAVE) ================= -->
<template v-else-if="currentRoom === 'hinterzimmer'">
<img src="/assets/scenes/hinterzimmer.jpg" alt="Geheimes VIP Hinterzimmer" class="tavern-bg" />
<div class="strobe-overlay"></div>
<!-- Pü with pulsing crystal staff and disco lasers -->
<button class="hotspot hotspot-pue rave-pulse" @click="openCharacterDialog('pue')">
<span class="crystal-laser">💎✨</span>
<img :src="CHARACTERS.pue.portrait" class="hotspot-thumb" alt="Pü" />
<span class="hotspot-badge">Pü (Kristall-Zauber) 💡</span>
</button>
<!-- Flo Maier at mixing deck -->
<button class="hotspot hotspot-flo" @click="openCharacterDialog('flo_maier')">
<img :src="CHARACTERS.flo_maier.portrait" class="hotspot-thumb" alt="Flo Maier" />
<span class="hotspot-badge">DJ Flo Maier 🎧</span>
</button>
<!-- Viktor the vampire night-guard -->
<button class="hotspot hotspot-viktor" @click="openCharacterDialog('viktor')">
<img :src="CHARACTERS.viktor.portrait" class="hotspot-thumb" alt="Viktor" />
<span class="hotspot-badge">Viktor 🧛</span>
</button>
<!-- Door back to Gaststube -->
<button class="hotspot door-hotspot hotspot-door-back-vip" @click="switchRoom('schankraum')">
<span class="door-icon">🚪</span>
<span class="hotspot-badge glow-door">◀ Zurück in den Schankraum</span>
</button>
</template>
</div><!-- /.scene-stage -->
<!-- Right Medieval Ornament Border -->
<div class="medieval-side-border border-right" aria-hidden="true">
<div class="ornament-column">
<span class="ornament-symbol">⚜️</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">👑</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">🍻</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">⚔️</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">🛡️</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">🌿</span>
<span class="ornament-divider">✦</span>
<span class="ornament-symbol">⚜️</span>
</div>
</div>
</div><!-- /.scene-viewport -->
<!-- Bottom Scene Navigation & Duel -->
<div class="scene-bottom-nav">
<!-- Room Title Badge -->
<div class="tavern-bar-title">
{{
currentRoom === 'schankraum' ? '🍻 AKT III: GASTSTUBE ZUM WUMMERNDEN RITTER 🍻' :
currentRoom === 'nebenraum' ? '🎨 AKT III: KÖNIGLICHER NEBENRAUM & ATELIER 🎨' :
currentRoom === 'marktplatz' ? '🎪 AKT III: HAUMDAUCHER MARKTPLATZ 🎪' :
currentRoom === 'hinterland' ? '🌲 AKT III: ALPINES HINTERLAND 🌲' :
currentRoom === 'hinter_den_bergen' ? '🏮 AKT III: HINTER DEN BERGEN (TAIWAN) 🏮' :
'🪩 AKT III: DER GEHEIME PARTY BUNKER 🪩'
}}
</div>
<!-- Quick Room Navigation Tabs -->
<div class="room-nav-tabs">
<button
class="room-tab"
:class="{ active: currentRoom === 'schankraum' }"
@click="switchRoom('schankraum')"
>
🍺 Gaststube
</button>
<button
class="room-tab"
:class="{ active: currentRoom === 'nebenraum' }"
@click="switchRoom('nebenraum')"
>
🎨 Nebenraum
</button>
<button
class="room-tab"
:class="{ active: currentRoom === 'marktplatz' }"
@click="switchRoom('marktplatz')"
>
🎪 Marktplatz
</button>
<button
class="room-tab"
:class="{ active: currentRoom === 'hinterland' }"
@click="switchRoom('hinterland')"
>
🌲 Hinterland
</button>
<button
class="room-tab"
:class="{ active: currentRoom === 'hinter_den_bergen' }"
@click="switchRoom('hinter_den_bergen')"
>
🏮 Hinter den Bergen
</button>
<button
v-if="store.unlockedEasterEggs.includes('hinterzimmer') || currentRoom === 'hinterzimmer'"
class="room-tab vip-tab"
:class="{ active: currentRoom === 'hinterzimmer' }"
@click="switchRoom('hinterzimmer')"
>
🪩 Party Bunker
</button>
</div>
<!-- Action Row (Duel & Next Level) -->
<div class="tavern-nav-buttons">
<button class="duel-trigger" @click="openGstanzlIntro">
⚔️ Gstanzl-Fechten
</button>
<button class="next-level-btn" @click="proceedToExpress">
🎸 Zu Akt IV: Abschiedskonzert!
</button>
</div>
</div>
<!-- Monkey Island Dialog Modal -->
<div v-if="activeDialog" class="dialog-overlay" @click="activeDialog = null">
<div class="dialog-box" @click.stop>
<div class="dialog-header">
<img :src="activeDialog.portrait" class="speaker-avatar" alt="Avatar" />
<div class="speaker-info">
<h3 class="speaker-name">{{ activeDialog.name }}</h3>
<span class="speaker-title">{{ activeDialog.title }}</span>
</div>
</div>
<p class="dialog-text">„{{ activeDialog.text }}“</p>
<div class="dialog-actions">
<button class="action-btn" @click="activeDialog.actionFn ? activeDialog.actionFn() : (activeDialog = null)">
{{ activeDialog.actionLabel || 'Antworten' }}
</button>
<button class="close-btn" @click="activeDialog = null">Schließen</button>
</div>
</div>
</div>
<!-- Zuluman Magic Mirror Easter Egg -->
<div v-if="zulumanMirrorActive" class="dialog-overlay" @click="zulumanMirrorActive = false">
<div class="mirror-box" @click.stop>
<h2 class="mirror-title">🌴 DER ZAUBERSPIEGEL DER VERGANGENHEIT 🌴</h2>
<div class="mirror-frame">
<p class="mirror-lore">
Du blickst tief in das uralte Glas...<br>
Das Schloss verschwimmt... Palmen rauschen...<br>
<strong>VORHANG AUF FÜR BUSCHKÖNIG ZULUMAN!</strong>
</p>
<div class="zuluman-badge">👑 ZULUMAN: LEGENDARY RESORT KING UNLOCKED! 🏖️</div>
<p class="mirror-bonus">+5 Kronkorken & 500 Highscore-Punkte!</p>
</div>
<button class="action-btn" @click="zulumanMirrorActive = false">Zurück in die Gegenwart</button>
</div>
</div>
<!-- Gstanzl Intro Modal -->
<div v-if="duelIntroOpen" class="dialog-overlay" @click="duelIntroOpen = false">
<div class="dialog-box intro-modal-box" @click.stop>
<div class="intro-modal-header">
<span class="intro-tag">MINISPIEL INTRO</span>
<button class="duel-close-btn" @click="duelIntroOpen = false" aria-label="Schließen">✕</button>
</div>
<h2 class="intro-modal-title">⚔️ BAYERISCHES GSTANZL-FECHTEN</h2>
<p class="intro-modal-story">
Ein frecher Raubritter zweifelt an Saschas Regentschaft und lästert über 135-BPM-Techno! Zeige ihm, dass die bayerische Zunge schärfer sticht als jedes Schwert!
</p>
<div class="intro-modal-rules">
<div class="rule-line">🎶 <strong>3 Runden:</strong> Wähle den treffendsten bayerischen Reim-Konter.</div>
<div class="rule-line">🏆 <strong>Belohnung:</strong> +15 Kronkorken & 1.500 Punkte für den Highscore!</div>
</div>
<button class="action-btn start-duel-action-btn" @click="startGstanzlDuel">
🎤 Auf geht's zum Gstanzl!
</button>
</div>
</div>
<!-- Wildschwein Duel Intro Modal -->
<div v-if="wildBoarIntroOpen" class="dialog-overlay" @click="wildBoarIntroOpen = false">
<div class="dialog-box intro-modal-box" @click.stop>
<div class="intro-modal-header">
<span class="intro-tag">MINISPIEL INTRO</span>
<button class="duel-close-btn" @click="wildBoarIntroOpen = false" aria-label="Schließen">✕</button>
</div>
<h2 class="intro-modal-title">🐗 WILDSCHWEIN-SCHNICK-SCHNACK-SCHNUCK</h2>
<p class="intro-modal-story">
Hof-Chronist NAT fordert König Sascha zum traditionellen Haumdaucher Dickicht-Duell heraus! Wer beherrscht die Taktik des Waldes?
</p>
<div class="intro-modal-rules">
<div class="rule-line">🐗 <strong>Keiler</strong> bricht durch 🌿 <strong>Dickicht</strong></div>
<div class="rule-line">🌿 <strong>Dickicht</strong> fängt 🏹 <strong>Lanze</strong> ab</div>
<div class="rule-line">🏹 <strong>Lanze</strong> erlegt 🐗 <strong>Keiler</strong></div>
<div class="rule-line">👑 <strong>Format:</strong> Best of 3 (wer zuerst 2 Siege holt!).</div>
</div>
<button class="action-btn start-duel-action-btn" @click="startWildBoarDuel">
⚔️ Duell starten!
</button>
</div>
</div>
<!-- Gstanzl Duel Modal -->
<div v-if="duelActive" class="dialog-overlay">
<div class="dialog-box duel-box">
<div class="duel-header">
<div class="duel-title-group">
<h3>⚔️ BAYERISCHES BELEIDIGUNGS-GSTANZL</h3>
<span class="duel-score">Runde {{ duelStep + 1 }} von 3</span>
</div>
<button class="duel-close-btn" @click="duelActive = false" aria-label="Minispiel verlassen">✕</button>
</div>
<div v-if="!duelWon">
<div class="insult-bubble">
<span class="speaker">Kater-Ritter:</span>
<p>„{{ GSTANZL_DUEL[duelStep].insult }}“</p>
</div>
<div class="choices-list">
<button
v-for="(ans, idx) in GSTANZL_DUEL[duelStep].answers"
:key="idx"
class="choice-btn"
@click="answerGstanzl(ans.correct)"
>
„{{ ans.text }}“
</button>
</div>
</div>
<div v-else class="duel-victory">
<h2 class="victory-title">👑 SIEG FÜR KÖNIG SASCHA!</h2>
<p>Der Kater-Ritter sinkt beschämt zu Boden. Deine Reime haben den Saal zum Beben gebracht!</p>
<p class="reward-text">+15 Kronkorken & 1.500 Highscore-Punkte!</p>
<button class="action-btn" @click="duelActive = false">Herrlich!</button>
</div>
</div>
</div>
<!-- Wildschwein Schnick-Schnack-Schnuck Minigame Modal -->
<div v-if="wildBoarDuelActive" class="dialog-overlay">
<div class="dialog-box boar-duel-box">
<div class="boar-header">
<div class="boar-title-group">
<h3>🐗 WILDSCHWEIN-SCHNICK-SCHNACK-SCHNUCK</h3>
<span class="boar-score">Stand: Sascha {{ wildBoarPlayerScore }} : {{ wildBoarNatScore }} NAT (Best of 3)</span>
</div>
<button class="duel-close-btn" @click="wildBoarDuelActive = false" aria-label="Duell verlassen">✕</button>
</div>
<!-- Rules / Explanations -->
<div class="boar-rules-banner">
🐗 Keiler bricht 🌿 Dickicht | 🌿 Dickicht fängt 🏹 Lanze | 🏹 Lanze erlegt 🐗 Keiler
</div>
<div class="boar-arena">
<div class="boar-status-msg">
{{ wildBoarResultMsg }}
</div>
<div v-if="wildBoarLastPlayerMove && wildBoarLastNatMove" class="boar-clash">
<div class="clash-side">
<span class="clash-label">Sascha:</span>
<span class="clash-move">{{ formatBoarMove(wildBoarLastPlayerMove) }}</span>
</div>
<span class="clash-vs">VS</span>
<div class="clash-side">
<span class="clash-label">NAT:</span>
<span class="clash-move">{{ formatBoarMove(wildBoarLastNatMove) }}</span>
</div>
</div>
</div>
<!-- Move Choices -->
<div v-if="!wildBoarGameOver" class="boar-choices">
<button class="boar-choice-btn" @click="playWildBoarMove('keiler', $event)">
<span class="choice-icon">🐗</span>
<span class="choice-name">Keiler</span>
<span class="choice-sub">Bricht Dickicht</span>
</button>
<button class="boar-choice-btn" @click="playWildBoarMove('dickicht', $event)">
<span class="choice-icon">🌿</span>
<span class="choice-name">Dickicht</span>
<span class="choice-sub">Fängt Lanze</span>
</button>
<button class="boar-choice-btn" @click="playWildBoarMove('lanze', $event)">
<span class="choice-icon">🏹</span>
<span class="choice-name">Lanze</span>
<span class="choice-sub">Erlegt Keiler</span>
</button>
</div>
<!-- Game Over Screen -->
<div v-else class="boar-game-over">
<template v-if="wildBoarPlayerWon">
<h2 class="victory-title">👑 SASCHA IST WILDSCHWEIN-MEISTER!</h2>
<p class="boar-victory-desc">NAT verneigt sich ehrfürchtig! „Du kennst das Dickicht besser als jeder Keiler!“</p>
<p class="reward-text">+10 Kronkorken, 1 Kaltes Helles & Titel freigeschaltet!</p>
<button class="action-btn" @click="wildBoarDuelActive = false">Herrlich! Prost NAT!</button>
</template>
<template v-else>
<h2 class="defeat-title">🐗 NAT GEWINNT DAS DUELL!</h2>
<p class="boar-defeat-desc">NAT lacht schallend: „Das Haumdaucher Dickicht verlangt höchste Meisterschaft, Sascha!“</p>
<div class="boar-retry-actions">
<button class="action-btn retry-btn" @click="startWildBoarDuel">Revanche fordern!</button>
<button class="close-btn" @click="wildBoarDuelActive = false">Vielleicht später</button>
</div>
</template>
</div>
</div>
</div>
<!-- Liar's Dice Intro Modal -->
<div v-if="liarsDiceIntroOpen" class="dialog-overlay" @click="liarsDiceIntroOpen = false">
<div class="dialog-box intro-modal-box" @click.stop>
<div class="intro-modal-header">
<span class="intro-tag">MINISPIEL INTRO</span>
<button class="duel-close-btn" @click="liarsDiceIntroOpen = false" aria-label="Schließen">✕</button>
</div>
<h2 class="intro-modal-title">🎲 LIAR'S DICE (CHUI NIU / 吹牛)</h2>
<p class="intro-modal-story">
Papa Markus fordert dich zu einer Partie Liar's Dice heraus, wie man es in den Gassen von Taipeh und Kinmen spielt! Wer blufft besser und wer muss den 58% Gaoliang-Schnaps kippen?
</p>
<div class="intro-modal-rules">
<div class="rule-line">🎲 <strong>5 Würfel pro Spieler:</strong> Jeder sieht nur seine eigenen Würfel!</div>
<div class="rule-line">✨ <strong>1er sind Joker:</strong> Zählen für alle Augenzahlen (außer wenn auf 1er geboten wird).</div>
<div class="rule-line">🗣️ <strong>Gebote steigern:</strong> Schätze die Gesamtzahl aller Würfel (deine + Markus). Nächstes Gebot muss höhere Anzahl oder bei gleicher Anzahl höhere Augenzahl sein.</div>
<div class="rule-line">🍶 <strong>Bluff anzweifeln:</strong> Glaubst du Markus lügt? Sag „Bluff!“ – wer unrecht hat, kippt einen Schnaps!</div>
<div class="rule-line">🏆 <strong>Modus:</strong> Best of 3! Belohnung: +10 Kronkorken & 58% Kinmen Gaoliang!</div>
</div>
<button class="action-btn start-duel-action-btn" @click="startLiarsDiceMatch">
🎲 Würfelbecher schütteln!
</button>
</div>
</div>
<!-- Liar's Dice Game Modal -->
<div v-if="liarsDiceActive" class="dialog-overlay">
<div class="dialog-box liars-dice-box">
<div class="liars-dice-header">
<div class="dice-title-group">
<h3>🎲 LIAR'S DICE GEGEN PAPA MARKUS</h3>
<span class="dice-score">Stand: Sascha {{ liarsDicePlayerWins }} : {{ liarsDiceMarkusWins }} Markus (Best of 3)</span>
</div>
<button class="duel-close-btn" @click="liarsDiceActive = false" aria-label="Spiel verlassen">✕</button>
</div>
<!-- Status Message -->
<div class="dice-status-msg">
{{ liarsDiceMsg }}
</div>
<!-- Cups and Dice Arena -->
<div class="dice-arena">
<!-- Markus Side -->
<div class="dice-player-row markus-row">
<div class="dice-player-label">
<img :src="CHARACTERS.markus.portrait" class="dice-avatar" alt="Papa Markus" />
<span>Papa Markus</span>
</div>
<div class="dice-cup-area">
<div v-if="!markusDiceRevealed" class="cup-covered">
<span class="cup-icon">🥤</span>
<span class="cup-text">[ 5 verdeckte Würfel ]</span>
</div>
<div v-else class="dice-revealed-row">
<span v-for="(die, idx) in markusDice" :key="idx" class="dice-die markus-die">
{{ dieIcon(die) }}
</span>
</div>
</div>
</div>
<!-- Current Bid Banner -->
<div class="current-bid-banner" v-if="currentBid">
<span class="bid-label">Aktuelles Gebot:</span>
<span class="bid-value">Mindestens {{ currentBid.count }}x {{ dieIcon(currentBid.value) }}</span>
<span class="bidder-tag">({{ lastBidder === 'player' ? 'von Sascha' : 'von Papa Markus' }})</span>
</div>
<!-- Player Side -->
<div class="dice-player-row player-row">
<div class="dice-player-label">
<span class="player-icon">👑</span>
<span>König Sascha (Du)</span>
</div>
<div class="dice-revealed-row">
<span v-for="(die, idx) in playerDice" :key="idx" class="dice-die player-die">
{{ dieIcon(die) }}
</span>
</div>
</div>
<!-- Shot Drink Penalty Animation Banner -->
<div v-if="shotAnimation" class="shot-animation-banner">
🍶 {{ shotAnimation === 'sascha' ? 'Sascha trinkt einen 58% Gaoliang Shot! 🥴' : 'Markus kippt den Gaoliang Shot! 乾杯! 🎉' }}
</div>
</div>
<!-- Controls / Actions -->
<div v-if="!liarsDiceGameOver && !isRoundEvaluating" class="dice-controls">
<div class="bid-selectors">
<div class="selector-col">
<label>Anzahl:</label>
<div class="counter-btns">
<button class="mini-btn" @click="selectedBidCount = Math.max(1, selectedBidCount - 1)">-</button>
<span class="counter-val">{{ selectedBidCount }}</span>
<button class="mini-btn" @click="selectedBidCount = Math.min(10, selectedBidCount + 1)">+</button>
</div>
</div>
<div class="selector-col">
<label>Augenzahl:</label>
<div class="die-val-btns">
<button
v-for="val in [2, 3, 4, 5, 6, 1]"
:key="val"
class="die-select-btn"
:class="{ active: selectedBidValue === val }"
@click="selectedBidValue = val"
>
{{ dieIcon(val) }} ({{ val }})
</button>
</div>
</div>
</div>
<div class="dice-action-buttons">
<button class="action-btn bid-action-btn" @click="makePlayerBid">
🗣️ Gebot abgeben ({{ selectedBidCount }}x {{ dieIcon(selectedBidValue) }})
</button>
<button
v-if="currentBid && lastBidder === 'markus'"
class="action-btn call-bluff-btn"
@click="callBluff('player')"
>
🤥 Lüge! (Bluff aufdecken)
</button>
</div>
</div>
<!-- Next Round or Game Over -->
<div v-else-if="isRoundEvaluating && !liarsDiceGameOver" class="dice-round-eval">
<button class="action-btn next-round-btn" @click="proceedNextDiceRound">
🎲 Nächste Runde starten ▶
</button>
</div>
<div v-else-if="liarsDiceGameOver" class="dice-game-over">
<template v-if="liarsDicePlayerWon">
<h2 class="victory-title">👑 SASCHA GEWINNT DAS LIAR'S DICE MATCH!</h2>
<p class="dice-over-desc">Papa Markus klopft dir auf die Schulter: „Respekt, Sascha! Du hast das Zeug zum König von Taiwan!“</p>
<p class="reward-text">+10 Kronkorken, 58% Kinmen Gaoliang Flasche & Titel freigeschaltet!</p>
<button class="action-btn" @click="liarsDiceActive = false">乾杯 Papa! Zurücklehnen</button>
</template>
<template v-else>
<h2 class="defeat-title">🍶 PAPA MARKUS TRIUMPHIERT!</h2>
<p class="dice-over-desc">Papa Markus lacht schallend: „Gegen jahrzehntelange Gaoliang-Erfahrung kommst du noch nicht an, Sascha!“</p>
<div class="boar-retry-actions">
<button class="action-btn retry-btn" @click="startLiarsDiceMatch">Revanche spielen!</button>
<button class="close-btn" @click="liarsDiceActive = false">Vielleicht später</button>
</div>
</template>
</div>
</div>
</div>
</div>
</template>
<style scoped>
.tavern-container {
display: flex;
flex-direction: column;
position: relative;
width: 100%;
height: 100%;
background: #100603;
overflow: hidden;
}
.scene-viewport {
flex: 1;
min-height: 0;
width: 100%;
position: relative;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background-color: #120703;
background-image:
radial-gradient(circle at 50% 50%, rgba(69, 26, 3, 0.45) 0%, rgba(10, 4, 1, 0.95) 100%),
repeating-linear-gradient(45deg, rgba(212, 175, 55, 0.05) 0, rgba(212, 175, 55, 0.05) 1px, transparent 0, transparent 20px),
repeating-linear-gradient(-45deg, rgba(212, 175, 55, 0.05) 0, rgba(212, 175, 55, 0.05) 1px, transparent 0, transparent 20px);
}
.scene-stage {
position: relative;
max-width: 100%;
max-height: 100%;
aspect-ratio: 9 / 16;
height: min(100%, calc(100vw * 16 / 9));
width: auto;
box-shadow:
0 0 0 2px #78350f,
0 0 0 4px #b45309,
0 0 20px rgba(0, 0, 0, 0.95),
0 0 16px rgba(245, 158, 11, 0.3);
border: 2px solid #b45309;
border-radius: 4px;
overflow: hidden;
flex-shrink: 0;
}
.tavern-bg {
width: 100%;
height: 100%;
display: block;
object-fit: fill;
image-rendering: pixelated;
image-rendering: crisp-edges;
}
/* Corner Ornaments on Scene Stage */
.corner-ornament {
position: absolute;
width: 14px;
height: 14px;
z-index: 14;
pointer-events: none;
}
.corner-tl { top: 3px; left: 3px; border-top: 2px solid #ffd700; border-left: 2px solid #ffd700; }
.corner-tr { top: 3px; right: 3px; border-top: 2px solid #ffd700; border-right: 2px solid #ffd700; }
.corner-bl { bottom: 3px; left: 3px; border-bottom: 2px solid #ffd700; border-left: 2px solid #ffd700; }
.corner-br { bottom: 3px; right: 3px; border-bottom: 2px solid #ffd700; border-right: 2px solid #ffd700; }
/* Side Medieval Ornament Borders */
.medieval-side-border {
flex: 1;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
min-width: 0;
overflow: hidden;
opacity: 0.85;
pointer-events: none;
user-select: none;
}
.ornament-column {
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
padding: 8px;
}
.ornament-symbol {
font-size: 16px;
filter: drop-shadow(0 0 4px rgba(255, 215, 0, 0.35));
}
.ornament-divider {
font-size: 9px;
color: #b45309;
text-shadow: 0 0 4px rgba(245, 158, 11, 0.5);
}
@media (max-width: 480px) {
.medieval-side-border {
display: none;
}
}
/* Hotspots with Avatar Thumbnails */
.hotspot {
position: absolute;
display: flex;
align-items: center;
gap: 4px;
background: rgba(26, 15, 10, 0.88);
border: 1.5px solid #ffd700;
border-radius: 16px;
padding: 2px 6px 2px 2px;
cursor: pointer;
transform: translate(-50%, -50%);
transition: transform 0.2s, background 0.2s;
z-index: 10;
box-shadow: 0 2px 8px rgba(0,0,0,0.7);
}
.hotspot:hover, .hotspot:active {
transform: translate(-50%, -50%) scale(1.1);
background: rgba(185, 28, 28, 0.95);
border-color: #fef08a;
}
.hotspot-thumb {
width: 22px;
height: 22px;
border-radius: 50%;
border: 1px solid #ffd700;
object-fit: cover;
}
.mirror-icon, .door-icon {
font-size: 15px;
padding: 0 2px;
}
.hotspot-badge {
font-family: 'Press Start 2P', monospace;
font-size: 6.5px;
color: #fff;
white-space: nowrap;
}
/* Door Hotspots */
.door-hotspot {
background: rgba(15, 23, 42, 0.9);
border: 1.5px solid #38bdf8;
padding: 4px 8px;
}
.glow-door {
color: #38bdf8;
}
/* Trapdoor to Party Bunker Easter Egg */
.trapdoor-hotspot {
position: absolute;
top: 83%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(212, 175, 55, 0.25);
border: 2px dashed #ffd700;
border-radius: 6px;
padding: 6px 10px;
cursor: pointer;
z-index: 12;
transition: all 0.2s;
display: flex;
align-items: center;
gap: 6px;
box-shadow: 0 0 12px rgba(255, 215, 0, 0.4);
}
.trapdoor-hotspot.trap-invisible,
.trapdoor-hotspot.trap-invisible:hover,
.trapdoor-hotspot.trap-invisible:active,
.trapdoor-hotspot.trap-invisible:focus {
background: transparent !important;
border: none !important;
box-shadow: none !important;
outline: none !important;
transform: translate(-50%, -50%) !important;
width: 140px;
height: 65px;
padding: 0 !important;
cursor: pointer;
}
.trapdoor-icon {
font-size: 14px;
}
.trapdoor-badge {
font-family: 'Press Start 2P', monospace;
font-size: 6px;
color: #fef08a;
white-space: nowrap;
}
/* 1. Schankraum Hotspots */
.hotspot-verena { top: 25%; left: 26%; }
.hotspot-door-nebenraum { top: 52%; left: 13%; }
.hotspot-armend { top: 70%; left: 86%; }
.hotspot-lutz { top: 27%; left: 84%; }
.hotspot-door-marktplatz { top: 48%; left: 82%; }
/* 2. Nebenraum Hotspots */
.hotspot-mirror-nebenraum { top: 36%; left: 24%; }
.hotspot-door-back-gaststube-nr { top: 48%; left: 56%; }
.hotspot-amy-nebenraum { top: 77%; left: 80%; }
/* 3. Marktplatz Hotspots */
.mario-container {
position: absolute;
top: 79%;
left: 76%;
z-index: 10;
}
.hotspot-mario {
position: relative;
transform: translate(-50%, -50%);
}
.chainsaw-circus {
position: absolute;
top: -24px;
left: 50%;
transform: translateX(-50%);
width: 50px;
height: 24px;
pointer-events: none;
}
.saw {
position: absolute;
font-size: 14px;
animation: spinSaw 0.3s infinite linear;
}
.saw-left {
left: -4px;
animation-direction: normal;
}
.saw-right {
right: -4px;
animation-direction: reverse;
}
.spark {
position: absolute;
font-size: 10px;
animation: sparkFlicker 0.2s infinite alternate;
}
.spark-1 { top: -6px; left: 10px; color: #f59e0b; }
.spark-2 { top: 4px; right: 10px; color: #ef4444; }
@keyframes spinSaw {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes sparkFlicker {
0% { opacity: 0.2; transform: scale(0.8); }
100% { opacity: 1; transform: scale(1.3); }
}
.hotspot-nat { top: 70%; left: 22%; }
.hotspot-door-back-gaststube { top: 82%; left: 16%; }
.hotspot-door-hinterland { top: 34%; left: 76%; }
/* 4. Hinterland Hotspots */
.hotspot-mane { top: 70%; left: 84%; }
.hiking-anim {
animation: hikeBob 1.2s infinite ease-in-out;
}
@keyframes hikeBob {
0%, 100% { transform: translate(-50%, -50%) translateY(0); }
50% { transform: translate(-50%, -50%) translateY(-6px) rotate(-2deg); }
}
.hiking-stick {
font-size: 12px;
transform: rotate(20deg);
}
.hotspot-hansi { top: 30%; left: 64%; }
.hotspot-door-hinter-den-bergen { top: 34%; left: 24%; }
.hotspot-door-back-marktplatz { top: 80%; left: 16%; }
/* 5. Hinter den Bergen Hotspots */
.hotspot-markus { top: 55%; left: 52%; }
.hotspot-door-back-hinterland { top: 90%; left: 16%; }
/* 6. Hinterzimmer Hotspots & Strobe */
.strobe-overlay {
position: absolute;
inset: 0;
pointer-events: none;
mix-blend-mode: overlay;
animation: discoFlash 0.6s infinite alternate;
z-index: 5;
}
@keyframes discoFlash {
0% { background: rgba(236, 72, 153, 0.25); }
33% { background: rgba(59, 130, 246, 0.25); }
66% { background: rgba(234, 179, 8, 0.25); }
100% { background: rgba(168, 85, 247, 0.25); }
}
.hotspot-flo { top: 38%; left: 24%; }
.hotspot-viktor { top: 58%; left: 60%; }
.hotspot-pue { top: 66%; left: 70%; }
.hotspot-door-back-vip { top: 74%; left: 45%; }
.pulse {
animation: pulseAnim 1.5s infinite;
}
@keyframes pulseAnim {
0%, 100% { box-shadow: 0 0 4px #facc15; }
50% { box-shadow: 0 0 12px #ec4899; }
}
/* Bottom Navigation */
.scene-bottom-nav {
position: relative;
flex-shrink: 0;
width: 100%;
height: 94px;
background: linear-gradient(180deg, #451a03 0%, #26140b 100%);
border-top: 3px solid #b45309;
box-shadow: 0 -4px 16px rgba(0, 0, 0, 0.95);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
padding: 6px 10px;
z-index: 15;
}
.tavern-bar-title {
font-family: 'Press Start 2P', monospace;
font-size: 6.5px;
color: #fef08a;
text-shadow: 1px 1px 0 #000;
letter-spacing: 0.3px;
text-align: center;
}
.room-nav-tabs {
display: flex;
gap: 4px;
width: 100%;
justify-content: center;
}
.room-tab {
background: #2b1810;
border: 1px solid #78350f;
color: #cbd5e1;
font-family: 'Press Start 2P', monospace;
font-size: 6px;
padding: 4px 6px;
border-radius: 4px;
cursor: pointer;
transition: all 0.15s;
}
.room-tab.active {
background: #b45309;
border-color: #ffd700;
color: #fff;
box-shadow: 0 0 6px rgba(255, 215, 0, 0.4);
}
.vip-tab {
background: #3b0764;
border-color: #a855f7;
color: #f0abfc;
animation: pulseAnim 1.5s infinite;
}
.tavern-nav-buttons {
display: flex;
width: 100%;
align-items: center;
justify-content: space-between;
gap: 8px;
}
.duel-trigger {
background: #b91c1c;
color: #fef08a;
border: 1.5px solid #ffd700;
border-radius: 6px;
font-family: 'Press Start 2P', monospace;
font-size: 7px;
padding: 6px 8px;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.6);
}
.next-level-btn {
background: #15803d;
color: #fff;
border: 1.5px solid #86efac;
border-radius: 6px;
font-family: 'Press Start 2P', monospace;
font-size: 7px;
padding: 6px 8px;
cursor: pointer;
box-shadow: 0 2px 8px rgba(0,0,0,0.6);
}
/* Modals */
.dialog-overlay {
position: absolute;
inset: 0;
background: rgba(0, 0, 0, 0.85);
display: flex;
align-items: center;
justify-content: center;
z-index: 100;
padding: 12px;
}
.dialog-box {
background: #2b1810;
border: 3px solid #d4af37;
border-radius: 8px;
padding: 14px;
max-width: 320px;
width: 100%;
color: #fef3c7;
box-shadow: 0 0 20px rgba(0,0,0,0.9);
}
.dialog-header {
display: flex;
align-items: center;
gap: 10px;
border-bottom: 1px solid #78350f;
padding-bottom: 8px;
margin-bottom: 10px;
}
.speaker-avatar {
width: 56px;
height: 56px;
border-radius: 6px;
border: 2px solid #ffd700;
object-fit: cover;
background: #1a0f0a;
}
.speaker-name {
font-family: 'MedievalSharp', serif;
font-size: 17px;
color: #ffd700;
margin: 0;
}
.speaker-title {
font-size: 11px;
color: #cbd5e1;
}
.dialog-text {
font-family: 'MedievalSharp', serif;
font-size: 15px;
line-height: 1.4;
margin: 0 0 14px 0;
}
.dialog-actions {
display: flex;
gap: 8px;
}
.action-btn {
flex: 1;
background: #d97706;
color: #fff;
border: 1.5px solid #fef08a;
border-radius: 4px;
padding: 8px 10px;
font-family: 'MedievalSharp', serif;
font-size: 13px;
font-weight: bold;
cursor: pointer;
}
.close-btn {
background: #475569;
color: #fff;
border: 1px solid #94a3b8;
border-radius: 4px;
padding: 8px;
font-size: 12px;
cursor: pointer;
}
/* Mirror */
.mirror-box {
background: #0f172a;
border: 3px solid #38bdf8;
border-radius: 10px;
padding: 16px;
text-align: center;
color: #e0f2fe;
}
.mirror-title {
font-family: 'Press Start 2P', monospace;
font-size: 9px;
color: #38bdf8;
margin-bottom: 12px;
}
.mirror-frame {
background: rgba(56, 189, 248, 0.15);
border: 1px dashed #38bdf8;
padding: 12px;
margin-bottom: 14px;
border-radius: 6px;
}
.mirror-lore {
font-family: 'MedievalSharp', serif;
font-size: 14px;
}
.zuluman-badge {
font-weight: bold;
color: #facc15;
margin: 10px 0;
font-size: 12px;
}
.mirror-bonus {
font-family: 'Press Start 2P', monospace;
font-size: 7px;
color: #38bdf8;
}
/* Gstanzl */
.duel-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 1px solid #78350f;
margin-bottom: 10px;
padding-bottom: 6px;
}
.duel-title-group {
flex: 1;
}
.duel-header h3 {
font-family: 'Press Start 2P', monospace;
font-size: 8px;
color: #facc15;
margin: 0 0 4px 0;
}
.duel-score {
font-size: 10px;
color: #94a3b8;
}
.duel-close-btn {
background: #7f1d1d;
color: #fef08a;
border: 1px solid #ef4444;
border-radius: 50%;
width: 26px;
height: 26px;
display: flex;
align-items: center;
justify-content: center;
font-size: 13px;
font-weight: bold;
cursor: pointer;
margin-left: 8px;
flex-shrink: 0;
transition: all 0.2s ease;
line-height: 1;
}
.duel-close-btn:hover {
background: #dc2626;
transform: scale(1.1);
box-shadow: 0 0 8px rgba(239, 68, 68, 0.6);
}
.insult-bubble {
background: #451a03;
border-left: 3px solid #ef4444;
padding: 8px 10px;
border-radius: 4px;
margin-bottom: 12px;
}
.insult-bubble .speaker {
font-weight: bold;
color: #f87171;
font-size: 12px;
}
.choices-list {
display: flex;
flex-direction: column;
gap: 8px;
}
.choice-btn {
background: #3b2014;
color: #fef08a;
border: 1px solid #b45309;
border-radius: 4px;
padding: 8px 10px;
text-align: left;
font-family: 'MedievalSharp', serif;
font-size: 13.5px;
cursor: pointer;
transition: background 0.15s;
}
.choice-btn:hover {
background: #78350f;
}
.duel-victory {
text-align: center;
}
.victory-title {
color: #ffd700;
font-size: 12px;
font-family: 'Press Start 2P', monospace;
}
/* Wild Boar Duel Modal */
.boar-duel-box {
max-width: 360px;
}
.boar-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 1px solid #78350f;
margin-bottom: 10px;
padding-bottom: 6px;
}
.boar-title-group {
flex: 1;
}
.boar-title-group h3 {
font-family: 'Press Start 2P', monospace;
font-size: 7.5px;
color: #22c55e;
margin: 0 0 4px 0;
line-height: 1.3;
}
.boar-score {
font-family: 'Press Start 2P', monospace;
font-size: 6.5px;
color: #fef08a;
}
.boar-rules-banner {
background: rgba(34, 197, 94, 0.15);
border: 1px dashed #22c55e;
border-radius: 4px;
padding: 5px 8px;
font-size: 8.5px;
color: #bbf7d0;
text-align: center;
margin-bottom: 10px;
font-family: 'MedievalSharp', serif;
}
.boar-arena {
background: #1a0f0a;
border: 1.5px solid #78350f;
border-radius: 6px;
padding: 10px;
text-align: center;
margin-bottom: 12px;
}
.boar-status-msg {
font-family: 'MedievalSharp', serif;
font-size: 14px;
color: #fef3c7;
min-height: 38px;
display: flex;
align-items: center;
justify-content: center;
}
.boar-clash {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 8px;
padding-top: 8px;
border-top: 1px dashed #451a03;
}
.clash-side {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
}
.clash-label {
font-family: 'Press Start 2P', monospace;
font-size: 6px;
color: #94a3b8;
}
.clash-move {
font-size: 13px;
font-weight: bold;
color: #ffd700;
}
.clash-vs {
font-family: 'Press Start 2P', monospace;
font-size: 9px;
color: #ef4444;
}
.boar-choices {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 6px;
}
.boar-choice-btn {
background: #3b2014;
border: 1.5px solid #16a34a;
border-radius: 6px;
padding: 8px 4px;
display: flex;
flex-direction: column;
align-items: center;
gap: 3px;
cursor: pointer;
outline: none;
transition: all 0.15s ease;
-webkit-tap-highlight-color: transparent;
}
.boar-choice-btn:focus {
outline: none;
}
.boar-choice-btn:active {
transform: scale(0.95);
background: #14532d;
}
@media (hover: hover) {
.boar-choice-btn:hover {
background: #14532d;
border-color: #4ade80;
transform: translateY(-2px);
}
}
.choice-icon {
font-size: 20px;
}
.choice-name {
font-family: 'Press Start 2P', monospace;
font-size: 7px;
color: #fef08a;
}
.choice-sub {
font-size: 8.5px;
color: #94a3b8;
font-family: 'MedievalSharp', serif;
}
.boar-game-over {
text-align: center;
}
.boar-victory-desc, .boar-defeat-desc {
font-family: 'MedievalSharp', serif;
font-size: 13.5px;
margin: 6px 0;
color: #fef3c7;
}
.defeat-title {
color: #ef4444;
font-size: 10px;
font-family: 'Press Start 2P', monospace;
}
.boar-retry-actions {
display: flex;
gap: 8px;
margin-top: 10px;
}
.retry-btn {
background: #16a34a;
border-color: #86efac;
}
/* Intro Modals for Minigames */
.intro-modal-box {
max-width: 320px;
width: 95%;
background: #231209;
border: 3px double #d4af37;
border-radius: 10px;
padding: 14px;
color: #fef3c7;
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.95);
}
.intro-modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
}
.intro-tag {
font-family: 'Press Start 2P', monospace;
font-size: 6px;
color: #f59e0b;
}
.intro-modal-title {
font-family: 'Press Start 2P', monospace;
font-size: 8.5px;
color: #ffd700;
margin: 0 0 8px 0;
line-height: 1.35;
}
.intro-modal-story {
font-family: 'MedievalSharp', serif;
font-size: 13px;
color: #fef08a;
line-height: 1.35;
margin: 0 0 10px 0;
}
.intro-modal-rules {
background: rgba(0, 0, 0, 0.45);
border-left: 3px solid #ffd700;
border-radius: 4px;
padding: 8px 10px;
margin-bottom: 12px;
display: flex;
flex-direction: column;
gap: 5px;
}
.rule-line {
font-family: 'MedievalSharp', serif;
font-size: 12px;
color: #fed7aa;
line-height: 1.3;
}
.start-duel-action-btn {
width: 100%;
background: #15803d !important;
border-color: #86efac !important;
font-family: 'Press Start 2P', monospace;
font-size: 7.5px;
padding: 10px !important;
}
.start-duel-action-btn:hover {
background: #16a34a !important;
}
/* Liar's Dice (Chui Niu) Modal & UI */
.liars-dice-box {
max-width: 360px;
width: 95%;
}
.liars-dice-header {
display: flex;
justify-content: space-between;
align-items: flex-start;
border-bottom: 1px solid #78350f;
margin-bottom: 10px;
padding-bottom: 6px;
}
.dice-title-group {
flex: 1;
}
.dice-title-group h3 {
font-family: 'Press Start 2P', monospace;
font-size: 7.5px;
color: #ffd700;
margin: 0 0 4px 0;
line-height: 1.3;
}
.dice-score {
font-family: 'Press Start 2P', monospace;
font-size: 6.5px;
color: #fef08a;
}
.dice-status-msg {
font-family: 'MedievalSharp', serif;
font-size: 13.5px;
color: #fef3c7;
min-height: 38px;
display: flex;
align-items: center;
justify-content: center;
text-align: center;
line-height: 1.3;
margin-bottom: 8px;
}
.dice-arena {
background: #1a0f0a;
border: 1.5px solid #d97706;
border-radius: 6px;
padding: 10px;
margin-bottom: 12px;
display: flex;
flex-direction: column;
gap: 8px;
}
.dice-player-row {
display: flex;
align-items: center;
justify-content: space-between;
background: rgba(0, 0, 0, 0.4);
padding: 6px 8px;
border-radius: 6px;
}
.dice-player-label {
display: flex;
align-items: center;
gap: 6px;
font-family: 'Press Start 2P', monospace;
font-size: 6.5px;
color: #fef08a;
}
.dice-avatar {
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid #ffd700;
object-fit: cover;
}
.player-icon {
font-size: 14px;
}
.dice-cup-area {
display: flex;
align-items: center;
}
.cup-covered {
display: flex;
align-items: center;
gap: 4px;
color: #94a3b8;
font-family: 'MedievalSharp', serif;
font-size: 12px;
}
.cup-icon {
font-size: 18px;
}
.dice-revealed-row {
display: flex;
gap: 4px;
}
.dice-die {
font-size: 20px;
line-height: 1;
}
.markus-die {
color: #f87171;
}
.player-die {
color: #60a5fa;
}
.current-bid-banner {
background: #451a03;
border: 1px dashed #ffd700;
border-radius: 4px;
padding: 5px 8px;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
font-size: 11px;
color: #fef08a;
text-align: center;
}
.bid-label {
font-family: 'Press Start 2P', monospace;
font-size: 6px;
color: #f59e0b;
}
.bid-value {
font-weight: bold;
font-size: 13px;
color: #ffd700;
}
.bidder-tag {
font-size: 9.5px;
color: #cbd5e1;
}
.shot-animation-banner {
background: #7f1d1d;
border: 1.5px solid #ef4444;
border-radius: 4px;
padding: 6px;
text-align: center;
font-family: 'MedievalSharp', serif;
font-size: 13px;
color: #fef08a;
animation: pulseAnim 0.8s infinite alternate;
}
.dice-controls {
display: flex;
flex-direction: column;
gap: 10px;
}
.bid-selectors {
display: flex;
gap: 8px;
}
.selector-col {
flex: 1;
display: flex;
flex-direction: column;
gap: 4px;
}
.selector-col label {
font-family: 'Press Start 2P', monospace;
font-size: 6px;
color: #fed7aa;
}
.counter-btns {
display: flex;
align-items: center;
background: #1a0f0a;
border: 1px solid #78350f;
border-radius: 4px;
justify-content: space-between;
padding: 2px 4px;
}
.mini-btn {
background: #b45309;
border: 1px solid #f59e0b;
color: #fff;
border-radius: 3px;
width: 24px;
height: 24px;
font-weight: bold;
cursor: pointer;
}
.counter-val {
font-family: 'Press Start 2P', monospace;
font-size: 10px;
color: #ffd700;
}
.die-val-btns {
display: flex;
gap: 2px;
}
.die-select-btn {
background: #231209;
border: 1px solid #78350f;
border-radius: 3px;
color: #fef3c7;
font-size: 10px;
padding: 4px 3px;
cursor: pointer;
flex: 1;
}
.die-select-btn.active {
background: #d97706;
border-color: #ffd700;
color: #fff;
font-weight: bold;
}
.dice-action-buttons {
display: flex;
gap: 8px;
}
.bid-action-btn {
flex: 1.5;
background: #15803d !important;
border-color: #86efac !important;
font-family: 'Press Start 2P', monospace;
font-size: 6.5px;
padding: 8px !important;
}
.call-bluff-btn {
flex: 1;
background: #dc2626 !important;
border-color: #fca5a5 !important;
font-family: 'Press Start 2P', monospace;
font-size: 6.5px;
padding: 8px !important;
}
.dice-round-eval {
text-align: center;
}
.next-round-btn {
width: 100%;
background: #2563eb !important;
border-color: #93c5fd !important;
font-family: 'Press Start 2P', monospace;
font-size: 7.5px;
padding: 10px !important;
}
.dice-game-over {
text-align: center;
}
.dice-over-desc {
font-family: 'MedievalSharp', serif;
font-size: 13.5px;
margin: 6px 0;
color: #fef3c7;
}
</style>