Student Focus Reminder Avatar

Displays an avatar that reminds students to stay focused with customizable prompts at adjustable intervals

Size

30.1 KB

Version

1.1.30

Created

Oct 28, 2025

Updated

about 2 months ago

1// ==UserScript==
2// @name		Student Focus Reminder Avatar
3// @description		Displays an avatar that reminds students to stay focused with customizable prompts at adjustable intervals
4// @version		1.1.30
5// @match		https://*.google.com/*
6// @icon		https://www.gstatic.com/images/branding/searchlogo/ico/favicon.ico
7// ==/UserScript==
8(function() {
9    'use strict';
10
11    // Prevent multiple instances - check if already running
12    if (window.focusReminderRunning) {
13        console.log('Focus Reminder already running, exiting...');
14        return;
15    }
16    window.focusReminderRunning = true;
17
18    // Array of on-task prompts
19    const prompts = [
20        'Keep your eyes on the teacher! πŸ‘€',
21        'Stay focused on the lesson! πŸ“š',
22        'Are you paying attention? 🎯',
23        'Listen carefully to your teacher! πŸ‘‚',
24        'Focus on what\'s being taught! πŸ’‘',
25        'Eyes up front, please! πŸ‘οΈ',
26        'Stay engaged with the lesson! ✨',
27        'Remember to concentrate! 🧠',
28        'Keep your attention on the teacher! πŸŽ“',
29        'Stay on task! ⭐'
30    ];
31
32    let reminderInterval;
33    let currentIntervalMinutes = 2; // Default 2 minutes
34    let customAvatarImage = null; // Store custom avatar
35    let textToSpeechEnabled = true; // Enable text-to-speech by default
36    let avatarVerticalPosition = 15; // Default vertical position in pixels
37    let isShowingPrompt = false; // Prevent overlapping prompts
38    let isInitialized = false; // Prevent double initialization
39    let customPrompts = []; // Store custom prompts
40    let selectedFemaleVoice = null; // Cache the selected female voice
41    let selectedVoiceName = null; // Store the user's selected voice name
42    let isPaused = false; // Track if extension is paused
43
44    // Create the avatar container
45    function createAvatar() {
46        const avatarContainer = document.createElement('div');
47        avatarContainer.id = 'focus-reminder-avatar';
48        avatarContainer.style.cssText = `
49            position: fixed;
50            bottom: 20px;
51            right: 20px;
52            z-index: 999999;
53            font-family: Arial, sans-serif;
54        `;
55
56        // Avatar circle
57        const avatar = document.createElement('div');
58        avatar.id = 'avatar-circle';
59        avatar.style.cssText = `
60            width: 60px;
61            height: 60px;
62            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
63            border-radius: 50%;
64            display: flex;
65            align-items: center;
66            justify-content: center;
67            font-size: 30px;
68            cursor: pointer;
69            box-shadow: 0 4px 12px rgba(0,0,0,0.3);
70            transition: transform 0.3s ease;
71            overflow: hidden;
72        `;
73
74        updateAvatarDisplay(avatar);
75
76        avatar.addEventListener('mouseenter', () => {
77            avatar.style.transform = 'scale(1.1)';
78        });
79
80        avatar.addEventListener('mouseleave', () => {
81            avatar.style.transform = 'scale(1)';
82        });
83
84        avatar.addEventListener('click', () => {
85            showSettingsPanel();
86        });
87
88        // Pause/Play button
89        const pauseButton = document.createElement('div');
90        pauseButton.id = 'pause-play-button';
91        pauseButton.style.cssText = `
92            position: absolute;
93            top: -10px;
94            right: -10px;
95            width: 30px;
96            height: 30px;
97            background: ${isPaused ? '#28a745' : '#ffc107'};
98            border-radius: 50%;
99            display: flex;
100            align-items: center;
101            justify-content: center;
102            font-size: 16px;
103            cursor: pointer;
104            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
105            transition: all 0.3s ease;
106            z-index: 1000000;
107        `;
108        pauseButton.textContent = isPaused ? '▢️' : '⏸️';
109        pauseButton.title = isPaused ? 'Resume reminders' : 'Pause reminders';
110        
111        pauseButton.addEventListener('click', async (e) => {
112            e.stopPropagation(); // Prevent opening settings panel
113            e.preventDefault();
114            console.log('Pause button clicked! Current state:', isPaused ? 'paused' : 'running');
115            
116            isPaused = !isPaused;
117            await GM.setValue('isPaused', isPaused);
118            
119            if (isPaused) {
120                clearInterval(reminderInterval);
121                pauseButton.style.background = '#28a745';
122                pauseButton.textContent = '▢️';
123                pauseButton.title = 'Resume reminders';
124                console.log('Extension paused');
125            } else {
126                startReminderInterval();
127                pauseButton.style.background = '#ffc107';
128                pauseButton.textContent = '⏸️';
129                pauseButton.title = 'Pause reminders';
130                console.log('Extension resumed');
131            }
132        }, true); // Use capture phase
133
134        pauseButton.addEventListener('mouseenter', () => {
135            pauseButton.style.transform = 'scale(1.2)';
136        });
137
138        pauseButton.addEventListener('mouseleave', () => {
139            pauseButton.style.transform = 'scale(1)';
140        });
141
142        // Message bubble (hidden by default)
143        const messageBubble = document.createElement('div');
144        messageBubble.id = 'message-bubble';
145        messageBubble.style.cssText = `
146            position: absolute;
147            bottom: 70px;
148            right: 0;
149            background: white;
150            color: #333;
151            padding: 15px 20px;
152            border-radius: 15px;
153            box-shadow: 0 4px 12px rgba(0,0,0,0.2);
154            max-width: 250px;
155            display: none;
156            font-size: 16px;
157            font-weight: 600;
158            text-align: center;
159            border: 3px solid #667eea;
160        `;
161
162        avatarContainer.appendChild(messageBubble);
163        avatarContainer.appendChild(avatar);
164        avatarContainer.appendChild(pauseButton);
165        document.body.appendChild(avatarContainer);
166
167        console.log('Focus reminder avatar created');
168    }
169
170    // Update avatar display with custom image or emoji
171    function updateAvatarDisplay(avatar) {
172        if (!avatar) avatar = document.getElementById('avatar-circle');
173        if (!avatar) return;
174
175        if (customAvatarImage) {
176            const img = document.createElement('img');
177            img.src = customAvatarImage;
178            img.style.width = '100%';
179            img.style.height = '100%';
180            img.style.objectFit = 'cover';
181            img.style.borderRadius = '50%';
182            img.style.transform = `translateY(${avatarVerticalPosition}px)`;
183            avatar.innerHTML = '';
184            avatar.appendChild(img);
185            console.log('Custom avatar displayed with position:', avatarVerticalPosition);
186        } else {
187            // Use Google Drive image with proper positioning
188            const img = document.createElement('img');
189            img.src = 'https://drive.google.com/uc?export=view&id=1yVtZLFNhkWHfaay5nDEJg60iEjhooJ-h';
190            img.style.width = '100%';
191            img.style.height = '100%';
192            img.style.objectFit = 'cover';
193            img.style.borderRadius = '50%';
194            img.style.transform = `translateY(${avatarVerticalPosition}px)`;
195            img.onerror = function() {
196                console.error('Failed to load avatar image from Google Drive');
197                avatar.innerHTML = 'πŸ‘¨β€πŸ«';
198            };
199            img.onload = function() {
200                console.log('Avatar image loaded successfully with position:', avatarVerticalPosition);
201            };
202            avatar.innerHTML = '';
203            avatar.appendChild(img);
204        }
205    }
206
207    // Show a random prompt
208    function showPrompt() {
209        if (isPaused) {
210            console.log('Extension is paused, skipping prompt');
211            return;
212        }
213        
214        if (isShowingPrompt) {
215            console.log('Prompt blocked - already showing a prompt');
216            return; // Prevent overlapping prompts
217        }
218        
219        const messageBubble = document.getElementById('message-bubble');
220        if (!messageBubble) {
221            console.log('Message bubble not found');
222            return;
223        }
224
225        isShowingPrompt = true;
226        
227        // Use custom prompts if available, otherwise use default prompts
228        const promptsToUse = customPrompts.length > 0 ? customPrompts : prompts;
229        const randomPrompt = promptsToUse[Math.floor(Math.random() * promptsToUse.length)];
230        
231        messageBubble.textContent = randomPrompt;
232        messageBubble.style.display = 'block';
233
234        // Animate the avatar
235        const avatar = document.getElementById('avatar-circle');
236        avatar.style.animation = 'bounce 0.5s ease';
237
238        console.log('Showing and speaking prompt:', randomPrompt);
239
240        // Text-to-speech
241        if (textToSpeechEnabled) {
242            speakPrompt(randomPrompt);
243        }
244
245        // Hide the message after 10 seconds
246        setTimeout(() => {
247            messageBubble.style.display = 'none';
248            avatar.style.animation = '';
249            isShowingPrompt = false;
250            console.log('Prompt hidden, ready for next prompt');
251        }, 10000);
252    }
253
254    // Text-to-speech function
255    function speakPrompt(text) {
256        try {
257            // Cancel any ongoing speech
258            window.speechSynthesis.cancel();
259            
260            // Remove all emojis and special characters for better speech
261            const cleanText = text.replace(/[\u{1F000}-\u{1F9FF}]|[\u{2600}-\u{26FF}]|[\u{2700}-\u{27BF}]/gu, '').trim();
262            
263            const utterance = new SpeechSynthesisUtterance(cleanText);
264            
265            // Get voices every time to ensure they're loaded
266            const voices = window.speechSynthesis.getVoices();
267            
268            // Use user-selected voice if available
269            if (selectedVoiceName) {
270                const userVoice = voices.find(voice => voice.name === selectedVoiceName);
271                if (userVoice) {
272                    utterance.voice = userVoice;
273                    console.log('Using user-selected voice:', userVoice.name);
274                } else {
275                    console.log('User-selected voice not found, falling back to female voice');
276                }
277            }
278            
279            // If no user voice or user voice not found, use female voice
280            if (!utterance.voice) {
281                if (!selectedFemaleVoice || !voices.includes(selectedFemaleVoice)) {
282                    // Re-find female voice if not cached or not in current voices list
283                    selectedFemaleVoice = voices.find(voice => 
284                        voice.name.includes('Google US English Female') ||
285                        voice.name.includes('Microsoft Zira') ||
286                        voice.name.includes('Samantha') ||
287                        voice.name.includes('Victoria') ||
288                        voice.name.includes('Karen') ||
289                        voice.name.includes('Moira') ||
290                        voice.name.includes('Fiona') ||
291                        voice.name.includes('Female') || 
292                        voice.name.includes('female')
293                    );
294                }
295                
296                if (selectedFemaleVoice) {
297                    utterance.voice = selectedFemaleVoice;
298                    console.log('Using female voice:', selectedFemaleVoice.name);
299                } else {
300                    console.log('No female voice found, using system default');
301                }
302            }
303            
304            utterance.rate = 0.9; // Slightly slower for clarity
305            utterance.pitch = 1.0;
306            utterance.volume = 1.0;
307            
308            // Add event listeners for debugging
309            utterance.onstart = () => {
310                console.log('Speech started');
311            };
312            
313            utterance.onend = () => {
314                console.log('Speech ended');
315            };
316            
317            utterance.onerror = (event) => {
318                console.error('Speech error:', event.error, event);
319            };
320            
321            window.speechSynthesis.speak(utterance);
322            console.log('TTS speaking:', cleanText);
323        } catch (error) {
324            console.error('Text-to-speech error:', error);
325        }
326    }
327
328    // Create settings panel
329    function showSettingsPanel() {
330        // Remove existing panel if any
331        const existingPanel = document.getElementById('focus-settings-panel');
332        if (existingPanel) {
333            existingPanel.remove();
334            return;
335        }
336
337        const panel = document.createElement('div');
338        panel.id = 'focus-settings-panel';
339        panel.style.cssText = `
340            position: fixed;
341            bottom: 90px;
342            right: 20px;
343            background: white;
344            color: #333;
345            padding: 20px;
346            border-radius: 10px;
347            box-shadow: 0 4px 20px rgba(0,0,0,0.3);
348            z-index: 999998;
349            min-width: 280px;
350            max-height: 500px;
351            overflow-y: auto;
352            border: 2px solid #667eea;
353        `;
354
355        // Build custom prompts list HTML
356        const promptsToDisplay = customPrompts.length > 0 ? customPrompts : prompts;
357        const promptsListHTML = promptsToDisplay.map((prompt, index) => `
358            <div style="display: flex; align-items: center; gap: 8px; margin-bottom: 8px; padding: 8px; background: #f5f5f5; border-radius: 5px;">
359                <span style="flex: 1; font-size: 14px;">${prompt}</span>
360                ${customPrompts.length > 0 ? `<button class="delete-prompt-btn" data-index="${index}" style="
361                    background: #dc3545;
362                    color: white;
363                    border: none;
364                    border-radius: 3px;
365                    padding: 4px 8px;
366                    font-size: 12px;
367                    cursor: pointer;
368                ">Delete</button>` : ''}
369            </div>
370        `).join('');
371
372        // Build voice options
373        const voices = window.speechSynthesis.getVoices();
374        const voiceOptionsHTML = voices.map(voice => 
375            `<option value="${voice.name}" ${selectedVoiceName === voice.name ? 'selected' : ''}>${voice.name} (${voice.lang})</option>`
376        ).join('');
377
378        panel.innerHTML = `
379            <h3 style="margin: 0 0 15px 0; color: #667eea; font-size: 18px;">βš™οΈ Reminder Settings</h3>
380            
381            <label style="display: block; margin-bottom: 10px; font-weight: 600; color: #333;">
382                Custom Avatar Image:
383            </label>
384            <input type="file" id="avatar-upload" accept="image/*" 
385                style="width: 100%; padding: 8px; border: 2px solid #667eea; border-radius: 5px; font-size: 14px; margin-bottom: 10px;">
386            <button id="reset-avatar-btn" style="
387                width: 100%;
388                padding: 8px;
389                background: #dc3545;
390                color: white;
391                border: none;
392                border-radius: 5px;
393                font-size: 14px;
394                font-weight: 600;
395                cursor: pointer;
396                margin-bottom: 15px;
397            ">Reset to Default Avatar</button>
398            
399            <label style="display: block; margin-bottom: 10px; font-weight: 600; color: #333;">
400                Avatar Vertical Position:
401            </label>
402            <input type="range" id="avatar-position-slider" min="-20" max="40" step="1" value="${avatarVerticalPosition}" 
403                style="width: 100%; margin-bottom: 5px;">
404            <div style="text-align: center; font-size: 12px; color: #666; margin-bottom: 15px;">
405                Position: <span id="position-value">${avatarVerticalPosition}</span>px
406            </div>
407            
408            <label style="display: block; margin-bottom: 10px; font-weight: 600; color: #333;">
409                <input type="checkbox" id="tts-toggle" ${textToSpeechEnabled ? 'checked' : ''} 
410                    style="margin-right: 8px; width: 18px; height: 18px; vertical-align: middle; cursor: pointer;">
411                Enable Text-to-Speech
412            </label>
413            
414            <label style="display: block; margin-bottom: 10px; font-weight: 600; color: #333;">
415                Voice Selection:
416            </label>
417            <select id="voice-select" style="width: 100%; padding: 8px; border: 2px solid #667eea; border-radius: 5px; font-size: 14px; margin-bottom: 15px;">
418                <option value="">Default (Auto-select Female)</option>
419                ${voiceOptionsHTML}
420            </select>
421            
422            <label style="display: block; margin-bottom: 10px; margin-top: 15px; font-weight: 600; color: #333;">
423                Reminder Interval (minutes):
424            </label>
425            <input type="number" id="interval-input" min="0.5" max="60" step="0.5" value="${currentIntervalMinutes}" 
426                style="width: 100%; padding: 8px; border: 2px solid #667eea; border-radius: 5px; font-size: 16px; margin-bottom: 15px;">
427            
428            <label style="display: block; margin-bottom: 10px; margin-top: 15px; font-weight: 600; color: #333;">
429                Custom Reminder Messages:
430            </label>
431            <div id="prompts-list" style="margin-bottom: 10px; max-height: 200px; overflow-y: auto;">
432                ${promptsListHTML}
433            </div>
434            <div style="display: flex; gap: 8px; margin-bottom: 15px;">
435                <input type="text" id="new-prompt-input" placeholder="Add new reminder message..." 
436                    style="flex: 1; padding: 8px; border: 2px solid #667eea; border-radius: 5px; font-size: 14px;">
437                <button id="add-prompt-btn" style="
438                    padding: 8px 15px;
439                    background: #28a745;
440                    color: white;
441                    border: none;
442                    border-radius: 5px;
443                    font-size: 14px;
444                    font-weight: 600;
445                    cursor: pointer;
446                ">Add</button>
447            </div>
448            ${customPrompts.length > 0 ? `<button id="reset-prompts-btn" style="
449                width: 100%;
450                padding: 8px;
451                background: #ffc107;
452                color: #333;
453                border: none;
454                border-radius: 5px;
455                font-size: 14px;
456                font-weight: 600;
457                cursor: pointer;
458                margin-bottom: 15px;
459            ">Reset to Default Messages</button>` : ''}
460            
461            <button id="save-interval-btn" style="
462                width: 100%;
463                padding: 10px;
464                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
465                color: white;
466                border: none;
467                border-radius: 5px;
468                font-size: 16px;
469                font-weight: 600;
470                cursor: pointer;
471                margin-bottom: 10px;
472            ">Save Changes</button>
473            <button id="test-prompt-btn" style="
474                width: 100%;
475                padding: 10px;
476                background: #28a745;
477                color: white;
478                border: none;
479                border-radius: 5px;
480                font-size: 16px;
481                font-weight: 600;
482                cursor: pointer;
483            ">Test Prompt Now</button>
484            <p style="margin: 15px 0 0 0; font-size: 12px; color: #666;">
485                Current: Every ${currentIntervalMinutes} minute(s)
486            </p>
487        `;
488
489        document.body.appendChild(panel);
490
491        // Voice selection handler
492        document.getElementById('voice-select').addEventListener('change', async (e) => {
493            selectedVoiceName = e.target.value || null;
494            await GM.setValue('selectedVoiceName', selectedVoiceName);
495            console.log('Voice changed to:', selectedVoiceName || 'Default (Auto-select Female)');
496        });
497
498        // Add prompt button handler
499        document.getElementById('add-prompt-btn').addEventListener('click', async () => {
500            const input = document.getElementById('new-prompt-input');
501            const newPrompt = input.value.trim();
502            if (newPrompt) {
503                customPrompts.push(newPrompt);
504                await GM.setValue('customPrompts', customPrompts);
505                input.value = '';
506                console.log('Added new prompt:', newPrompt);
507                // Refresh the panel
508                panel.remove();
509                showSettingsPanel();
510            }
511        });
512
513        // Delete prompt button handlers
514        document.querySelectorAll('.delete-prompt-btn').forEach(btn => {
515            btn.addEventListener('click', async (e) => {
516                const index = parseInt(e.target.getAttribute('data-index'));
517                customPrompts.splice(index, 1);
518                await GM.setValue('customPrompts', customPrompts);
519                console.log('Deleted prompt at index:', index);
520                // Refresh the panel
521                panel.remove();
522                showSettingsPanel();
523            });
524        });
525
526        // Reset prompts button handler
527        const resetPromptsBtn = document.getElementById('reset-prompts-btn');
528        if (resetPromptsBtn) {
529            resetPromptsBtn.addEventListener('click', async () => {
530                customPrompts = [];
531                await GM.deleteValue('customPrompts');
532                console.log('Reset to default prompts');
533                // Refresh the panel
534                panel.remove();
535                showSettingsPanel();
536            });
537        }
538
539        // Avatar position slider handler
540        const positionSlider = document.getElementById('avatar-position-slider');
541        const positionValue = document.getElementById('position-value');
542        positionSlider.addEventListener('input', (e) => {
543            avatarVerticalPosition = parseInt(e.target.value);
544            positionValue.textContent = avatarVerticalPosition;
545            updateAvatarDisplay();
546        });
547
548        // Avatar upload handler
549        const avatarUpload = document.getElementById('avatar-upload');
550        avatarUpload.addEventListener('change', async (e) => {
551            const file = e.target.files[0];
552            if (file && file.type.startsWith('image/')) {
553                const reader = new FileReader();
554                reader.onload = async (event) => {
555                    customAvatarImage = event.target.result;
556                    await GM.setValue('customAvatarImage', customAvatarImage);
557                    updateAvatarDisplay();
558                    console.log('Custom avatar uploaded successfully');
559                    alert('Avatar uploaded successfully! Now adjust the position and click Save Changes.');
560                };
561                reader.onerror = (error) => {
562                    console.error('Error reading file:', error);
563                    alert('Error uploading avatar. Please try again.');
564                };
565                reader.readAsDataURL(file);
566            } else {
567                alert('Please select a valid image file.');
568            }
569        });
570
571        // Reset avatar handler
572        document.getElementById('reset-avatar-btn').addEventListener('click', async () => {
573            customAvatarImage = null;
574            await GM.deleteValue('customAvatarImage');
575            updateAvatarDisplay();
576            console.log('Avatar reset to default');
577            alert('Avatar reset to default!');
578        });
579
580        // Text-to-speech toggle handler
581        document.getElementById('tts-toggle').addEventListener('change', async (e) => {
582            textToSpeechEnabled = e.target.checked;
583            await GM.setValue('textToSpeechEnabled', textToSpeechEnabled);
584            console.log('Text-to-speech:', textToSpeechEnabled ? 'enabled' : 'disabled');
585        });
586
587        // Save button handler
588        document.getElementById('save-interval-btn').addEventListener('click', async () => {
589            const newInterval = parseFloat(document.getElementById('interval-input').value);
590            if (newInterval > 0) {
591                currentIntervalMinutes = newInterval;
592                await GM.setValue('focusReminderInterval', currentIntervalMinutes);
593                await GM.setValue('avatarVerticalPosition', avatarVerticalPosition);
594                await GM.setValue('selectedVoiceName', selectedVoiceName);
595                startReminderInterval();
596                panel.remove();
597                console.log('Interval updated to:', currentIntervalMinutes, 'minutes');
598                console.log('Avatar position saved:', avatarVerticalPosition, 'px');
599                console.log('Voice saved:', selectedVoiceName || 'Default');
600            }
601        });
602
603        // Test button handler
604        document.getElementById('test-prompt-btn').addEventListener('click', () => {
605            showPrompt();
606            panel.remove();
607        });
608
609        // Close panel when clicking outside
610        setTimeout(() => {
611            document.addEventListener('click', function closePanel(e) {
612                if (!panel.contains(e.target) && !document.getElementById('avatar-circle').contains(e.target)) {
613                    panel.remove();
614                    document.removeEventListener('click', closePanel);
615                }
616            });
617        }, 100);
618    }
619
620    // Start the reminder interval
621    function startReminderInterval() {
622        // Clear existing interval
623        if (reminderInterval) {
624            clearInterval(reminderInterval);
625        }
626
627        // Convert minutes to milliseconds
628        const intervalMs = currentIntervalMinutes * 60 * 1000;
629
630        // Set new interval
631        reminderInterval = setInterval(() => {
632            showPrompt();
633        }, intervalMs);
634
635        console.log('Reminder interval started:', currentIntervalMinutes, 'minutes');
636    }
637
638    // Add CSS animations
639    function addStyles() {
640        const style = document.createElement('style');
641        style.textContent = `
642            @keyframes bounce {
643                0%, 100% { transform: translateY(0); }
644                50% { transform: translateY(-10px); }
645            }
646        `;
647        document.head.appendChild(style);
648    }
649
650    // Initialize the extension
651    async function init() {
652        if (isInitialized) {
653            console.log('Extension already initialized, skipping...');
654            return;
655        }
656        isInitialized = true;
657        
658        console.log('Student Focus Reminder Avatar initializing...');
659
660        // Check if avatar already exists and remove it
661        const existingAvatar = document.getElementById('focus-reminder-avatar');
662        if (existingAvatar) {
663            console.log('Removing existing avatar');
664            existingAvatar.remove();
665        }
666
667        // Load saved interval
668        const savedInterval = await GM.getValue('focusReminderInterval', 2);
669        currentIntervalMinutes = savedInterval;
670
671        // Load saved custom avatar
672        const savedAvatar = await GM.getValue('customAvatarImage', null);
673        customAvatarImage = savedAvatar;
674
675        // Load text-to-speech preference
676        const savedTTS = await GM.getValue('textToSpeechEnabled', true);
677        textToSpeechEnabled = savedTTS;
678
679        // Load saved avatar position
680        const savedPosition = await GM.getValue('avatarVerticalPosition', 15);
681        avatarVerticalPosition = savedPosition;
682
683        // Load custom prompts
684        const savedPrompts = await GM.getValue('customPrompts', []);
685        customPrompts = savedPrompts;
686
687        // Load selected voice name
688        const savedVoiceName = await GM.getValue('selectedVoiceName', null);
689        selectedVoiceName = savedVoiceName;
690
691        // Load paused state
692        const savedPausedState = await GM.getValue('isPaused', false);
693        isPaused = savedPausedState;
694
695        // Initialize voices for text-to-speech
696        if ('speechSynthesis' in window) {
697            // Load voices immediately if available
698            if (window.speechSynthesis.getVoices().length > 0) {
699                const voices = window.speechSynthesis.getVoices();
700                selectedFemaleVoice = voices.find(voice => 
701                    voice.name.includes('Google US English Female') ||
702                    voice.name.includes('Microsoft Zira') ||
703                    voice.name.includes('Samantha') ||
704                    voice.name.includes('Victoria') ||
705                    voice.name.includes('Karen') ||
706                    voice.name.includes('Moira') ||
707                    voice.name.includes('Fiona') ||
708                    voice.name.includes('Female') || 
709                    voice.name.includes('female')
710                );
711                if (selectedFemaleVoice) {
712                    console.log('Female voice selected:', selectedFemaleVoice.name);
713                }
714            }
715            
716            // Also listen for voiceschanged event
717            window.speechSynthesis.addEventListener('voiceschanged', () => {
718                const voices = window.speechSynthesis.getVoices();
719                selectedFemaleVoice = voices.find(voice => 
720                    voice.name.includes('Google US English Female') ||
721                    voice.name.includes('Microsoft Zira') ||
722                    voice.name.includes('Samantha') ||
723                    voice.name.includes('Victoria') ||
724                    voice.name.includes('Karen') ||
725                    voice.name.includes('Moira') ||
726                    voice.name.includes('Fiona') ||
727                    voice.name.includes('Female') || 
728                    voice.name.includes('female')
729                );
730                if (selectedFemaleVoice) {
731                    console.log('Female voice selected after voiceschanged:', selectedFemaleVoice.name);
732                }
733            });
734        }
735
736        addStyles();
737        createAvatar();
738        
739        // Only start interval if not paused
740        if (!isPaused) {
741            startReminderInterval();
742            
743            // Show initial prompt after 5 seconds
744            setTimeout(() => {
745                showPrompt();
746            }, 5000);
747        } else {
748            console.log('Extension is paused, not starting interval');
749        }
750
751        console.log('Student Focus Reminder Avatar initialized successfully');
752    }
753
754    // Wait for page to load
755    if (document.readyState === 'loading') {
756        document.addEventListener('DOMContentLoaded', init);
757    } else {
758        init();
759    }
760})();
Student Focus Reminder Avatar | Robomonkey