feat(sasch40): multi-room tavern, authentic character portraits, pre-level start screens & bollerwagen redesign

This commit is contained in:
Moritz Graf
2026-09-24 10:02:57 +02:00
parent 4488749d6f
commit 7a7038e2c2
26 changed files with 1243 additions and 334 deletions
@@ -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>