|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
<!DOCTYPE html> |
|
<html dir="rtl" lang="he"> |
|
<head> |
|
<meta charset="UTF-8" /> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> |
|
<title>ארגז הכלים שלי לבינה מלאכותית</title> |
|
<link |
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" |
|
rel="stylesheet" |
|
/> |
|
|
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Arimo:wght@400;500;600;700&display=swap'); |
|
|
|
body { |
|
font-family: 'Arimo', sans-serif; |
|
background-color: #f9fafb; |
|
} |
|
|
|
.tool-card:hover { |
|
transform: translateY(-5px); |
|
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), |
|
0 10px 10px -5px rgba(0, 0, 0, 0.04); |
|
} |
|
|
|
.category-filter .active { |
|
background-color: #3b82f6; |
|
color: white; |
|
} |
|
|
|
.gradient-text { |
|
background: linear-gradient(90deg, #3b82f6, #8b5cf6); |
|
-webkit-background-clip: text; |
|
background-clip: text; |
|
color: transparent; |
|
} |
|
|
|
|
|
[dir='rtl'] .rotate-180 { |
|
transform: rotate(180deg); |
|
} |
|
|
|
|
|
.mobile-menu { |
|
max-height: 0; |
|
overflow: hidden; |
|
transition: max-height 0.3s ease-out; |
|
} |
|
|
|
.mobile-menu.open { |
|
max-height: 500px; |
|
} |
|
|
|
|
|
.admin-badge { |
|
position: absolute; |
|
top: -8px; |
|
right: -8px; |
|
background-color: #ef4444; |
|
color: white; |
|
border-radius: 9999px; |
|
width: 20px; |
|
height: 20px; |
|
display: flex; |
|
align-items: center; |
|
justify-content: center; |
|
font-size: 10px; |
|
} |
|
|
|
|
|
.profile-container { |
|
position: relative; |
|
width: 200px; |
|
height: 200px; |
|
margin: 0 auto; |
|
} |
|
|
|
.profile-image { |
|
width: 100%; |
|
height: 100%; |
|
border-radius: 50%; |
|
object-fit: cover; |
|
border: 4px solid #3b82f6; |
|
box-shadow: 0 0 20px rgba(59, 130, 246, 0.5); |
|
position: relative; |
|
z-index: 2; |
|
} |
|
|
|
.tech-circle { |
|
position: absolute; |
|
border-radius: 50%; |
|
border: 2px solid rgba(59, 130, 246, 0.7); |
|
animation: rotate infinite linear; |
|
} |
|
|
|
.tech-circle-1 { |
|
width: 220px; |
|
height: 220px; |
|
top: -10px; |
|
left: -10px; |
|
animation-duration: 15s; |
|
border-style: dashed; |
|
} |
|
|
|
.tech-circle-2 { |
|
width: 240px; |
|
height: 240px; |
|
top: -20px; |
|
left: -20px; |
|
animation-duration: 20s; |
|
animation-direction: reverse; |
|
} |
|
|
|
.tech-circle-3 { |
|
width: 260px; |
|
height: 260px; |
|
top: -30px; |
|
left: -30px; |
|
animation-duration: 25s; |
|
border-style: dotted; |
|
} |
|
|
|
@keyframes rotate { |
|
from { |
|
transform: rotate(0deg); |
|
} |
|
to { |
|
transform: rotate(360deg); |
|
} |
|
} |
|
|
|
.tech-dots { |
|
position: absolute; |
|
width: 100%; |
|
height: 100%; |
|
border-radius: 50%; |
|
z-index: 1; |
|
} |
|
|
|
.tech-dot { |
|
position: absolute; |
|
width: 8px; |
|
height: 8px; |
|
background-color: #8b5cf6; |
|
border-radius: 50%; |
|
transform: translate(-50%, -50%); |
|
} |
|
|
|
|
|
.ai-particle { |
|
position: absolute; |
|
background: linear-gradient(135deg, #3b82f6, #8b5cf6); |
|
border-radius: 50%; |
|
opacity: 0.6; |
|
filter: blur(10px); |
|
z-index: 0; |
|
} |
|
|
|
.ai-circuit { |
|
position: absolute; |
|
width: 100%; |
|
height: 100%; |
|
background-image: radial-gradient( |
|
circle at center, |
|
transparent 0%, |
|
#f9fafb 100% |
|
), |
|
linear-gradient( |
|
90deg, |
|
transparent 49%, |
|
rgba(59, 130, 246, 0.1) 50%, |
|
transparent 51% |
|
), |
|
linear-gradient( |
|
0deg, |
|
transparent 49%, |
|
rgba(59, 130, 246, 0.1) 50%, |
|
transparent 51% |
|
); |
|
background-size: 20px 20px; |
|
border-radius: 50%; |
|
opacity: 0.3; |
|
} |
|
|
|
.clickable-stat:hover { |
|
cursor: pointer; |
|
background-color: #f3f4f6; |
|
} |
|
</style> |
|
|
|
<script |
|
async |
|
src="https://www.googletagmanager.com/gtag/js?id=G-JVF8N1DVSG" |
|
></script> |
|
<script> |
|
window.dataLayer = window.dataLayer || []; |
|
function gtag() { |
|
dataLayer.push(arguments); |
|
} |
|
gtag('js', new Date()); |
|
|
|
gtag('config', 'G-JVF8N1DVSG'); |
|
</script> |
|
<link href="favicon-32.png" rel="icon" sizes="32x32" type="image/png" /> |
|
<link href="manifest.json" rel="manifest" /> |
|
<meta content="yes" name="apple-mobile-web-app-capable" /> |
|
<meta content="AI Tools" name="apple-mobile-web-app-title" /> |
|
<link href="icon-192.png" rel="apple-touch-icon" /> |
|
<meta content="#3b82f6" name="theme-color" /> |
|
</head> |
|
<body class="min-h-screen"> |
|
<div |
|
id="newToolBanner" |
|
class="hidden bg-green-100 text-green-800 text-center py-2 text-sm font-semibold" |
|
> |
|
🎉 התווסף כלי חדש: <span id="newToolName"></span> – |
|
<a href="#toolsContainer" class="underline">צפו עכשיו</a> |
|
<button id="closeBanner" class="ml-4 text-green-800 font-bold">×</button> |
|
</div> |
|
|
|
|
|
<header |
|
class="sticky top-0 z-50 backdrop-blur-md bg-white/80 shadow-md border-b border-gray-200" |
|
> |
|
<div class="container mx-auto px-4 py-4"> |
|
<div class="flex justify-between items-center"> |
|
<div class="flex items-center"> |
|
|
|
<button id="mobileMenuButton" class="md:hidden text-gray-600 mr-4"> |
|
<i class="fas fa-bars text-xl"></i> |
|
</button> |
|
|
|
<div> |
|
<h1 class="text-2xl md:text-3xl font-bold gradient-text"> |
|
ארגז הכלים שלי ל-AI |
|
</h1> |
|
<p class="text-gray-600 text-sm md:text-base mt-1"> |
|
אוסף כלי הבינה המלאכותית המומלצים שלי |
|
</p> |
|
</div> |
|
</div> |
|
|
|
<div class="hidden md:flex items-center space-x-4 space-x-reverse"> |
|
|
|
<button |
|
id="refreshBtn" |
|
class="px-5 py-2 rounded-xl bg-gradient-to-l from-blue-600 to-indigo-500 text-white shadow-md hover:shadow-lg hover:from-blue-700 hover:to-indigo-600 transition-all duration-300" |
|
> |
|
<i class="fas fa-sync-alt ml-2"></i> אפס תצוגה |
|
</button> |
|
|
|
|
|
<button |
|
id="editJsonBtn" |
|
class="px-5 py-2 rounded-xl bg-gradient-to-l from-pink-500 to-purple-600 text-white shadow-md hover:shadow-lg hover:from-pink-600 hover:to-purple-700 transition-all duration-300 relative" |
|
> |
|
<i class="fas fa-edit ml-2"></i> הציעו כלי חדש |
|
<span class="admin-badge">N</span> |
|
</button> |
|
</div> |
|
</div> |
|
|
|
|
|
<div id="mobileMenu" class="mobile-menu md:hidden mt-4"> |
|
<div class="flex flex-col space-y-2 py-2"> |
|
<button |
|
id="refreshBtnMobile" |
|
class="px-5 py-2 rounded-xl bg-gradient-to-l from-blue-600 to-indigo-500 text-white shadow-md hover:shadow-lg hover:from-blue-700 hover:to-indigo-600 transition-all duration-300" |
|
> |
|
<i class="fas fa-sync-alt ml-2"></i> אפס תצוגה |
|
</button> |
|
|
|
<button |
|
id="editJsonBtn" |
|
class="px-5 py-2 rounded-xl bg-gradient-to-l from-pink-500 to-purple-600 text-white shadow-md hover:shadow-lg hover:from-pink-600 hover:to-purple-700 transition-all duration-300 relative" |
|
> |
|
<i class="fas fa-edit ml-2"></i> הציעו כלי חדש |
|
<span class="admin-badge">N</span> |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</header> |
|
|
|
|
|
<main class="container mx-auto px-4 py-8"> |
|
|
|
<div |
|
class="bg-white rounded-lg shadow-sm p-6 border border-gray-100 mb-8 relative overflow-hidden" |
|
> |
|
|
|
<div |
|
class="ai-particle" |
|
style="width: 100px; height: 100px; top: -30px; right: -30px" |
|
></div> |
|
<div |
|
class="ai-particle" |
|
style="width: 150px; height: 150px; bottom: -50px; left: -50px" |
|
></div> |
|
<div |
|
class="ai-particle" |
|
style="width: 80px; height: 80px; top: 50%; right: 20%" |
|
></div> |
|
|
|
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-2"> |
|
קצת עליי |
|
</h2> |
|
<div class="flex flex-col md:flex-row gap-6"> |
|
<div class="md:w-1/3"> |
|
<div class="profile-container"> |
|
<div class="ai-circuit"></div> |
|
<div class="tech-circle tech-circle-1"></div> |
|
<div class="tech-circle tech-circle-2"></div> |
|
<div class="tech-circle tech-circle-3"></div> |
|
<div class="tech-dots"> |
|
<div class="tech-dot" style="top: 10%; left: 50%"></div> |
|
<div class="tech-dot" style="top: 50%; left: 10%"></div> |
|
<div class="tech-dot" style="top: 90%; left: 50%"></div> |
|
<div class="tech-dot" style="top: 50%; left: 90%"></div> |
|
<div class="tech-dot" style="top: 30%; left: 30%"></div> |
|
<div class="tech-dot" style="top: 70%; left: 70%"></div> |
|
<div class="tech-dot" style="top: 30%; left: 70%"></div> |
|
<div class="tech-dot" style="top: 70%; left: 30%"></div> |
|
</div> |
|
<img |
|
src="https://i.imgur.com/cnlxCuj.jpeg" |
|
alt="שגיא בר און" |
|
class="profile-image" |
|
/> |
|
</div> |
|
</div> |
|
<div class="md:w-2/3"> |
|
<h3 class="text-xl font-semibold mb-4">שגיא בר און</h3> |
|
<p class="text-gray-700 mb-4"> |
|
אני חוקר ויועץ בתחום הבינה המלאכותית, מאסטר NLP, ובעל ניסיון של |
|
למעלה מ-25 שנה בתעשיית ההייטק. בעל מומחיות רחבה בפיתוח תוכנה, |
|
אוטומציה, ניהול פרויקטים ואסטרטגיה עסקית. |
|
</p> |
|
<p class="text-gray-700 mb-4"> |
|
מרצה אורח באוניברסיטת רייכמן וחבר בסגל הבוחנים של מה"ט - המכון |
|
הממשלתי להכשרה בטכנולוגיה ובמדע - לבחינות מהנדסי תוכנה |
|
באוניברסיטאות ובמכללות, וכן מנטור לAI במסגרת משרד החינוך. |
|
</p> |
|
<p class="text-gray-700 mb-4"> |
|
בעל תואר שני במנהל עסקים עם התמחות בבינה מלאכותית, תואר ראשון |
|
(BSc) במדעי המחשב והנדסאי תוכנה. ההרצאות משלבות ידע עדכני, חשיבה |
|
ביקורתית והתנסות חווייתית, מתוך מטרה להעצים אנשים ולאפשר להם |
|
להשתמש בטכנולוגיה בחוכמה ובקלות. |
|
</p> |
|
<p class="text-gray-700"> |
|
שמתי לי למטרה להנגיש, להסביר ולחבר את הטכנולוגיה בצורה פשוטה |
|
וברורה לכולם. |
|
</p> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="mb-8"> |
|
|
|
<div |
|
class="flex flex-col md:flex-row md:items-center md:justify-between gap-4" |
|
> |
|
<div class="relative w-full md:w-96"> |
|
<input |
|
type="text" |
|
id="searchInput" |
|
placeholder="חפש כלים..." |
|
class="w-full pr-10 pl-4 py-3 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500 outline-none transition" |
|
/> |
|
<i class="fas fa-search absolute right-3 top-3.5 text-gray-400"></i> |
|
</div> |
|
<div class="flex items-center space-x-2 space-x-reverse"> |
|
<span class="text-gray-600 hidden md:block">סנן לפי:</span> |
|
<div class="category-filter flex flex-wrap gap-2"> |
|
<button |
|
class="filter-btn px-3 py-1 rounded-full border text-sm transition border-gray-300 text-gray-700 bg-white hover:bg-blue-50" |
|
data-category="all" |
|
> |
|
הכל |
|
</button> |
|
<button |
|
class="filter-btn px-3 py-1 rounded-full border text-sm transition border-gray-300 text-gray-700 bg-white hover:bg-blue-50" |
|
data-category="productivity" |
|
> |
|
פרודוקטיביות |
|
</button> |
|
<button |
|
class="filter-btn px-3 py-1 rounded-full border text-sm transition border-gray-300 text-gray-700 bg-white hover:bg-blue-50" |
|
data-category="writing" |
|
> |
|
כתיבה |
|
</button> |
|
<button |
|
class="filter-btn px-3 py-1 rounded-full border text-sm transition border-gray-300 text-gray-700 bg-white hover:bg-blue-50" |
|
data-category="design" |
|
> |
|
עיצוב |
|
</button> |
|
<button |
|
class="filter-btn px-3 py-1 rounded-full border text-sm transition border-gray-300 text-gray-700 bg-white hover:bg-blue-50" |
|
data-category="coding" |
|
> |
|
תכנות |
|
</button> |
|
<button |
|
class="filter-btn px-3 py-1 rounded-full border text-sm transition border-gray-300 text-gray-700 bg-white hover:bg-blue-50" |
|
data-category="video" |
|
> |
|
וידאו |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div class="grid grid-cols-2 md:grid-cols-4 gap-4 mb-8"> |
|
|
|
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100"> |
|
<div class="flex items-center"> |
|
<div class="p-3 rounded-full bg-blue-50 text-blue-600 ml-3"> |
|
<i class="fas fa-tools text-lg"></i> |
|
</div> |
|
<div> |
|
<p class="text-gray-500 text-sm">סה"כ כלים</p> |
|
<h3 class="text-xl font-semibold" id="totalTools">0</h3> |
|
</div> |
|
</div> |
|
</div> |
|
<div |
|
id="topRatedStatBox" |
|
class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 transition clickable-stat" |
|
> |
|
<div class="flex items-center"> |
|
<div class="p-3 rounded-full bg-purple-50 text-purple-600 ml-3"> |
|
<i class="fas fa-star text-lg"></i> |
|
</div> |
|
<div> |
|
<p class="text-gray-500 text-sm">מובילים בדירוג</p> |
|
<h3 class="text-xl font-semibold" id="topRated">0</h3> |
|
</div> |
|
</div> |
|
</div> |
|
<div |
|
id="newToolsStatBox" |
|
class="bg-white p-4 rounded-lg shadow-sm border border-gray-100 transition clickable-stat" |
|
> |
|
<div class="flex items-center"> |
|
<div class="p-3 rounded-full bg-green-50 text-green-600 ml-3"> |
|
<i class="fas fa-bolt text-lg"></i> |
|
</div> |
|
<div> |
|
<p class="text-gray-500 text-sm">חדשים השבוע</p> |
|
<h3 class="text-xl font-semibold" id="newTools">0</h3> |
|
</div> |
|
</div> |
|
</div> |
|
<div class="bg-white p-4 rounded-lg shadow-sm border border-gray-100"> |
|
<div class="flex items-center"> |
|
<div class="p-3 rounded-full bg-yellow-50 text-yellow-600 ml-3"> |
|
<i class="fas fa-tags text-lg"></i> |
|
</div> |
|
<div> |
|
<p class="text-gray-500 text-sm">קטגוריות</p> |
|
<h3 class="text-xl font-semibold" id="totalCategories">0</h3> |
|
</div> |
|
</div> |
|
</div> |
|
</div> |
|
|
|
|
|
<div |
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" |
|
id="toolsContainer" |
|
> |
|
|
|
<p id="loadingMessage" class="text-center text-gray-500 col-span-full"> |
|
טוען כלים... |
|
</p> |
|
</div> |
|
|
|
|
|
<div class="mt-16"> |
|
|
|
<h2 class="text-2xl font-bold mb-6 text-gray-800 border-b pb-2"> |
|
סרטונים נבחרים מערוץ היוטיוב שלי |
|
</h2> |
|
<div |
|
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" |
|
id="videosContainer" |
|
> |
|
|
|
</div> |
|
</div> |
|
</main> |
|
|
|
|
|
<div |
|
id="jsonEditorModal" |
|
class="fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center z-50 hidden" |
|
> |
|
|
|
<div |
|
class="bg-white rounded-lg shadow-xl w-full max-w-4xl max-h-[90vh] flex flex-col" |
|
> |
|
<div |
|
class="px-6 py-4 border-b border-gray-200 flex justify-between items-center" |
|
> |
|
<h3 class="text-lg font-semibold">עריכת הנתונים ב-JSON</h3> |
|
<button id="closeModalBtn" class="text-gray-500 hover:text-gray-700"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
<div class="p-6 flex-1 overflow-auto"> |
|
<div class="flex mb-4"> |
|
<button |
|
id="showToolsBtn" |
|
class="px-4 py-2 bg-blue-600 text-white rounded-l-lg" |
|
> |
|
כלים |
|
</button> |
|
<button |
|
id="showVideosBtn" |
|
class="px-4 py-2 bg-gray-200 text-gray-700 rounded-r-lg" |
|
> |
|
סרטונים |
|
</button> |
|
</div> |
|
<textarea |
|
id="jsonEditor" |
|
class="w-full h-96 p-4 border border-gray-300 rounded-lg font-mono text-sm" |
|
spellcheck="false" |
|
style="direction: ltr; text-align: left" |
|
></textarea> |
|
</div> |
|
<div |
|
class="px-6 py-4 border-t border-gray-200 flex justify-end space-x-3 space-x-reverse" |
|
> |
|
<button |
|
id="cancelEditBtn" |
|
class="px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition" |
|
> |
|
ביטול |
|
</button> |
|
<button |
|
id="saveJsonBtn" |
|
class="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-700 transition" |
|
> |
|
שמור שינויים (באחסון המקומי) |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
<div |
|
id="suggestToolModal" |
|
class="fixed inset-0 bg-black bg-opacity-40 flex items-center justify-center z-[9999] px-4 hidden" |
|
> |
|
<div |
|
class="bg-white rounded-lg w-full max-w-md shadow-lg max-h-[90vh] overflow-hidden flex flex-col text-right relative" |
|
> |
|
|
|
<div class="p-6 overflow-y-auto flex-grow"> |
|
<h2 class="text-xl font-semibold mb-4">הצעת כלי חדש</h2> |
|
<form id="suggestToolForm" class="space-y-4"> |
|
<div> |
|
<label class="block mb-1">שם הכלי</label> |
|
<input |
|
type="text" |
|
name="name" |
|
required |
|
class="w-full border rounded p-2" |
|
/> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1">לינק</label> |
|
<input |
|
type="url" |
|
name="url" |
|
required |
|
placeholder="כתובת URL של הכלי: לדוגמא: https://linktr.ee/sagib" |
|
dir="ltr" |
|
class="w-full border rounded p-2" |
|
/> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1">תיאור קצר</label> |
|
<textarea |
|
name="description" |
|
required |
|
class="w-full border rounded p-2" |
|
></textarea> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1">השם שלך</label> |
|
<input |
|
type="text" |
|
name="userName" |
|
placeholder="איך לקרוא לך כשנחזור אליך?" |
|
class="w-full border rounded p-2" |
|
/> |
|
</div> |
|
|
|
<div> |
|
<label class="block mb-1">מס' טלפון נייד</label> |
|
<input |
|
type="tel" |
|
name="userPhone" |
|
placeholder="05X-XXXXXXX" |
|
class="w-full border rounded p-2" |
|
dir="ltr" |
|
/> |
|
</div> |
|
|
|
<p class="text-sm text-gray-500"> |
|
נשמח לחזור אליך כשהכלי שצעת נוסף לאתר ❤️ |
|
</p> |
|
<p class="text-sm text-gray-500 italic"> |
|
✦ הפרטים שלך נשמרים אצלנו רק לצורך עדכון – אין שימוש אחר. |
|
</p> |
|
</form> |
|
</div> |
|
|
|
|
|
<div |
|
class="sticky bottom-0 bg-white/80 backdrop-blur-md px-6 py-4 border-t flex justify-center space-x-4 space-x-reverse z-10 rounded-b-lg" |
|
> |
|
<button |
|
type="button" |
|
onclick="closeSuggestModal()" |
|
class="px-5 py-2 bg-gray-200 text-gray-800 rounded hover:bg-gray-300" |
|
> |
|
ביטול |
|
</button> |
|
<button |
|
type="submit" |
|
form="suggestToolForm" |
|
class="px-6 py-2 bg-purple-600 text-white rounded hover:bg-purple-700" |
|
> |
|
שלח |
|
</button> |
|
</div> |
|
</div> |
|
</div> |
|
<div |
|
id="videoModal" |
|
class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center hidden z-[9999]" |
|
> |
|
<div |
|
class="bg-white rounded-lg overflow-hidden w-full max-w-3xl shadow-lg relative" |
|
> |
|
<button |
|
onclick="closeVideoModal()" |
|
class="absolute top-2 left-2 text-gray-700 hover:text-red-600 text-xl" |
|
> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
<iframe |
|
id="videoIframe" |
|
class="w-full h-[300px] sm:h-[500px]" |
|
src="" |
|
frameborder="0" |
|
allowfullscreen |
|
></iframe> |
|
</div> |
|
</div> |
|
|
|
|
|
<footer |
|
class="fixed bottom-0 left-0 w-full z-50 backdrop-blur-md bg-white/80 shadow-xl border-t border-gray-200" |
|
> |
|
|
|
|
|
<div id="chatBubble" class="fixed bottom-8 right-5 z-[9999]"> |
|
|
|
<div |
|
id="chatIntro" |
|
class="bg-white p-3 rounded-lg shadow-md mb-2 text-sm max-w-xs chat-intro-enter hidden" |
|
> |
|
שלום! אני עוזר AI של שגיא. אשמח לעזור לך למצוא כלי AI מתאים או לענות |
|
על שאלות. |
|
<button onclick="hideChatIntro()" class="text-gray-500 float-left"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
</div> |
|
|
|
|
|
<button |
|
id="chatButton" |
|
onclick="toggleChatWindow()" |
|
class="chat-pulse bg-gradient-to-r from-purple-500 to-indigo-600 hover:from-purple-600 hover:to-indigo-700 text-white px-4 py-3 rounded-full shadow-xl flex items-center space-x-2 space-x-reverse" |
|
> |
|
<i class="fas fa-robot"></i> |
|
<span>צ'אט</span> |
|
</button> |
|
|
|
|
|
<div |
|
id="chatWindow" |
|
class="hidden mt-3 w-[360px] h-[500px] bg-white rounded-2xl shadow-2xl border border-gray-200 overflow-hidden relative" |
|
> |
|
|
|
<div |
|
class="bg-gradient-to-r from-purple-500 to-indigo-600 text-white p-3 flex justify-between items-center" |
|
> |
|
<button onclick="toggleChatWindow()" class="text-white"> |
|
<i class="fas fa-times"></i> |
|
</button> |
|
<div class="chat-header"> |
|
צ'אט עם הבינה של שגיא |
|
<span |
|
id="toolsCount" |
|
class="ml-2 text-sm bg-white text-purple-600 font-semibold px-2 py-1 rounded-full shadow-sm" |
|
></span> |
|
</div> |
|
|
|
<div class="w-6"></div> |
|
|
|
</div> |
|
|
|
|
|
<iframe |
|
src="https://sagi-ba-sagi-ai-tools-chatbot-main-g1prqf.streamlit.app/?embed=true" |
|
class="w-full h-full border-none" |
|
></iframe> |
|
</div> |
|
</div> |
|
|
|
<script> |
|
function toggleChatWindow() { |
|
const chatWindow = document.getElementById('chatWindow'); |
|
chatWindow.classList.toggle('hidden'); |
|
} |
|
</script> |
|
|
|
|
|
<div class="container mx-auto px-4 py-3 text-center space-y-2"> |
|
|
|
<div id="installAppContainer" class="flex justify-center"> |
|
<button |
|
id="installAppBtn" |
|
class="mt-2 px-4 py-2 rounded-xl bg-gradient-to-br from-green-500 to-emerald-600 text-white font-bold shadow hover:scale-105 transition" |
|
style="display: none" |
|
> |
|
📲 התקנת האפליקציה |
|
</button> |
|
</div> |
|
|
|
<div class="flex justify-center gap-4"> |
|
|
|
<a |
|
href="https://chat.whatsapp.com/GPFASYBEA9CFGUMCVZ5RXP" |
|
target="_blank" |
|
class="w-9 h-9 rounded-full bg-white shadow flex items-center justify-center text-green-500 hover:bg-green-500 hover:text-white transition" |
|
><i class="fab fa-whatsapp"></i |
|
></a> |
|
<a |
|
href="http://www.linkedin.com/in/sagi-bar-on" |
|
target="_blank" |
|
class="w-9 h-9 rounded-full bg-white shadow flex items-center justify-center text-blue-500 hover:bg-blue-700 hover:text-white transition" |
|
><i class="fab fa-linkedin-in"></i |
|
></a> |
|
<a |
|
href="https://www.facebook.com/SAGI.BARON" |
|
target="_blank" |
|
class="w-9 h-9 rounded-full bg-white shadow flex items-center justify-center text-blue-600 hover:bg-blue-800 hover:text-white transition" |
|
><i class="fab fa-facebook-f"></i |
|
></a> |
|
<a |
|
href="https://www.youtube.com/@SAGIBARON" |
|
target="_blank" |
|
class="w-9 h-9 rounded-full bg-white shadow flex items-center justify-center text-red-500 hover:bg-red-700 hover:text-white transition" |
|
><i class="fab fa-youtube"></i |
|
></a> |
|
</div> |
|
<button |
|
id="editJsonBtnFooter" |
|
class="px-4 py-2 rounded-xl bg-gradient-to-br from-pink-500 to-purple-600 text-white font-bold shadow hover:scale-105 transition" |
|
> |
|
<i class="fas fa-pen-to-square ml-2"></i> הציעו כלי חדש |
|
</button> |
|
<p class="text-gray-500 text-xs"> |
|
© 2025 כל הזכויות שמורות לשגיא בר און |
|
</p> |
|
</div> |
|
|
|
</footer> |
|
|
|
|
|
<script src="https://cdn.tailwindcss.com"></script> |
|
|
|
|
|
|
|
|
|
<script> |
|
// --- DOM Elements --- |
|
const toolsContainer = document.getElementById('toolsContainer'); |
|
const videosContainer = document.getElementById('videosContainer'); |
|
const searchInput = document.getElementById('searchInput'); |
|
const filterButtons = document.querySelectorAll('.filter-btn'); |
|
const totalToolsElement = document.getElementById('totalTools'); |
|
const topRatedElement = document.getElementById('topRated'); |
|
const newToolsElement = document.getElementById('newTools'); |
|
const totalCategoriesElement = document.getElementById('totalCategories'); |
|
const mobileMenuButton = document.getElementById('mobileMenuButton'); |
|
const mobileMenu = document.getElementById('mobileMenu'); |
|
const jsonEditorModal = document.getElementById('jsonEditorModal'); |
|
const jsonEditor = document.getElementById('jsonEditor'); |
|
const closeModalBtn = document.getElementById('closeModalBtn'); |
|
const cancelEditBtn = document.getElementById('cancelEditBtn'); |
|
const saveJsonBtn = document.getElementById('saveJsonBtn'); |
|
const showToolsBtn = document.getElementById('showToolsBtn'); |
|
const showVideosBtn = document.getElementById('showVideosBtn'); |
|
const refreshBtn = document.getElementById('refreshBtn'); |
|
const refreshBtnMobile = document.getElementById('refreshBtnMobile'); |
|
const loadingMessage = document.getElementById('loadingMessage'); // For showing loading status |
|
|
|
const topRatedStatBox = document.getElementById('topRatedStatBox'); |
|
const newToolsStatBox = document.getElementById('newToolsStatBox'); |
|
const totalToolsStatBox = document |
|
.querySelector('[id="totalTools"]') |
|
.closest('.bg-white'); |
|
let filteredStatView = null; // 'topRated', 'newTools', או null |
|
|
|
if (totalToolsStatBox) { |
|
totalToolsStatBox.classList.add('clickable-stat'); |
|
totalToolsStatBox.addEventListener('click', () => { |
|
filteredStatView = null; |
|
currentSearchTerm = ''; |
|
currentCategory = 'all'; |
|
document |
|
.querySelectorAll('.filter-btn') |
|
.forEach((btn) => btn.classList.remove('active')); |
|
const allBtn = document.querySelector( |
|
'.filter-btn[data-category="all"]' |
|
); |
|
if (allBtn) allBtn.classList.add('active'); |
|
renderTools(); |
|
scrollToTools(); |
|
}); |
|
} |
|
|
|
if (topRatedStatBox) { |
|
topRatedStatBox.addEventListener('click', () => { |
|
filteredStatView = 'topRated'; |
|
currentSearchTerm = ''; |
|
currentCategory = 'all'; |
|
renderTools(); |
|
scrollToTools(); |
|
}); |
|
} |
|
|
|
if (newToolsStatBox) { |
|
newToolsStatBox.addEventListener('click', () => { |
|
filteredStatView = 'newTools'; |
|
currentSearchTerm = ''; |
|
currentCategory = 'all'; |
|
renderTools(); |
|
scrollToTools(); |
|
}); |
|
} |
|
|
|
// --- State --- |
|
let currentCategory = 'all'; |
|
let currentSearchTerm = ''; |
|
let toolsData = { tools: [], videos: [] }; // Holds the data fetched from server |
|
let currentSort = 'newest'; // 'newest', 'rating' |
|
|
|
// --- Initialize --- |
|
async function init() { |
|
showLoading(); |
|
try { |
|
const savedSearch = localStorage.getItem('searchTerm'); |
|
if (savedSearch) { |
|
searchInput.value = savedSearch; |
|
currentSearchTerm = savedSearch; |
|
} |
|
|
|
await loadData(); // ← הנתונים נטענים כאן |
|
sortTools(currentSort); |
|
renderTools(); |
|
renderVideos(); |
|
updateStats(); |
|
setupEventListeners(); |
|
|
|
// 👇 כאן נוסיף את הקריאה לבאנר לאחר שהכלים נטענו |
|
showNewToolBanner(); |
|
|
|
if (!currentSearchTerm) { |
|
const allFilterBtn = document.querySelector( |
|
'.filter-btn[data-category="all"]' |
|
); |
|
if (allFilterBtn) { |
|
allFilterBtn.classList.add('active'); |
|
} |
|
} |
|
} catch (error) { |
|
console.error('Initialization failed:', error); |
|
showError('שגיאה בטעינת הנתונים הראשונית. נסה לרענן את הדף.'); |
|
} finally { |
|
hideLoading(); |
|
} |
|
} |
|
|
|
const speechSynthesis = window.speechSynthesis; |
|
let currentUtterance = null; |
|
|
|
// פונקציה להקראת טקסט בעברית |
|
function speakText(text) { |
|
// עצירת הקראה קודמת אם יש |
|
if (speechSynthesis.speaking) { |
|
speechSynthesis.cancel(); |
|
|
|
// אם זאת אותה הקראה שכבר פועלת, פשוט נעצור ונצא |
|
if (currentUtterance && currentUtterance.text === text) { |
|
currentUtterance = null; |
|
return; |
|
} |
|
} |
|
|
|
// יצירת הקראה חדשה |
|
const utterance = new SpeechSynthesisUtterance(text); |
|
|
|
// הגדרת השפה לעברית |
|
utterance.lang = 'he-IL'; |
|
|
|
// בדיקת קולות זמינים ובחירת קול עברי |
|
const voices = speechSynthesis.getVoices(); |
|
|
|
if (voices.length) { |
|
const hebrewVoice = voices.find((voice) => |
|
/he|hebrew|ivrit/i.test(voice.lang) |
|
); |
|
|
|
if (hebrewVoice) { |
|
utterance.voice = hebrewVoice; |
|
console.log('נבחר קול עברי:', hebrewVoice.name); |
|
} else { |
|
console.log('לא נמצא קול עברי, משתמש בקול ברירת מחדל'); |
|
} |
|
// ננסה להשתמש בקול גוגל |
|
const googleVoice = voices.find( |
|
(voice) => |
|
voice.name.includes('Google') && |
|
(voice.name.includes('US') || voice.name.includes('UK')) |
|
); |
|
|
|
if (googleVoice) { |
|
utterance.voice = googleVoice; |
|
} |
|
} else { |
|
// במקרה שהקולות עדיין לא נטענו, נגדיר מאזין חד-פעמי |
|
speechSynthesis.addEventListener( |
|
'voiceschanged', |
|
function loadVoice() { |
|
const voices = speechSynthesis.getVoices(); |
|
const hebrewVoice = voices.find((voice) => |
|
/he|hebrew|ivrit/i.test(voice.lang) |
|
); |
|
if (hebrewVoice) { |
|
utterance.voice = hebrewVoice; |
|
console.log('נבחר קול עברי (מדחייה):', hebrewVoice.name); |
|
} |
|
// הסרת המאזין אחרי הפעלה ראשונה |
|
speechSynthesis.removeEventListener('voiceschanged', loadVoice); |
|
}, |
|
{ once: true } |
|
); |
|
} |
|
|
|
// שמירת ההקראה הנוכחית |
|
currentUtterance = utterance; |
|
|
|
// טיפול באירוע סיום הקראה |
|
utterance.onend = () => { |
|
currentUtterance = null; |
|
|
|
// עדכון כל הכפתורים למצב "לא מנגן" |
|
document.querySelectorAll('.speak-button').forEach((btn) => { |
|
btn.innerHTML = '<i class="fas fa-volume-up"></i>'; |
|
btn.classList.remove('speaking'); |
|
}); |
|
}; |
|
|
|
// הפעלת ההקראה |
|
speechSynthesis.speak(utterance); |
|
} |
|
|
|
// --- UI Feedback --- |
|
function showLoading() { |
|
if (loadingMessage) loadingMessage.style.display = 'block'; |
|
if (toolsContainer) toolsContainer.innerHTML = ''; // Clear container while loading |
|
} |
|
|
|
function hideLoading() { |
|
if (loadingMessage) loadingMessage.style.display = 'none'; |
|
} |
|
|
|
function showError(message) { |
|
if (toolsContainer) { |
|
toolsContainer.innerHTML = `<p class="text-center text-red-600 col-span-full">${message}</p>`; |
|
} |
|
hideLoading(); // Make sure loading message is hidden |
|
} |
|
|
|
// --- Data Fetching --- |
|
async function fetchDefaultData() { |
|
console.log('Fetching latest data from server...'); // Log fetching attempt |
|
try { |
|
const [toolsResponse, videosResponse] = await Promise.all([ |
|
fetch('tools.json?cacheBust=' + Date.now()), // Cache busting |
|
fetch('videos.json?cacheBust=' + Date.now()), // Cache busting |
|
]); |
|
|
|
if (!toolsResponse.ok || !videosResponse.ok) { |
|
const errorMsg = `HTTP error! Status: Tools ${toolsResponse.status}, Videos ${videosResponse.status}`; |
|
console.error(errorMsg); |
|
throw new Error('שגיאה בקבלת נתונים מהשרת.'); // User-friendly error |
|
} |
|
|
|
const [toolsArray, videosArray] = await Promise.all([ |
|
toolsResponse.json(), |
|
videosResponse.json(), |
|
]); |
|
|
|
// --- Process fetched data with defaults --- |
|
const processedTools = toolsArray.map((tool) => ({ |
|
...tool, |
|
rating: tool.rating ?? 0, |
|
isNew: tool.isNew ?? false, |
|
category: tool.category ?? 'general', |
|
icon: tool.icon || 'fas fa-tools', |
|
url: tool.url || '#', |
|
description: tool.description || 'אין תיאור זמין.', |
|
name: tool.name || 'שם לא ידוע', |
|
})); |
|
const processedVideos = videosArray.map((video) => ({ |
|
...video, |
|
url: video.url || '#', |
|
title: video.title || 'כותרת חסרה', |
|
description: video.description || 'אין תיאור זמין.', |
|
date: video.date ?? new Date().toISOString(), |
|
})); |
|
|
|
console.log('Data fetched successfully.'); |
|
return { tools: processedTools, videos: processedVideos }; |
|
} catch (error) { |
|
console.error('Failed to fetch default data:', error); |
|
// Re-throw the error or a more specific one to be caught by the caller |
|
throw new Error( |
|
'כשל בטעינת נתוני ברירת המחדל מהשרת. בדוק את הקבצים tools.json ו-videos.json.' |
|
); |
|
} |
|
} |
|
|
|
// --- Data Loading Strategy --- |
|
// MODIFIED: Always fetch fresh data from the server on load. |
|
async function loadData() { |
|
console.log('loadData called - fetching fresh data.'); |
|
try { |
|
toolsData = await fetchDefaultData(); |
|
// Note: We are NOT saving to localStorage here anymore for loading purposes. |
|
// saveData(); // Removed - No longer saving fetched data automatically |
|
} catch (error) { |
|
console.error('Error in loadData:', error); |
|
toolsData = { tools: [], videos: [] }; // Set empty data on error |
|
// Re-throw error so init() can handle UI feedback |
|
throw error; |
|
} |
|
} |
|
|
|
// --- Save Data to Local Storage (Used ONLY by JSON Editor) --- |
|
// Note: Data saved here will be overwritten on next page load/refresh. |
|
function saveData() { |
|
try { |
|
// Save the current state (potentially modified by editor) to localStorage |
|
localStorage.setItem('aiToolsData', JSON.stringify(toolsData)); |
|
console.log( |
|
'Data saved to localStorage (by editor). Will be overwritten on next load.' |
|
); |
|
} catch (e) { |
|
console.error('Error saving data to localStorage:', e); |
|
alert('שגיאה בשמירת הנתונים באחסון המקומי.'); |
|
} |
|
} |
|
|
|
// --- Sorting Function (Unchanged) --- |
|
function sortTools(sortBy) { |
|
if (!toolsData || !Array.isArray(toolsData.tools)) return; |
|
console.log('Sorting by:', sortBy); |
|
currentSort = sortBy; |
|
toolsData.tools.sort((a, b) => { |
|
if (sortBy === 'newest') { |
|
const aIsNew = a.isNew || false; |
|
const bIsNew = b.isNew || false; |
|
if (aIsNew !== bIsNew) return bIsNew - aIsNew; |
|
return 0; |
|
} else if (sortBy === 'rating') { |
|
const aRating = a.rating || 0; |
|
const bRating = b.rating || 0; |
|
if (bRating !== aRating) return bRating - aRating; |
|
return 0; |
|
} |
|
return 0; |
|
}); |
|
} |
|
function scrollToTools() { |
|
const toolsSection = document.getElementById('toolsContainer'); |
|
if (toolsSection) { |
|
toolsSection.scrollIntoView({ behavior: 'smooth', block: 'start' }); |
|
} |
|
} |
|
|
|
// --- Rendering Functions (Unchanged logic, but added hideLoading) --- |
|
function renderTools() { |
|
hideLoading(); // Ensure loading message is hidden before rendering |
|
if (!toolsContainer || !toolsData || !Array.isArray(toolsData.tools)) { |
|
console.error( |
|
'Cannot render tools: Missing container or invalid data.' |
|
); |
|
showError('שגיאה בהצגת הכלים.'); |
|
return; |
|
} |
|
const filteredTools = filterTools(); |
|
toolsContainer.innerHTML = ''; // Clear previous content |
|
|
|
if ( |
|
filteredTools.length === 0 && |
|
currentSearchTerm === '' && |
|
currentCategory === 'all' |
|
) { |
|
// Show specific message if no tools loaded at all |
|
showError('לא נטענו כלים. בדוק את קובץ tools.json או נסה לרענן.'); |
|
} else if (filteredTools.length === 0) { |
|
toolsContainer.innerHTML = |
|
'<p class="text-center text-gray-500 col-span-full">לא נמצאו כלים התואמים את החיפוש, הסינון והמיון.</p>'; |
|
} else { |
|
filteredTools.forEach((tool) => { |
|
const toolCard = document.createElement('div'); |
|
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' : '' |
|
}`; |
|
|
|
// הכנת הטקסט להקראה - שילוב של שם הכלי והתיאור שלו |
|
const speakableText = `${tool.name}. ${tool.description}`; |
|
// `${tool.name}`; |
|
// |
|
|
|
toolCard.innerHTML = ` |
|
<div class="flex items-center justify-between mb-4"> |
|
<div class="flex items-center gap-4"> |
|
|
|
<img src="${tool.logo}" alt="${ |
|
tool.name |
|
} Logo" class="w-14 h-14 object-contain rounded bg-white p-1 shadow-sm"> |
|
|
|
<div> |
|
|
|
<div class="flex items-center gap-2"> |
|
<h3 class="text-xl font-semibold">${ |
|
tool.name |
|
}</h3> |
|
${ |
|
tool.isNew |
|
? `<span class="text-xs bg-green-500 text-white px-2 py-1 rounded-full animate-pulse">חדש!</span>` |
|
: '' |
|
} |
|
<button title="הקרא תיאור" onclick="event.stopPropagation(); speakText('${tool.description.replace( |
|
/'/g, |
|
"\\'" |
|
)}')" class="text-blue-600 hover:text-blue-800 focus:outline-none"> |
|
<i class="fas fa-volume-up"></i> |
|
</button> |
|
</div> |
|
|
|
<span class="text-xs px-2 py-1 rounded-full ${getCategoryBadgeColor( |
|
tool.category |
|
)}"> |
|
${getCategoryName(tool.category)} |
|
</span> |
|
</div> |
|
</div> |
|
|
|
|
|
<i class="${tool.icon} text-gray-500 text-2xl"></i> |
|
</div> |
|
|
|
|
|
<p class="text-gray-700 mb-4 text-base leading-relaxed min-h-[60px]">${ |
|
tool.description |
|
}</p> |
|
|
|
|
|
<div class="flex justify-between items-center mb-4"> |
|
<div class="flex items-center gap-3"> |
|
${renderRatingStars(tool.rating)} |
|
${ |
|
tool.video |
|
? ` |
|
<button title="צפה בהדרכה" onclick="openVideoModal('${tool.video}')" class="text-red-500 hover:text-red-600 transition text-xl"> |
|
<i class="fab fa-youtube"></i> |
|
</button>` |
|
: '' |
|
} |
|
</div> |
|
</div> |
|
|
|
|
|
<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' |
|
: '' |
|
}"> |
|
<i class="fas fa-external-link-alt ml-2"></i> ${ |
|
tool.url !== '#' ? 'גישה לכלי' : 'אין קישור' |
|
} |
|
</a> |
|
`; |
|
|
|
toolsContainer.appendChild(toolCard); |
|
}); |
|
// אנימציה של הופעה חלקה |
|
requestAnimationFrame(() => { |
|
toolCard.classList.add('visible'); |
|
}); |
|
|
|
// הוספת סגנון CSS לכפתור הקראה פעיל |
|
if (!document.getElementById('speakButtonStyle')) { |
|
const style = document.createElement('style'); |
|
style.id = 'speakButtonStyle'; |
|
style.innerHTML = ` |
|
.speak-button.speaking { color: #4f46e5; animation: pulse 1.5s infinite; } |
|
@keyframes pulse { |
|
0% { transform: scale(1); } |
|
50% { transform: scale(1.2); } |
|
100% { transform: scale(1); } |
|
} |
|
`; |
|
document.head.appendChild(style); |
|
} |
|
} |
|
} |
|
|
|
// פונקציה נוספת שמוחקת את ההקראה בעת עזיבת העמוד |
|
function cleanupSpeech() { |
|
if (speechSynthesis) { |
|
speechSynthesis.cancel(); |
|
} |
|
} |
|
|
|
// הוספת אירוע לניקוי ההקראה בעת עזיבת העמוד |
|
window.addEventListener('beforeunload', cleanupSpeech); |
|
|
|
// עדכון אירוע הקלקה על כפתור הקראה - להוסיף מחלקת CSS ולשנות אייקון |
|
document.addEventListener('click', function (e) { |
|
if (e.target.closest('.speak-button')) { |
|
const button = e.target.closest('.speak-button'); |
|
|
|
// החלפת האייקון והוספת מחלקת CSS מהבהבת |
|
if (speechSynthesis.speaking && currentUtterance) { |
|
// אם יש הקראה פעילה, נעדכן את כל הכפתורים |
|
document.querySelectorAll('.speak-button').forEach((btn) => { |
|
btn.innerHTML = '<i class="fas fa-volume-up"></i>'; |
|
btn.classList.remove('speaking'); |
|
}); |
|
|
|
// ואז נעדכן את הכפתור הנוכחי אם ההקראה ממשיכה |
|
if (!button.classList.contains('speaking')) { |
|
button.innerHTML = '<i class="fas fa-volume-mute"></i>'; |
|
button.classList.add('speaking'); |
|
} |
|
} else { |
|
button.innerHTML = '<i class="fas fa-volume-up"></i>'; |
|
button.classList.remove('speaking'); |
|
} |
|
} |
|
}); |
|
|
|
function renderVideos() { |
|
// ... (renderVideos logic remains the same as before) ... |
|
if ( |
|
!videosContainer || |
|
!toolsData || |
|
!Array.isArray(toolsData.videos) |
|
) { |
|
console.error( |
|
'Cannot render videos: Missing container or invalid data.' |
|
); |
|
if (videosContainer) |
|
videosContainer.innerHTML = |
|
'<p class="text-center text-red-500 col-span-full">שגיאה בהצגת הסרטונים.</p>'; |
|
return; |
|
} |
|
videosContainer.innerHTML = ''; |
|
if (toolsData.videos.length === 0) { |
|
videosContainer.innerHTML = |
|
'<p class="text-center text-gray-500 col-span-full">אין סרטונים להצגה.</p>'; |
|
} else { |
|
toolsData.videos.forEach((video) => { |
|
const videoId = getYouTubeID(video.url); |
|
const embedUrl = videoId |
|
? `https://www.youtube.com/embed/${videoId}` |
|
: '#'; |
|
const videoCard = document.createElement('div'); |
|
videoCard.className = |
|
'bg-white rounded-lg shadow-sm border border-gray-100 overflow-hidden'; |
|
videoCard.innerHTML = ` |
|
<div class="relative pt-[56.25%] ${ |
|
!videoId |
|
? 'bg-gray-200 flex items-center justify-center' |
|
: '' |
|
}"> |
|
${ |
|
videoId |
|
? `<iframe class="absolute top-0 left-0 w-full h-full" src="${embedUrl}" frameborder="0" title="${video.title}" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen></iframe>` |
|
: `<p class="text-gray-500 text-sm p-4">${ |
|
video.url === '#' |
|
? 'אין קישור וידאו' |
|
: 'קישור וידאו לא תקין' |
|
}</p>` |
|
} |
|
</div> |
|
<div class="p-4"> |
|
<h3 class="text-lg font-semibold mb-2">${ |
|
video.title |
|
}</h3> |
|
<p class="text-gray-600 text-sm mb-3 min-h-[40px]">${ |
|
video.description |
|
}</p> |
|
<p class="text-gray-500 text-xs">${formatDate( |
|
video.date |
|
)}</p> |
|
</div>`; |
|
videosContainer.appendChild(videoCard); |
|
}); |
|
} |
|
} |
|
|
|
// --- Helper Functions (Filtering, Stats, Stars, Category, Date - Unchanged) --- |
|
function filterTools() { |
|
if (!toolsData || !Array.isArray(toolsData.tools)) return []; |
|
|
|
let filtered = toolsData.tools; |
|
|
|
if (filteredStatView === 'topRated') { |
|
filtered = filtered.filter((tool) => tool.rating === 5); |
|
} else if (filteredStatView === 'newTools') { |
|
filtered = filtered.filter((tool) => tool.isNew); |
|
} |
|
|
|
return filtered.filter((tool) => { |
|
const searchTermLower = currentSearchTerm.toLowerCase(); |
|
const nameMatch = tool.name.toLowerCase().includes(searchTermLower); |
|
const descMatch = tool.description |
|
.toLowerCase() |
|
.includes(searchTermLower); |
|
const categoryNameMatch = getCategoryName(tool.category) |
|
.toLowerCase() |
|
.includes(searchTermLower); |
|
const matchesSearch = nameMatch || descMatch || categoryNameMatch; |
|
const matchesCategoryFilter = |
|
currentCategory === 'all' || tool.category === currentCategory; |
|
return matchesCategoryFilter && matchesSearch; |
|
}); |
|
} |
|
|
|
function updateStats() { |
|
/* ... (unchanged) ... */ |
|
const toolsCount = |
|
toolsData && Array.isArray(toolsData.tools) |
|
? toolsData.tools.length |
|
: 0; |
|
totalToolsElement.textContent = toolsCount; |
|
const topRatedCount = |
|
toolsCount > 0 |
|
? toolsData.tools.filter((tool) => tool.rating >= 5).length |
|
: 0; |
|
topRatedElement.textContent = topRatedCount; |
|
const newToolsCount = |
|
toolsCount > 0 |
|
? toolsData.tools.filter((tool) => tool.isNew).length |
|
: 0; |
|
newToolsElement.textContent = newToolsCount; |
|
const categories = |
|
toolsCount > 0 |
|
? new Set(toolsData.tools.map((tool) => tool.category)) |
|
: new Set(); |
|
totalCategoriesElement.textContent = categories.size; |
|
} |
|
|
|
function renderRatingStars(rating) { |
|
/* ... (unchanged) ... */ |
|
let stars = ''; |
|
const filledStars = Math.max(0, Math.min(5, Math.round(rating))); |
|
for (let i = 1; i <= 5; i++) { |
|
stars += `<i class="${ |
|
i <= filledStars |
|
? 'fas fa-star text-yellow-400' |
|
: 'far fa-star text-gray-300' |
|
} ml-1"></i>`; |
|
} |
|
return stars; |
|
} |
|
function getCategoryName(category) { |
|
/* ... (unchanged) ... */ |
|
const categories = { |
|
'ai-agents': 'סוכן AI', |
|
productivity: 'פרודוקטיביות', |
|
writing: 'כתיבה', |
|
design: 'עיצוב', |
|
coding: 'תכנות', |
|
video: 'וידאו', |
|
image: 'תמונה', |
|
education: 'חינוך', |
|
data: 'נתונים', |
|
search: 'חיפוש', |
|
builder: 'בנייה', |
|
'customer-support': 'תמיכה', |
|
automation: 'אוטומציה', |
|
hosting: 'אחסון', |
|
agents: 'סוכנים', |
|
directory: 'אינדקס', |
|
utility: 'כלי עזר', |
|
platform: 'פלטפורמה', |
|
media: 'מדיה', |
|
presentation: 'מצגות', |
|
audio: 'שמע', |
|
infrastructure: 'תשתיות', |
|
nlp: 'עיבוד שפה', |
|
accessibility: 'נגישות', |
|
general: 'כללי', |
|
}; |
|
return categories[category] || category || 'כללי'; |
|
} |
|
function getCategoryColor(category) { |
|
/* ... (unchanged) ... */ |
|
const colors = { |
|
productivity: 'bg-blue-600', |
|
writing: 'bg-purple-600', |
|
design: 'bg-pink-600', |
|
coding: 'bg-green-600', |
|
video: 'bg-red-600', |
|
image: 'bg-yellow-600', |
|
education: 'bg-indigo-600', |
|
data: 'bg-cyan-600', |
|
search: 'bg-teal-600', |
|
builder: 'bg-orange-600', |
|
'customer-support': 'bg-lime-600', |
|
automation: 'bg-sky-600', |
|
hosting: 'bg-amber-600', |
|
agents: 'bg-violet-600', |
|
directory: 'bg-fuchsia-600', |
|
utility: 'bg-rose-600', |
|
platform: 'bg-emerald-600', |
|
media: 'bg-stone-600', |
|
presentation: 'bg-red-500', |
|
audio: 'bg-blue-500', |
|
infrastructure: 'bg-gray-700', |
|
nlp: 'bg-purple-500', |
|
accessibility: 'bg-green-500', |
|
general: 'bg-gray-600', |
|
}; |
|
return colors[category] || 'bg-gray-600'; |
|
} |
|
function getCategoryBadgeColor(category) { |
|
/* ... (unchanged) ... */ |
|
const colors = { |
|
productivity: 'bg-blue-100 text-blue-800', |
|
writing: 'bg-purple-100 text-purple-800', |
|
design: 'bg-pink-100 text-pink-800', |
|
coding: 'bg-green-100 text-green-800', |
|
video: 'bg-red-100 text-red-800', |
|
image: 'bg-yellow-100 text-yellow-800', |
|
education: 'bg-indigo-100 text-indigo-800', |
|
data: 'bg-cyan-100 text-cyan-800', |
|
search: 'bg-teal-100 text-teal-800', |
|
builder: 'bg-orange-100 text-orange-800', |
|
'customer-support': 'bg-lime-100 text-lime-800', |
|
automation: 'bg-sky-100 text-sky-800', |
|
hosting: 'bg-amber-100 text-amber-800', |
|
agents: 'bg-violet-100 text-violet-800', |
|
directory: 'bg-fuchsia-100 text-fuchsia-800', |
|
utility: 'bg-rose-100 text-rose-800', |
|
platform: 'bg-emerald-100 text-emerald-800', |
|
media: 'bg-stone-100 text-stone-800', |
|
presentation: 'bg-red-100 text-red-800', |
|
audio: 'bg-blue-100 text-blue-800', |
|
infrastructure: 'bg-gray-200 text-gray-800', |
|
nlp: 'bg-purple-100 text-purple-800', |
|
accessibility: 'bg-green-100 text-green-800', |
|
general: 'bg-gray-100 text-gray-800', |
|
}; |
|
return colors[category] || 'bg-gray-100 text-gray-800'; |
|
} |
|
function getYouTubeID(url) { |
|
/* ... (unchanged) ... */ |
|
if (!url || url === '#') return ''; |
|
const match = url.match( |
|
/(?:youtu\.be\/|youtube\.com\/(?:watch\?v=|embed\/|v\/|shorts\/))([^?&\/\s]+)/ |
|
); |
|
return match ? match[1] : ''; |
|
} |
|
function formatDate(dateString) { |
|
/* ... (unchanged, includes fallback parsing) ... */ |
|
if (!dateString) return 'תאריך לא זמין'; |
|
try { |
|
const date = new Date(dateString); |
|
if (isNaN(date.getTime())) { |
|
console.warn('Could not parse date directly:', dateString); |
|
const parts = dateString.match( |
|
/(\d{1,2})[\/\-](\d{1,2})[\/\-](\d{4})/ |
|
); |
|
if (parts) { |
|
const isoDate = new Date( |
|
`${parts[3]}-${parts[2].padStart(2, '0')}-${parts[1].padStart( |
|
2, |
|
'0' |
|
)}T00:00:00Z` |
|
); |
|
if (!isNaN(isoDate.getTime())) |
|
return isoDate.toLocaleDateString('he-IL', { |
|
year: 'numeric', |
|
month: 'long', |
|
day: 'numeric', |
|
}); |
|
} |
|
return 'תאריך לא תקין'; |
|
} |
|
return date.toLocaleDateString('he-IL', { |
|
year: 'numeric', |
|
month: 'long', |
|
day: 'numeric', |
|
}); |
|
} catch (e) { |
|
console.error('Error formatting date:', dateString, e); |
|
return 'תאריך לא תקין'; |
|
} |
|
} |
|
|
|
// --- Event Listeners Setup --- |
|
function setupEventListeners() { |
|
// Search input |
|
searchInput.addEventListener('input', (e) => { |
|
currentSearchTerm = e.target.value; |
|
localStorage.setItem('searchTerm', currentSearchTerm); // שמור בזיכרון הדפדפן |
|
renderTools(); |
|
}); |
|
|
|
// Filter buttons |
|
filterButtons.forEach((button) => { |
|
button.addEventListener('click', () => { |
|
filterButtons.forEach((btn) => btn.classList.remove('active')); |
|
button.classList.add('active'); |
|
currentCategory = button.dataset.category; |
|
renderTools(); |
|
}); |
|
}); |
|
|
|
// Mobile menu toggle |
|
mobileMenuButton.addEventListener('click', () => { |
|
mobileMenu.classList.toggle('open'); |
|
}); |
|
|
|
// Uncomment if using admin buttons |
|
const editJsonBtn = document.getElementById('editJsonBtn'); |
|
const editJsonBtnMobile = document.getElementById('editJsonBtnMobile'); |
|
document |
|
.getElementById('editJsonBtnFooter') |
|
?.addEventListener('click', openEditorHandler); |
|
|
|
if (editJsonBtn) |
|
editJsonBtn.addEventListener('click', openEditorHandler); |
|
if (editJsonBtnMobile) |
|
editJsonBtnMobile.addEventListener('click', openEditorHandler); |
|
|
|
closeModalBtn.addEventListener('click', () => |
|
jsonEditorModal.classList.add('hidden') |
|
); |
|
cancelEditBtn.addEventListener('click', () => |
|
jsonEditorModal.classList.add('hidden') |
|
); |
|
|
|
// JSON Editor Save Button -> Saves ONLY to localStorage for the current session |
|
saveJsonBtn.addEventListener('click', () => { |
|
try { |
|
const rawData = JSON.parse(jsonEditor.value); |
|
if (!Array.isArray(rawData)) |
|
throw new Error('Data must be an array.'); |
|
if (showToolsBtn.classList.contains('bg-blue-600')) { |
|
// Update in-memory data and sort |
|
toolsData.tools = rawData.map((tool) => ({ |
|
/* add defaults */ ...tool, |
|
rating: tool.rating ?? 0, |
|
isNew: tool.isNew ?? false, |
|
category: tool.category ?? 'general', |
|
icon: tool.icon || 'fas fa-tools', |
|
url: tool.url || '#', |
|
description: tool.description || 'אין תיאור זמין.', |
|
name: tool.name || 'שם לא ידוע', |
|
})); |
|
sortTools(currentSort); |
|
} else { |
|
toolsData.videos = rawData.map((video) => ({ |
|
/* add defaults */ ...video, |
|
url: video.url || '#', |
|
title: video.title || 'כותרת חסרה', |
|
description: video.description || 'אין תיאור זמין.', |
|
date: video.date ?? new Date().toISOString(), |
|
})); |
|
} |
|
// Save the edited data to localStorage (will be lost on next full load) |
|
saveData(); |
|
// Re-render the UI with the edited data for the current session |
|
renderTools(); |
|
renderVideos(); |
|
updateStats(); |
|
jsonEditorModal.classList.add('hidden'); |
|
alert( |
|
'הנתונים נשמרו זמנית (באחסון המקומי). הם יתעדכנו מחדש מהשרת בטעינה הבאה של הדף.' |
|
); |
|
} catch (e) { |
|
alert('JSON לא תקין או שגיאה בשמירה:\n' + e.message); |
|
console.error('JSON Save Error:', e); |
|
} |
|
}); |
|
|
|
showToolsBtn.addEventListener('click', () => { |
|
/* ... (unchanged editor tab logic) ... */ |
|
showToolsBtn.classList.add('bg-blue-600', 'text-white'); |
|
showToolsBtn.classList.remove('bg-gray-200', 'text-gray-700'); |
|
showVideosBtn.classList.add('bg-gray-200', 'text-gray-700'); |
|
showVideosBtn.classList.remove('bg-blue-600', 'text-white'); |
|
jsonEditor.value = JSON.stringify(toolsData.tools, null, 2); // Show current in-memory tools data |
|
}); |
|
showVideosBtn.addEventListener('click', () => { |
|
/* ... (unchanged editor tab logic) ... */ |
|
showVideosBtn.classList.add('bg-blue-600', 'text-white'); |
|
showVideosBtn.classList.remove('bg-gray-200', 'text-gray-700'); |
|
showToolsBtn.classList.add('bg-gray-200', 'text-gray-700'); |
|
showToolsBtn.classList.remove('bg-blue-600', 'text-white'); |
|
jsonEditor.value = JSON.stringify(toolsData.videos, null, 2); // Show current in-memory videos data |
|
}); |
|
|
|
// Sorting stat boxes (Unchanged) |
|
if (topRatedStatBox) { |
|
topRatedStatBox.addEventListener('click', () => { |
|
sortTools('rating'); |
|
renderTools(); |
|
scrollToTools(); // גלילה חלקה לתחילת רשימת הכלים |
|
}); |
|
} |
|
|
|
if (newToolsStatBox) { |
|
newToolsStatBox.addEventListener('click', () => { |
|
sortTools('newest'); |
|
renderTools(); |
|
scrollToTools(); // גלילה חלקה לתחילת רשימת הכלים |
|
}); |
|
} |
|
|
|
// Refresh buttons - Now primarily resets filters/sort/search and re-renders current data |
|
// It *could* re-fetch, but init() already does that on load. |
|
// Let's make it just reset the view state. |
|
const resetViewHandler = () => { |
|
console.log('Resetting view state (filters, sort, search)...'); |
|
// Reset filters and search |
|
searchInput.value = ''; |
|
currentSearchTerm = ''; |
|
filterButtons.forEach((btn) => btn.classList.remove('active')); |
|
const allFilterBtn = document.querySelector( |
|
'.filter-btn[data-category="all"]' |
|
); |
|
if (allFilterBtn) allFilterBtn.classList.add('active'); |
|
currentCategory = 'all'; |
|
|
|
// Reset sort to newest and re-render |
|
sortTools('newest'); |
|
renderTools(); |
|
// No need to re-render videos unless their source changes, which this button doesn't do |
|
// No need to update stats as the underlying data hasn't changed |
|
alert('התצוגה אופסה (פילטרים, מיון וחיפוש נוקו).'); |
|
}; |
|
|
|
refreshBtn.addEventListener('click', resetViewHandler); |
|
refreshBtnMobile.addEventListener('click', resetViewHandler); |
|
} |
|
|
|
// --- Initialize the app --- |
|
document.addEventListener('DOMContentLoaded', init); |
|
|
|
// JSON Editor Modal Logic (If admin buttons are enabled) |
|
const openEditorHandler = () => { |
|
document.getElementById('suggestToolModal').classList.remove('hidden'); |
|
}; |
|
|
|
const closeSuggestModal = () => { |
|
document.getElementById('suggestToolModal').classList.add('hidden'); |
|
}; |
|
|
|
document |
|
.getElementById('suggestToolForm') |
|
.addEventListener('submit', async (e) => { |
|
e.preventDefault(); |
|
const form = e.target; |
|
const rawPhone = form.userPhone.value; |
|
const cleanedPhone = rawPhone.replace(/\D/g, ''); // מסיר כל תו שאינו ספרה |
|
// alert(cleanedPhone) |
|
const data = { |
|
name: form.name.value, |
|
url: form.url.value, |
|
description: form.description.value, |
|
userName: form.userName.value, |
|
userPhone: cleanedPhone, |
|
}; |
|
|
|
try { |
|
await fetch( |
|
'https://hook.eu2.make.com/lgo6nh36dk804dq1msfmwxo34nzf4o3y', |
|
{ |
|
method: 'POST', |
|
headers: { |
|
'Content-Type': 'application/json', |
|
}, |
|
body: JSON.stringify(data), |
|
} |
|
); |
|
|
|
alert('הכלי נשלח בהצלחה!'); |
|
closeSuggestModal(); |
|
form.reset(); |
|
} catch (err) { |
|
alert('שגיאה בשליחה 😢'); |
|
console.error(err); |
|
} |
|
}); |
|
function openVideoModal(url) { |
|
const modal = document.getElementById('videoModal'); |
|
const iframe = document.getElementById('videoIframe'); |
|
iframe.src = url; |
|
modal.classList.remove('hidden'); |
|
} |
|
|
|
function closeVideoModal() { |
|
const modal = document.getElementById('videoModal'); |
|
const iframe = document.getElementById('videoIframe'); |
|
iframe.src = ''; |
|
modal.classList.add('hidden'); |
|
} |
|
fetch('tools.json') |
|
.then((res) => res.json()) |
|
.then((tools) => { |
|
const count = tools.length; |
|
document.getElementById( |
|
'toolsCount' |
|
).textContent = `${count} כלים זמינים`; |
|
}) |
|
.catch((err) => { |
|
console.error('שגיאה בטעינת כמות הכלים:', err); |
|
document.getElementById('toolsCount').textContent = 'טעינה נכשלה'; |
|
}); |
|
function showNewToolBanner() { |
|
const tools = toolsData.tools; |
|
const lastSeenToolDate = localStorage.getItem('lastSeenToolDate'); |
|
const newTools = tools.filter((tool) => tool.isNew); |
|
if (newTools.length === 0) return; |
|
|
|
const latestTool = newTools.reduce((latest, tool) => { |
|
return new Date(tool.dateAdded) > new Date(latest.dateAdded) |
|
? tool |
|
: latest; |
|
}, newTools[0]); |
|
|
|
if ( |
|
!lastSeenToolDate || |
|
new Date(latestTool.dateAdded) > new Date(lastSeenToolDate) |
|
) { |
|
const banner = document.getElementById('newToolBanner'); |
|
const toolNameSpan = document.getElementById('newToolName'); |
|
const closeBtn = document.getElementById('closeBanner'); |
|
|
|
toolNameSpan.textContent = latestTool.name; |
|
banner.classList.remove('hidden'); |
|
const viewNowLink = banner.querySelector('a'); |
|
viewNowLink.addEventListener('click', (e) => { |
|
e.preventDefault(); |
|
filteredStatView = 'newTools'; |
|
currentCategory = 'all'; |
|
currentSearchTerm = ''; |
|
renderTools(); |
|
scrollToTools(); |
|
}); |
|
|
|
closeBtn.addEventListener('click', () => { |
|
banner.classList.add('hidden'); |
|
localStorage.setItem('lastSeenToolDate', latestTool.dateAdded); |
|
}); |
|
} |
|
} |
|
|
|
if ('serviceWorker' in navigator) { |
|
navigator.serviceWorker |
|
.register('/service-worker.js') |
|
.then(() => console.log('Service Worker registered successfully.')) |
|
.catch((err) => |
|
console.error('Service Worker registration failed:', err) |
|
); |
|
} |
|
|
|
let deferredPrompt; |
|
const installBtn = document.getElementById('installAppBtn'); |
|
const container = document.getElementById('installAppContainer'); |
|
|
|
window.addEventListener('beforeinstallprompt', (e) => { |
|
e.preventDefault(); |
|
deferredPrompt = e; |
|
installBtn.style.display = 'inline-block'; |
|
|
|
installBtn.addEventListener('click', () => { |
|
deferredPrompt.prompt(); |
|
deferredPrompt.userChoice.then((choiceResult) => { |
|
if (choiceResult.outcome === 'accepted') { |
|
console.log('User accepted the A2HS prompt'); |
|
installBtn.style.display = 'none'; |
|
} else { |
|
console.log('User dismissed the A2HS prompt'); |
|
} |
|
deferredPrompt = null; |
|
}); |
|
}); |
|
}); |
|
</script> |
|
|
|
|
|
|
|
|
|
|
|
<script> |
|
function speakText(text) { |
|
const audio = new Audio( |
|
`https://translate.google.com/translate_tts?ie=UTF-8&tl=iw&client=tw-ob&q=${encodeURIComponent( |
|
text |
|
)}` |
|
); |
|
audio.play(); |
|
} |
|
</script> |
|
</body> |
|
</html> |
|
|