jan/website/src/scripts/robot-interactions.js
2025-07-31 18:52:00 +10:00

698 lines
17 KiB
JavaScript

// Robot Interactions - Make robots come alive with personality!
class RobotInteractions {
constructor() {
this.robots = [];
this.mousePosition = { x: 0, y: 0 };
this.isInitialized = false;
// Robot moods and states
this.moods = ['happy', 'curious', 'excited', 'sleepy', 'thinking'];
this.currentMood = 'happy';
// Easter egg: secret robot dance
this.konami = [];
this.konamiCode = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
}
init() {
if (this.isInitialized) return;
this.findRobots();
this.setupEventListeners();
this.startIdleAnimations();
this.setupEasterEggs();
this.isInitialized = true;
}
findRobots() {
// Find all robot elements
const robotElements = document.querySelectorAll('.robot-head, .robot-container, .model-robot, .platform-robot, .tool-robot');
robotElements.forEach(robot => {
const eyes = robot.querySelectorAll('.robot-eye');
if (eyes.length > 0) {
this.robots.push({
element: robot,
eyes: Array.from(eyes),
originalPositions: Array.from(eyes).map(eye => {
const rect = eye.getBoundingClientRect();
return {
x: rect.left + rect.width / 2,
y: rect.top + rect.height / 2
};
}),
isBlinking: false,
mood: 'happy'
});
}
});
}
setupEventListeners() {
// Track mouse movement
document.addEventListener('mousemove', (e) => {
this.mousePosition = { x: e.clientX, y: e.clientY };
this.updateEyes();
});
// Robot hover interactions
this.robots.forEach(robot => {
robot.element.addEventListener('mouseenter', () => this.onRobotHover(robot));
robot.element.addEventListener('mouseleave', () => this.onRobotLeave(robot));
robot.element.addEventListener('click', () => this.onRobotClick(robot));
});
// Keyboard interactions
document.addEventListener('keydown', (e) => this.handleKeyPress(e));
}
updateEyes() {
this.robots.forEach(robot => {
if (robot.isBlinking) return;
robot.eyes.forEach((eye, index) => {
// Skip sleeping or closed eyes
if (eye.classList.contains('sleeping') || eye.classList.contains('closed')) return;
const eyeRect = eye.getBoundingClientRect();
const eyeCenter = {
x: eyeRect.left + eyeRect.width / 2,
y: eyeRect.top + eyeRect.height / 2
};
// Calculate angle between eye and mouse
const angle = Math.atan2(
this.mousePosition.y - eyeCenter.y,
this.mousePosition.x - eyeCenter.x
);
// Calculate distance (capped for natural movement)
const distance = Math.min(
Math.hypot(
this.mousePosition.x - eyeCenter.x,
this.mousePosition.y - eyeCenter.y
) / 10,
3 // Maximum pupil movement
);
// Move the pupil (eye shine)
const pupil = eye.querySelector('::after') || eye;
const offsetX = Math.cos(angle) * distance;
const offsetY = Math.sin(angle) * distance;
// Apply transform to eye or create inner pupil
if (!eye.querySelector('.pupil')) {
const pupilElement = document.createElement('div');
pupilElement.className = 'pupil';
eye.appendChild(pupilElement);
}
const pupilElement = eye.querySelector('.pupil');
if (pupilElement) {
pupilElement.style.transform = `translate(${offsetX}px, ${offsetY}px)`;
}
});
});
}
onRobotHover(robot) {
// Add excited animation
robot.element.classList.add('hover-active');
// Make eyes bigger
robot.eyes.forEach(eye => {
eye.style.transform = 'scale(1.2)';
eye.classList.add('excited');
});
// Add blush effect
this.addBlush(robot);
// Random reaction
const reactions = ['happy', 'surprised', 'love'];
const reaction = reactions[Math.floor(Math.random() * reactions.length)];
if (reaction === 'love') {
robot.eyes.forEach(eye => eye.classList.add('love'));
}
}
onRobotLeave(robot) {
robot.element.classList.remove('hover-active');
// Reset eyes
robot.eyes.forEach(eye => {
eye.style.transform = '';
eye.classList.remove('excited', 'love');
});
// Remove blush
this.removeBlush(robot);
}
onRobotClick(robot) {
// Trigger a fun animation
this.makeRobotJump(robot);
this.makeRobotSpeak(robot);
// Easter egg: clicking 5 times makes robot do a dance
if (!robot.clickCount) robot.clickCount = 0;
robot.clickCount++;
if (robot.clickCount >= 5) {
this.robotDance(robot);
robot.clickCount = 0;
}
}
makeRobotJump(robot) {
robot.element.style.animation = 'robotJump 0.6s ease-out';
setTimeout(() => {
robot.element.style.animation = '';
}, 600);
}
makeRobotSpeak(robot) {
const messages = [
"Hello there! 👋",
"Beep boop! 🤖",
"AI at your service!",
"Let's build something cool!",
"Privacy first! 🔒",
"Running locally! 💪",
"*happy robot noises*",
"01001000 01101001! (Hi in binary!)"
];
const message = messages[Math.floor(Math.random() * messages.length)];
this.showSpeechBubble(robot, message);
}
showSpeechBubble(robot, message) {
// Remove existing bubble
const existingBubble = robot.element.querySelector('.robot-speech-bubble');
if (existingBubble) existingBubble.remove();
// Create new bubble
const bubble = document.createElement('div');
bubble.className = 'robot-speech-bubble';
bubble.innerHTML = `
<span>${message}</span>
<div class="bubble-tail"></div>
`;
robot.element.appendChild(bubble);
// Remove after 3 seconds
setTimeout(() => {
bubble.classList.add('fade-out');
setTimeout(() => bubble.remove(), 300);
}, 3000);
}
startIdleAnimations() {
// Random blinking
setInterval(() => {
this.robots.forEach(robot => {
if (Math.random() < 0.1 && !robot.isBlinking) {
this.makeRobotBlink(robot);
}
});
}, 2000);
// Random mood changes
setInterval(() => {
const robot = this.robots[Math.floor(Math.random() * this.robots.length)];
if (robot && Math.random() < 0.3) {
this.changeRobotMood(robot);
}
}, 5000);
}
makeRobotBlink(robot) {
robot.isBlinking = true;
robot.eyes.forEach(eye => {
eye.style.transition = 'height 0.1s ease';
eye.style.height = '2px';
setTimeout(() => {
eye.style.height = '';
robot.isBlinking = false;
}, 150);
});
}
changeRobotMood(robot) {
const moods = ['thinking', 'happy', 'curious'];
const mood = moods[Math.floor(Math.random() * moods.length)];
switch(mood) {
case 'thinking':
this.showThinkingBubbles(robot);
break;
case 'happy':
this.makeRobotSmile(robot);
break;
case 'curious':
this.makeRobotLookAround(robot);
break;
}
}
showThinkingBubbles(robot) {
const bubbles = document.createElement('div');
bubbles.className = 'thinking-bubbles';
bubbles.innerHTML = `
<div class="think-bubble small"></div>
<div class="think-bubble medium"></div>
<div class="think-bubble large">💭</div>
`;
robot.element.appendChild(bubbles);
setTimeout(() => {
bubbles.classList.add('fade-out');
setTimeout(() => bubbles.remove(), 300);
}, 3000);
}
makeRobotSmile(robot) {
const smile = robot.element.querySelector('.robot-smile');
if (smile) {
smile.classList.add('big-smile');
setTimeout(() => smile.classList.remove('big-smile'), 2000);
}
}
makeRobotLookAround(robot) {
let direction = -1;
const lookInterval = setInterval(() => {
robot.eyes.forEach(eye => {
eye.style.transform = `translateX(${direction * 3}px)`;
});
direction *= -1;
}, 500);
setTimeout(() => {
clearInterval(lookInterval);
robot.eyes.forEach(eye => {
eye.style.transform = '';
});
}, 2000);
}
addBlush(robot) {
const head = robot.element.querySelector('.robot-head') || robot.element;
['left', 'right'].forEach(side => {
const blush = document.createElement('div');
blush.className = `robot-blush ${side}`;
head.appendChild(blush);
});
}
removeBlush(robot) {
const blushes = robot.element.querySelectorAll('.robot-blush');
blushes.forEach(blush => blush.remove());
}
setupEasterEggs() {
// Konami code for robot dance party
document.addEventListener('keydown', (e) => {
this.konami.push(e.key);
this.konami = this.konami.slice(-10);
if (this.konami.join(',') === this.konamiCode.join(',')) {
this.robotDanceParty();
}
});
// Secret robot activation phrase
let secretPhrase = '';
document.addEventListener('keypress', (e) => {
secretPhrase += e.key;
secretPhrase = secretPhrase.slice(-10);
if (secretPhrase.includes('robot')) {
this.activateSecretRobotMode();
}
});
}
robotDanceParty() {
document.body.classList.add('robot-dance-party');
// Make all robots dance
this.robots.forEach((robot, index) => {
setTimeout(() => {
this.robotDance(robot);
}, index * 200);
});
// Add disco lights
this.addDiscoLights();
// Stop after 10 seconds
setTimeout(() => {
document.body.classList.remove('robot-dance-party');
this.removeDiscoLights();
}, 10000);
}
robotDance(robot) {
robot.element.classList.add('dancing');
// Random dance moves
const dances = ['wiggle', 'spin', 'bounce', 'shake'];
const dance = dances[Math.floor(Math.random() * dances.length)];
robot.element.style.animation = `robot-${dance} 1s ease-in-out infinite`;
setTimeout(() => {
robot.element.style.animation = '';
robot.element.classList.remove('dancing');
}, 5000);
}
addDiscoLights() {
const disco = document.createElement('div');
disco.className = 'disco-lights';
disco.innerHTML = `
<div class="disco-ball">🪩</div>
<div class="light-beam red"></div>
<div class="light-beam blue"></div>
<div class="light-beam green"></div>
<div class="light-beam yellow"></div>
`;
document.body.appendChild(disco);
}
removeDiscoLights() {
const disco = document.querySelector('.disco-lights');
if (disco) disco.remove();
}
activateSecretRobotMode() {
// Make all robots super happy
this.robots.forEach(robot => {
robot.eyes.forEach(eye => {
eye.classList.add('sparkle', 'rainbow');
});
this.showSpeechBubble(robot, "Secret mode activated! 🎉");
});
// Remove after 5 seconds
setTimeout(() => {
this.robots.forEach(robot => {
robot.eyes.forEach(eye => {
eye.classList.remove('sparkle', 'rainbow');
});
});
}, 5000);
}
handleKeyPress(e) {
// Number keys change robot expressions
if (e.key >= '1' && e.key <= '9') {
const expressions = ['happy', 'sad', 'surprised', 'angry', 'love', 'sleepy', 'wink', 'dizzy', 'cool'];
const expression = expressions[parseInt(e.key) - 1];
if (expression) {
this.robots.forEach(robot => {
this.setRobotExpression(robot, expression);
});
}
}
}
setRobotExpression(robot, expression) {
// Clear previous expressions
const allExpressions = ['happy', 'sad', 'surprised', 'angry', 'love', 'sleepy', 'wink', 'dizzy', 'cool'];
robot.eyes.forEach(eye => {
allExpressions.forEach(exp => eye.classList.remove(exp));
eye.classList.add(expression);
});
// Remove after 3 seconds
setTimeout(() => {
robot.eyes.forEach(eye => {
eye.classList.remove(expression);
});
}, 3000);
}
}
// Initialize when DOM is ready
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', () => {
window.robotInteractions = new RobotInteractions();
window.robotInteractions.init();
});
} else {
window.robotInteractions = new RobotInteractions();
window.robotInteractions.init();
}
// Add required styles dynamically
const style = document.createElement('style');
style.textContent = `
/* Pupil for eye tracking */
.robot-eye .pupil {
position: absolute;
top: 50%;
left: 50%;
width: 40%;
height: 40%;
background: white;
border-radius: 50%;
transform: translate(-50%, -50%);
transition: transform 0.1s ease-out;
pointer-events: none;
}
/* Speech bubble */
.robot-speech-bubble {
position: absolute;
bottom: 110%;
left: 50%;
transform: translateX(-50%);
background: white;
color: #1A1A2E;
padding: 0.75rem 1rem;
border-radius: 20px;
border: 3px solid #1A1A2E;
font-size: 0.9rem;
font-weight: 600;
white-space: nowrap;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2);
animation: bubbleIn 0.3s ease-out;
z-index: 100;
}
.robot-speech-bubble.fade-out {
animation: bubbleOut 0.3s ease-in forwards;
}
.bubble-tail {
position: absolute;
bottom: -8px;
left: 50%;
transform: translateX(-50%);
width: 0;
height: 0;
border-left: 8px solid transparent;
border-right: 8px solid transparent;
border-top: 10px solid white;
}
.bubble-tail::before {
content: '';
position: absolute;
bottom: 2px;
left: -10px;
width: 0;
height: 0;
border-left: 10px solid transparent;
border-right: 10px solid transparent;
border-top: 12px solid #1A1A2E;
}
/* Thinking bubbles */
.thinking-bubbles {
position: absolute;
top: -40px;
right: -20px;
animation: bubbleFloat 3s ease-in-out;
}
.thinking-bubbles.fade-out {
animation: fadeOut 0.3s ease-in forwards;
}
.think-bubble {
position: absolute;
background: white;
border: 2px solid #1A1A2E;
border-radius: 50%;
}
.think-bubble.small {
width: 8px;
height: 8px;
bottom: 0;
left: 0;
}
.think-bubble.medium {
width: 12px;
height: 12px;
bottom: 10px;
left: -5px;
}
.think-bubble.large {
width: 30px;
height: 30px;
bottom: 25px;
left: -15px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
}
/* Big smile animation */
.robot-smile.big-smile {
width: 30px !important;
height: 15px !important;
border-width: 4px !important;
}
/* Dance animations */
@keyframes robot-wiggle {
0%, 100% { transform: rotate(-5deg); }
50% { transform: rotate(5deg); }
}
@keyframes robot-spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
@keyframes robot-bounce {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-20px); }
}
@keyframes robot-shake {
0%, 100% { transform: translateX(0); }
25% { transform: translateX(-5px); }
75% { transform: translateX(5px); }
}
@keyframes robotJump {
0%, 100% { transform: translateY(0) scale(1); }
50% { transform: translateY(-30px) scale(1.1); }
}
@keyframes bubbleIn {
0% { opacity: 0; transform: translateX(-50%) scale(0.8); }
100% { opacity: 1; transform: translateX(-50%) scale(1); }
}
@keyframes bubbleOut {
0% { opacity: 1; transform: translateX(-50%) scale(1); }
100% { opacity: 0; transform: translateX(-50%) scale(0.8); }
}
@keyframes fadeOut {
0% { opacity: 1; }
100% { opacity: 0; }
}
@keyframes bubbleFloat {
0%, 100% { transform: translateY(0); }
50% { transform: translateY(-5px); }
}
/* Disco mode */
.disco-lights {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
pointer-events: none;
z-index: 9999;
}
.disco-ball {
position: absolute;
top: 50px;
left: 50%;
transform: translateX(-50%);
font-size: 60px;
animation: discoSpin 2s linear infinite;
}
.light-beam {
position: absolute;
top: 100px;
left: 50%;
width: 100px;
height: 500px;
opacity: 0.3;
transform-origin: top center;
animation: lightSweep 4s ease-in-out infinite;
}
.light-beam.red { background: linear-gradient(transparent, #FF006E); }
.light-beam.blue { background: linear-gradient(transparent, #3A86FF); animation-delay: 1s; }
.light-beam.green { background: linear-gradient(transparent, #06FFA5); animation-delay: 2s; }
.light-beam.yellow { background: linear-gradient(transparent, #FFB700); animation-delay: 3s; }
@keyframes discoSpin {
0% { transform: translateX(-50%) rotate(0deg); }
100% { transform: translateX(-50%) rotate(360deg); }
}
@keyframes lightSweep {
0%, 100% { transform: translateX(-50%) rotate(-30deg); }
50% { transform: translateX(-50%) rotate(30deg); }
}
/* Rainbow eyes */
.robot-eye.rainbow {
animation: rainbowEyes 2s linear infinite;
}
@keyframes rainbowEyes {
0% { background: #FF006E; }
25% { background: #3A86FF; }
50% { background: #06FFA5; }
75% { background: #FFB700; }
100% { background: #FF006E; }
}
/* Sparkle effect */
.robot-eye.sparkle::before {
content: '✨';
position: absolute;
top: -10px;
right: -10px;
font-size: 12px;
animation: sparkleFloat 1s ease-in-out infinite;
}
@keyframes sparkleFloat {
0%, 100% { transform: translateY(0) rotate(0deg); }
50% { transform: translateY(-5px) rotate(180deg); }
}
`;
document.head.appendChild(style);