add daily reminder notification at 20:00
This commit is contained in:
parent
8a52f181cb
commit
c5bec7fed6
@ -926,6 +926,94 @@ footer a:hover {
|
|||||||
opacity: 0.9;
|
opacity: 0.9;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ===== DAILY REMINDER TOAST ===== */
|
||||||
|
.daily-toast {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 24px;
|
||||||
|
right: 24px;
|
||||||
|
max-width: 380px;
|
||||||
|
background: var(--white);
|
||||||
|
border: 1px solid var(--gray-100);
|
||||||
|
border-radius: 20px;
|
||||||
|
padding: 24px;
|
||||||
|
box-shadow: 0 8px 40px rgba(108, 92, 231, 0.2);
|
||||||
|
z-index: 200;
|
||||||
|
transform: translateY(120%);
|
||||||
|
opacity: 0;
|
||||||
|
transition: all 0.4s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast.show {
|
||||||
|
transform: translateY(0);
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast .toast-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast .toast-title {
|
||||||
|
font-size: 16px;
|
||||||
|
font-weight: 800;
|
||||||
|
color: var(--accent);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast .toast-close {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 20px;
|
||||||
|
cursor: pointer;
|
||||||
|
color: var(--gray-300);
|
||||||
|
padding: 4px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast .toast-close:hover {
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast p {
|
||||||
|
font-size: 14px;
|
||||||
|
color: var(--gray-500);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast .toast-actions {
|
||||||
|
display: flex;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast .toast-actions .btn {
|
||||||
|
font-size: 13px;
|
||||||
|
padding: 10px 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daily-toast .streak {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 700;
|
||||||
|
color: #e17055;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 480px) {
|
||||||
|
.daily-toast {
|
||||||
|
left: 12px;
|
||||||
|
right: 12px;
|
||||||
|
bottom: 12px;
|
||||||
|
max-width: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* ===== ANIMATIONS ===== */
|
/* ===== ANIMATIONS ===== */
|
||||||
@keyframes fadeInUp {
|
@keyframes fadeInUp {
|
||||||
from { opacity: 0; transform: translateY(20px); }
|
from { opacity: 0; transform: translateY(20px); }
|
||||||
|
|||||||
115
js/script.js
115
js/script.js
@ -220,6 +220,121 @@ function speakPhrase(btn) {
|
|||||||
setTimeout(function() { btn.innerHTML = '🔊 Hear Phrase'; }, 1500);
|
setTimeout(function() { btn.innerHTML = '🔊 Hear Phrase'; }, 1500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Daily Reminder at 20:00 ---
|
||||||
|
(function() {
|
||||||
|
var LS_KEY = 'english_easy_daily';
|
||||||
|
var TOAST_SHOWN = false;
|
||||||
|
|
||||||
|
function getToday() {
|
||||||
|
return new Date().toDateString();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getStreak() {
|
||||||
|
var data = JSON.parse(localStorage.getItem(LS_KEY) || '{"streak":0,"lastDate":""}');
|
||||||
|
var yesterday = new Date();
|
||||||
|
yesterday.setDate(yesterday.getDate() - 1);
|
||||||
|
var yesterdayStr = yesterday.toDateString();
|
||||||
|
var today = getToday();
|
||||||
|
if (data.lastDate === today) {
|
||||||
|
// already done today
|
||||||
|
} else if (data.lastDate === yesterdayStr) {
|
||||||
|
// consecutive day
|
||||||
|
} else if (data.lastDate && data.lastDate !== today) {
|
||||||
|
data.streak = 0;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveStreak(data) {
|
||||||
|
data.lastDate = getToday();
|
||||||
|
localStorage.setItem(LS_KEY, JSON.stringify(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showDailyToast() {
|
||||||
|
if (TOAST_SHOWN) return;
|
||||||
|
TOAST_SHOWN = true;
|
||||||
|
|
||||||
|
var data = getStreak();
|
||||||
|
var existing = document.querySelector('.daily-toast');
|
||||||
|
if (existing) existing.remove();
|
||||||
|
|
||||||
|
var toast = document.createElement('div');
|
||||||
|
toast.className = 'daily-toast';
|
||||||
|
|
||||||
|
var streakHTML = '';
|
||||||
|
if (data.streak > 0) {
|
||||||
|
streakHTML = '<div class="streak">🔥 Day ' + data.streak + ' streak!</div>';
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.innerHTML =
|
||||||
|
'<div class="toast-header">' +
|
||||||
|
'<div class="toast-title">⏰ Daily Practice</div>' +
|
||||||
|
'<button class="toast-close" onclick="this.closest(\'.daily-toast\').remove()">✕</button>' +
|
||||||
|
'</div>' +
|
||||||
|
streakHTML +
|
||||||
|
'<p>It\'s time for your daily English practice! Spend 5 minutes on an exercise or test.</p>' +
|
||||||
|
'<div class="toast-actions">' +
|
||||||
|
'<a href="exercises.html" class="btn" onclick="saveStreakFunc()">Practice Now</a>' +
|
||||||
|
'<button class="btn btn-outline" onclick="this.closest(\'.daily-toast\').remove()">Later</button>' +
|
||||||
|
'</div>';
|
||||||
|
|
||||||
|
document.body.appendChild(toast);
|
||||||
|
|
||||||
|
// trigger animation
|
||||||
|
setTimeout(function() { toast.classList.add('show'); }, 100);
|
||||||
|
|
||||||
|
// also try browser notification
|
||||||
|
if ('Notification' in window && Notification.permission === 'granted') {
|
||||||
|
new Notification('English Easy — Daily Practice', {
|
||||||
|
body: 'Time for your daily English practice! 5 minutes is all you need.',
|
||||||
|
icon: '/images/icon.png'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expose saveStreak for the toast button
|
||||||
|
window.saveStreakFunc = function() {
|
||||||
|
var data = getStreak();
|
||||||
|
data.streak = (data.streak || 0) + 1;
|
||||||
|
saveStreak(data);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Request notification permission
|
||||||
|
function requestNotifPermission() {
|
||||||
|
if ('Notification' in window && Notification.permission === 'default') {
|
||||||
|
Notification.requestPermission();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function checkDailyReminder() {
|
||||||
|
var now = new Date();
|
||||||
|
var hours = now.getHours();
|
||||||
|
var minutes = now.getMinutes();
|
||||||
|
var data = getStreak();
|
||||||
|
|
||||||
|
// Show if it's >= 20:00 and we haven't shown today
|
||||||
|
if (hours >= 20 && data.lastDate !== getToday()) {
|
||||||
|
showDailyToast();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check on page load
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
requestNotifPermission();
|
||||||
|
checkDailyReminder();
|
||||||
|
|
||||||
|
// Check every 30 seconds if it's 20:00
|
||||||
|
setInterval(function() {
|
||||||
|
var now = new Date();
|
||||||
|
if (now.getHours() === 20 && now.getMinutes() === 0) {
|
||||||
|
checkDailyReminder();
|
||||||
|
}
|
||||||
|
}, 30000);
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
// --- Vocabulary filter ---
|
// --- Vocabulary filter ---
|
||||||
function filterVocab(category, btn) {
|
function filterVocab(category, btn) {
|
||||||
const rows = document.querySelectorAll('.vocab-row');
|
const rows = document.querySelectorAll('.vocab-row');
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user