diff --git a/projects/sasch40/frontend/public/assets/characters/flo_maier/portrait.jpg b/projects/sasch40/frontend/public/assets/characters/flo_maier/portrait.jpg new file mode 100644 index 0000000..b39a4ae Binary files /dev/null and b/projects/sasch40/frontend/public/assets/characters/flo_maier/portrait.jpg differ diff --git a/projects/sasch40/frontend/public/assets/characters/mane/portrait.jpg b/projects/sasch40/frontend/public/assets/characters/mane/portrait.jpg index e856595..19e6c34 100644 Binary files a/projects/sasch40/frontend/public/assets/characters/mane/portrait.jpg and b/projects/sasch40/frontend/public/assets/characters/mane/portrait.jpg differ diff --git a/projects/sasch40/frontend/public/assets/characters/mario/portrait.jpg b/projects/sasch40/frontend/public/assets/characters/mario/portrait.jpg index 9fba1fb..92726c0 100644 Binary files a/projects/sasch40/frontend/public/assets/characters/mario/portrait.jpg and b/projects/sasch40/frontend/public/assets/characters/mario/portrait.jpg differ diff --git a/projects/sasch40/frontend/public/assets/characters/pue/portrait.jpg b/projects/sasch40/frontend/public/assets/characters/pue/portrait.jpg index ecb9c2c..9adfd16 100644 Binary files a/projects/sasch40/frontend/public/assets/characters/pue/portrait.jpg and b/projects/sasch40/frontend/public/assets/characters/pue/portrait.jpg differ diff --git a/projects/sasch40/frontend/public/assets/characters/sascha/portrait.jpg b/projects/sasch40/frontend/public/assets/characters/sascha/portrait.jpg index c716891..7012dde 100644 Binary files a/projects/sasch40/frontend/public/assets/characters/sascha/portrait.jpg and b/projects/sasch40/frontend/public/assets/characters/sascha/portrait.jpg differ diff --git a/projects/sasch40/frontend/public/assets/characters/viktor/portrait.jpg b/projects/sasch40/frontend/public/assets/characters/viktor/portrait.jpg new file mode 100644 index 0000000..41f7e24 Binary files /dev/null and b/projects/sasch40/frontend/public/assets/characters/viktor/portrait.jpg differ diff --git a/projects/sasch40/frontend/public/assets/scenes/hinterland.jpg b/projects/sasch40/frontend/public/assets/scenes/hinterland.jpg new file mode 100644 index 0000000..f6b80e3 Binary files /dev/null and b/projects/sasch40/frontend/public/assets/scenes/hinterland.jpg differ diff --git a/projects/sasch40/frontend/public/assets/scenes/hinterzimmer.jpg b/projects/sasch40/frontend/public/assets/scenes/hinterzimmer.jpg new file mode 100644 index 0000000..4c18a7a Binary files /dev/null and b/projects/sasch40/frontend/public/assets/scenes/hinterzimmer.jpg differ diff --git a/projects/sasch40/frontend/public/assets/scenes/marktplatz.jpg b/projects/sasch40/frontend/public/assets/scenes/marktplatz.jpg new file mode 100644 index 0000000..81fae32 Binary files /dev/null and b/projects/sasch40/frontend/public/assets/scenes/marktplatz.jpg differ diff --git a/projects/sasch40/frontend/public/assets/scenes/tavern.jpg b/projects/sasch40/frontend/public/assets/scenes/tavern.jpg index d35fbd5..2c9a67e 100644 Binary files a/projects/sasch40/frontend/public/assets/scenes/tavern.jpg and b/projects/sasch40/frontend/public/assets/scenes/tavern.jpg differ diff --git a/projects/sasch40/frontend/scripts/playtest.mjs b/projects/sasch40/frontend/scripts/playtest.mjs index e2066d9..10bc7bd 100644 --- a/projects/sasch40/frontend/scripts/playtest.mjs +++ b/projects/sasch40/frontend/scripts/playtest.mjs @@ -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(), diff --git a/projects/sasch40/frontend/src/components/GameBoyFrame.vue b/projects/sasch40/frontend/src/components/GameBoyFrame.vue index a148c76..215f7f1 100644 --- a/projects/sasch40/frontend/src/components/GameBoyFrame.vue +++ b/projects/sasch40/frontend/src/components/GameBoyFrame.vue @@ -43,9 +43,9 @@ function toggleInventory() {
- KING SASCHA XL + KÖNIG SASCHAS KRONKORKENKREUZZUG - {{ 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' : '' }}
Tippe, wenn du an deinen Freunden vorbeifährst! + + +
+
+
👑 AKT IV: FAHRGESCHÄFT
+

DER 135-BPM-FEST-KONVOI

+

+ Der Hof-Bass wummert, der Bollerwagen ist poliert!
+ König Sascha zieht mit der Reichs-Soundanlage über die Festmeile von Haumdaucher. Deine Freunde stehen durstig am Straßenrand! +

+
+ 🎯 +
+ KÖNIGLICHE MISSION:
+ Versorge Hansi, Viktor, Albin, Fabian, Flo Maier & das Gefolge mit kühlem Gerstensaft im Takt des Basses! +
+
+
+
+ 🍺 BIER SCHENKEN + Tippe den Button, sobald du an einem Freund bist +
+
+ 🏆 TIMING + Jedes glückliche Gefolge bringt fette Highscore-Punkte! +
+
+ +
+
@@ -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); +} diff --git a/projects/sasch40/frontend/src/components/PlatformerView.vue b/projects/sasch40/frontend/src/components/PlatformerView.vue index b42e0e9..abffa38 100644 --- a/projects/sasch40/frontend/src/components/PlatformerView.vue +++ b/projects/sasch40/frontend/src/components/PlatformerView.vue @@ -5,8 +5,14 @@ import { sound } from '../audio/synthAudio' const store = useGameStore() const canvasRef = ref(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(() => {
👑 {{ store.kronkorken }} KORKEN
- AKTE II: DIE HOPFEN-PFADE + AKT II: DIE HOPFEN-PFADE Tippe oder drücke SPRUNG zum Hüpfen
+ + +
+
+
👑 AKT II: JUMP & RUN
+

DIE HOPFEN-PFADE

+

+ Hört die Kunde, edler König Sascha!
+ Deine Untertanen dürsten im ganzen Herzogtum nach frischem Gerstensaft. Doch ohne deine 40 heiligen Kronkorken bleiben die Fässer versiegelt! +

+
+ 🎯 +
+ KÖNIGLICHE MISSION:
+ Hüpfe über Ziegeldächer und Holzgerüste. Sammle mindestens 5 Kronkorken, um das Tor zur Taverne zu entriegeln! +
+
+
+
+ 🕹️ STEUERKREUZ + Laufen (Links / Rechts) +
+
+ 🅰️ A-TASTE / TIPPE + Sprung (auch Doppel-Sprung!) +
+
+ +
+
@@ -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); +} diff --git a/projects/sasch40/frontend/src/components/PrologueView.vue b/projects/sasch40/frontend/src/components/PrologueView.vue index 8929e00..9315c54 100644 --- a/projects/sasch40/frontend/src/components/PrologueView.vue +++ b/projects/sasch40/frontend/src/components/PrologueView.vue @@ -36,6 +36,9 @@ const currentSpeaker = () => CHARACTERS[currentNode().speakerId] || CHARACTERS['
+
+ SCHLOSS HAUMDAUCHER – DER BALKON +
CHARACTERS[currentNode().speakerId] || CHARACTERS[' class="speaker-visual" />
-
- SCHLOSS HAUMDAUCHER – DER BALKON -
@@ -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; diff --git a/projects/sasch40/frontend/src/components/TavernView.vue b/projects/sasch40/frontend/src/components/TavernView.vue index 7b871f2..848c20b 100644 --- a/projects/sasch40/frontend/src/components/TavernView.vue +++ b/projects/sasch40/frontend/src/components/TavernView.vue @@ -7,6 +7,9 @@ import { sound } from '../audio/synthAudio' const store = useGameStore() +type TavernRoom = 'schankraum' | 'marktplatz' | 'hinterland' | 'hinterzimmer' +const currentRoom = ref('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() {