feat(sasch40): add Nebenraum, Marktplatz minigame & trapdoor to Hinterzimmer
- Split GameBoy chassis brand header: 'AKT III' on clean dedicated second line
- Add 5-room tavern architecture: Schankraum, Nebenraum, Marktplatz, Hinterland, Hinterzimmer
- Introduce Nebenraum (Kemenate/Atelier) with Amy at her easel painting the neon banner and Zuluman magic mirror
- Update Marktplatz scene with Mario juggling chainsaws/hops and NAT on the festival stage
- Implement Wildschwein-Schnick-Schnack-Schnuck minigame against NAT (Best of 3, Keiler > Dickicht > Lanze > Keiler)
- Replace secret tap with cellar floor trapdoor ('Luke zum Hinterraum') initially invisible
- Update automated playtest covering all 25 screens with zero console/network errors
- Document all architectural decisions in projects/sasch40/AGENTS.md
@@ -115,16 +115,29 @@ This document records the foundational design decisions, their rationale ("Why")
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 9. Easter Egg Invisibility Pattern (`TavernView.vue`)
|
### 9. Cellar Trapdoor & Easter Egg Invisibility Pattern (`TavernView.vue`)
|
||||||
* **The Decision**: Secret hotspots (e.g. `secret-tap-hotspot` in the Schankraum) must be 100% invisible initially.
|
* **The Decision**: Secret hotspots (the cellar trapdoor `.trapdoor-hotspot` in the Schankraum leading to the VIP Hinterzimmer) must be 100% invisible initially.
|
||||||
* **Why**: An Easter egg marked with a glowing button or text badge is not a secret; it invites immediate trivial clicking.
|
* **Why**: An Easter egg marked with a glowing button or text badge is not a secret; it invites immediate trivial clicking.
|
||||||
* **The Rule**:
|
* **The Rule**:
|
||||||
* The button must render with transparent background, no border, no outline, no box shadow, no hover change, and no tooltip (`:title="undefined"`).
|
* The button must render with transparent background, no border, no outline, no box shadow, no hover change, and no tooltip (`:title="undefined"`).
|
||||||
* Only upon the user's first click does it reveal the gold key icon, play a celebratory fanfare, unlock the Easter egg in the game store (+500 points), and transition to the secret room.
|
* Only upon the user's first click does it reveal the door icon, label itself *„Luke zum Hinterraum“*, play a celebratory fanfare, unlock the Easter egg in the game store (+500 points), and transition to the secret room.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### 10. LocalStorage Persistence & Clean Game Reset (`gameStore.ts`)
|
### 10. Multi-Room World Architecture & Tavern Minigames (`TavernView.vue`)
|
||||||
|
* **The Decision**: Act III is structured as a spatial, interconnected five-room village hub rather than a cramped single screen:
|
||||||
|
1. **Gaststube (Schankraum)**: King Sascha's table, Queen Verena on the balcony, Armend at the forge, Lutz (ZTUL) at the fireplace, door to Nebenraum, door to Marktplatz, and the secret *„Luke zum Hinterraum“* in the floor.
|
||||||
|
2. **Königlicher Nebenraum (Kemenate & Atelier)**: Amy painting the royal *„Rave the Kingdom!“* banner at her easel in overalls, and the golden Zuluman magic mirror on the wall.
|
||||||
|
3. **Haumdaucher Marktplatz**: Mario acrobatically juggling chainsaws and hops on the cobblestones; NAT (Josef) on the wooden festival stage with wild boar banners telling stories of his girls and heroic deeds.
|
||||||
|
4. **Alpines Hinterland**: Mane (Ramona) hiking the alpine trail with walking stick; Hansi on the panoramic bench.
|
||||||
|
5. **Geheimes VIP-Hinterzimmer**: Secret rave with disco chandelier, stroboscope, Pü's crystal staff, DJ Flo Maier, and Viktor the night-guard.
|
||||||
|
* **Minigames & Exit Safety**:
|
||||||
|
* **Bayerisches Gstanzl-Fechten**: Multi-round insult rhyming duel. Must always include a visible `[✕]` close button so players are never trapped.
|
||||||
|
* **Wildschwein-Schnick-Schnack-Schnuck**: Best-of-3 tactical duel against NAT. Rules: 🐗 Keiler bricht 🌿 Dickicht, 🌿 Dickicht fängt 🏹 Lanze, 🏹 Lanze erlegt 🐗 Keiler. First to 2 points wins (+10 Korken, 1 Helles, `wildschwein_meister` achievement). Includes `[✕]` close button.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### 11. LocalStorage Persistence & Clean Game Reset (`gameStore.ts`)
|
||||||
* **The Decision**: Game state automatically synchronizes to `localStorage` (`sasch40_game_save_v1`) on every mutation and is cleanly wiped when clicking *„🔄 NOCHMAL SPIELEN“*.
|
* **The Decision**: Game state automatically synchronizes to `localStorage` (`sasch40_game_save_v1`) on every mutation and is cleanly wiped when clicking *„🔄 NOCHMAL SPIELEN“*.
|
||||||
* **Why**: Mobile users frequently refresh or switch apps to snap photos. Without persistence, a browser reload resets their progress back to Act I.
|
* **Why**: Mobile users frequently refresh or switch apps to snap photos. Without persistence, a browser reload resets their progress back to Act I.
|
||||||
* **The Rule**:
|
* **The Rule**:
|
||||||
@@ -151,7 +164,7 @@ make all
|
|||||||
|
|
||||||
The `Makefile` targets:
|
The `Makefile` targets:
|
||||||
* `compile`: Runs `npm run build` (`vue-tsc -b && vite build`).
|
* `compile`: Runs `npm run build` (`vue-tsc -b && vite build`).
|
||||||
* `build`: Runs `docker build --platform linux/amd64 -t sasch40:latest .` (copies `dist/` directly into `nginx:alpine`).
|
* `build`: Runs `docker build --no-cache --platform linux/amd64 -t sasch40:latest .` (copies `dist/` directly into `nginx:alpine`).
|
||||||
* `tag`: Tags as `registry.haumdaucher.de/sasch40:latest`.
|
* `tag`: Tags as `registry.haumdaucher.de/sasch40:latest`.
|
||||||
* `push`: Pushes to the Haumdaucher registry.
|
* `push`: Pushes to the Haumdaucher registry.
|
||||||
* `deploy`: Executes `kubectl rollout restart deployment/sasch40 -n web` and monitors rollout status.
|
* `deploy`: Executes `kubectl rollout restart deployment/sasch40 -n web` and monitors rollout status.
|
||||||
@@ -171,7 +184,9 @@ This script:
|
|||||||
2. Verifies the Intro Screen and clicks Start.
|
2. Verifies the Intro Screen and clicks Start.
|
||||||
3. Simulates dialogue options on the Castle Balcony (Prologue).
|
3. Simulates dialogue options on the Castle Balcony (Prologue).
|
||||||
4. Tests movement and jump controls in Act II (Hopfen-Pfade).
|
4. Tests movement and jump controls in Act II (Hopfen-Pfade).
|
||||||
5. Interacts with hotspots, dialogue modals, and the Gstanzl duel in Act III (Taverne).
|
5. Interacts with all 5 rooms, hotspots, dialogue modals, Zuluman mirror, Luke zum Hinterraum, Gstanzl duel, and the Wildschwein duel in Act III (Taverne).
|
||||||
6. Advances through the Party Express and reaches the Royal Certificate.
|
6. Tests reload persistence in `localStorage`.
|
||||||
7. Captures 10 screenshots to verify zero visual regressions.
|
7. Advances through the Party Express (Act IV) and reaches the Royal Certificate (Act V).
|
||||||
8. Asserts zero console errors, zero failed HTTP requests, and zero broken images.
|
8. Tests the `🔄 NOCHMAL SPIELEN` button and verifies return to Intro.
|
||||||
|
9. Captures 25 screenshots to verify zero visual regressions.
|
||||||
|
10. Asserts zero console errors, zero failed HTTP requests, and zero broken images.
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 MiB After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 1.2 MiB |
|
After Width: | Height: | Size: 990 KiB |
|
After Width: | Height: | Size: 990 KiB |
|
Before Width: | Height: | Size: 972 KiB After Width: | Height: | Size: 489 KiB |
|
Before Width: | Height: | Size: 972 KiB After Width: | Height: | Size: 489 KiB |
|
After Width: | Height: | Size: 489 KiB |
@@ -151,13 +151,52 @@ async function runPlaytest() {
|
|||||||
await new Promise(r => setTimeout(r, 400));
|
await new Promise(r => setTimeout(r, 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 6. TAVERN: MARKTPLATZ (Mario with spinning chainsaws)
|
// 5b. TAVERN: NEBENRAUM (Amy painting & Zuluman Mirror)
|
||||||
|
console.log('🎨 Navigating to Nebenraum (Kemenate / Atelier)...');
|
||||||
|
const nebenraumDoor = await page.$('.hotspot-door-nebenraum');
|
||||||
|
if (nebenraumDoor) {
|
||||||
|
await nebenraumDoor.click();
|
||||||
|
} else {
|
||||||
|
const nrTab = await page.$('.room-tab:nth-child(2)');
|
||||||
|
if (nrTab) await nrTab.click();
|
||||||
|
}
|
||||||
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
|
console.log('📸 05b: Capturing Nebenraum (Atelier & Mirror)...');
|
||||||
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05b_tavern_nebenraum.png') });
|
||||||
|
|
||||||
|
// Test Amy in Nebenraum
|
||||||
|
console.log('🎨 Testing Amy Dialog in Nebenraum...');
|
||||||
|
const amyBtn = await page.$('.hotspot-amy-nebenraum');
|
||||||
|
if (amyBtn) {
|
||||||
|
await amyBtn.click();
|
||||||
|
await new Promise(r => setTimeout(r, 700));
|
||||||
|
console.log('📸 05c: Capturing Amy Dialog...');
|
||||||
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05c_tavern_amy_dialog.png') });
|
||||||
|
const actionBtn = await page.$('.dialog-actions .action-btn');
|
||||||
|
if (actionBtn) await actionBtn.click();
|
||||||
|
await new Promise(r => setTimeout(r, 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Zuluman Mirror in Nebenraum
|
||||||
|
console.log('🪞 Testing Zuluman Magic Mirror in Nebenraum...');
|
||||||
|
const mirrorBtn = await page.$('.hotspot-mirror-nebenraum');
|
||||||
|
if (mirrorBtn) {
|
||||||
|
await mirrorBtn.click();
|
||||||
|
await new Promise(r => setTimeout(r, 700));
|
||||||
|
console.log('📸 14: Capturing Zuluman Mirror...');
|
||||||
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '14_tavern_zuluman_mirror.png') });
|
||||||
|
const closeBtn = await page.$('.mirror-box .action-btn');
|
||||||
|
if (closeBtn) await closeBtn.click();
|
||||||
|
await new Promise(r => setTimeout(r, 400));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6. TAVERN: MARKTPLATZ (Mario with spinning chainsaws & NAT on stage)
|
||||||
console.log('🎪 Navigating to Marktplatz...');
|
console.log('🎪 Navigating to Marktplatz...');
|
||||||
const marktplatzTab = await page.$('.room-tab:nth-child(2)');
|
const marktplatzTab = await page.$('.room-tab:nth-child(3)');
|
||||||
if (marktplatzTab) {
|
if (marktplatzTab) {
|
||||||
await marktplatzTab.click();
|
await marktplatzTab.click();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
console.log('📸 08: Capturing Marktplatz (Mario chainsaws)...');
|
console.log('📸 08: Capturing Marktplatz (Mario chainsaws & NAT stage)...');
|
||||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_marktplatz.png') });
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_marktplatz.png') });
|
||||||
|
|
||||||
// Click Mario
|
// Click Mario
|
||||||
@@ -172,22 +211,45 @@ async function runPlaytest() {
|
|||||||
await new Promise(r => setTimeout(r, 400));
|
await new Promise(r => setTimeout(r, 400));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Click NAT (Josef)
|
// Click NAT (Josef) & Test Wildschwein Duell
|
||||||
const natBtn = await page.$('.hotspot-nat');
|
const natBtn = await page.$('.hotspot-nat');
|
||||||
if (natBtn) {
|
if (natBtn) {
|
||||||
await natBtn.click();
|
await natBtn.click();
|
||||||
await new Promise(r => setTimeout(r, 700));
|
await new Promise(r => setTimeout(r, 700));
|
||||||
console.log('📸 09b: Capturing NAT (Josef) Dialog...');
|
console.log('📸 09b: Capturing NAT (Josef) Dialog...');
|
||||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09b_tavern_nat_dialog.png') });
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09b_tavern_nat_dialog.png') });
|
||||||
const actionBtn = await page.$('.dialog-actions .action-btn');
|
|
||||||
if (actionBtn) await actionBtn.click();
|
// Start Wild Boar Duel
|
||||||
await new Promise(r => setTimeout(r, 400));
|
console.log('🐗 Launching Wildschwein-Schnick-Schnack-Schnuck Duel...');
|
||||||
|
const startBoarBtn = await page.$('.dialog-actions .action-btn');
|
||||||
|
if (startBoarBtn) {
|
||||||
|
await startBoarBtn.click();
|
||||||
|
await new Promise(r => setTimeout(r, 800));
|
||||||
|
console.log('📸 09c: Capturing Wildschwein Duel Modal...');
|
||||||
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09c_tavern_boar_duel.png') });
|
||||||
|
|
||||||
|
// Play a move: Keiler!
|
||||||
|
const keilerBtn = await page.$('.boar-choice-btn:nth-child(1)');
|
||||||
|
if (keilerBtn) {
|
||||||
|
await keilerBtn.click();
|
||||||
|
await new Promise(r => setTimeout(r, 600));
|
||||||
|
console.log('📸 09d: Capturing Boar Duel Clash...');
|
||||||
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09d_tavern_boar_duel_round.png') });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close Boar Duel modal
|
||||||
|
const closeBoarBtn = await page.$('.boar-duel-box .duel-close-btn');
|
||||||
|
if (closeBoarBtn) {
|
||||||
|
await closeBoarBtn.click();
|
||||||
|
await new Promise(r => setTimeout(r, 400));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. TAVERN: HINTERLAND (Mane hiking)
|
// 7. TAVERN: HINTERLAND (Mane hiking)
|
||||||
console.log('🌲 Navigating to Hinterland...');
|
console.log('🌲 Navigating to Hinterland...');
|
||||||
const hinterlandTab = await page.$('.room-tab:nth-child(3)');
|
const hinterlandTab = await page.$('.room-tab:nth-child(4)');
|
||||||
if (hinterlandTab) {
|
if (hinterlandTab) {
|
||||||
await hinterlandTab.click();
|
await hinterlandTab.click();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
@@ -207,18 +269,18 @@ async function runPlaytest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 8. TAVERN: SECRET HINTERZIMMER (VIP Rave via Secret Tap)
|
// 8. TAVERN: SECRET HINTERZIMMER (VIP Rave via Luke zum Hinterraum)
|
||||||
console.log('🗝️ Returning to Schankraum to check Secret Tap (initially invisible)...');
|
console.log('🗝️ Returning to Schankraum to check Luke zum Hinterraum (trapdoor)...');
|
||||||
const schankraumTab = await page.$('.room-tab:nth-child(1)');
|
const schankraumTab = await page.$('.room-tab:nth-child(1)');
|
||||||
if (schankraumTab) {
|
if (schankraumTab) {
|
||||||
await schankraumTab.click();
|
await schankraumTab.click();
|
||||||
await new Promise(r => setTimeout(r, 600));
|
await new Promise(r => setTimeout(r, 600));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🪩 Triggering Secret Tap Easter Egg...');
|
console.log('🪩 Triggering Luke zum Hinterraum Easter Egg...');
|
||||||
const secretTap = await page.$('.secret-tap-hotspot');
|
const trapdoor = await page.$('.trapdoor-hotspot');
|
||||||
if (secretTap) {
|
if (trapdoor) {
|
||||||
await secretTap.click();
|
await trapdoor.click();
|
||||||
await new Promise(r => setTimeout(r, 1000));
|
await new Promise(r => setTimeout(r, 1000));
|
||||||
console.log('📸 12: Capturing Secret VIP Hinterzimmer...');
|
console.log('📸 12: Capturing Secret VIP Hinterzimmer...');
|
||||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12_tavern_hinterzimmer_vip.png') });
|
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12_tavern_hinterzimmer_vip.png') });
|
||||||
@@ -236,25 +298,13 @@ async function runPlaytest() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 9. ZULUMAN MAGIC MIRROR & GSTANZL DUEL
|
// 9. GSTANZL DUEL
|
||||||
console.log('🪞 Testing Zuluman Magic Mirror...');
|
console.log('⚔️ Testing Gstanzl-Duel and [✕] Exit Button...');
|
||||||
const backToGaststube = await page.$('.hotspot-door-back-vip');
|
const backToGaststube = await page.$('.hotspot-door-back-vip');
|
||||||
if (backToGaststube) {
|
if (backToGaststube) {
|
||||||
await backToGaststube.click();
|
await backToGaststube.click();
|
||||||
await new Promise(r => setTimeout(r, 600));
|
await new Promise(r => setTimeout(r, 600));
|
||||||
}
|
}
|
||||||
const mirrorBtn = await page.$('.hotspot-mirror');
|
|
||||||
if (mirrorBtn) {
|
|
||||||
await mirrorBtn.click();
|
|
||||||
await new Promise(r => setTimeout(r, 700));
|
|
||||||
console.log('📸 14: Capturing Zuluman Mirror...');
|
|
||||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '14_tavern_zuluman_mirror.png') });
|
|
||||||
const closeBtn = await page.$('.mirror-box .action-btn');
|
|
||||||
if (closeBtn) await closeBtn.click();
|
|
||||||
await new Promise(r => setTimeout(r, 400));
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('⚔️ Testing Gstanzl-Duel and [✕] Exit Button...');
|
|
||||||
const duelBtn = await page.$('.duel-trigger');
|
const duelBtn = await page.$('.duel-trigger');
|
||||||
if (duelBtn) {
|
if (duelBtn) {
|
||||||
await duelBtn.click();
|
await duelBtn.click();
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ function toggleInventory() {
|
|||||||
<div class="chassis-brand">
|
<div class="chassis-brand">
|
||||||
<span class="brand-text">KÖNIG SASCHAS KRONKORKENKREUZZUG</span>
|
<span class="brand-text">KÖNIG SASCHAS KRONKORKENKREUZZUG</span>
|
||||||
<span v-if="store.currentAct !== 'intro'" class="brand-act">
|
<span v-if="store.currentAct !== 'intro'" class="brand-act">
|
||||||
{{ store.currentAct === 'prologue' ? '• AKT I' : store.currentAct === 'platformer' ? '• AKT II' : store.currentAct === 'tavern' ? '• AKT III' : store.currentAct === 'party_express' ? '• AKT IV' : store.currentAct === 'finale' ? '• AKT V' : '' }}
|
{{ store.currentAct === 'prologue' ? 'AKT I' : store.currentAct === 'platformer' ? 'AKT II' : store.currentAct === 'tavern' ? 'AKT III' : store.currentAct === 'party_express' ? 'AKT IV' : store.currentAct === 'finale' ? 'AKT V' : '' }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<button class="sound-toggle" @click="toggleAudio">
|
<button class="sound-toggle" @click="toggleAudio">
|
||||||
@@ -203,8 +203,10 @@ function toggleInventory() {
|
|||||||
|
|
||||||
.chassis-brand {
|
.chassis-brand {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 4px;
|
justify-content: center;
|
||||||
|
gap: 2px;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,7 +220,7 @@ function toggleInventory() {
|
|||||||
|
|
||||||
.brand-act {
|
.brand-act {
|
||||||
font-family: 'Press Start 2P', monospace;
|
font-family: 'Press Start 2P', monospace;
|
||||||
font-size: 6px;
|
font-size: 5.5px;
|
||||||
color: #f59e0b;
|
color: #f59e0b;
|
||||||
white-space: nowrap;
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { sound } from '../audio/synthAudio'
|
|||||||
|
|
||||||
const store = useGameStore()
|
const store = useGameStore()
|
||||||
|
|
||||||
type TavernRoom = 'schankraum' | 'marktplatz' | 'hinterland' | 'hinterzimmer'
|
type TavernRoom = 'schankraum' | 'nebenraum' | 'marktplatz' | 'hinterland' | 'hinterzimmer'
|
||||||
const currentRoom = ref<TavernRoom>('schankraum')
|
const currentRoom = ref<TavernRoom>('schankraum')
|
||||||
|
|
||||||
interface ActiveDialog {
|
interface ActiveDialog {
|
||||||
@@ -26,16 +26,16 @@ const duelStep = ref(0)
|
|||||||
const duelWon = ref(false)
|
const duelWon = ref(false)
|
||||||
const zulumanMirrorActive = ref(false)
|
const zulumanMirrorActive = ref(false)
|
||||||
|
|
||||||
const secretTapDiscovered = ref(store.unlockedEasterEggs.includes('hinterzimmer'))
|
const trapdoorDiscovered = ref(store.unlockedEasterEggs.includes('hinterzimmer'))
|
||||||
|
|
||||||
function switchRoom(room: TavernRoom) {
|
function switchRoom(room: TavernRoom) {
|
||||||
sound.playDialogueBlip()
|
sound.playDialogueBlip()
|
||||||
currentRoom.value = room
|
currentRoom.value = room
|
||||||
}
|
}
|
||||||
|
|
||||||
function triggerSecretTap() {
|
function triggerTrapdoor() {
|
||||||
sound.playFanfare()
|
sound.playFanfare()
|
||||||
secretTapDiscovered.value = true
|
trapdoorDiscovered.value = true
|
||||||
store.unlockEasterEgg('hinterzimmer')
|
store.unlockEasterEgg('hinterzimmer')
|
||||||
currentRoom.value = 'hinterzimmer'
|
currentRoom.value = 'hinterzimmer'
|
||||||
}
|
}
|
||||||
@@ -46,6 +46,91 @@ function enterSecretHinterzimmer() {
|
|||||||
currentRoom.value = 'hinterzimmer'
|
currentRoom.value = 'hinterzimmer'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Wildschwein Schnick-Schnack-Schnuck Minigame with NAT
|
||||||
|
type WildBoarChoice = 'keiler' | 'dickicht' | 'lanze'
|
||||||
|
|
||||||
|
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 startWildBoarDuel() {
|
||||||
|
sound.playDialogueBlip()
|
||||||
|
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) {
|
||||||
|
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) {
|
function openCharacterDialog(charId: string) {
|
||||||
const char = CHARACTERS[charId]
|
const char = CHARACTERS[charId]
|
||||||
if (!char) return
|
if (!char) return
|
||||||
@@ -123,12 +208,11 @@ function openCharacterDialog(charId: string) {
|
|||||||
name: char.name,
|
name: char.name,
|
||||||
title: char.title,
|
title: char.title,
|
||||||
portrait: char.portrait,
|
portrait: char.portrait,
|
||||||
text: 'Servus Sascha! Schau dir meinen Wildschwein-Harnisch an – der glänzt wie ein Kronjuwel! Ich hab dir schon 3 kühle Helle und 5 Kronkorken gesichert, bevor die Meute alles wegsäuft!',
|
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: 'Sauber Josef! (+5 Kronkorken & Helles)',
|
actionLabel: '🐗 Wildschwein-Duell starten! (Best of 3)',
|
||||||
actionFn: () => {
|
actionFn: () => {
|
||||||
store.addKronkorken(5)
|
|
||||||
store.addBeer()
|
|
||||||
activeDialog.value = null
|
activeDialog.value = null
|
||||||
|
startWildBoarDuel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (charId === 'mane') {
|
} else if (charId === 'mane') {
|
||||||
@@ -238,7 +322,7 @@ function proceedToExpress() {
|
|||||||
|
|
||||||
<!-- ================= 1. GASTSTUBE (SCHANKRAUM) ================= -->
|
<!-- ================= 1. GASTSTUBE (SCHANKRAUM) ================= -->
|
||||||
<template v-if="currentRoom === 'schankraum'">
|
<template v-if="currentRoom === 'schankraum'">
|
||||||
<img src="/assets/scenes/schankraum_v2.jpg?v=2" alt="Gaststube Zum wummernden Ritter" class="tavern-bg" />
|
<img src="/assets/scenes/schankraum_v2.jpg?v=3" alt="Gaststube Zum wummernden Ritter" class="tavern-bg" />
|
||||||
|
|
||||||
<!-- Verena on the wooden gallery -->
|
<!-- Verena on the wooden gallery -->
|
||||||
<button class="hotspot hotspot-verena" @click="openCharacterDialog('verena')">
|
<button class="hotspot hotspot-verena" @click="openCharacterDialog('verena')">
|
||||||
@@ -246,6 +330,12 @@ function proceedToExpress() {
|
|||||||
<span class="hotspot-badge">Verena 👑</span>
|
<span class="hotspot-badge">Verena 👑</span>
|
||||||
</button>
|
</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 -->
|
<!-- Armend at the forge/anvil -->
|
||||||
<button class="hotspot hotspot-armend" @click="openCharacterDialog('armend')">
|
<button class="hotspot hotspot-armend" @click="openCharacterDialog('armend')">
|
||||||
<img :src="CHARACTERS.armend.portrait" class="hotspot-thumb" alt="Armend" />
|
<img :src="CHARACTERS.armend.portrait" class="hotspot-thumb" alt="Armend" />
|
||||||
@@ -258,43 +348,60 @@ function proceedToExpress() {
|
|||||||
<span class="hotspot-badge">Lutz (ZTUL) 🎩</span>
|
<span class="hotspot-badge">Lutz (ZTUL) 🎩</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- Amy at the tavern table -->
|
|
||||||
<button class="hotspot hotspot-amy" @click="openCharacterDialog('amy')">
|
|
||||||
<img :src="CHARACTERS.amy.portrait" class="hotspot-thumb" alt="Amy" />
|
|
||||||
<span class="hotspot-badge">Amy 🎨</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Zuluman Magic Mirror on the pillar -->
|
|
||||||
<button class="hotspot hotspot-mirror" @click="inspectMirror">
|
|
||||||
<span class="mirror-icon">🪞</span>
|
|
||||||
<span class="hotspot-badge pulse">Zuluman-Spiegel</span>
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Doorway to Marktplatz -->
|
<!-- Doorway to Marktplatz -->
|
||||||
<button class="hotspot door-hotspot hotspot-door-marktplatz" @click="switchRoom('marktplatz')">
|
<button class="hotspot door-hotspot hotspot-door-marktplatz" @click="switchRoom('marktplatz')">
|
||||||
<span class="door-icon">🚪</span>
|
<span class="door-icon">🎪</span>
|
||||||
<span class="hotspot-badge glow-door">Zum Marktplatz ▶</span>
|
<span class="hotspot-badge glow-door">Zum Marktplatz ▶</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<!-- SECRET EASTER EGG TRIGGER: Hidden Beer Tap to Hinterzimmer (Completely invisible until clicked) -->
|
<!-- Luke zum Hinterraum (Completely invisible until clicked) -->
|
||||||
<button
|
<button
|
||||||
class="hotspot secret-tap-hotspot"
|
class="hotspot trapdoor-hotspot"
|
||||||
:class="{ 'tap-invisible': !secretTapDiscovered && !store.unlockedEasterEggs.includes('hinterzimmer') }"
|
:class="{ 'trap-invisible': !trapdoorDiscovered && !store.unlockedEasterEggs.includes('hinterzimmer') }"
|
||||||
@click="triggerSecretTap"
|
@click="triggerTrapdoor"
|
||||||
:title="secretTapDiscovered || store.unlockedEasterEggs.includes('hinterzimmer') ? 'Ein sonderbarer Zapfhahn...' : undefined"
|
:title="trapdoorDiscovered || store.unlockedEasterEggs.includes('hinterzimmer') ? 'Luke zum Hinterraum' : undefined"
|
||||||
>
|
>
|
||||||
<template v-if="secretTapDiscovered || store.unlockedEasterEggs.includes('hinterzimmer')">
|
<template v-if="trapdoorDiscovered || store.unlockedEasterEggs.includes('hinterzimmer')">
|
||||||
<span class="secret-tap-icon pulse">🗝️</span>
|
<span class="trapdoor-icon">🚪</span>
|
||||||
<span class="secret-badge">Geheimer Zapfhahn</span>
|
<span class="trapdoor-badge">Luke zum Hinterraum</span>
|
||||||
</template>
|
</template>
|
||||||
</button>
|
</button>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- ================= 2. MARKTPLATZ ================= -->
|
<!-- ================= 2. NEBENRAUM (KEMENATE & ATELIER) ================= -->
|
||||||
<template v-else-if="currentRoom === 'marktplatz'">
|
<template v-else-if="currentRoom === 'nebenraum'">
|
||||||
<img src="/assets/scenes/marktplatz.jpg" alt="Marktplatz Haumdaucher" class="tavern-bg" />
|
<img src="/assets/scenes/nebenraum_v2.jpg?v=2" alt="Königlicher Nebenraum & Atelier" class="tavern-bg" />
|
||||||
|
|
||||||
<!-- Gaukler Mario juggling chainsaws with animations & flying sparks -->
|
<!-- 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">
|
<div class="mario-container">
|
||||||
<button class="hotspot hotspot-mario" @click="openCharacterDialog('mario')">
|
<button class="hotspot hotspot-mario" @click="openCharacterDialog('mario')">
|
||||||
<div class="chainsaw-circus">
|
<div class="chainsaw-circus">
|
||||||
@@ -308,15 +415,9 @@ function proceedToExpress() {
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- NAT at the fountain -->
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<!-- Door back to Gaststube -->
|
<!-- Door back to Gaststube -->
|
||||||
<button class="hotspot door-hotspot hotspot-door-back-gaststube" @click="switchRoom('schankraum')">
|
<button class="hotspot door-hotspot hotspot-door-back-gaststube" @click="switchRoom('schankraum')">
|
||||||
<span class="door-icon">🚪</span>
|
<span class="door-icon">🍺</span>
|
||||||
<span class="hotspot-badge glow-door">◀ Zur Gaststube</span>
|
<span class="hotspot-badge glow-door">◀ Zur Gaststube</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
@@ -388,6 +489,7 @@ function proceedToExpress() {
|
|||||||
<div class="tavern-bar-title">
|
<div class="tavern-bar-title">
|
||||||
{{
|
{{
|
||||||
currentRoom === 'schankraum' ? '🍻 AKT III: GASTSTUBE ZUM WUMMERNDEN RITTER 🍻' :
|
currentRoom === 'schankraum' ? '🍻 AKT III: GASTSTUBE ZUM WUMMERNDEN RITTER 🍻' :
|
||||||
|
currentRoom === 'nebenraum' ? '🎨 AKT III: KÖNIGLICHER NEBENRAUM & ATELIER 🎨' :
|
||||||
currentRoom === 'marktplatz' ? '🎪 AKT III: HAUMDAUCHER MARKTPLATZ 🎪' :
|
currentRoom === 'marktplatz' ? '🎪 AKT III: HAUMDAUCHER MARKTPLATZ 🎪' :
|
||||||
currentRoom === 'hinterland' ? '🌲 AKT III: ALPINES HINTERLAND 🌲' :
|
currentRoom === 'hinterland' ? '🌲 AKT III: ALPINES HINTERLAND 🌲' :
|
||||||
'🪩 AKT III: GEHEIMES VIP-HINTERZIMMER 🪩'
|
'🪩 AKT III: GEHEIMES VIP-HINTERZIMMER 🪩'
|
||||||
@@ -403,6 +505,13 @@ function proceedToExpress() {
|
|||||||
>
|
>
|
||||||
🍺 Gaststube
|
🍺 Gaststube
|
||||||
</button>
|
</button>
|
||||||
|
<button
|
||||||
|
class="room-tab"
|
||||||
|
:class="{ active: currentRoom === 'nebenraum' }"
|
||||||
|
@click="switchRoom('nebenraum')"
|
||||||
|
>
|
||||||
|
🎨 Nebenraum
|
||||||
|
</button>
|
||||||
<button
|
<button
|
||||||
class="room-tab"
|
class="room-tab"
|
||||||
:class="{ active: currentRoom === 'marktplatz' }"
|
:class="{ active: currentRoom === 'marktplatz' }"
|
||||||
@@ -514,6 +623,79 @@ function proceedToExpress() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</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')">
|
||||||
|
<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')">
|
||||||
|
<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')">
|
||||||
|
<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>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -594,24 +776,27 @@ function proceedToExpress() {
|
|||||||
color: #38bdf8;
|
color: #38bdf8;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Secret Tap Easter Egg */
|
/* Trapdoor to Hinterzimmer Easter Egg */
|
||||||
.secret-tap-hotspot {
|
.trapdoor-hotspot {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 72%;
|
top: 73%;
|
||||||
left: 82%;
|
left: 82%;
|
||||||
background: rgba(234, 179, 8, 0.2);
|
background: rgba(180, 83, 9, 0.4);
|
||||||
border: 1.5px dashed #ffd700;
|
border: 1.5px dashed #ffd700;
|
||||||
border-radius: 12px;
|
border-radius: 8px;
|
||||||
padding: 3px 6px;
|
padding: 3px 6px;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
z-index: 12;
|
z-index: 12;
|
||||||
transition: all 0.2s;
|
transition: all 0.2s;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secret-tap-hotspot.tap-invisible,
|
.trapdoor-hotspot.trap-invisible,
|
||||||
.secret-tap-hotspot.tap-invisible:hover,
|
.trapdoor-hotspot.trap-invisible:hover,
|
||||||
.secret-tap-hotspot.tap-invisible:active,
|
.trapdoor-hotspot.trap-invisible:active,
|
||||||
.secret-tap-hotspot.tap-invisible:focus {
|
.trapdoor-hotspot.trap-invisible:focus {
|
||||||
background: transparent !important;
|
background: transparent !important;
|
||||||
border: none !important;
|
border: none !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
@@ -623,29 +808,34 @@ function proceedToExpress() {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secret-tap-icon {
|
.trapdoor-icon {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.secret-badge {
|
.trapdoor-badge {
|
||||||
font-family: 'Press Start 2P', monospace;
|
font-family: 'Press Start 2P', monospace;
|
||||||
font-size: 5.5px;
|
font-size: 5.5px;
|
||||||
color: #fef08a;
|
color: #fef08a;
|
||||||
|
white-space: nowrap;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 1. Schankraum Hotspots */
|
/* 1. Schankraum Hotspots */
|
||||||
.hotspot-verena { top: 17%; left: 28%; }
|
.hotspot-verena { top: 17%; left: 28%; }
|
||||||
.hotspot-mirror { top: 30%; left: 68%; }
|
.hotspot-door-nebenraum { top: 42%; left: 34%; }
|
||||||
.hotspot-amy { top: 45%; left: 32%; }
|
|
||||||
.hotspot-lutz { top: 54%; left: 56%; }
|
|
||||||
.hotspot-armend { top: 62%; left: 18%; }
|
.hotspot-armend { top: 62%; left: 18%; }
|
||||||
|
.hotspot-lutz { top: 54%; left: 56%; }
|
||||||
.hotspot-door-marktplatz { top: 50%; left: 84%; }
|
.hotspot-door-marktplatz { top: 50%; left: 84%; }
|
||||||
|
|
||||||
/* 2. Marktplatz Hotspots */
|
/* 2. Nebenraum Hotspots */
|
||||||
|
.hotspot-mirror-nebenraum { top: 36%; left: 24%; }
|
||||||
|
.hotspot-door-back-gaststube-nr { top: 48%; left: 56%; }
|
||||||
|
.hotspot-amy-nebenraum { top: 64%; left: 80%; }
|
||||||
|
|
||||||
|
/* 3. Marktplatz Hotspots */
|
||||||
.mario-container {
|
.mario-container {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 44%;
|
top: 66%;
|
||||||
left: 30%;
|
left: 70%;
|
||||||
z-index: 10;
|
z-index: 10;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -699,9 +889,9 @@ function proceedToExpress() {
|
|||||||
100% { opacity: 1; transform: scale(1.3); }
|
100% { opacity: 1; transform: scale(1.3); }
|
||||||
}
|
}
|
||||||
|
|
||||||
.hotspot-nat { top: 62%; left: 70%; }
|
.hotspot-nat { top: 54%; left: 22%; }
|
||||||
.hotspot-door-back-gaststube { top: 72%; left: 20%; }
|
.hotspot-door-back-gaststube { top: 74%; left: 20%; }
|
||||||
.hotspot-door-hinterland { top: 32%; left: 76%; }
|
.hotspot-door-hinterland { top: 34%; left: 76%; }
|
||||||
|
|
||||||
/* 3. Hinterland Hotspots */
|
/* 3. Hinterland Hotspots */
|
||||||
.hotspot-mane { top: 44%; left: 36%; }
|
.hotspot-mane { top: 44%; left: 36%; }
|
||||||
@@ -1074,4 +1264,170 @@ function proceedToExpress() {
|
|||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
font-family: 'Press Start 2P', monospace;
|
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;
|
||||||
|
transition: all 0.15s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.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;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||