diff --git a/src/app/pages/welcome/welcome.component.css b/src/app/pages/welcome/welcome.component.css index 579c502..d0242fe 100644 --- a/src/app/pages/welcome/welcome.component.css +++ b/src/app/pages/welcome/welcome.component.css @@ -650,6 +650,240 @@ font-weight: 600; } +/* Speech Bubble Styles */ +.speech-bubble-container { + position: fixed; + top: 35%; + left: 55%; + transform: translate(-50%, -50%); + z-index: 500; + cursor: pointer; + animation: slideInBubble 0.8s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +@keyframes slideInBubble { + 0% { + opacity: 0; + transform: translate(-50%, -50%) scale(0.5); + } + 100% { + opacity: 1; + transform: translate(-50%, -50%) scale(1); + } +} + +.speech-bubble { + background: linear-gradient(135deg, #B9DAD1 0%, #B9C2DA 100%); + border: 2px solid #8b1270; + border-radius: 20px; + padding: 16px 24px; + font-family: 'Comic Sans MS', 'Comic Sans', cursive; + font-size: 18px; + font-weight: bold; + color: #5A2C28; + white-space: nowrap; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2), + inset 0 1px 3px rgba(255, 255, 255, 0.3); + position: relative; + transition: all 0.3s ease; +} + +.speech-bubble:hover { + transform: scale(1.05); + box-shadow: 0 6px 20px rgba(0, 0, 0, 0.3), + inset 0 1px 3px rgba(255, 255, 255, 0.5); +} + +.bubble-text { + margin: 0; +} + +.bubble-tail { + position: absolute; + bottom: -12px; + left: 20px; + width: 0; + height: 0; + border-left: 10px solid transparent; + border-right: 0px solid transparent; + border-top: 12px solid #B9DAD1; +} + +/* Zoom Overlay and Window */ +.zoom-overlay { + position: fixed; + inset: 0; + background: rgba(0, 0, 0, 0.7); + z-index: 1000; + display: flex; + align-items: center; + justify-content: center; + animation: fadeIn 0.3s ease; + cursor: pointer; +} + +@keyframes fadeIn { + from { + opacity: 0; + } + to { + opacity: 1; + } +} + +.zoom-window-wrapper { + cursor: auto; + animation: zoomInWindow 1.2s cubic-bezier(0.34, 1.56, 0.64, 1); +} + +@keyframes zoomInWindow { + 0% { + transform: scale(0.1); + opacity: 0; + } + 100% { + transform: scale(1); + opacity: 1; + } +} + +.zoom-window-frame { + background: #13072a; + border: 8px solid #f26ae2; + border-radius: 16px; + padding: 40px 20px 20px 20px; + box-shadow: 0 0 40px rgba(242, 106, 226, 0.6), + inset 0 0 20px rgba(242, 106, 226, 0.2); + max-width: 90vw; + max-height: 90vh; + position: relative; +} + +.zoom-player-2 { + position: relative; + display: flex; + align-items: center; + justify-content: center; +} + +.player-2-gif { + max-width: 100%; + max-height: 70vh; + width: auto; + height: auto; + display: block; + border-radius: 12px; + cursor: pointer; + transition: transform 0.2s ease; +} + +.player-2-gif:hover { + transform: scale(1.02); +} + +.second-speech-bubble { + position: absolute; + top: -60px; + left: 50%; + transform: translateX(-50%); + background: linear-gradient(135deg, #C19EF5 0%, #E1EAA9 100%); + border: 2px solid #BA6D4B; + border-radius: 20px; + padding: 12px 18px; + font-family: 'Comic Sans MS', 'Comic Sans', cursive; + font-size: 16px; + font-weight: bold; + color: #5A2C28; + white-space: nowrap; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2), + inset 0 1px 3px rgba(255, 255, 255, 0.3); + animation: popInBubble 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); + z-index: 10; +} + +@keyframes popInBubble { + 0% { + opacity: 0; + transform: translateX(-50%) scale(0.3); + } + 100% { + opacity: 1; + transform: translateX(-50%) scale(1); + } +} + +.second-speech-bubble .bubble-tail { + top: 100%; + bottom: auto; + left: 50%; + transform: translateX(-50%); + border-top: 12px solid #C19EF5; +} + +/* Happy Meow Bubble */ +.happy-speech-bubble { + position: absolute; + top: -60px; + left: 50%; + transform: translateX(-50%); + background: linear-gradient(135deg, #F3C8A0 0%, #BA6D4B 100%); + border: 2px solid #5A2C28; + border-radius: 20px; + padding: 12px 18px; + font-family: 'Comic Sans MS', 'Comic Sans', cursive; + font-size: 16px; + font-weight: bold; + color: #fff; + white-space: nowrap; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.3), + inset 0 1px 3px rgba(255, 255, 255, 0.4), + 0 0 20px rgba(243, 200, 160, 0.5); + animation: popInBubble 0.5s cubic-bezier(0.34, 1.56, 0.64, 1); + z-index: 10; +} + +.happy-speech-bubble .bubble-tail { + top: 100%; + bottom: auto; + left: 50%; + transform: translateX(-50%); + border-top: 12px solid #F3C8A0; +} + +/* Meat Emoji */ +.meat-emoji { + position: fixed; + font-size: 48px; + cursor: grab; + user-select: none; + z-index: 1001; + display: flex; + align-items: center; + justify-content: center; + width: 60px; + height: 60px; + animation: meatAppear 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); + filter: drop-shadow(0 2px 8px rgba(0, 0, 0, 0.3)); + transition: transform 0.1s ease; +} + +.meat-emoji:active { + cursor: grabbing; + transform: scale(1.1); + filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.5)); +} + +@keyframes meatAppear { + 0% { + opacity: 0; + transform: scale(0); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + @media (max-width: 900px) { .bwrap { transform: scale(0.9); diff --git a/src/app/pages/welcome/welcome.component.html b/src/app/pages/welcome/welcome.component.html index b7db787..0f26090 100644 --- a/src/app/pages/welcome/welcome.component.html +++ b/src/app/pages/welcome/welcome.component.html @@ -193,6 +193,58 @@ + + @if (showSpeechBubble) { +
+
+
{{ bubbleMessage }}
+
+
+
+ } + + + @if (isZoomedIn) { +
+
+
+
+ Player 2 + @if (showSecondSpeechBubble) { +
+
Feed me! 🍖
+
+
+ } + @if (showHappyBubble) { +
+
Happy meow! 😸
+
+
+ } +
+
+ + + @if (showMeatEmoji) { +
+ 🍖 +
+ } +
+
+ } +
diff --git a/src/app/pages/welcome/welcome.component.ts b/src/app/pages/welcome/welcome.component.ts index cc4dc5f..f6cdb5b 100644 --- a/src/app/pages/welcome/welcome.component.ts +++ b/src/app/pages/welcome/welcome.component.ts @@ -49,11 +49,28 @@ export class WelcomeComponent implements OnInit, OnDestroy { isSunsetMode = false; modeBadge = 'NIGHT MODE'; + // Speech bubble and zoom features + showSpeechBubble = false; + isZoomedIn = false; + showSecondSpeechBubble = false; + showHappyBubble = false; + showMeatEmoji = false; + bubbleMessage = 'meow'; + + // Meat emoji drag state + meatX = 0; + meatY = 0; + isDraggingMeat = false; + meatDragOffsetX = 0; + meatDragOffsetY = 0; + stars: Star[] = []; bgBuildings: BackgroundBuilding[] = []; windows: Record = {}; private flickerIntervalId: ReturnType | undefined; + private speechBubbleTimeoutId: ReturnType | undefined; + private zoomTimeoutId: ReturnType | undefined; private coolColors = ['#7de8ff', '#00d5ff', '#5bc0de', '#31b0d5', '#4fc3f7', '#29b6f6']; private coolGlowColors = ['#00d5ff', '#00d5ff', '#31b0d5', '#31b0d5', '#03a9f4', '#0288d1']; @@ -73,10 +90,21 @@ export class WelcomeComponent implements OnInit, OnDestroy { this.generateBackgroundBuildings(); this.generateWindowsForAllBuildings(); this.startWindowFlicker(); + + // Show speech bubble after 5 seconds + this.speechBubbleTimeoutId = setTimeout(() => { + this.showSpeechBubble = true; + }, 5000); } ngOnDestroy(): void { this.stopWindowFlicker(); + if (this.speechBubbleTimeoutId) { + clearTimeout(this.speechBubbleTimeoutId); + } + if (this.zoomTimeoutId) { + clearTimeout(this.zoomTimeoutId); + } } toggleTheme(): void { @@ -245,6 +273,88 @@ export class WelcomeComponent implements OnInit, OnDestroy { }); } + onSpeechBubbleClick(): void { + this.showSpeechBubble = false; + this.isZoomedIn = true; + this.bubbleMessage = 'meow'; + this.showMeatEmoji = true; + this.showHappyBubble = false; + this.showSecondSpeechBubble = true; + + // Reset meat position + this.meatX = window.innerWidth / 2 - 100; + this.meatY = window.innerHeight / 2 + 150; + } + + onZoomedViewClick(): void { + this.isZoomedIn = false; + this.showSecondSpeechBubble = false; + this.showHappyBubble = false; + this.showMeatEmoji = false; + this.bubbleMessage = 'meow'; + + if (this.zoomTimeoutId) { + clearTimeout(this.zoomTimeoutId); + } + } + + onMeatMouseDown(event: MouseEvent): void { + this.isDraggingMeat = true; + const rect = (event.target as HTMLElement).getBoundingClientRect(); + this.meatDragOffsetX = event.clientX - rect.left; + this.meatDragOffsetY = event.clientY - rect.top; + } + + onMouseMove(event: MouseEvent): void { + if (!this.isDraggingMeat) { + return; + } + + this.meatX = event.clientX - this.meatDragOffsetX; + this.meatY = event.clientY - this.meatDragOffsetY; + + // Get gif element position + const gifElement = document.querySelector('.player-2-gif') as HTMLElement; + if (!gifElement) { + return; + } + + const gifRect = gifElement.getBoundingClientRect(); + const gifCenterX = gifRect.left + gifRect.width / 2; + const gifCenterY = gifRect.top + gifRect.height / 2; + + // Get meat center position + const meatElement = document.querySelector('.meat-emoji') as HTMLElement; + if (!meatElement) { + return; + } + + const meatRect = meatElement.getBoundingClientRect(); + const meatCenterX = meatRect.left + meatRect.width / 2; + const meatCenterY = meatRect.top + meatRect.height / 2; + + // Calculate distance + const distance = Math.sqrt( + Math.pow(meatCenterX - gifCenterX, 2) + Math.pow(meatCenterY - gifCenterY, 2) + ); + + // If meat is close enough to gif center (within 50px), trigger the interaction + if (distance < 50) { + this.onMeatFed(); + } + } + + onMouseUp(): void { + this.isDraggingMeat = false; + } + + onMeatFed(): void { + this.showMeatEmoji = false; + this.showSecondSpeechBubble = false; + this.showHappyBubble = true; + this.isDraggingMeat = false; + } + private closeAllDialogs(): void { this.showDifficultyDialog = false; this.showOptionsDialog = false;