feat(sasch40): multi-room tavern, authentic character portraits, pre-level start screens & bollerwagen redesign
This commit is contained in:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user