feat(sasch40): authentic 3 sisters portraits, 9:16 responsive stage with medieval ornaments, and hotspot fixes
- Recreate 16-bit LucasArts portraits for Saskia, Maja, and Miriam matching real-life source photos - Add 3 sisters showcase in Act II intro modal and golden portrait badges on goal platforms - Fix Act III background scaling: lock scene to 9:16 aspect ratio with medieval ornamental side borders and corner brackets - Ensure 100% full background visibility with zero cropping and lock hotspot buttons to image features across all screen sizes - Fix cache issue by referencing schankraum_v5.jpg with cache buster query - Re-position NAT button to feet (top: 70%) and Mario button to knees (top: 79%) to eliminate face occlusion
This commit is contained in:
@@ -542,36 +542,101 @@ function drawPlayer(ctx: CanvasRenderingContext2D) {
|
||||
ctx.restore()
|
||||
}
|
||||
|
||||
const sisterImages: Record<number, HTMLImageElement> = {}
|
||||
|
||||
function preloadSisterImages() {
|
||||
const sources: Record<number, string> = {
|
||||
1: CHARACTERS.saskia.portrait,
|
||||
2: CHARACTERS.maja.portrait,
|
||||
3: CHARACTERS.miriam.portrait
|
||||
}
|
||||
for (const [lvl, src] of Object.entries(sources)) {
|
||||
const img = new Image()
|
||||
img.src = src
|
||||
sisterImages[Number(lvl)] = img
|
||||
}
|
||||
}
|
||||
|
||||
function drawSisterSprite(ctx: CanvasRenderingContext2D, x: number, y: number, level: number) {
|
||||
// Dirndl color per sister
|
||||
const dirndlColor = level === 1 ? '#f43f5e' : level === 2 ? '#8b5cf6' : '#06b6d4'
|
||||
const hairColor = level === 1 ? '#fde047' : level === 2 ? '#78350f' : '#b45309'
|
||||
const img = sisterImages[level]
|
||||
const wave = Math.sin(Date.now() / 160) * 3
|
||||
|
||||
// Body
|
||||
ctx.fillStyle = dirndlColor
|
||||
ctx.fillRect(x + 2, y + 14, 18, 18)
|
||||
// White apron
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.fillRect(x + 6, y + 18, 10, 14)
|
||||
if (img && img.complete && img.naturalWidth > 0) {
|
||||
const size = 32
|
||||
const drawX = x - 4
|
||||
const drawY = y - 4 + wave
|
||||
|
||||
// Head
|
||||
ctx.fillStyle = '#fbcfe8'
|
||||
ctx.fillRect(x + 4, y + 4, 14, 12)
|
||||
ctx.save()
|
||||
// Golden border
|
||||
ctx.beginPath()
|
||||
ctx.arc(drawX + size / 2, drawY + size / 2, size / 2 + 2, 0, Math.PI * 2)
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.fill()
|
||||
ctx.lineWidth = 2
|
||||
ctx.strokeStyle = '#b45309'
|
||||
ctx.stroke()
|
||||
|
||||
// Hair
|
||||
ctx.fillStyle = hairColor
|
||||
ctx.fillRect(x + 2, y, 18, 6)
|
||||
ctx.fillRect(x + 1, y + 5, 4, 14) // Left braid
|
||||
ctx.fillRect(x + 17, y + 5, 4, 14) // Right braid
|
||||
// Circular portrait
|
||||
ctx.beginPath()
|
||||
ctx.arc(drawX + size / 2, drawY + size / 2, size / 2, 0, Math.PI * 2)
|
||||
ctx.clip()
|
||||
ctx.drawImage(img, drawX, drawY, size, size)
|
||||
ctx.restore()
|
||||
|
||||
// Tiara / flowers
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.fillRect(x + 5, y - 2, 12, 3)
|
||||
// Little waving hand
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.font = '10px monospace'
|
||||
ctx.fillText('👋', drawX + size - 2, drawY + 8 + wave)
|
||||
} else {
|
||||
// Custom pixel-art styling per sister matching real photos
|
||||
const dirndlColor = level === 1 ? '#f43f5e' : level === 2 ? '#8b5cf6' : '#06b6d4'
|
||||
const hairColor = level === 1 ? '#3d2314' : level === 2 ? '#6b4226' : '#18181b'
|
||||
|
||||
// Waving arm
|
||||
const wave = Math.sin(Date.now() / 150) * 4
|
||||
ctx.fillStyle = '#fbcfe8'
|
||||
ctx.fillRect(x - 3, y + 6 + wave, 5, 5)
|
||||
// Body
|
||||
ctx.fillStyle = dirndlColor
|
||||
ctx.fillRect(x + 2, y + 14, 18, 18)
|
||||
// White apron
|
||||
ctx.fillStyle = '#ffffff'
|
||||
ctx.fillRect(x + 6, y + 18, 10, 14)
|
||||
|
||||
// Head
|
||||
ctx.fillStyle = '#fce7f3'
|
||||
ctx.fillRect(x + 4, y + 4, 14, 12)
|
||||
|
||||
// Hair
|
||||
ctx.fillStyle = hairColor
|
||||
if (level === 1) {
|
||||
// Saskia: elegant bun updo with side strands
|
||||
ctx.fillRect(x + 2, y, 18, 6)
|
||||
ctx.fillRect(x + 5, y - 5, 12, 6)
|
||||
ctx.fillRect(x + 1, y + 5, 2, 6)
|
||||
ctx.fillRect(x + 19, y + 5, 2, 6)
|
||||
} else if (level === 2) {
|
||||
// Maja: short pixie cut with side bangs
|
||||
ctx.fillRect(x + 2, y, 18, 6)
|
||||
ctx.fillRect(x + 1, y + 3, 5, 8)
|
||||
// Gold hoop earring
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.fillRect(x + 1, y + 9, 2, 2)
|
||||
ctx.fillStyle = hairColor
|
||||
} else {
|
||||
// Miriam: long flowing dark hair with sunglasses on head
|
||||
ctx.fillRect(x + 2, y, 18, 6)
|
||||
ctx.fillRect(x + 1, y + 4, 4, 16)
|
||||
ctx.fillRect(x + 17, y + 4, 4, 16)
|
||||
// Sunglasses
|
||||
ctx.fillStyle = '#0f172a'
|
||||
ctx.fillRect(x + 5, y - 3, 12, 3)
|
||||
}
|
||||
|
||||
// Tiara
|
||||
ctx.fillStyle = '#ffd700'
|
||||
ctx.fillRect(x + 6, y - 2, 10, 3)
|
||||
|
||||
// Waving arm
|
||||
ctx.fillStyle = '#fce7f3'
|
||||
ctx.fillRect(x - 3, y + 6 + wave, 5, 5)
|
||||
}
|
||||
}
|
||||
|
||||
function drawHUD(ctx: CanvasRenderingContext2D) {
|
||||
@@ -604,6 +669,7 @@ function gameLoop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
preloadSisterImages()
|
||||
window.addEventListener('keydown', handleKeyDown)
|
||||
window.addEventListener('keyup', handleKeyUp)
|
||||
;(window as any).__platformer = {
|
||||
@@ -652,6 +718,28 @@ onUnmounted(() => {
|
||||
Gierige Kronkorken-Kobolde haben Saschas drei Schwestern <strong>Saskia</strong>, <strong>Maja</strong> und <strong>Miriam</strong> auf luftige Himmelszinnen entführt!
|
||||
</p>
|
||||
|
||||
<!-- 3 Sisters Preview Showcase -->
|
||||
<div class="sisters-showcase">
|
||||
<div class="sister-mini-card">
|
||||
<div class="sister-mini-portrait">
|
||||
<img :src="CHARACTERS.saskia.portrait" alt="Saskia" />
|
||||
</div>
|
||||
<span class="sister-mini-label">1. Saskia 👑</span>
|
||||
</div>
|
||||
<div class="sister-mini-card">
|
||||
<div class="sister-mini-portrait">
|
||||
<img :src="CHARACTERS.maja.portrait" alt="Maja" />
|
||||
</div>
|
||||
<span class="sister-mini-label">2. Maja 🌸</span>
|
||||
</div>
|
||||
<div class="sister-mini-card">
|
||||
<div class="sister-mini-portrait">
|
||||
<img :src="CHARACTERS.miriam.portrait" alt="Miriam" />
|
||||
</div>
|
||||
<span class="sister-mini-label">3. Miriam ✨</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mission-callout">
|
||||
<span class="callout-icon">☁️</span>
|
||||
<div class="callout-text">
|
||||
@@ -962,6 +1050,46 @@ onUnmounted(() => {
|
||||
font-size: 6.5px;
|
||||
}
|
||||
|
||||
/* Sisters Showcase in Act II Intro */
|
||||
.sisters-showcase {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
gap: 12px;
|
||||
margin: 10px 0 12px 0;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sister-mini-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.sister-mini-portrait {
|
||||
width: 52px;
|
||||
height: 52px;
|
||||
border-radius: 8px;
|
||||
border: 2px solid #ffd700;
|
||||
box-shadow: 0 0 8px rgba(255, 215, 0, 0.4);
|
||||
overflow: hidden;
|
||||
background: #1e1b4b;
|
||||
}
|
||||
|
||||
.sister-mini-portrait img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
image-rendering: crisp-edges;
|
||||
}
|
||||
|
||||
.sister-mini-label {
|
||||
font-family: 'Press Start 2P', monospace;
|
||||
font-size: 5.5px;
|
||||
color: #ffd700;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Animations */
|
||||
.fade-enter-active, .fade-leave-active {
|
||||
transition: opacity 0.3s;
|
||||
|
||||
Reference in New Issue
Block a user