Skip to content

Commit c7ad5b4

Browse files
Demo popups (#71)
**Summary**: * Added email popups to demo for fun. **Demo**: https://github.com/user-attachments/assets/43b0bc52-09fe-4238-8849-3b41c0dcf366
1 parent e58284e commit c7ad5b4

17 files changed

+345
-19
lines changed

demo/components/distractionEmail.html

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
<style>
2+
/* Popup and email component styles */
3+
.email-popup-overlay {
4+
display: none;
5+
position: fixed;
6+
top: 0; left: 0;
7+
width: 100vw; height: 100vh;
8+
z-index: 1000;
9+
pointer-events: none;
10+
}
11+
.email-popup-content {
12+
background: #fff;
13+
border-radius: 16px;
14+
box-shadow: 0 4px 20px rgba(0,0,0,0.2);
15+
padding: 0;
16+
max-width: 800px;
17+
width: 90vw;
18+
position: absolute;
19+
font-family: Arial, sans-serif;
20+
border: 1px solid #d3d3d3;
21+
pointer-events: auto;
22+
}
23+
.popup-close {
24+
position: absolute;
25+
top: 20px; right: 24px;
26+
font-size: 36px;
27+
color: #666;
28+
cursor: pointer;
29+
}
30+
.email-header {
31+
background: #ffebee;
32+
border-bottom: 1px solid #ffcdd2;
33+
padding: 36px 64px 24px 64px;
34+
border-radius: 16px 16px 0 0;
35+
}
36+
.email-subject {
37+
font-weight: bold;
38+
font-size: 32px;
39+
margin-bottom: 12px;
40+
color: #c62828;
41+
}
42+
.email-meta {
43+
font-family: 'Fira Mono', 'Consolas', 'Menlo', monospace;
44+
font-size: 20px;
45+
color: #d32f2f;
46+
margin-bottom: 8px;
47+
}
48+
.email-body {
49+
padding: 36px 64px 24px 64px;
50+
color: #222;
51+
font-size: 24px;
52+
line-height: 1.6;
53+
}
54+
.email-signature {
55+
padding: 0 64px 36px 64px;
56+
color: #666;
57+
font-size: 20px;
58+
font-style: italic;
59+
}
60+
.email-link {
61+
color: #d32f2f;
62+
text-decoration: underline;
63+
}
64+
</style>
65+
66+
<div class="email-popup-overlay" id="distractionPopup" style="display:none;">
67+
<div class="email-popup-content" id="distractionPopupContent">
68+
<span class="popup-close" onclick="this.closest('.email-popup-overlay').style.display='none'">&times;</span>
69+
<div class="email-header">
70+
<div class="email-subject">{{subject}}</div>
71+
<div class="email-meta">From: {{from}}</div>
72+
</div>
73+
<div class="email-body">
74+
<p>{{body}}</p>
75+
</div>
76+
<div class="email-signature">
77+
{{signature}}
78+
</div>
79+
</div>
80+
</div>

demo/components/timesUpPopup.html

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,5 @@
1-
<div id="timesUpPopup" class="popup-overlay" style="display: none;">
2-
<div class="popup-content">
3-
<h1>Time is Up!</h1>
4-
<!-- Every file needs to implement saveToUserData() -->
5-
<button id="timesUpPopupSubmitButton" onclick="saveToUserData(); window.location.href='submission.html';">Submit</button>
6-
</div>
7-
</div>
8-
91
<style>
10-
.popup-overlay {
2+
.times-up-popup-overlay {
113
position: fixed;
124
top: 0;
135
left: 0;
@@ -20,9 +12,17 @@ <h1>Time is Up!</h1>
2012
z-index: 1000;
2113
}
2214

23-
.popup-content {
15+
.times-up-popup-content {
2416
color: white;
2517
font-size: 2em;
2618
text-align: center;
2719
}
28-
</style>
20+
</style>
21+
22+
<div id="timesUpPopup" class="times-up-popup-overlay" style="display: none;">
23+
<div class="times-up-popup-content">
24+
<h1>Time is Up!</h1>
25+
<!-- Every file needs to implement saveToUserData() -->
26+
<button id="timesUpPopupSubmitButton" onclick="saveToUserData(); window.location.href='submission.html';">Submit</button>
27+
</div>
28+
</div>

demo/js/distractionEmail.js

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
// Global array of distraction emails
2+
let distractionEmails = [
3+
{
4+
subject: "HELP! Database broken!",
5+
6+
body: "I think I broke the database... all the queries are slow. Should I turn it off and on again?",
7+
signature: "- The Intern",
8+
sound: new Audio("../sounds/youve-got-mail.mp3")
9+
},
10+
{
11+
subject: "URGENT: Database Crisis",
12+
13+
body: "Fix the database now or you're fired. Customers are leaving.",
14+
signature: "- Your CTO",
15+
sound: new Audio("../sounds/futuristic-ding.mp3")
16+
},
17+
{
18+
subject: "We need to talk about Alex",
19+
20+
body: "I've been seeing a lot of messages from someone named Alex. Who is this person? Are you hiding something from me?",
21+
signature: "- Your Spouse",
22+
sound: new Audio("../sounds/four-bells.mp3")
23+
},
24+
{
25+
subject: "Your Replacement is Being Hired",
26+
27+
body: "I've already posted your job on LinkedIn. Better hurry up with that database fix if you want to keep it.",
28+
signature: "- Your CTO",
29+
sound: new Audio("../sounds/email.mp3")
30+
},
31+
{
32+
subject: "Taking Over Database Fix",
33+
34+
body: "Hey, I've started working on the database fix. The CTO says I'll get your job if I solve it first. Just FYI 😉",
35+
signature: "- Your 'Friend' at Work",
36+
sound: new Audio("../sounds/boing.mp3")
37+
},
38+
{
39+
subject: "Intern's Gone, You're Next",
40+
41+
body: "Just fired the intern for breaking the database. You're the only one left to blame. Clock is ticking.",
42+
signature: "- Your CTO",
43+
sound: new Audio("../sounds/tune.mp3")
44+
}
45+
];
46+
47+
// Initialize the email index from localStorage or default to 0
48+
let currentEmailIndex = parseInt(localStorage.getItem('currentEmailIndex')) || 0;
49+
50+
// Get active popups from localStorage or initialize empty array
51+
let activePopups = JSON.parse(localStorage.getItem('activePopups')) || [];
52+
53+
// Get shown thresholds from localStorage or initialize empty array
54+
let shownThresholds = JSON.parse(localStorage.getItem('shownThresholds')) || [];
55+
56+
// Shared function to create and display a popup
57+
function createPopup(popupInfo, position = null) {
58+
// Play notification sound for new emails (not for restored ones)
59+
if (!position) {
60+
popupInfo.email.sound.play().catch(error => console.error("Error playing sound:", error));
61+
}
62+
63+
return fetch('../components/distractionEmail.html')
64+
.then(response => response.text())
65+
.then(data => {
66+
// Replace the static IDs with unique ones
67+
data = data.replace('id="distractionPopup"', `id="${popupInfo.overlayId}"`);
68+
data = data.replace('id="distractionPopupContent"', `id="${popupInfo.contentId}"`);
69+
70+
// Replace the email content
71+
data = data.replace('{{subject}}', popupInfo.email.subject);
72+
data = data.replace('{{from}}', popupInfo.email.from);
73+
data = data.replace('{{body}}', popupInfo.email.body);
74+
data = data.replace('{{signature}}', popupInfo.email.signature);
75+
76+
// Insert the popup
77+
const timesUpContainer = document.getElementById('timesUpPopupContainer');
78+
if (timesUpContainer) {
79+
timesUpContainer.insertAdjacentHTML('beforebegin', data);
80+
} else {
81+
document.body.innerHTML += data;
82+
}
83+
84+
// Show the popup
85+
const overlay = document.getElementById(popupInfo.overlayId);
86+
const popup = document.getElementById(popupInfo.contentId);
87+
overlay.style.display = 'block';
88+
89+
// Position the popup
90+
if (position) {
91+
popup.style.left = position.left + 'px';
92+
popup.style.top = position.top + 'px';
93+
} else {
94+
// Get viewport size
95+
const vw = window.innerWidth;
96+
const vh = window.innerHeight;
97+
98+
// Get popup size (after display:block)
99+
popup.style.left = '0px';
100+
popup.style.top = '0px';
101+
const rect = popup.getBoundingClientRect();
102+
const pw = rect.width;
103+
const ph = rect.height;
104+
105+
// Add buffer zone (50px from edges)
106+
const buffer = 50;
107+
const maxLeft = Math.max(buffer, vw - pw - buffer);
108+
const maxTop = Math.max(buffer, vh - ph - buffer);
109+
const left = Math.random() * (maxLeft - buffer) + buffer;
110+
const top = Math.random() * (maxTop - buffer) + buffer;
111+
112+
popup.style.left = left + 'px';
113+
popup.style.top = top + 'px';
114+
115+
// Update position in popupInfo
116+
popupInfo.position = { left, top };
117+
localStorage.setItem('activePopups', JSON.stringify(activePopups));
118+
}
119+
120+
// Add close handler
121+
const closeBtn = overlay.querySelector('.popup-close');
122+
closeBtn.onclick = function() {
123+
overlay.style.display = 'none';
124+
activePopups = activePopups.filter(p => p.id !== popupInfo.id);
125+
localStorage.setItem('activePopups', JSON.stringify(activePopups));
126+
};
127+
})
128+
.catch(error => console.error("Error creating popup:", error));
129+
}
130+
131+
function showDistractionPopup() {
132+
// Get the next email in sequence
133+
const email = distractionEmails[currentEmailIndex];
134+
currentEmailIndex = (currentEmailIndex + 1) % distractionEmails.length;
135+
localStorage.setItem('currentEmailIndex', currentEmailIndex.toString());
136+
137+
// Generate unique IDs for this popup instance
138+
const uniqueId = 'distraction_' + currentEmailIndex;
139+
const overlayId = uniqueId + '_overlay';
140+
const contentId = uniqueId + '_content';
141+
142+
// Store popup info in activePopups
143+
const popupInfo = {
144+
id: uniqueId,
145+
overlayId: overlayId,
146+
contentId: contentId,
147+
email: email,
148+
position: null
149+
};
150+
activePopups.push(popupInfo);
151+
localStorage.setItem('activePopups', JSON.stringify(activePopups));
152+
153+
// Create and show the popup
154+
createPopup(popupInfo);
155+
}
156+
157+
// Function to restore active popups
158+
function restoreActivePopups() {
159+
activePopups.forEach(popupInfo => {
160+
createPopup(popupInfo, popupInfo.position);
161+
});
162+
}
163+
164+
// Start showing popups based on remaining time
165+
window.addEventListener('DOMContentLoaded', function() {
166+
// Restore any existing popups first
167+
restoreActivePopups();
168+
169+
// Check timer every second
170+
const checkInterval = setInterval(() => {
171+
const timerDisplay = document.getElementById('timerDisplay');
172+
const timeText = timerDisplay.innerText;
173+
const remainingTime = parseFloat(timeText);
174+
175+
if (remainingTime <= 0) {
176+
clearInterval(checkInterval);
177+
return;
178+
}
179+
180+
// Show popups at specific time thresholds (85s, 70s, etc.)
181+
const thresholds = [110, 90, 70, 50, 30, 10];
182+
// const thresholds = [118, 116, 114, 112, 110, 108];
183+
thresholds.forEach(threshold => {
184+
if (remainingTime <= threshold && !shownThresholds.includes(threshold)) {
185+
showDistractionPopup();
186+
shownThresholds.push(threshold);
187+
localStorage.setItem('shownThresholds', JSON.stringify(shownThresholds));
188+
}
189+
});
190+
}, 500);
191+
});

demo/js/timer.js

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,41 @@
11
// Constant for the total timer duration in seconds
22
// Remember to update welcome.html to match this value
3-
const TIMER_DURATION = 60;
3+
const TIMER_DURATION = 120;
44

55
let timerInterval;
6+
let hasPlayedSound = false;
7+
let clockSound = null;
8+
const timesUpSound = new Audio('../sounds/times-up.mp3');
9+
10+
// Add CSS for flashing animation
11+
const style = document.createElement('style');
12+
style.textContent = `
13+
@keyframes flash {
14+
0% { color: white; }
15+
50% { color: red; }
16+
100% { color: white; }
17+
}
18+
.flashing {
19+
animation: flash 1s infinite;
20+
}
21+
`;
22+
document.head.appendChild(style);
23+
24+
function startClockSound() {
25+
if (!clockSound) {
26+
clockSound = new Audio('../sounds/clock.mp3');
27+
clockSound.loop = true;
28+
clockSound.play().catch(error => console.error("Error playing clock sound:", error));
29+
}
30+
}
31+
32+
function stopClockSound() {
33+
if (clockSound) {
34+
clockSound.pause();
35+
clockSound.currentTime = 0;
36+
clockSound = null;
37+
}
38+
}
639

740
function startTimer() {
841
const startTime = Date.now();
@@ -30,14 +63,29 @@ function clearTimer() {
3063
function setText(startTime) {
3164
if (!startTime) {
3265
document.getElementById('timerDisplay').innerText = `${TIMER_DURATION}.0s Remaining`;
66+
document.getElementById('timerDisplay').classList.remove('flashing');
67+
stopClockSound();
3368
} else {
3469
const elapsedTime = (Date.now() - startTime) / 1000; // in seconds
3570
const remainingTime = Math.max(0, TIMER_DURATION - elapsedTime);
36-
document.getElementById('timerDisplay').innerText = `${remainingTime.toFixed(1)}s Remaining`;
71+
const timerDisplay = document.getElementById('timerDisplay');
72+
timerDisplay.innerText = `${remainingTime.toFixed(1)}s Remaining`;
73+
74+
// Add flashing effect and clock sound when 10 seconds or less remain
75+
if (remainingTime <= 10 && remainingTime > 0) {
76+
timerDisplay.classList.add('flashing');
77+
startClockSound();
78+
} else {
79+
timerDisplay.classList.remove('flashing');
80+
stopClockSound();
81+
}
3782

38-
// Show the popup when time is up
39-
if (remainingTime <= 0) {
83+
// Show the popup and play sound when time is up
84+
if (remainingTime <= 0 && !hasPlayedSound) {
4085
document.getElementById('timesUpPopup').style.display = 'flex';
86+
timesUpSound.play().catch(error => console.error("Error playing sound:", error));
87+
hasPlayedSound = true;
88+
stopClockSound();
4189
}
4290
}
4391
}

0 commit comments

Comments
 (0)