const OutlookDefender = (function() {
let gameRunning = false;
let gamePaused = false;
let score = 0;
let paddlePosition = 50;
let fallingMeetings = [];
let appointments = [];
let gameSpeed = 1;
let spawnRate = 0.005;
const meetingTypes = [
"Client Review", "Team Standup", "Budget Discussion", "Project Update",
"Stakeholder Meeting", "Design Review", "Sprint Planning", "User Testing",
"Platform Demo", "Creative Brief"
];
let elements = {}; // Will hold references to DOM elements
const CSS = `
.outlook-defender {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f8f9fa;
padding: 20px;
max-width: 1000px;
margin: 0 auto;
}
.od-game-title {
text-align: center;
margin-bottom: 20px;
}
.od-pixel-logo {
display: inline-block;
margin-bottom: 10px;
}
.od-pixel-outlook {
font-family: 'Courier New', monospace;
font-size: 24px;
font-weight: bold;
color: #0078d4;
letter-spacing: 2px;
background: linear-gradient(45deg, #0078d4, #106ebe);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
text-shadow: 2px 2px 0px rgba(0,120,212,0.3);
}
.od-pixel-defender {
font-family: 'Courier New', monospace;
font-size: 28px;
font-weight: bold;
color: #d13438;
letter-spacing: 3px;
text-shadow: 2px 2px 0px rgba(209,52,56,0.3);
margin-top: 5px;
}
.od-logo-icon {
width: 40px;
height: 40px;
background: #0078d4;
display: inline-block;
margin-right: 10px;
position: relative;
box-shadow: 0 0 0 2px #106ebe, 0 0 0 4px #005a9e;
vertical-align: middle;
}
.od-logo-icon::before {
content: '';
position: absolute;
top: 8px;
left: 8px;
width: 24px;
height: 16px;
background: white;
box-shadow: inset 0 0 0 2px #0078d4;
}
.od-logo-icon::after {
content: '';
position: absolute;
top: 12px;
left: 12px;
width: 16px;
height: 8px;
background: #0078d4;
}
.od-outlook-header {
background: #0078d4;
color: white;
padding: 10px 20px;
border-radius: 4px 4px 0 0;
font-weight: 600;
display: flex;
justify-content: space-between;
align-items: center;
}
.od-calendar-container {
background: white;
border: 1px solid #e1e5e9;
border-radius: 0 0 4px 4px;
overflow: hidden;
position: relative;
}
.od-calendar-grid {
display: grid;
grid-template-columns: repeat(7, 1fr);
height: 500px;
position: relative;
overflow: hidden;
}
.od-day-column {
border-right: 1px solid #e1e5e9;
position: relative;
display: flex;
flex-direction: column;
justify-content: flex-end;
}
.od-day-column:last-child {
border-right: none;
}
.od-day-header {
background: #f3f2f1;
padding: 8px 12px;
font-weight: 600;
font-size: 12px;
color: #323130;
border-bottom: 1px solid #e1e5e9;
position: absolute;
top: 0;
left: 0;
right: 0;
z-index: 10;
}
.od-appointment {
background: #0078d4;
color: white;
padding: 4px 8px;
margin: 2px;
border-radius: 3px;
font-size: 11px;
font-weight: 500;
position: relative;
z-index: 5;
position: absolute;
left: 2px;
right: 2px;
}
.od-paddle {
position: absolute;
bottom: 10px;
width: 120px;
height: 30px;
background: #d13438;
color: white;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 12px;
z-index: 20;
transition: left 0.1s ease;
}
.od-falling-meeting {
position: absolute;
width: 100px;
height: 25px;
background: #ff6b35;
color: white;
border-radius: 3px;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 500;
z-index: 15;
transition: transform 0.1s ease;
}
.od-game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background: rgba(0, 0, 0, 0.9);
color: white;
padding: 30px;
border-radius: 8px;
text-align: center;
z-index: 100;
display: none;
}
.od-controls {
margin-top: 20px;
text-align: center;
}
.od-btn {
background: #0078d4;
color: white;
border: none;
padding: 10px 20px;
border-radius: 4px;
cursor: pointer;
font-family: inherit;
font-weight: 600;
margin: 0 10px;
}
.od-btn:hover {
background: #106ebe;
}
.od-stats {
display: flex;
gap: 20px;
font-size: 14px;
color: #605e5c;
}
`;
function createHTML() {
return `
`;
}
function updatePaddlePosition() {
elements.paddle.style.left = `${paddlePosition}%`;
}
function spawnMeeting() {
if (Math.random() < spawnRate) {
const meeting = {
x: Math.random() * 85 + 7.5,
y: 40,
velocityY: gameSpeed,
velocityX: (Math.random() - 0.5) * 2,
type: meetingTypes[Math.floor(Math.random() * meetingTypes.length)],
element: null
};
const element = document.createElement('div');
element.className = 'od-falling-meeting';
element.textContent = meeting.type;
element.style.left = meeting.x + '%';
element.style.top = meeting.y + 'px';
elements.calendar.appendChild(element);
meeting.element = element;
fallingMeetings.push(meeting);
}
}
function updateMeetings() {
for (let i = fallingMeetings.length - 1; i >= 0; i--) {
const meeting = fallingMeetings[i];
meeting.y += meeting.velocityY;
meeting.x += meeting.velocityX;
if (meeting.x <= 0 || meeting.x >= 85) {
meeting.velocityX *= -1;
}
meeting.element.style.left = meeting.x + '%';
meeting.element.style.top = meeting.y + 'px';
if (meeting.y > 450 && meeting.y < 480 &&
meeting.x > paddlePosition - 8 && meeting.x < paddlePosition + 8) {
meeting.velocityY = -Math.abs(meeting.velocityY) * 1.2;
meeting.velocityX += (Math.random() - 0.5) * 2;
if ((score + Math.floor(Math.random() * 10)) % 5 === 0) {
gameSpeed += 0.2;
spawnRate += 0.002;
}
continue;
}
if (meeting.y >= 470 && meeting.velocityY > 0) {
const dayColumn = Math.floor(meeting.x / 14.28);
if (dayColumn >= 0 && dayColumn < 7) {
addAppointmentToDay(dayColumn, meeting.type);
score++;
elements.scoreElement.textContent = score;
meeting.element.parentNode.removeChild(meeting.element);
fallingMeetings.splice(i, 1);
continue;
}
}
if (meeting.y < 0 && meeting.velocityY < 0) {
meeting.element.parentNode.removeChild(meeting.element);
fallingMeetings.splice(i, 1);
}
}
}
function addAppointmentToDay(dayIndex, meetingType) {
const dayColumn = elements.calendar.children[dayIndex];
const appointment = document.createElement('div');
appointment.className = 'od-appointment';
appointment.textContent = meetingType;
const currentHeight = appointments[dayIndex].length;
appointment.style.position = 'absolute';
appointment.style.bottom = (10 + currentHeight * 32) + 'px';
appointment.style.left = '2px';
appointment.style.right = '2px';
appointment.style.zIndex = '5';
dayColumn.appendChild(appointment);
appointments[dayIndex].push(meetingType);
if (appointments[dayIndex].length > 12) {
endGame();
}
}
function handleKeyPress(e) {
if (!gameRunning || gamePaused) return;
if (e.key === 'ArrowLeft' && paddlePosition > 5) {
paddlePosition -= 3;
updatePaddlePosition();
} else if (e.key === 'ArrowRight' && paddlePosition < 75) {
paddlePosition += 3;
updatePaddlePosition();
}
}
function handleMouseMove(e) {
if (!gameRunning || gamePaused) return;
const rect = elements.calendar.getBoundingClientRect();
const mouseX = e.clientX - rect.left;
const percentage = (mouseX / rect.width) * 100;
if (percentage >= 5 && percentage <= 75) {
paddlePosition = percentage;
updatePaddlePosition();
}
}
function gameLoop() {
if (!gameRunning || gamePaused) return;
spawnMeeting();
updateMeetings();
requestAnimationFrame(gameLoop);
}
function startGame() {
gameRunning = true;
gamePaused = false;
score = 0;
paddlePosition = 50;
fallingMeetings = [];
gameSpeed = 1;
spawnRate = 0.005;
for (let i = 0; i < 7; i++) {
appointments[i] = [];
const dayColumn = elements.calendar.children[i];
const existingAppointments = dayColumn.querySelectorAll('.od-appointment');
existingAppointments.forEach(app => app.remove());
}
const existingMeetings = elements.calendar.querySelectorAll('.od-falling-meeting');
existingMeetings.forEach(meeting => meeting.remove());
elements.scoreElement.textContent = '0';
elements.statusElement.textContent = 'Protected';
elements.gameOverElement.style.display = 'none';
elements.startBtn.style.display = 'none';
elements.pauseBtn.style.display = 'inline-block';
elements.pauseBtn.textContent = 'Pause';
updatePaddlePosition();
gameLoop();
}
function pauseGame() {
gamePaused = !gamePaused;
elements.pauseBtn.textContent = gamePaused ? 'Resume' : 'Pause';
if (!gamePaused) {
gameLoop();
}
}
function endGame() {
gameRunning = false;
elements.statusElement.textContent = 'Overwhelmed';
elements.finalScoreElement.textContent = score;
elements.gameOverElement.style.display = 'block';
elements.startBtn.style.display = 'inline-block';
elements.pauseBtn.style.display = 'none';
}
function restartGame() {
startGame();
}
// Public API
return {
init: function(containerId) {
const container = document.getElementById(containerId);
if (!container) {
console.error('OutlookDefender: Container element not found:', containerId);
return;
}
// Add CSS
const style = document.createElement('style');
style.textContent = CSS;
document.head.appendChild(style);
// Add HTML
container.className = 'outlook-defender';
container.innerHTML = createHTML();
// Get element references
elements.calendar = document.getElementById('od-calendar');
elements.paddle = document.getElementById('od-paddle');
elements.scoreElement = document.getElementById('od-score');
elements.statusElement = document.getElementById('od-status');
elements.gameOverElement = document.getElementById('od-gameOver');
elements.startBtn = document.getElementById('od-startBtn');
elements.pauseBtn = document.getElementById('od-pauseBtn');
elements.finalScoreElement = document.getElementById('od-finalScore');
// Initialize appointments array
for (let i = 0; i < 7; i++) {
appointments[i] = [];
}
// Event listeners
document.addEventListener('keydown', handleKeyPress);
elements.calendar.addEventListener('mousemove', handleMouseMove);
updatePaddlePosition();
},
start: startGame,
pause: pauseGame,
restart: restartGame
};
})();
// Auto-initialize if there's a container with default ID
document.addEventListener('DOMContentLoaded', function() {
const defaultContainer = document.getElementById('outlook-defender-container');
if (defaultContainer) {
OutlookDefender.init('outlook-defender-container');
}
});
OUTLOOK
DEFENDER
Leon Jacobs - Calendar (Week View)
Meetings Missed: 0
Vacation Status: Protected
Mon 22
Tue 23
Wed 24
Thu 25
Fri 26
Sat 27
Sun 28
OUT OF OFFICE
Calendar Overwhelmed!
Your vacation time has been completely consumed by meetings.
Final Score: 0 meetings missed
Use arrow keys or mouse to move your OUT OF OFFICE paddle