Spaces:
Running
Running
Commit
·
0a563e9
1
Parent(s):
91d24eb
add speaker
Browse files- index.html +163 -21
index.html
CHANGED
@@ -537,6 +537,83 @@ https://chatgpt.com/c/67efa5ae-ab80-8005-a7d4-de3ced6ccec4
|
|
537 |
}
|
538 |
}
|
539 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
540 |
// --- UI Feedback ---
|
541 |
function showLoading() {
|
542 |
if (loadingMessage) loadingMessage.style.display = 'block';
|
@@ -649,9 +726,9 @@ https://chatgpt.com/c/67efa5ae-ab80-8005-a7d4-de3ced6ccec4
|
|
649 |
function renderTools() {
|
650 |
hideLoading(); // Ensure loading message is hidden before rendering
|
651 |
if (!toolsContainer || !toolsData || !Array.isArray(toolsData.tools)) {
|
652 |
-
|
653 |
-
|
654 |
-
|
655 |
}
|
656 |
const filteredTools = filterTools();
|
657 |
toolsContainer.innerHTML = ''; // Clear previous content
|
@@ -664,27 +741,92 @@ https://chatgpt.com/c/67efa5ae-ab80-8005-a7d4-de3ced6ccec4
|
|
664 |
} else {
|
665 |
filteredTools.forEach(tool => {
|
666 |
const toolCard = document.createElement('div');
|
667 |
-
|
668 |
-
|
669 |
-
|
670 |
-
|
671 |
-
|
672 |
-
|
673 |
-
|
674 |
-
|
675 |
-
|
676 |
-
|
677 |
-
|
678 |
-
|
679 |
-
|
|
|
|
|
|
|
|
|
|
|
680 |
</div>
|
681 |
-
<
|
682 |
-
|
683 |
-
|
684 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
685 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
686 |
}
|
687 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
688 |
|
689 |
function renderVideos() {
|
690 |
// ... (renderVideos logic remains the same as before) ...
|
|
|
537 |
}
|
538 |
}
|
539 |
|
540 |
+
const speechSynthesis = window.speechSynthesis;
|
541 |
+
let currentUtterance = null;
|
542 |
+
|
543 |
+
// פונקציה להקראת טקסט בעברית
|
544 |
+
function speakText(text) {
|
545 |
+
// עצירת הקראה קודמת אם יש
|
546 |
+
if (speechSynthesis.speaking) {
|
547 |
+
speechSynthesis.cancel();
|
548 |
+
|
549 |
+
// אם זאת אותה הקראה שכבר פועלת, פשוט נעצור ונצא
|
550 |
+
if (currentUtterance && currentUtterance.text === text) {
|
551 |
+
currentUtterance = null;
|
552 |
+
return;
|
553 |
+
}
|
554 |
+
}
|
555 |
+
|
556 |
+
// יצירת הקראה חדשה
|
557 |
+
const utterance = new SpeechSynthesisUtterance(text);
|
558 |
+
|
559 |
+
// הגדרת השפה לעברית
|
560 |
+
utterance.lang = 'he-IL';
|
561 |
+
|
562 |
+
// בדיקת קולות זמינים ובחירת קול עברי
|
563 |
+
const voices = speechSynthesis.getVoices();
|
564 |
+
|
565 |
+
|
566 |
+
if (voices.length) {
|
567 |
+
const hebrewVoice = voices.find(voice => /he|hebrew|ivrit/i.test(voice.lang));
|
568 |
+
|
569 |
+
if (hebrewVoice) {
|
570 |
+
utterance.voice = hebrewVoice;
|
571 |
+
console.log("נבחר קול עברי:", hebrewVoice.name);
|
572 |
+
} else {
|
573 |
+
console.log("לא נמצא קול עברי, משתמש בקול ברירת מחדל");
|
574 |
+
}
|
575 |
+
// ננסה להשתמש בקול גוגל
|
576 |
+
const googleVoice = voices.find(voice =>
|
577 |
+
voice.name.includes('Google') &&
|
578 |
+
(voice.name.includes('US') || voice.name.includes('UK'))
|
579 |
+
);
|
580 |
+
|
581 |
+
|
582 |
+
if (googleVoice) {
|
583 |
+
utterance.voice = googleVoice;
|
584 |
+
}
|
585 |
+
} else {
|
586 |
+
// במקרה שהקולות עדיין לא נטענו, נגדיר מאזין חד-פעמי
|
587 |
+
speechSynthesis.addEventListener('voiceschanged', function loadVoice() {
|
588 |
+
const voices = speechSynthesis.getVoices();
|
589 |
+
const hebrewVoice = voices.find(voice => /he|hebrew|ivrit/i.test(voice.lang));
|
590 |
+
if (hebrewVoice) {
|
591 |
+
utterance.voice = hebrewVoice;
|
592 |
+
console.log("נבחר קול עברי (מדחייה):", hebrewVoice.name);
|
593 |
+
}
|
594 |
+
// הסרת המאזין אחרי הפעלה ראשונה
|
595 |
+
speechSynthesis.removeEventListener('voiceschanged', loadVoice);
|
596 |
+
}, { once: true });
|
597 |
+
}
|
598 |
+
|
599 |
+
// שמירת ההקראה הנוכחית
|
600 |
+
currentUtterance = utterance;
|
601 |
+
|
602 |
+
// טיפול באירוע סיום הקראה
|
603 |
+
utterance.onend = () => {
|
604 |
+
currentUtterance = null;
|
605 |
+
|
606 |
+
// עדכון כל הכפתורים למצב "לא מנגן"
|
607 |
+
document.querySelectorAll('.speak-button').forEach(btn => {
|
608 |
+
btn.innerHTML = '<i class="fas fa-volume-up"></i>';
|
609 |
+
btn.classList.remove('speaking');
|
610 |
+
});
|
611 |
+
};
|
612 |
+
|
613 |
+
// הפעלת ההקראה
|
614 |
+
speechSynthesis.speak(utterance);
|
615 |
+
}
|
616 |
+
|
617 |
// --- UI Feedback ---
|
618 |
function showLoading() {
|
619 |
if (loadingMessage) loadingMessage.style.display = 'block';
|
|
|
726 |
function renderTools() {
|
727 |
hideLoading(); // Ensure loading message is hidden before rendering
|
728 |
if (!toolsContainer || !toolsData || !Array.isArray(toolsData.tools)) {
|
729 |
+
console.error("Cannot render tools: Missing container or invalid data.");
|
730 |
+
showError("שגיאה בהצגת הכלים.");
|
731 |
+
return;
|
732 |
}
|
733 |
const filteredTools = filterTools();
|
734 |
toolsContainer.innerHTML = ''; // Clear previous content
|
|
|
741 |
} else {
|
742 |
filteredTools.forEach(tool => {
|
743 |
const toolCard = document.createElement('div');
|
744 |
+
toolCard.className = `relative tool-card bg-white rounded-lg shadow-sm border border-gray-100 p-6 transition duration-300 ${tool.isFeatured ? 'ring-2 ring-blue-500' : ''}`;
|
745 |
+
|
746 |
+
// הכנת הטקסט להקראה - שילוב של שם הכלי והתיאור שלו
|
747 |
+
const speakableText = `${tool.name}`;
|
748 |
+
// `${tool.name}. ${tool.description}`;
|
749 |
+
|
750 |
+
toolCard.innerHTML = `
|
751 |
+
<div class="flex items-start mb-4">
|
752 |
+
<div class="p-3 rounded-lg ${getCategoryColor(tool.category)} text-white mr-4 flex-shrink-0">
|
753 |
+
<i class="${tool.icon} text-xl"></i>
|
754 |
+
</div>
|
755 |
+
<div class="flex-grow">
|
756 |
+
<div class="flex justify-between items-start">
|
757 |
+
<h3 class="text-xl font-semibold">${tool.name}</h3>
|
758 |
+
<button class="speak-button p-2 text-blue-600 hover:text-blue-800 focus:outline-none"
|
759 |
+
onclick="event.stopPropagation(); speakText('${speakableText.replace(/'/g, "\\'")}')">
|
760 |
+
<i class="fas fa-volume-up"></i>
|
761 |
+
</button>
|
762 |
</div>
|
763 |
+
<span class="text-xs px-2 py-1 rounded-full ${getCategoryBadgeColor(tool.category)}">${getCategoryName(tool.category)}</span>
|
764 |
+
</div>
|
765 |
+
${tool.isNew ? '<span class="absolute top-2 left-2 bg-green-100 text-green-800 text-xs font-medium px-2.5 py-0.5 rounded-full">חדש!</span>' : ''}
|
766 |
+
</div>
|
767 |
+
<p class="text-gray-700 mb-4 text-sm min-h-[60px]">${tool.description}</p>
|
768 |
+
<div class="flex justify-between items-center mb-4">
|
769 |
+
<div class="flex"> ${renderRatingStars(tool.rating)} </div>
|
770 |
+
</div>
|
771 |
+
<a href="${tool.url}" target="_blank" rel="noopener noreferrer" class="inline-block w-full text-center px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition ${tool.url === '#' ? 'opacity-50 cursor-not-allowed' : ''}">
|
772 |
+
<i class="fas fa-external-link-alt ml-2"></i> ${tool.url !== '#' ? 'גישה לכלי' : 'אין קישור'}
|
773 |
+
</a>`;
|
774 |
+
|
775 |
+
toolsContainer.appendChild(toolCard);
|
776 |
});
|
777 |
+
|
778 |
+
// הוספת סגנון CSS לכפתור הקראה פעיל
|
779 |
+
if (!document.getElementById('speakButtonStyle')) {
|
780 |
+
const style = document.createElement('style');
|
781 |
+
style.id = 'speakButtonStyle';
|
782 |
+
style.innerHTML = `
|
783 |
+
.speak-button.speaking { color: #4f46e5; animation: pulse 1.5s infinite; }
|
784 |
+
@keyframes pulse {
|
785 |
+
0% { transform: scale(1); }
|
786 |
+
50% { transform: scale(1.2); }
|
787 |
+
100% { transform: scale(1); }
|
788 |
+
}
|
789 |
+
`;
|
790 |
+
document.head.appendChild(style);
|
791 |
+
}
|
792 |
}
|
793 |
+
}
|
794 |
+
|
795 |
+
// פונקציה נוספת שמוחקת את ההקראה בעת עזיבת העמוד
|
796 |
+
function cleanupSpeech() {
|
797 |
+
if (speechSynthesis) {
|
798 |
+
speechSynthesis.cancel();
|
799 |
+
}
|
800 |
+
}
|
801 |
+
|
802 |
+
// הוספת אירוע לניקוי ההקראה בעת עזיבת העמוד
|
803 |
+
window.addEventListener('beforeunload', cleanupSpeech);
|
804 |
+
|
805 |
+
// עדכון אירוע הקלקה על כפתור הקראה - להוסיף מחלקת CSS ולשנות אייקון
|
806 |
+
document.addEventListener('click', function(e) {
|
807 |
+
if (e.target.closest('.speak-button')) {
|
808 |
+
const button = e.target.closest('.speak-button');
|
809 |
+
|
810 |
+
// החלפת האייקון והוספת מחלקת CSS מהבהבת
|
811 |
+
if (speechSynthesis.speaking && currentUtterance) {
|
812 |
+
// אם יש הקראה פעילה, נעדכן את כל הכפתורים
|
813 |
+
document.querySelectorAll('.speak-button').forEach(btn => {
|
814 |
+
btn.innerHTML = '<i class="fas fa-volume-up"></i>';
|
815 |
+
btn.classList.remove('speaking');
|
816 |
+
});
|
817 |
+
|
818 |
+
// ואז נעדכן את הכפתור הנוכחי אם ההקראה ממשיכה
|
819 |
+
if (!button.classList.contains('speaking')) {
|
820 |
+
button.innerHTML = '<i class="fas fa-volume-mute"></i>';
|
821 |
+
button.classList.add('speaking');
|
822 |
+
}
|
823 |
+
} else {
|
824 |
+
button.innerHTML = '<i class="fas fa-volume-up"></i>';
|
825 |
+
button.classList.remove('speaking');
|
826 |
+
}
|
827 |
+
}
|
828 |
+
});
|
829 |
+
|
830 |
|
831 |
function renderVideos() {
|
832 |
// ... (renderVideos logic remains the same as before) ...
|