feat(sasch40): multi-room tavern, authentic character portraits, pre-level start screens & bollerwagen redesign
|
After Width: | Height: | Size: 304 KiB |
|
Before Width: | Height: | Size: 869 KiB After Width: | Height: | Size: 656 KiB |
|
Before Width: | Height: | Size: 979 KiB After Width: | Height: | Size: 661 KiB |
|
Before Width: | Height: | Size: 924 KiB After Width: | Height: | Size: 775 KiB |
|
Before Width: | Height: | Size: 544 KiB After Width: | Height: | Size: 679 KiB |
|
After Width: | Height: | Size: 360 KiB |
|
After Width: | Height: | Size: 1.0 MiB |
|
After Width: | Height: | Size: 797 KiB |
|
After Width: | Height: | Size: 1.1 MiB |
|
Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 972 KiB |
@@ -9,7 +9,7 @@ if (!fs.existsSync(SCREENSHOTS_DIR)) {
|
||||
}
|
||||
|
||||
async function runPlaytest() {
|
||||
console.log('🚀 Starting Chrome Playtest for Saschas Kronkorken Kreuzzug...');
|
||||
console.log('🚀 Starting Comprehensive Chrome Playtest for Saschas Kronkorken Kreuzzug...');
|
||||
|
||||
const logs = [];
|
||||
const errors = [];
|
||||
@@ -60,165 +60,206 @@ async function runPlaytest() {
|
||||
|
||||
try {
|
||||
console.log('🌐 Navigating to https://sasch40.haumdaucher.de...');
|
||||
await page.goto('https://sasch40.haumdaucher.de', { waitUntil: 'networkidle0', timeout: 30000 });
|
||||
await page.goto('https://sasch40.haumdaucher.de', { waitUntil: 'networkidle0', timeout: 35000 });
|
||||
|
||||
// 1. INTRO SCREEN
|
||||
console.log('📸 Capturing Intro Screen...');
|
||||
// 1. INTRO / PREQUEL SCREEN
|
||||
console.log('📸 01: Capturing Intro Screen...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '01_intro_screen.png') });
|
||||
|
||||
// Inspect layout and controls presence in Intro
|
||||
const introControlsVisible = await page.evaluate(() => {
|
||||
const dpad = document.querySelector('.dpad');
|
||||
const intro = document.querySelector('.intro-screen');
|
||||
const chassis = document.querySelector('.gameboy-chassis');
|
||||
return {
|
||||
hasDpad: !!dpad,
|
||||
dpadOffsetHeight: dpad ? (dpad.offsetHeight > 0) : false,
|
||||
hasIntro: !!intro,
|
||||
layoutClass: chassis ? chassis.className : ''
|
||||
};
|
||||
});
|
||||
console.log('Intro Layout Check:', introControlsVisible);
|
||||
|
||||
// Click Start Button
|
||||
console.log('🖱️ Clicking Start Button...');
|
||||
await page.click('.start-btn');
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
|
||||
// 2. PROLOGUE SCREEN
|
||||
console.log('📸 Capturing Prologue Screen...');
|
||||
// 2. PROLOGUE SCREEN (Balcony Scene)
|
||||
console.log('📸 02: Capturing Prologue Screen (Verifying banner/portrait alignment)...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02_prologue_screen.png') });
|
||||
|
||||
// Advance Prologue Dialogues
|
||||
console.log('💬 Advancing Prologue Dialogues (clicking choice buttons)...');
|
||||
// Click choice 1 on 'start'
|
||||
await page.waitForSelector('.choice-btn', { timeout: 5000 });
|
||||
const firstChoice = await page.$('.choice-btn');
|
||||
if (firstChoice) {
|
||||
await firstChoice.click();
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
console.log('📸 Capturing Mop Appears Prologue Dialogue...');
|
||||
console.log('📸 02b: Capturing Prologue Mop Dialog...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '02b_prologue_mop.png') });
|
||||
}
|
||||
|
||||
// Click choice on 'mop_appears' -> triggers 'startPlatformer'
|
||||
// Advance to Platformer
|
||||
const mopChoice = await page.$('.choice-btn');
|
||||
if (mopChoice) {
|
||||
await mopChoice.click();
|
||||
await new Promise(r => setTimeout(r, 1200));
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
}
|
||||
|
||||
// 3. PLATFORMER SCREEN
|
||||
console.log('📸 Capturing Platformer (Jump & Run)...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03_platformer_screen.png') });
|
||||
// 3. PLATFORMER PRE-LEVEL START SCREEN (Act II)
|
||||
console.log('📸 03: Capturing Platformer Start Screen Modal...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03_platformer_start_modal.png') });
|
||||
|
||||
const platformerControls = await page.evaluate(() => {
|
||||
const dpad = document.querySelector('.dpad');
|
||||
const btnA = document.querySelector('.btn-a');
|
||||
const canvas = document.querySelector('canvas');
|
||||
return {
|
||||
hasDpad: !!dpad,
|
||||
hasBtnA: !!btnA,
|
||||
hasCanvas: !!canvas,
|
||||
canvasW: canvas?.width,
|
||||
canvasH: canvas?.height
|
||||
};
|
||||
});
|
||||
console.log('Platformer Controls Check:', platformerControls);
|
||||
// Click Start Act Button on the modal
|
||||
const startPlatformerBtn = await page.$('.start-act-btn');
|
||||
if (startPlatformerBtn) {
|
||||
await startPlatformerBtn.click();
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
}
|
||||
|
||||
// Test Jump & Move
|
||||
console.log('🎮 Testing Jump & Move Controls in Platformer...');
|
||||
// 4. PLATFORMER GAMEPLAY
|
||||
console.log('📸 04: Capturing Platformer Gameplay...');
|
||||
const btnA = await page.$('.btn-a');
|
||||
if (btnA) {
|
||||
await btnA.click();
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
await btnA.click();
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
const dpadRight = await page.$('.dpad-right');
|
||||
if (dpadRight) {
|
||||
await dpadRight.hover();
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '03b_platformer_jump.png') });
|
||||
await new Promise(r => setTimeout(r, 300));
|
||||
}
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '04_platformer_gameplay.png') });
|
||||
|
||||
// Directly set act to 'tavern' to test Tavern Point & Click
|
||||
console.log('🍺 Advancing to Tavern Act...');
|
||||
// 5. TAVERN: SCHANKRAUM (Gaststube)
|
||||
console.log('🍺 Advancing to Tavern Act (Schankraum)...');
|
||||
await page.evaluate(() => {
|
||||
const chassis = document.querySelector('.gameboy-chassis');
|
||||
// access pinia store from window if exposed, or trigger level finish
|
||||
// We can click the level finish or evaluate store change
|
||||
window.dispatchEvent(new CustomEvent('test-set-act', { detail: 'tavern' }));
|
||||
});
|
||||
|
||||
// Check if act switched, or use page.evaluate to trigger store change
|
||||
await page.evaluate(() => {
|
||||
// Find Pinia root if possible
|
||||
const app = document.querySelector('#app')?.__vue_app__;
|
||||
if (app && app.config?.globalProperties?.$pinia) {
|
||||
const pinia = app.config.globalProperties.$pinia;
|
||||
// find game store
|
||||
for (const [id, store] of pinia._s.entries()) {
|
||||
if (id === 'game') {
|
||||
store.setAct('tavern');
|
||||
}
|
||||
if (id === 'game') store.setAct('tavern');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log('📸 Capturing Tavern Screen...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '04_tavern_screen.png') });
|
||||
console.log('📸 05: Capturing Tavern Gaststube (Schankraum)...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05_tavern_schankraum.png') });
|
||||
|
||||
// Test Tavern Hotspots
|
||||
console.log('👥 Testing Verena Hotspot...');
|
||||
// Test Verena Hotspot
|
||||
console.log('👥 Testing Verena Dialog...');
|
||||
const verenaBtn = await page.$('.hotspot-verena');
|
||||
if (verenaBtn) {
|
||||
await verenaBtn.click();
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '05_tavern_verena_dialog.png') });
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '06_tavern_verena_dialog.png') });
|
||||
const actionBtn = await page.$('.dialog-actions .action-btn');
|
||||
if (actionBtn) await actionBtn.click();
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
|
||||
console.log('🔨 Testing Armend Hotspot...');
|
||||
// Test Armend Hotspot
|
||||
console.log('🔨 Testing Armend Dialog...');
|
||||
const armendBtn = await page.$('.hotspot-armend');
|
||||
if (armendBtn) {
|
||||
await armendBtn.click();
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '06_tavern_armend_dialog.png') });
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '07_tavern_armend_dialog.png') });
|
||||
const actionBtn = await page.$('.dialog-actions .action-btn');
|
||||
if (actionBtn) await actionBtn.click();
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
|
||||
console.log('🪞 Testing Zuluman Magic Mirror Hotspot...');
|
||||
// 6. TAVERN: MARKTPLATZ (Mario with spinning chainsaws)
|
||||
console.log('🎪 Navigating to Marktplatz...');
|
||||
const marktplatzTab = await page.$('.room-tab:nth-child(2)');
|
||||
if (marktplatzTab) {
|
||||
await marktplatzTab.click();
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log('📸 08: Capturing Marktplatz (Mario chainsaws)...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_marktplatz.png') });
|
||||
|
||||
// Click Mario
|
||||
const marioBtn = await page.$('.hotspot-mario');
|
||||
if (marioBtn) {
|
||||
await marioBtn.click();
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
console.log('📸 09: Capturing Mario Dialog...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09_tavern_mario_dialog.png') });
|
||||
const actionBtn = await page.$('.dialog-actions .action-btn');
|
||||
if (actionBtn) await actionBtn.click();
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
}
|
||||
|
||||
// 7. TAVERN: HINTERLAND (Mane hiking)
|
||||
console.log('🌲 Navigating to Hinterland...');
|
||||
const hinterlandTab = await page.$('.room-tab:nth-child(3)');
|
||||
if (hinterlandTab) {
|
||||
await hinterlandTab.click();
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log('📸 10: Capturing Hinterland (Mane hiking)...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '10_tavern_hinterland.png') });
|
||||
|
||||
// Click Mane
|
||||
const maneBtn = await page.$('.hotspot-mane');
|
||||
if (maneBtn) {
|
||||
await maneBtn.click();
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
console.log('📸 11: Capturing Mane Dialog...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '11_tavern_mane_dialog.png') });
|
||||
const actionBtn = await page.$('.dialog-actions .action-btn');
|
||||
if (actionBtn) await actionBtn.click();
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
}
|
||||
|
||||
// 8. TAVERN: SECRET HINTERZIMMER (VIP Rave via Secret Tap)
|
||||
console.log('🗝️ Returning to Schankraum to trigger Secret Tap...');
|
||||
const schankraumTab = await page.$('.room-tab:nth-child(1)');
|
||||
if (schankraumTab) {
|
||||
await schankraumTab.click();
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
}
|
||||
|
||||
console.log('🪩 Triggering Secret Tap Easter Egg...');
|
||||
const secretTap = await page.$('.secret-tap-hotspot');
|
||||
if (secretTap) {
|
||||
await secretTap.click();
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log('📸 12: Capturing Secret VIP Hinterzimmer...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '12_tavern_hinterzimmer_vip.png') });
|
||||
|
||||
// Click Pü in Hinterzimmer
|
||||
const pueBtn = await page.$('.hotspot-pue');
|
||||
if (pueBtn) {
|
||||
await pueBtn.click();
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
console.log('📸 13: Capturing Pü Dialog...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '13_tavern_pue_dialog.png') });
|
||||
const actionBtn = await page.$('.dialog-actions .action-btn');
|
||||
if (actionBtn) await actionBtn.click();
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
}
|
||||
|
||||
// 9. ZULUMAN MAGIC MIRROR & GSTANZL DUEL
|
||||
console.log('🪞 Testing Zuluman Magic Mirror...');
|
||||
const backToGaststube = await page.$('.hotspot-door-back-vip');
|
||||
if (backToGaststube) {
|
||||
await backToGaststube.click();
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
}
|
||||
const mirrorBtn = await page.$('.hotspot-mirror');
|
||||
if (mirrorBtn) {
|
||||
await mirrorBtn.click();
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '07_tavern_zuluman_mirror.png') });
|
||||
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, 500));
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
|
||||
console.log('⚔️ Testing Gstanzl-Duel...');
|
||||
const duelBtn = await page.$('.duel-trigger');
|
||||
if (duelBtn) {
|
||||
await duelBtn.click();
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '08_tavern_gstanzl_duel.png') });
|
||||
|
||||
// Answer Gstanzl question
|
||||
const correctOption = await page.$('.option-btn');
|
||||
if (correctOption) {
|
||||
await correctOption.click();
|
||||
await new Promise(r => setTimeout(r, 600));
|
||||
await new Promise(r => setTimeout(r, 700));
|
||||
console.log('📸 15: Capturing Gstanzl Duel...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '15_tavern_gstanzl_duel.png') });
|
||||
const firstAnswer = await page.$('.choice-btn');
|
||||
if (firstAnswer) {
|
||||
await firstAnswer.click();
|
||||
await new Promise(r => setTimeout(r, 500));
|
||||
}
|
||||
// Close duel if won or modal
|
||||
const actionBtn = await page.$('.duel-box .action-btn');
|
||||
if (actionBtn) await actionBtn.click();
|
||||
}
|
||||
|
||||
// 4. PARTY EXPRESS (Act IV)
|
||||
// 10. PARTY EXPRESS (Act IV)
|
||||
console.log('🚂 Advancing to Party Express (Act IV)...');
|
||||
await page.evaluate(() => {
|
||||
const app = document.querySelector('#app')?.__vue_app__;
|
||||
@@ -230,10 +271,26 @@ async function runPlaytest() {
|
||||
}
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 1000));
|
||||
console.log('📸 Capturing Party Express...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '09_party_express.png') });
|
||||
console.log('📸 16: Capturing Party Express Start Screen Modal...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '16_party_express_start_modal.png') });
|
||||
|
||||
// 5. FINALE / CERTIFICATE (Act V)
|
||||
// Start Party Express
|
||||
const startExpressBtn = await page.$('.start-act-btn');
|
||||
if (startExpressBtn) {
|
||||
await startExpressBtn.click();
|
||||
await new Promise(r => setTimeout(r, 800));
|
||||
}
|
||||
|
||||
console.log('📸 17: Capturing Party Express Gameplay (Redesigned Bollerwagen)...');
|
||||
// Serve beer
|
||||
const throwBeerBtn = await page.$('.throw-beer-btn');
|
||||
if (throwBeerBtn) {
|
||||
await throwBeerBtn.click();
|
||||
await new Promise(r => setTimeout(r, 400));
|
||||
}
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '17_party_express_gameplay.png') });
|
||||
|
||||
// 11. FINALE / CERTIFICATE (Act V)
|
||||
console.log('👑 Advancing to Finale Certificate (Act V)...');
|
||||
await page.evaluate(() => {
|
||||
const app = document.querySelector('#app')?.__vue_app__;
|
||||
@@ -241,7 +298,7 @@ async function runPlaytest() {
|
||||
const pinia = app.config.globalProperties.$pinia;
|
||||
for (const [id, store] of pinia._s.entries()) {
|
||||
if (id === 'game') {
|
||||
store.addKronkorken(25);
|
||||
store.addKronkorken(35);
|
||||
store.serveGuest();
|
||||
store.serveGuest();
|
||||
store.setAct('finale');
|
||||
@@ -250,10 +307,10 @@ async function runPlaytest() {
|
||||
}
|
||||
});
|
||||
await new Promise(r => setTimeout(r, 1500));
|
||||
console.log('📸 Capturing Certificate Modal...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '10_certificate_screen.png') });
|
||||
console.log('📸 18: Capturing Certificate Modal...');
|
||||
await page.screenshot({ path: path.join(SCREENSHOTS_DIR, '18_certificate_screen.png') });
|
||||
|
||||
// Check images in document
|
||||
// Verify all images loaded properly
|
||||
const brokenImages = await page.evaluate(() => {
|
||||
const imgs = Array.from(document.querySelectorAll('img'));
|
||||
return imgs
|
||||
@@ -265,7 +322,7 @@ async function runPlaytest() {
|
||||
console.log('Total Console Logs:', logs.length);
|
||||
console.log('Console Errors:', errors);
|
||||
console.log('Failed Requests:', failedRequests);
|
||||
console.log('Broken Images on Certificate Page:', brokenImages);
|
||||
console.log('Broken Images:', brokenImages);
|
||||
|
||||
const report = {
|
||||
timestamp: new Date().toISOString(),
|
||||
|
||||
@@ -43,9 +43,9 @@ function toggleInventory() {
|
||||
<span></span><span></span><span></span>
|
||||
</div>
|
||||
<div class="chassis-brand">
|
||||
<span class="brand-text">KING SASCHA XL</span>
|
||||
<span class="brand-text">KÖNIG SASCHAS KRONKORKENKREUZZUG</span>
|
||||
<span v-if="store.currentAct !== 'intro'" class="brand-act">
|
||||
{{ store.currentAct === 'platformer' ? '• AKTE II' : store.currentAct === 'tavern' ? '• AKTE III' : store.currentAct === 'party_express' ? '• AKTE IV' : '' }}
|
||||
{{ 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>
|
||||
</div>
|
||||
<button class="sound-toggle" @click="toggleAudio">
|
||||
@@ -204,20 +204,23 @@ function toggleInventory() {
|
||||
.chassis-brand {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
gap: 4px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.brand-text {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 9px;
|
||||
font-size: 6.8px;
|
||||
color: #ffd700;
|
||||
letter-spacing: 0.5px;
|
||||
letter-spacing: 0.2px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.brand-act {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7px;
|
||||
font-size: 6px;
|
||||
color: #f59e0b;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.speaker-grill-left {
|
||||
|
||||
@@ -5,13 +5,19 @@ import { sound } from '../audio/synthAudio'
|
||||
|
||||
const store = useGameStore()
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const showExpressIntro = ref(true)
|
||||
let animId: number | null = null
|
||||
|
||||
let wagonX = 60
|
||||
let wagonY = 330
|
||||
let speed = 3.5
|
||||
let wagonX = 70
|
||||
let wagonY = 325
|
||||
let speed = 3.2
|
||||
let distance = 0
|
||||
const maxDistance = 2000
|
||||
const maxDistance = 2100
|
||||
|
||||
function startExpress() {
|
||||
sound.playDialogueBlip()
|
||||
showExpressIntro.value = false
|
||||
}
|
||||
|
||||
interface Guest {
|
||||
id: string
|
||||
@@ -24,18 +30,18 @@ interface Guest {
|
||||
let guests: Guest[] = [
|
||||
{ id: 'hansi', x: 300, name: 'Hansi 🧔', served: false, avatarColor: '#6366f1' },
|
||||
{ id: 'viktor', x: 550, name: 'Viktor (Vampir) 🧛', served: false, avatarColor: '#881337' },
|
||||
{ id: 'albin', x: 800, name: 'Albin (Fitness) 🏋️', served: false, avatarColor: '#0284c7' },
|
||||
{ id: 'fabian', x: 1050, name: 'Fabian (Brauer) 🍺', served: false, avatarColor: '#d97706' },
|
||||
{ id: 'maierflo', x: 1300, name: 'Maier Flo (Ton) 🎧', served: false, avatarColor: '#7c3aed' },
|
||||
{ id: 'guate', x: 1550, name: 'Guatemalteke 🌋', served: false, avatarColor: '#059669' },
|
||||
{ id: 'markus_nat', x: 1800, name: 'Markus & Nathalie 🥂', served: false, avatarColor: '#db2777' }
|
||||
{ id: 'albin', x: 820, name: 'Albin (Fitness) 🏋️', served: false, avatarColor: '#0284c7' },
|
||||
{ id: 'fabian', x: 1100, name: 'Fabian (Brauer) 🍺', served: false, avatarColor: '#d97706' },
|
||||
{ id: 'maierflo', x: 1380, name: 'Maier Flo (DJ) 🎧', served: false, avatarColor: '#7c3aed' },
|
||||
{ id: 'guate', x: 1650, name: 'Guatemalteke 🌋', served: false, avatarColor: '#059669' },
|
||||
{ id: 'markus_nat', x: 1920, name: 'Markus & Nathalie 🥂', served: false, avatarColor: '#db2777' }
|
||||
]
|
||||
|
||||
function throwBeer() {
|
||||
sound.playGulp()
|
||||
// Check if any guest is near
|
||||
// Check if any guest is near the wagon
|
||||
guests.forEach(g => {
|
||||
if (!g.served && Math.abs(distance + wagonX - g.x) < 90) {
|
||||
if (!g.served && Math.abs(distance + wagonX - g.x) < 95) {
|
||||
g.served = true
|
||||
store.serveGuest()
|
||||
}
|
||||
@@ -43,6 +49,8 @@ function throwBeer() {
|
||||
}
|
||||
|
||||
function updateGame() {
|
||||
if (showExpressIntro.value) return // Don't advance while reading intro!
|
||||
|
||||
distance += speed
|
||||
|
||||
if (distance >= maxDistance) {
|
||||
@@ -54,43 +62,68 @@ function updateGame() {
|
||||
function draw(ctx: CanvasRenderingContext2D, w: number, h: number) {
|
||||
ctx.clearRect(0, 0, w, h)
|
||||
|
||||
// Sunset Party Sky
|
||||
// Sunset Party Sky Gradient
|
||||
const grad = ctx.createLinearGradient(0, 0, 0, h)
|
||||
grad.addColorStop(0, '#31103f')
|
||||
grad.addColorStop(0.5, '#7a2048')
|
||||
grad.addColorStop(0.8, '#c2410c')
|
||||
grad.addColorStop(0, '#1e082b')
|
||||
grad.addColorStop(0.4, '#4a154b')
|
||||
grad.addColorStop(0.7, '#831843')
|
||||
grad.addColorStop(0.9, '#c2410c')
|
||||
grad.addColorStop(1, '#f59e0b')
|
||||
ctx.fillStyle = grad
|
||||
ctx.fillRect(0, 0, w, h)
|
||||
|
||||
// Distant party silhouettes with laser beams
|
||||
// Sweeping Festival Lasers & Party Beams
|
||||
const now = Date.now() * 0.003
|
||||
ctx.strokeStyle = `hsl(${(now * 60) % 360}, 100%, 75%)`
|
||||
ctx.lineWidth = 2
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(w / 2, 100)
|
||||
ctx.lineTo(w * ((Math.sin(now) + 1) / 2), 350)
|
||||
ctx.moveTo(w / 2, 100)
|
||||
ctx.lineTo(w * ((Math.cos(now) + 1) / 2), 350)
|
||||
ctx.stroke()
|
||||
|
||||
// Cobblestone road
|
||||
ctx.fillStyle = '#292524'
|
||||
ctx.fillRect(0, 350, w, 130)
|
||||
ctx.fillStyle = '#44403c'
|
||||
for (let r = 0; r < w + 30; r += 30) {
|
||||
ctx.fillRect((r - (distance % 30)), 350, 24, 6)
|
||||
ctx.save()
|
||||
for (let b = 0; b < 3; b++) {
|
||||
const angleOffset = b * (Math.PI / 3)
|
||||
const targetX = w * 0.5 + Math.sin(now + angleOffset) * (w * 0.45)
|
||||
ctx.strokeStyle = `hsla(${((now * 50) + b * 90) % 360}, 100%, 75%, 0.45)`
|
||||
ctx.lineWidth = 3
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(w * 0.5, 90)
|
||||
ctx.lineTo(targetX, 350)
|
||||
ctx.stroke()
|
||||
}
|
||||
ctx.restore()
|
||||
|
||||
// Medieval Castle silhouette on the horizon
|
||||
ctx.fillStyle = '#170c14'
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(0, 310)
|
||||
ctx.lineTo(30, 290); ctx.lineTo(50, 290); ctx.lineTo(50, 310)
|
||||
ctx.lineTo(110, 310); ctx.lineTo(130, 275); ctx.lineTo(160, 275); ctx.lineTo(170, 310)
|
||||
ctx.lineTo(240, 310); ctx.lineTo(255, 295); ctx.lineTo(275, 295); ctx.lineTo(290, 310)
|
||||
ctx.lineTo(w, 310); ctx.lineTo(w, 350); ctx.lineTo(0, 350)
|
||||
ctx.fill()
|
||||
|
||||
// Cobblestone Highway
|
||||
ctx.fillStyle = '#1c1917'
|
||||
ctx.fillRect(0, 350, w, 130)
|
||||
ctx.fillStyle = '#292524'
|
||||
for (let r = 0; r < w + 40; r += 32) {
|
||||
const rx = (r - (distance % 32))
|
||||
ctx.fillRect(rx, 352, 26, 6)
|
||||
ctx.fillRect(rx + 12, 362, 26, 6)
|
||||
}
|
||||
// Curbs / Grass edge
|
||||
ctx.fillStyle = '#365314'
|
||||
ctx.fillRect(0, 348, w, 3)
|
||||
|
||||
// Draw Guests along the road
|
||||
guests.forEach(g => {
|
||||
const screenX = g.x - distance
|
||||
if (screenX > -60 && screenX < w + 60) {
|
||||
// Custom sprite details per guest
|
||||
if (screenX > -70 && screenX < w + 70) {
|
||||
// Guest shadow
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.4)'
|
||||
ctx.beginPath()
|
||||
ctx.ellipse(screenX + 10, 350, 14, 5, 0, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
if (g.id === 'viktor') {
|
||||
// Vampire cape
|
||||
// Red velvet vampire cape
|
||||
ctx.fillStyle = '#4c0519'
|
||||
ctx.fillRect(screenX - 4, 305, 28, 42)
|
||||
ctx.fillRect(screenX - 5, 305, 30, 44)
|
||||
}
|
||||
|
||||
// Guest body
|
||||
@@ -107,12 +140,11 @@ function draw(ctx: CanvasRenderingContext2D, w: number, h: number) {
|
||||
if (g.id === 'hansi') {
|
||||
// Moustache
|
||||
ctx.fillStyle = '#451a03'
|
||||
ctx.fillRect(screenX + 6, 296, 8, 2.5)
|
||||
ctx.fillRect(screenX + 6, 297, 8, 2.5)
|
||||
} else if (g.id === 'viktor') {
|
||||
// Fangs
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.fillRect(screenX + 8, 297, 2, 3)
|
||||
ctx.fillRect(screenX + 12, 297, 2, 3)
|
||||
// Sunglasses & fangs
|
||||
ctx.fillStyle = '#0f172a'
|
||||
ctx.fillRect(screenX + 6, 293, 8, 3)
|
||||
} else if (g.id === 'albin') {
|
||||
// Gym Headband
|
||||
ctx.fillStyle = '#38bdf8'
|
||||
@@ -159,98 +191,209 @@ function draw(ctx: CanvasRenderingContext2D, w: number, h: number) {
|
||||
ctx.fillText('🍻 PROST!', screenX + 10, 264)
|
||||
} else {
|
||||
ctx.fillStyle = '#fbbf24'
|
||||
ctx.font = '9px "Press Start 2P", monospace'
|
||||
ctx.font = '8px "Press Start 2P", monospace'
|
||||
ctx.fillText('DURST!', screenX + 10, 264)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Draw Royal Party Wagon (Bollerwagen)
|
||||
// ================= REDESIGNED BOLLERWAGEN (RETRO PIXEL ART) =================
|
||||
const wx = wagonX
|
||||
const wy = wagonY
|
||||
const bob = Math.sin(Date.now() * 0.015) * 2
|
||||
const bob = showExpressIntro.value ? 0 : Math.sin(Date.now() * 0.015) * 2.5
|
||||
|
||||
// Wagon wooden bed
|
||||
ctx.fillStyle = '#78350f'
|
||||
ctx.fillRect(wx - 20, wy + bob, 95, 24)
|
||||
// 1. Shadow underneath the wagon
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.5)'
|
||||
ctx.beginPath()
|
||||
ctx.ellipse(wx + 30, wy + 32, 54, 8, 0, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
// 2. Heavy Rustic Oak Timber Bed with Iron Corner Brackets
|
||||
ctx.fillStyle = '#451a03' // Dark wood outline
|
||||
ctx.fillRect(wx - 26, wy + bob, 114, 26)
|
||||
ctx.fillStyle = '#78350f' // Main oak plank
|
||||
ctx.fillRect(wx - 24, wy + bob + 2, 110, 22)
|
||||
ctx.fillStyle = '#92400e' // Highlight upper plank
|
||||
ctx.fillRect(wx - 24, wy + bob + 4, 110, 8)
|
||||
|
||||
// Iron bands and golden rivets
|
||||
ctx.fillStyle = '#1c1917'
|
||||
ctx.fillRect(wx - 26, wy + bob, 6, 26)
|
||||
ctx.fillRect(wx + 28, wy + bob, 5, 26)
|
||||
ctx.fillRect(wx + 82, wy + bob, 6, 26)
|
||||
ctx.fillStyle = '#ffd700'
|
||||
;[wx - 23, wx + 30, wx + 85].forEach(rx => {
|
||||
ctx.fillRect(rx, wy + bob + 4, 2, 2)
|
||||
ctx.fillRect(rx, wy + bob + 14, 2, 2)
|
||||
})
|
||||
|
||||
// 3. Side-Mounted Hopfen Beer Keg with Brass Faucet
|
||||
ctx.fillStyle = '#292524'
|
||||
ctx.fillRect(wx + 5, wy + bob - 12, 22, 14)
|
||||
ctx.fillStyle = '#b45309'
|
||||
ctx.fillRect(wx - 18, wy + bob + 3, 91, 18)
|
||||
ctx.fillRect(wx + 6, wy + bob - 11, 20, 12)
|
||||
ctx.fillStyle = '#ffd700' // Faucet
|
||||
ctx.fillRect(wx + 26, wy + bob - 6, 5, 3)
|
||||
ctx.fillRect(wx + 29, wy + bob - 3, 2, 4)
|
||||
|
||||
// Wooden spoke wheels
|
||||
const wheelSpin = (distance * 0.1) % (Math.PI * 2)
|
||||
;[-6, 60].forEach(wheelOffset => {
|
||||
// 4. Giant 135-BPM Bass Subwoofer on the rear
|
||||
ctx.fillStyle = '#09090b'
|
||||
ctx.fillRect(wx - 22, wy - 36 + bob, 26, 38)
|
||||
ctx.strokeStyle = '#ffd700'
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.strokeRect(wx - 22, wy - 36 + bob, 26, 38)
|
||||
|
||||
// Pulsing Bass Speaker Cone
|
||||
const bassKick = Math.abs(Math.sin(Date.now() * 0.02) * 5)
|
||||
ctx.fillStyle = '#e11d48'
|
||||
ctx.beginPath()
|
||||
ctx.arc(wx - 9, wy - 17 + bob, 8 + bassKick, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.strokeStyle = '#f43f5e'
|
||||
ctx.lineWidth = 2
|
||||
ctx.stroke()
|
||||
// Speaker center cap
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.beginPath()
|
||||
ctx.arc(wx - 9, wy - 17 + bob, 3, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
// 5. Hof-DJ Mixer Console & Turntable on the front
|
||||
ctx.fillStyle = '#18181b'
|
||||
ctx.fillRect(wx + 46, wy - 16 + bob, 34, 18)
|
||||
ctx.strokeStyle = '#38bdf8'
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeRect(wx + 46, wy - 16 + bob, 34, 18)
|
||||
|
||||
// Vinyl Turntable with spinning vinyl
|
||||
ctx.fillStyle = '#0a0a0a'
|
||||
ctx.beginPath()
|
||||
ctx.arc(wx + 60, wy - 7 + bob, 7, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.fillStyle = '#ef4444' // Center vinyl label
|
||||
ctx.beginPath()
|
||||
ctx.arc(wx + 60, wy - 7 + bob, 2.5, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
// Flashing Mixer LEDs
|
||||
const ledFlash = Math.floor(Date.now() / 150) % 3
|
||||
ctx.fillStyle = ledFlash === 0 ? '#22c55e' : '#15803d'
|
||||
ctx.fillRect(wx + 72, wy - 12 + bob, 3, 3)
|
||||
ctx.fillStyle = ledFlash === 1 ? '#eab308' : '#854d0e'
|
||||
ctx.fillRect(wx + 72, wy - 8 + bob, 3, 3)
|
||||
ctx.fillStyle = ledFlash === 2 ? '#ef4444' : '#7f1d1d'
|
||||
ctx.fillRect(wx + 72, wy - 4 + bob, 3, 3)
|
||||
|
||||
// 6. KING SASCHA standing proud on the wagon
|
||||
// Flowing Royal Red Velvet Cape
|
||||
ctx.fillStyle = '#b91c1c'
|
||||
ctx.fillRect(wx + 10, wy - 36 + bob, 24, 38)
|
||||
|
||||
// Royal Blue Tunic with Gold Trim
|
||||
ctx.fillStyle = '#1d4ed8'
|
||||
ctx.fillRect(wx + 15, wy - 32 + bob, 18, 24)
|
||||
ctx.fillStyle = '#d4af37'
|
||||
ctx.fillRect(wx + 22, wy - 32 + bob, 4, 24)
|
||||
|
||||
// Face & Beard
|
||||
ctx.fillStyle = '#fed7aa'
|
||||
ctx.fillRect(wx + 16, wy - 46 + bob, 16, 14)
|
||||
// Short Blonde/Brown Hair (as requested)
|
||||
ctx.fillStyle = '#b45309'
|
||||
ctx.fillRect(wx + 15, wy - 50 + bob, 18, 5)
|
||||
|
||||
// Deep Black Royal Sunglasses
|
||||
ctx.fillStyle = '#0f172a'
|
||||
ctx.fillRect(wx + 18, wy - 43 + bob, 6, 4)
|
||||
ctx.fillRect(wx + 25, wy - 43 + bob, 6, 4)
|
||||
ctx.fillRect(wx + 23, wy - 42 + bob, 3, 2) // Bridge
|
||||
|
||||
// Golden Imperial Crown with Jewels
|
||||
ctx.fillStyle = '#facc15'
|
||||
ctx.fillRect(wx + 15, wy - 53 + bob, 18, 5)
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(wx + 15, wy - 53 + bob); ctx.lineTo(wx + 15, wy - 58 + bob); ctx.lineTo(wx + 19, wy - 53 + bob)
|
||||
ctx.lineTo(wx + 24, wy - 60 + bob); ctx.lineTo(wx + 29, wy - 53 + bob); ctx.lineTo(wx + 33, wy - 58 + bob)
|
||||
ctx.lineTo(wx + 33, wy - 53 + bob)
|
||||
ctx.fill()
|
||||
// Crown jewels
|
||||
ctx.fillStyle = '#dc2626'
|
||||
ctx.fillRect(wx + 23, wy - 54 + bob, 2, 2)
|
||||
|
||||
// Raised Beer Stein in Right Hand (Prost!)
|
||||
ctx.fillStyle = '#f59e0b' // Golden Beer
|
||||
ctx.fillRect(wx + 35, wy - 44 + bob, 8, 12)
|
||||
ctx.fillStyle = '#ffffff' // White froth/foam
|
||||
ctx.fillRect(wx + 34, wy - 47 + bob, 10, 4)
|
||||
ctx.fillStyle = '#cbd5e1' // Glass handle
|
||||
ctx.fillRect(wx + 42, wy - 42 + bob, 3, 7)
|
||||
|
||||
// 7. Detailed Wooden Spoke Wheels with Golden Brass Caps
|
||||
const wheelSpin = (distance * 0.08) % (Math.PI * 2)
|
||||
;[-8, 68].forEach(wheelOffset => {
|
||||
ctx.save()
|
||||
ctx.translate(wx + wheelOffset, wy + 24)
|
||||
ctx.translate(wx + wheelOffset, wy + 26)
|
||||
ctx.rotate(wheelSpin)
|
||||
ctx.fillStyle = '#451a03'
|
||||
|
||||
// Outer dark tyre
|
||||
ctx.fillStyle = '#1c1917'
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, 13, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
// Inner wooden rim
|
||||
ctx.fillStyle = '#78350f'
|
||||
ctx.beginPath()
|
||||
ctx.arc(0, 0, 11, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
|
||||
// Brass rim edge
|
||||
ctx.strokeStyle = '#ffd700'
|
||||
ctx.lineWidth = 2.5
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.stroke()
|
||||
// Spokes
|
||||
|
||||
// 8 Spokes
|
||||
ctx.strokeStyle = '#b45309'
|
||||
ctx.lineWidth = 1.5
|
||||
for (let s = 0; s < 4; s++) {
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(-11 * Math.cos(s * Math.PI / 4), -11 * Math.sin(s * Math.PI / 4))
|
||||
ctx.lineTo(11 * Math.cos(s * Math.PI / 4), 11 * Math.sin(s * Math.PI / 4))
|
||||
ctx.stroke()
|
||||
}
|
||||
|
||||
// Center brass axle cap
|
||||
ctx.fillStyle = '#facc15'
|
||||
ctx.beginPath()
|
||||
ctx.moveTo(-11, 0); ctx.lineTo(11, 0)
|
||||
ctx.moveTo(0, -11); ctx.lineTo(0, 11)
|
||||
ctx.stroke()
|
||||
ctx.arc(0, 0, 4, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.restore()
|
||||
})
|
||||
|
||||
// Sascha standing on wagon with crown, sunglasses & beer mug
|
||||
ctx.fillStyle = '#dc2626' // Cape
|
||||
ctx.fillRect(wx + 12, wy - 30 + bob, 22, 34)
|
||||
ctx.fillStyle = '#1d4ed8' // Tunic
|
||||
ctx.fillRect(wx + 16, wy - 28 + bob, 16, 22)
|
||||
ctx.fillStyle = '#fcd34d' // Face
|
||||
ctx.fillRect(wx + 17, wy - 42 + bob, 14, 14)
|
||||
// Sunglasses
|
||||
ctx.fillStyle = '#0f172a'
|
||||
ctx.fillRect(wx + 19, wy - 38 + bob, 5, 3)
|
||||
ctx.fillRect(wx + 25, wy - 38 + bob, 5, 3)
|
||||
// Crown
|
||||
ctx.fillStyle = '#facc15'
|
||||
ctx.fillRect(wx + 16, wy - 48 + bob, 16, 6)
|
||||
|
||||
// Giant Bass Speaker on the back of the wagon
|
||||
ctx.fillStyle = '#18181b'
|
||||
ctx.fillRect(wx - 16, wy - 34 + bob, 24, 34)
|
||||
ctx.fillStyle = '#f43f5e'
|
||||
ctx.beginPath()
|
||||
const bassPulse = Math.abs(Math.sin(Date.now() * 0.02) * 4)
|
||||
ctx.arc(wx - 4, wy - 18 + bob, 8 + bassPulse, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.strokeStyle = '#ffd700'
|
||||
ctx.lineWidth = 1.5
|
||||
ctx.stroke()
|
||||
|
||||
// DJ Console in front
|
||||
ctx.fillStyle = '#27272a'
|
||||
ctx.fillRect(wx + 44, wy - 14 + bob, 26, 16)
|
||||
// Turntable
|
||||
ctx.fillStyle = '#09090b'
|
||||
ctx.beginPath()
|
||||
ctx.arc(wx + 57, wy - 6 + bob, 6, 0, Math.PI * 2)
|
||||
ctx.fill()
|
||||
ctx.fillStyle = '#ef4444'
|
||||
ctx.fillRect(wx + 56, wy - 7 + bob, 2, 2)
|
||||
|
||||
// Hof-DJs Banner
|
||||
// Top Banner HUD
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.font = 'bold 8.5px "Press Start 2P", monospace'
|
||||
ctx.font = 'bold 8px "Press Start 2P", monospace'
|
||||
ctx.textAlign = 'center'
|
||||
ctx.fillText('🎧 135 BPM PARTY EXPRESS 🎧', w / 2, 24)
|
||||
ctx.fillText('AKT IV: DER 135-BPM-FEST-KONVOI', w / 2, 22)
|
||||
|
||||
// Progress Bar
|
||||
const progress = Math.min(1, distance / maxDistance)
|
||||
const barMargin = 24
|
||||
const barMargin = 20
|
||||
const barWidth = w - barMargin * 2
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.6)'
|
||||
ctx.fillRect(barMargin, 34, barWidth, 8)
|
||||
ctx.fillStyle = 'rgba(0,0,0,0.7)'
|
||||
ctx.fillRect(barMargin, 32, barWidth, 10)
|
||||
ctx.strokeStyle = '#ffd700'
|
||||
ctx.lineWidth = 1
|
||||
ctx.strokeRect(barMargin, 32, barWidth, 10)
|
||||
|
||||
// Progress fill
|
||||
ctx.fillStyle = '#22c55e'
|
||||
ctx.fillRect(barMargin, 34, barWidth * progress, 8)
|
||||
ctx.fillRect(barMargin + 1, 33, (barWidth - 2) * progress, 8)
|
||||
|
||||
// Moving Keg Icon on the progress bar
|
||||
const kegX = barMargin + (barWidth * progress)
|
||||
ctx.font = '10px sans-serif'
|
||||
ctx.fillText('🍺', kegX, 39)
|
||||
}
|
||||
|
||||
function loop() {
|
||||
@@ -281,6 +424,38 @@ onUnmounted(() => {
|
||||
</button>
|
||||
<span class="express-hint">Tippe, wenn du an deinen Freunden vorbeifährst!</span>
|
||||
</div>
|
||||
|
||||
<!-- Pre-Level Start Screen Modal -->
|
||||
<div v-if="showExpressIntro" class="level-intro-modal">
|
||||
<div class="intro-card">
|
||||
<div class="intro-act-tag">👑 AKT IV: FAHRGESCHÄFT</div>
|
||||
<h2 class="intro-card-title">DER 135-BPM-FEST-KONVOI</h2>
|
||||
<p class="intro-card-story">
|
||||
Der Hof-Bass wummert, der Bollerwagen ist poliert!<br />
|
||||
König Sascha zieht mit der Reichs-Soundanlage über die Festmeile von Haumdaucher. Deine Freunde stehen durstig am Straßenrand!
|
||||
</p>
|
||||
<div class="mission-callout">
|
||||
<span class="callout-icon">🎯</span>
|
||||
<div class="callout-text">
|
||||
<strong>KÖNIGLICHE MISSION:</strong><br />
|
||||
Versorge Hansi, Viktor, Albin, Fabian, Flo Maier & das Gefolge mit kühlem Gerstensaft im Takt des Basses!
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls-overview">
|
||||
<div class="ctrl-item">
|
||||
<span class="ctrl-badge">🍺 BIER SCHENKEN</span>
|
||||
<span class="ctrl-label">Tippe den Button, sobald du an einem Freund bist</span>
|
||||
</div>
|
||||
<div class="ctrl-item">
|
||||
<span class="ctrl-badge">🏆 TIMING</span>
|
||||
<span class="ctrl-label">Jedes glückliche Gefolge bringt fette Highscore-Punkte!</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="start-act-btn" @click="startExpress">
|
||||
🚀 PARTY-EXPRESS STARTEN!
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -320,7 +495,7 @@ onUnmounted(() => {
|
||||
border: 2px solid #fff;
|
||||
border-radius: 8px;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 9px;
|
||||
font-size: 8.5px;
|
||||
padding: 10px 14px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.7);
|
||||
@@ -329,7 +504,7 @@ onUnmounted(() => {
|
||||
|
||||
@keyframes pulse {
|
||||
0%, 100% { transform: scale(1); }
|
||||
50% { transform: scale(1.05); }
|
||||
50% { transform: scale(1.04); }
|
||||
}
|
||||
|
||||
.express-hint {
|
||||
@@ -338,4 +513,125 @@ onUnmounted(() => {
|
||||
color: #fef08a;
|
||||
text-shadow: 1px 1px 2px #000;
|
||||
}
|
||||
|
||||
/* Pre-Level Start Screen Modal */
|
||||
.level-intro-modal {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
z-index: 50;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.intro-card {
|
||||
background: #231209;
|
||||
border: 3px double #d4af37;
|
||||
border-radius: 10px;
|
||||
padding: 16px 14px;
|
||||
max-width: 320px;
|
||||
width: 100%;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.95);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.intro-act-tag {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
color: #f59e0b;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.intro-card-title {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 9.5px;
|
||||
color: #ffd700;
|
||||
margin: 0;
|
||||
text-shadow: 1px 1px 0 #000;
|
||||
}
|
||||
|
||||
.intro-card-story {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 13px;
|
||||
color: #fef08a;
|
||||
line-height: 1.35;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mission-callout {
|
||||
background: #451a03;
|
||||
border-left: 3px solid #22c55e;
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.callout-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.callout-text {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 12.5px;
|
||||
color: #bbf7d0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.controls-overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
background: #170c06;
|
||||
border: 1px solid #78350f;
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.ctrl-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.ctrl-badge {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.ctrl-label {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 11.5px;
|
||||
color: #fed7aa;
|
||||
}
|
||||
|
||||
.start-act-btn {
|
||||
background: linear-gradient(180deg, #dc2626 0%, #991b1b 100%);
|
||||
color: #fff;
|
||||
border: 2px solid #ffd700;
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 8px;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.7);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.start-act-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,8 +5,14 @@ import { sound } from '../audio/synthAudio'
|
||||
|
||||
const store = useGameStore()
|
||||
const canvasRef = ref<HTMLCanvasElement | null>(null)
|
||||
const showLevelIntro = ref(true)
|
||||
let animationFrameId: number | null = null
|
||||
|
||||
function startLevel() {
|
||||
sound.playDialogueBlip()
|
||||
showLevelIntro.value = false
|
||||
}
|
||||
|
||||
// Game state
|
||||
interface Platform {
|
||||
x: number
|
||||
@@ -428,7 +434,9 @@ function drawGame(ctx: CanvasRenderingContext2D, width: number, height: number)
|
||||
}
|
||||
|
||||
function gameLoop() {
|
||||
updatePhysics()
|
||||
if (!showLevelIntro.value) {
|
||||
updatePhysics()
|
||||
}
|
||||
if (canvasRef.value) {
|
||||
const ctx = canvasRef.value.getContext('2d')
|
||||
if (ctx) {
|
||||
@@ -481,10 +489,42 @@ onUnmounted(() => {
|
||||
<div class="platformer-hud-top">
|
||||
<div class="hud-korken-badge">👑 {{ store.kronkorken }} KORKEN</div>
|
||||
<div class="level-overlay">
|
||||
<span class="level-title">AKTE II: DIE HOPFEN-PFADE</span>
|
||||
<span class="level-title">AKT II: DIE HOPFEN-PFADE</span>
|
||||
<span class="level-hint">Tippe oder drücke SPRUNG zum Hüpfen</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Pre-Level Start Screen Modal -->
|
||||
<div v-if="showLevelIntro" class="level-intro-modal">
|
||||
<div class="intro-card">
|
||||
<div class="intro-act-tag">👑 AKT II: JUMP & RUN</div>
|
||||
<h2 class="intro-card-title">DIE HOPFEN-PFADE</h2>
|
||||
<p class="intro-card-story">
|
||||
Hört die Kunde, edler König Sascha!<br />
|
||||
Deine Untertanen dürsten im ganzen Herzogtum nach frischem Gerstensaft. Doch ohne deine 40 heiligen Kronkorken bleiben die Fässer versiegelt!
|
||||
</p>
|
||||
<div class="mission-callout">
|
||||
<span class="callout-icon">🎯</span>
|
||||
<div class="callout-text">
|
||||
<strong>KÖNIGLICHE MISSION:</strong><br />
|
||||
Hüpfe über Ziegeldächer und Holzgerüste. Sammle mindestens <strong>5 Kronkorken</strong>, um das Tor zur Taverne zu entriegeln!
|
||||
</div>
|
||||
</div>
|
||||
<div class="controls-overview">
|
||||
<div class="ctrl-item">
|
||||
<span class="ctrl-badge">🕹️ STEUERKREUZ</span>
|
||||
<span class="ctrl-label">Laufen (Links / Rechts)</span>
|
||||
</div>
|
||||
<div class="ctrl-item">
|
||||
<span class="ctrl-badge">🅰️ A-TASTE / TIPPE</span>
|
||||
<span class="ctrl-label">Sprung (auch Doppel-Sprung!)</span>
|
||||
</div>
|
||||
</div>
|
||||
<button class="start-act-btn" @click="startLevel">
|
||||
🚀 AUF DIE DÄCHER! (START)
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -550,4 +590,124 @@ onUnmounted(() => {
|
||||
color: #fef08a;
|
||||
text-shadow: 1px 1px 0 #000;
|
||||
}
|
||||
|
||||
/* Pre-Level Start Screen Modal */
|
||||
.level-intro-modal {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.88);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 12px;
|
||||
z-index: 50;
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.intro-card {
|
||||
background: #231209;
|
||||
border: 3px double #d4af37;
|
||||
border-radius: 10px;
|
||||
padding: 16px 14px;
|
||||
max-width: 320px;
|
||||
width: 100%;
|
||||
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.95);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
text-align: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.intro-act-tag {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
color: #f59e0b;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.intro-card-title {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 10px;
|
||||
color: #ffd700;
|
||||
margin: 0;
|
||||
text-shadow: 1px 1px 0 #000;
|
||||
}
|
||||
|
||||
.intro-card-story {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 13px;
|
||||
color: #fef08a;
|
||||
line-height: 1.35;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.mission-callout {
|
||||
background: #451a03;
|
||||
border-left: 3px solid #22c55e;
|
||||
border-radius: 4px;
|
||||
padding: 8px 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.callout-icon {
|
||||
font-size: 20px;
|
||||
}
|
||||
|
||||
.callout-text {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 12.5px;
|
||||
color: #bbf7d0;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.controls-overview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
width: 100%;
|
||||
background: #170c06;
|
||||
border: 1px solid #78350f;
|
||||
border-radius: 6px;
|
||||
padding: 6px 10px;
|
||||
}
|
||||
|
||||
.ctrl-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.ctrl-badge {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 6.5px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.ctrl-label {
|
||||
font-family: 'MedievalSharp', serif;
|
||||
font-size: 12px;
|
||||
color: #fed7aa;
|
||||
}
|
||||
|
||||
.start-act-btn {
|
||||
background: linear-gradient(180deg, #dc2626 0%, #991b1b 100%);
|
||||
color: #fff;
|
||||
border: 2px solid #ffd700;
|
||||
border-radius: 8px;
|
||||
padding: 10px 16px;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 8px;
|
||||
cursor: pointer;
|
||||
margin-top: 4px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.7);
|
||||
transition: transform 0.1s;
|
||||
}
|
||||
|
||||
.start-act-btn:active {
|
||||
transform: scale(0.96);
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -36,6 +36,9 @@ const currentSpeaker = () => CHARACTERS[currentNode().speakerId] || CHARACTERS['
|
||||
<!-- Balcony Visual Scene with Regal Portrait Frame -->
|
||||
<div class="scene-visual">
|
||||
<div class="balcony-backdrop"></div>
|
||||
<div class="scene-tag">
|
||||
<span class="scene-title">SCHLOSS HAUMDAUCHER – DER BALKON</span>
|
||||
</div>
|
||||
<div class="speaker-frame">
|
||||
<img
|
||||
:src="currentSpeaker().portrait"
|
||||
@@ -43,9 +46,6 @@ const currentSpeaker = () => CHARACTERS[currentNode().speakerId] || CHARACTERS['
|
||||
class="speaker-visual"
|
||||
/>
|
||||
</div>
|
||||
<div class="scene-tag">
|
||||
<span class="scene-title">SCHLOSS HAUMDAUCHER – DER BALKON</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Retro Dialogue Card -->
|
||||
@@ -88,13 +88,15 @@ const currentSpeaker = () => CHARACTERS[currentNode().speakerId] || CHARACTERS['
|
||||
|
||||
.scene-visual {
|
||||
position: relative;
|
||||
flex: 1;
|
||||
min-height: 160px;
|
||||
max-height: 200px;
|
||||
flex: 0 0 auto;
|
||||
min-height: 175px;
|
||||
padding: 10px 8px;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: radial-gradient(circle at 50% 40%, #4a2818 0%, #1a0f0a 100%);
|
||||
border-bottom: 2px solid #d4af37;
|
||||
}
|
||||
@@ -109,10 +111,27 @@ const currentSpeaker = () => CHARACTERS[currentNode().speakerId] || CHARACTERS['
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.scene-tag {
|
||||
position: relative;
|
||||
background: rgba(0, 0, 0, 0.85);
|
||||
border: 1px solid #ffd700;
|
||||
padding: 4px 10px;
|
||||
border-radius: 4px;
|
||||
z-index: 2;
|
||||
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.6);
|
||||
}
|
||||
|
||||
.scene-title {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7px;
|
||||
color: #ffd700;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.speaker-frame {
|
||||
position: relative;
|
||||
width: 140px;
|
||||
height: 140px;
|
||||
width: 116px;
|
||||
height: 116px;
|
||||
border-radius: 12px;
|
||||
border: 3px double #ffd700;
|
||||
box-shadow: 0 0 16px rgba(0, 0, 0, 0.8), 0 0 10px rgba(212, 175, 55, 0.4);
|
||||
@@ -129,22 +148,6 @@ const currentSpeaker = () => CHARACTERS[currentNode().speakerId] || CHARACTERS['
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
.scene-tag {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
background: rgba(0, 0, 0, 0.75);
|
||||
border: 1px solid #ffd700;
|
||||
padding: 3px 8px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.scene-title {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7px;
|
||||
color: #ffd700;
|
||||
}
|
||||
|
||||
.dialogue-card {
|
||||
flex: 1;
|
||||
background: #26160e;
|
||||
|
||||
@@ -7,6 +7,9 @@ import { sound } from '../audio/synthAudio'
|
||||
|
||||
const store = useGameStore()
|
||||
|
||||
type TavernRoom = 'schankraum' | 'marktplatz' | 'hinterland' | 'hinterzimmer'
|
||||
const currentRoom = ref<TavernRoom>('schankraum')
|
||||
|
||||
interface ActiveDialog {
|
||||
characterId: string
|
||||
name: string
|
||||
@@ -23,6 +26,17 @@ const duelStep = ref(0)
|
||||
const duelWon = ref(false)
|
||||
const zulumanMirrorActive = ref(false)
|
||||
|
||||
function switchRoom(room: TavernRoom) {
|
||||
sound.playDialogueBlip()
|
||||
currentRoom.value = room
|
||||
}
|
||||
|
||||
function enterSecretHinterzimmer() {
|
||||
sound.playFanfare()
|
||||
store.unlockEasterEgg('hinterzimmer')
|
||||
currentRoom.value = 'hinterzimmer'
|
||||
}
|
||||
|
||||
function openCharacterDialog(charId: string) {
|
||||
const char = CHARACTERS[charId]
|
||||
if (!char) return
|
||||
@@ -81,42 +95,16 @@ function openCharacterDialog(charId: string) {
|
||||
activeDialog.value = null
|
||||
}
|
||||
}
|
||||
} else if (charId === 'pue') {
|
||||
activeDialog.value = {
|
||||
characterId: charId,
|
||||
name: char.name,
|
||||
title: char.title,
|
||||
portrait: char.portrait,
|
||||
text: 'Majestät! Die magischen Kristall-Fackeln sind verkabelt und synchronisiert. Sobald der Drop bei 135 BPM einschlägt, brennt der Saal!',
|
||||
actionLabel: 'Zünde den Stroboskop-Zauber!',
|
||||
actionFn: () => {
|
||||
store.addKronkorken(4)
|
||||
activeDialog.value = null
|
||||
}
|
||||
}
|
||||
} else if (charId === 'mane') {
|
||||
activeDialog.value = {
|
||||
characterId: charId,
|
||||
name: char.name,
|
||||
title: char.title,
|
||||
portrait: char.portrait,
|
||||
text: 'Servus beinand! Ich wandere grad gemütlich durch die Schenke. Wander-Tipp: Wenn du nachher auf den Festwagen steigst, halte dich an die Kronkorken-Spur!',
|
||||
actionLabel: 'Danke Mane! Berg heil!',
|
||||
actionFn: () => {
|
||||
store.addKronkorken(2)
|
||||
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 Bierkrüge und Hopfendolden durch die Luft wirbeln! Ein Hoch auf König Sascha!',
|
||||
actionLabel: 'Sauber jongliert, Mario! (+3 Kronkorken)',
|
||||
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(3)
|
||||
store.addKronkorken(4)
|
||||
activeDialog.value = null
|
||||
}
|
||||
}
|
||||
@@ -134,6 +122,72 @@ function openCharacterDialog(charId: string) {
|
||||
activeDialog.value = null
|
||||
}
|
||||
}
|
||||
} 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! Schau dir dieses Panorama an. Ich halte hier die Stellung und habe dir 4 Kronkorken gesichert!',
|
||||
actionLabel: 'Danke 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-Kreis 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 Hinterzimmer 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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,57 +226,192 @@ function proceedToExpress() {
|
||||
<template>
|
||||
<div class="tavern-container">
|
||||
<div class="scene-viewport">
|
||||
<img src="/assets/scenes/tavern.jpg" alt="Taverne Zum wummernden Ritter" class="tavern-bg" />
|
||||
|
||||
<!-- Interactive Hotspots on the visual scene with Avatar Thumbnails -->
|
||||
<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>
|
||||
<!-- ================= 1. GASTSTUBE (SCHANKRAUM) ================= -->
|
||||
<template v-if="currentRoom === 'schankraum'">
|
||||
<img src="/assets/scenes/tavern.jpg" alt="Gaststube Zum wummernden Ritter" class="tavern-bg" />
|
||||
|
||||
<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>
|
||||
<!-- 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>
|
||||
|
||||
<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>
|
||||
<!-- 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>
|
||||
|
||||
<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>
|
||||
<!-- 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>
|
||||
|
||||
<button class="hotspot hotspot-mirror" @click="inspectMirror">
|
||||
<span class="mirror-icon">🪞</span>
|
||||
<span class="hotspot-badge pulse">Zuluman-Spiegel</span>
|
||||
</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>
|
||||
|
||||
<button class="hotspot hotspot-pue" @click="openCharacterDialog('pue')">
|
||||
<img :src="CHARACTERS.pue.portrait" class="hotspot-thumb" alt="Pü" />
|
||||
<span class="hotspot-badge">Pü (Licht) 💡</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>
|
||||
|
||||
<button class="hotspot hotspot-mane" @click="openCharacterDialog('mane')">
|
||||
<img :src="CHARACTERS.mane.portrait" class="hotspot-thumb" alt="Mane" />
|
||||
<span class="hotspot-badge">Mane 🥾</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>
|
||||
|
||||
<button class="hotspot hotspot-nat" @click="openCharacterDialog('nat')">
|
||||
<img :src="CHARACTERS.nat.portrait" class="hotspot-thumb" alt="NAT" />
|
||||
<span class="hotspot-badge">NAT 🐗</span>
|
||||
</button>
|
||||
<!-- SECRET EASTER EGG TRIGGER: Hidden Beer Tap to Hinterzimmer -->
|
||||
<button class="hotspot secret-tap-hotspot" @click="enterSecretHinterzimmer" title="Ein sonderbarer goldener Zapfhahn...">
|
||||
<span class="secret-tap-icon pulse">🗝️</span>
|
||||
<span class="secret-badge">Geheimer Zapfhahn</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<button class="hotspot hotspot-mario" @click="openCharacterDialog('mario')">
|
||||
<img :src="CHARACTERS.mario.portrait" class="hotspot-thumb" alt="Mario" />
|
||||
<span class="hotspot-badge">Mario 🤹</span>
|
||||
</button>
|
||||
<!-- ================= 2. MARKTPLATZ ================= -->
|
||||
<template v-else-if="currentRoom === 'marktplatz'">
|
||||
<img src="/assets/scenes/marktplatz.jpg" alt="Marktplatz Haumdaucher" class="tavern-bg" />
|
||||
|
||||
<!-- Gaukler Mario juggling chainsaws with animations & flying sparks -->
|
||||
<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>
|
||||
|
||||
<!-- 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 -->
|
||||
<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>
|
||||
|
||||
<!-- ================= 3. 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 enjoying the view on the bench -->
|
||||
<button class="hotspot hotspot-hansi" @click="openCharacterDialog('hansi')">
|
||||
<img :src="CHARACTERS.hansi.portrait" class="hotspot-thumb" alt="Hansi" />
|
||||
<span class="hotspot-badge">Hansi 🧔</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>
|
||||
|
||||
<!-- ================= 4. 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 die Gaststube</span>
|
||||
</button>
|
||||
</template>
|
||||
|
||||
<!-- Bottom Scene Navigation & Duel -->
|
||||
<div class="scene-bottom-nav">
|
||||
<div class="tavern-bar-title">🍻 TAVERNE ZUM WUMMERNDEN RITTER 🍻</div>
|
||||
<!-- Room Title Badge -->
|
||||
<div class="tavern-bar-title">
|
||||
{{
|
||||
currentRoom === 'schankraum' ? '🍻 AKT III: GASTSTUBE ZUM WUMMERNDEN RITTER 🍻' :
|
||||
currentRoom === 'marktplatz' ? '🎪 AKT III: HAUMDAUCHER MARKTPLATZ 🎪' :
|
||||
currentRoom === 'hinterland' ? '🌲 AKT III: ALPINES HINTERLAND 🌲' :
|
||||
'🪩 AKT III: GEHEIMES VIP-HINTERZIMMER 🪩'
|
||||
}}
|
||||
</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 === 'marktplatz' }"
|
||||
@click="switchRoom('marktplatz')"
|
||||
>
|
||||
🎪 Marktplatz
|
||||
</button>
|
||||
<button
|
||||
class="room-tab"
|
||||
:class="{ active: currentRoom === 'hinterland' }"
|
||||
@click="switchRoom('hinterland')"
|
||||
>
|
||||
🌲 Hinterland
|
||||
</button>
|
||||
<button
|
||||
v-if="store.unlockedEasterEggs.includes('hinterzimmer') || currentRoom === 'hinterzimmer'"
|
||||
class="room-tab vip-tab"
|
||||
:class="{ active: currentRoom === 'hinterzimmer' }"
|
||||
@click="switchRoom('hinterzimmer')"
|
||||
>
|
||||
🪩 Hinterzimmer
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Action Row (Duel & Next Level) -->
|
||||
<div class="tavern-nav-buttons">
|
||||
<button class="duel-trigger" @click="startGstanzlDuel">
|
||||
⚔️ Gstanzl-Fechten
|
||||
@@ -338,7 +527,7 @@ function proceedToExpress() {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
background: rgba(26, 15, 10, 0.85);
|
||||
background: rgba(26, 15, 10, 0.88);
|
||||
border: 1.5px solid #ffd700;
|
||||
border-radius: 16px;
|
||||
padding: 2px 6px 2px 2px;
|
||||
@@ -363,8 +552,8 @@ function proceedToExpress() {
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.mirror-icon {
|
||||
font-size: 16px;
|
||||
.mirror-icon, .door-icon {
|
||||
font-size: 15px;
|
||||
padding: 0 2px;
|
||||
}
|
||||
|
||||
@@ -375,15 +564,164 @@ function proceedToExpress() {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.hotspot-verena { top: 15%; left: 80%; }
|
||||
/* Door Hotspots */
|
||||
.door-hotspot {
|
||||
background: rgba(15, 23, 42, 0.9);
|
||||
border: 1.5px solid #38bdf8;
|
||||
padding: 4px 8px;
|
||||
}
|
||||
|
||||
.glow-door {
|
||||
color: #38bdf8;
|
||||
}
|
||||
|
||||
/* Secret Tap Easter Egg */
|
||||
.secret-tap-hotspot {
|
||||
position: absolute;
|
||||
top: 73%;
|
||||
left: 88%;
|
||||
background: rgba(234, 179, 8, 0.2);
|
||||
border: 1.5px dashed #ffd700;
|
||||
border-radius: 12px;
|
||||
padding: 3px 6px;
|
||||
cursor: pointer;
|
||||
z-index: 12;
|
||||
}
|
||||
|
||||
.secret-tap-icon {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.secret-badge {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 5.5px;
|
||||
color: #fef08a;
|
||||
}
|
||||
|
||||
/* 1. Schankraum Hotspots */
|
||||
.hotspot-verena { top: 16%; left: 78%; }
|
||||
.hotspot-armend { top: 62%; left: 24%; }
|
||||
.hotspot-lutz { top: 38%; left: 20%; }
|
||||
.hotspot-amy { top: 52%; left: 80%; }
|
||||
.hotspot-mirror { top: 32%; left: 63%; }
|
||||
.hotspot-amy { top: 52%; left: 82%; }
|
||||
.hotspot-pue { top: 20%; left: 35%; }
|
||||
.hotspot-mane { top: 45%; left: 66%; }
|
||||
.hotspot-nat { top: 63%; left: 85%; }
|
||||
.hotspot-mario { top: 36%; left: 90%; }
|
||||
.hotspot-door-marktplatz { top: 56%; left: 90%; }
|
||||
|
||||
/* 2. Marktplatz Hotspots */
|
||||
.mario-container {
|
||||
position: absolute;
|
||||
top: 52%;
|
||||
left: 34%;
|
||||
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: 62%; left: 74%; }
|
||||
.hotspot-door-back-gaststube { top: 72%; left: 16%; }
|
||||
.hotspot-door-hinterland { top: 36%; left: 84%; }
|
||||
|
||||
/* 3. Hinterland Hotspots */
|
||||
.hotspot-mane { top: 50%; left: 42%; }
|
||||
.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: 66%; left: 78%; }
|
||||
.hotspot-door-back-marktplatz { top: 74%; left: 20%; }
|
||||
|
||||
/* 4. 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-pue { top: 46%; left: 48%; }
|
||||
.rave-pulse {
|
||||
animation: raveGlow 1s infinite alternate;
|
||||
}
|
||||
|
||||
@keyframes raveGlow {
|
||||
0% { box-shadow: 0 0 4px #eab308; }
|
||||
100% { box-shadow: 0 0 16px #ec4899; }
|
||||
}
|
||||
|
||||
.crystal-laser {
|
||||
font-size: 13px;
|
||||
animation: pulseAnim 0.8s infinite;
|
||||
}
|
||||
|
||||
.hotspot-flo { top: 40%; left: 22%; }
|
||||
.hotspot-viktor { top: 62%; left: 78%; }
|
||||
.hotspot-door-back-vip { top: 78%; left: 50%; }
|
||||
|
||||
.pulse {
|
||||
animation: pulseAnim 1.5s infinite;
|
||||
@@ -394,12 +732,13 @@ function proceedToExpress() {
|
||||
50% { box-shadow: 0 0 12px #ec4899; }
|
||||
}
|
||||
|
||||
/* Bottom Navigation */
|
||||
.scene-bottom-nav {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 84px;
|
||||
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);
|
||||
@@ -407,20 +746,53 @@ function proceedToExpress() {
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 6px 12px;
|
||||
gap: 5px;
|
||||
padding: 6px 10px;
|
||||
z-index: 15;
|
||||
}
|
||||
|
||||
.tavern-bar-title {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7px;
|
||||
font-size: 6.5px;
|
||||
color: #fef08a;
|
||||
text-shadow: 1px 1px 0 #000;
|
||||
letter-spacing: 0.5px;
|
||||
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%;
|
||||
@@ -435,8 +807,8 @@ function proceedToExpress() {
|
||||
border: 1.5px solid #ffd700;
|
||||
border-radius: 6px;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7.5px;
|
||||
padding: 8px 10px;
|
||||
font-size: 7px;
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.6);
|
||||
}
|
||||
@@ -447,8 +819,8 @@ function proceedToExpress() {
|
||||
border: 1.5px solid #86efac;
|
||||
border-radius: 6px;
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 7.5px;
|
||||
padding: 8px 10px;
|
||||
font-size: 7px;
|
||||
padding: 6px 8px;
|
||||
cursor: pointer;
|
||||
box-shadow: 0 2px 8px rgba(0,0,0,0.6);
|
||||
}
|
||||
|
||||
@@ -84,35 +84,53 @@ export const CHARACTERS: Record<string, CharacterConfig> = {
|
||||
pue: {
|
||||
id: 'pue',
|
||||
name: 'Pü (Jakob)',
|
||||
title: 'Der Lichtler des Hofes',
|
||||
title: 'Lichtmeister & Kristall-Rave-Magier',
|
||||
portrait: '/assets/characters/pue/portrait.jpg',
|
||||
description: 'Früherer Club-Lichtmeister, bedient nun die magischen Stroboskop-Fackeln.',
|
||||
famousQuote: 'Majestät, wann zünden wir die Stroboskop-Fackeln zum Drop?',
|
||||
description: 'Lässt im geheimen Hinterzimmer die Disco-Kristalle tanzen und erhellt die Entourage mit bunten Lasern.',
|
||||
famousQuote: 'Bass auf Anschlag, Kristalle auf Stroboskop! Willkommen im geheimen Hinterzimmer!',
|
||||
themeColor: '#eab308'
|
||||
},
|
||||
mario: {
|
||||
id: 'mario',
|
||||
name: 'Mario der Gaukler',
|
||||
title: 'Hof-Jongleur & Stimmungs-Garant',
|
||||
name: 'Mario',
|
||||
title: 'Hof-Gaukler & Kettensägen-Virtuose',
|
||||
portrait: '/assets/characters/mario/portrait.jpg',
|
||||
description: 'Jongliert geschickt mit Hopfen, Hefe und Bierkrügen.',
|
||||
famousQuote: 'Hoppla die Waldfee! Ein Krug für den König!',
|
||||
description: 'Jongliert furchtlos auf dem Marktplatz mit kreischenden Kettensägen und Hopfenkrügen.',
|
||||
famousQuote: 'Pass auf die Zinken auf! Kettensägen-Akrobatik für König Sascha!',
|
||||
themeColor: '#14b8a6'
|
||||
},
|
||||
mane: {
|
||||
id: 'mane',
|
||||
name: 'Mane (Ramona)',
|
||||
title: 'Die königliche Wanderin',
|
||||
title: 'Königliche Alpen-Wanderin',
|
||||
portrait: '/assets/characters/mane/portrait.jpg',
|
||||
description: 'Wandert random mit Rucksack und Wanderstock quer durch alle Bildschirme.',
|
||||
famousQuote: 'Schee ists hier! Noch 3 Kilometer bis zum Gipfel-Bier!',
|
||||
description: 'Zieht mit Wanderstock, Sonnenbrille und Flechtzopf durchs alpine Hinterland von Haumdaucher.',
|
||||
famousQuote: 'Schee ists hier! Noch 3 Kilometer über den Grat bis zum Gipfel-Bier!',
|
||||
themeColor: '#84cc16'
|
||||
},
|
||||
flo_maier: {
|
||||
id: 'flo_maier',
|
||||
name: 'Flo Maier',
|
||||
title: 'Hof-DJ & Sound-Ingenieur',
|
||||
portrait: '/assets/characters/flo_maier/portrait.jpg',
|
||||
description: 'Mastermind an den Turntables und Behüter der heiligen 135-BPM-Frequenz.',
|
||||
famousQuote: 'Dieser Track drückt mehr als die königliche Kutsche!',
|
||||
themeColor: '#0284c7'
|
||||
},
|
||||
viktor: {
|
||||
id: 'viktor',
|
||||
name: 'Viktor',
|
||||
title: 'Nachtwächter & Party-Ritter',
|
||||
portrait: '/assets/characters/viktor/portrait.jpg',
|
||||
description: 'Wacht im samtenen Vampir-Umhang über das Nachtleben und sorgt für eiskaltes Helles.',
|
||||
famousQuote: 'Die Nacht ist jung und der Gerstensaft fließt in Strömen!',
|
||||
themeColor: '#b91c1c'
|
||||
},
|
||||
djs: {
|
||||
id: 'djs',
|
||||
name: 'Die Hof-DJs',
|
||||
title: 'Minnesänger der 135 BPM',
|
||||
portrait: '/assets/characters/pue/portrait.jpg',
|
||||
portrait: '/assets/characters/flo_maier/portrait.jpg',
|
||||
description: 'Stehen an der königlichen Kanzel am doppelten Eichenholz-Plattenteller.',
|
||||
famousQuote: 'Drop it like it\'s 1426! Mehr Bass auf die Burgzinnen!',
|
||||
themeColor: '#06b6d4'
|
||||
@@ -121,8 +139,8 @@ export const CHARACTERS: Record<string, CharacterConfig> = {
|
||||
id: 'gefolge',
|
||||
name: 'Das Haumdaucher Gefolge',
|
||||
title: 'Die feiernde Reichs-Crowd',
|
||||
portrait: '/assets/characters/hansi/portrait.jpg',
|
||||
description: 'Viktor der Vampir, der bärtige Guatemalteke, Fabian, Albin, Maier Flo, Markus & Nathalie.',
|
||||
portrait: '/assets/characters/viktor/portrait.jpg',
|
||||
description: 'Viktor der Vampir-Ritter, DJ Flo Maier, Hansi, Fabian, Albin, der bärtige Guatemalteke, Markus & Nathalie.',
|
||||
famousQuote: 'Ein Hoch auf König Sascha! Hoch soll er leben!',
|
||||
themeColor: '#e11d48'
|
||||
}
|
||||
|
||||
|
After Width: | Height: | Size: 119 KiB |
|
After Width: | Height: | Size: 312 KiB |
|
After Width: | Height: | Size: 96 KiB |
|
After Width: | Height: | Size: 8.1 MiB |
|
After Width: | Height: | Size: 71 KiB |
|
After Width: | Height: | Size: 100 KiB |
|
After Width: | Height: | Size: 102 KiB |
|
After Width: | Height: | Size: 13 MiB |
|
After Width: | Height: | Size: 78 KiB |