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

17 days 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