make an studiyng site that will be in hebrew and i have to upload files and it will make me exames and notes and get grades for the exames and more
Browse files- components/footer.js +2 -2
- components/navbar.js +20 -20
- index.html +65 -562
- script.js +147 -53
- style.css +156 -313
components/footer.js
CHANGED
|
@@ -4,7 +4,7 @@ class CustomFooter extends HTMLElement {
|
|
| 4 |
this.shadowRoot.innerHTML = `
|
| 5 |
<style>
|
| 6 |
footer {
|
| 7 |
-
background
|
| 8 |
color: white;
|
| 9 |
text-align: center;
|
| 10 |
padding: 2rem;
|
|
@@ -46,7 +46,7 @@ class CustomFooter extends HTMLElement {
|
|
| 46 |
<a href="/contact.html">צור קשר</a>
|
| 47 |
</div>
|
| 48 |
<div class="copyright">
|
| 49 |
-
© 2023
|
| 50 |
</div>
|
| 51 |
</div>
|
| 52 |
</footer>
|
|
|
|
| 4 |
this.shadowRoot.innerHTML = `
|
| 5 |
<style>
|
| 6 |
footer {
|
| 7 |
+
background: #1e293b;
|
| 8 |
color: white;
|
| 9 |
text-align: center;
|
| 10 |
padding: 2rem;
|
|
|
|
| 46 |
<a href="/contact.html">צור קשר</a>
|
| 47 |
</div>
|
| 48 |
<div class="copyright">
|
| 49 |
+
© 2023 StudyMate - כל הזכויות שמורות
|
| 50 |
</div>
|
| 51 |
</div>
|
| 52 |
</footer>
|
components/navbar.js
CHANGED
|
@@ -4,27 +4,28 @@ class CustomNavbar extends HTMLElement {
|
|
| 4 |
this.shadowRoot.innerHTML = `
|
| 5 |
<style>
|
| 6 |
nav {
|
| 7 |
-
background
|
| 8 |
-
color:
|
| 9 |
padding: 1rem 2rem;
|
| 10 |
display: flex;
|
| 11 |
justify-content: space-between;
|
| 12 |
align-items: center;
|
| 13 |
-
box-shadow:
|
| 14 |
position: sticky;
|
| 15 |
top: 0;
|
| 16 |
z-index: 1000;
|
| 17 |
}
|
| 18 |
-
|
|
|
|
| 19 |
display: flex;
|
| 20 |
align-items: center;
|
| 21 |
font-weight: bold;
|
| 22 |
-
font-size: 1.
|
|
|
|
| 23 |
}
|
| 24 |
|
| 25 |
.logo img {
|
| 26 |
height: 40px;
|
| 27 |
-
margin-left: 10px;
|
| 28 |
}
|
| 29 |
|
| 30 |
.nav-links {
|
|
@@ -36,12 +37,16 @@ class CustomNavbar extends HTMLElement {
|
|
| 36 |
color: white;
|
| 37 |
text-decoration: none;
|
| 38 |
padding: 0.5rem 1rem;
|
| 39 |
-
border-radius:
|
| 40 |
-
transition: background
|
| 41 |
}
|
| 42 |
|
| 43 |
.nav-links a:hover {
|
| 44 |
-
background
|
|
|
|
|
|
|
|
|
|
|
|
|
| 45 |
}
|
| 46 |
|
| 47 |
@media (max-width: 768px) {
|
|
@@ -55,23 +60,18 @@ class CustomNavbar extends HTMLElement {
|
|
| 55 |
width: 100%;
|
| 56 |
justify-content: space-around;
|
| 57 |
}
|
| 58 |
-
|
| 59 |
-
.nav-links a {
|
| 60 |
-
padding: 0.5rem;
|
| 61 |
-
}
|
| 62 |
}
|
| 63 |
</style>
|
| 64 |
<nav>
|
| 65 |
<div class="logo">
|
| 66 |
-
<img src="http://static.photos/education/200x200/1" alt="
|
| 67 |
-
<span
|
| 68 |
</div>
|
| 69 |
<div class="nav-links">
|
| 70 |
-
<a href="/">בית</a>
|
| 71 |
-
<a href="/
|
| 72 |
-
<a href="/
|
| 73 |
-
<a href="/
|
| 74 |
-
<a href="/login.html">התחברות</a>
|
| 75 |
</div>
|
| 76 |
</nav>
|
| 77 |
`;
|
|
|
|
| 4 |
this.shadowRoot.innerHTML = `
|
| 5 |
<style>
|
| 6 |
nav {
|
| 7 |
+
background: linear-gradient(90deg, #4f46e5, #7c3aed);
|
| 8 |
+
color: white;
|
| 9 |
padding: 1rem 2rem;
|
| 10 |
display: flex;
|
| 11 |
justify-content: space-between;
|
| 12 |
align-items: center;
|
| 13 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
|
| 14 |
position: sticky;
|
| 15 |
top: 0;
|
| 16 |
z-index: 1000;
|
| 17 |
}
|
| 18 |
+
|
| 19 |
+
.logo {
|
| 20 |
display: flex;
|
| 21 |
align-items: center;
|
| 22 |
font-weight: bold;
|
| 23 |
+
font-size: 1.5rem;
|
| 24 |
+
gap: 10px;
|
| 25 |
}
|
| 26 |
|
| 27 |
.logo img {
|
| 28 |
height: 40px;
|
|
|
|
| 29 |
}
|
| 30 |
|
| 31 |
.nav-links {
|
|
|
|
| 37 |
color: white;
|
| 38 |
text-decoration: none;
|
| 39 |
padding: 0.5rem 1rem;
|
| 40 |
+
border-radius: 8px;
|
| 41 |
+
transition: background 0.3s;
|
| 42 |
}
|
| 43 |
|
| 44 |
.nav-links a:hover {
|
| 45 |
+
background: rgba(255,255,255,0.2);
|
| 46 |
+
}
|
| 47 |
+
|
| 48 |
+
.nav-links a.active {
|
| 49 |
+
background: rgba(255,255,255,0.3);
|
| 50 |
}
|
| 51 |
|
| 52 |
@media (max-width: 768px) {
|
|
|
|
| 60 |
width: 100%;
|
| 61 |
justify-content: space-around;
|
| 62 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 63 |
}
|
| 64 |
</style>
|
| 65 |
<nav>
|
| 66 |
<div class="logo">
|
| 67 |
+
<img src="http://static.photos/education/200x200/1" alt="StudyMate Logo">
|
| 68 |
+
<span>StudyMate</span>
|
| 69 |
</div>
|
| 70 |
<div class="nav-links">
|
| 71 |
+
<a href="/" class="active">בית</a>
|
| 72 |
+
<a href="/materials.html">חומרים</a>
|
| 73 |
+
<a href="/grades.html">ציונים</a>
|
| 74 |
+
<a href="/profile.html">פרופיל</a>
|
|
|
|
| 75 |
</div>
|
| 76 |
</nav>
|
| 77 |
`;
|
index.html
CHANGED
|
@@ -1,567 +1,70 @@
|
|
| 1 |
-
|
| 2 |
-
node app.js
|
| 3 |
-
/**
|
| 4 |
-
* DeepStudy Pro — One-File Ultra-MVP for DeepSite
|
| 5 |
-
* Author: ChatGPT (for Olga)
|
| 6 |
-
*
|
| 7 |
-
* Highlights:
|
| 8 |
-
* - Upload: PDF / DOCX / TXT
|
| 9 |
-
* - Robust text extraction and cleaning
|
| 10 |
-
* - Chunking + Map-Reduce summaries with "citations" (section refs)
|
| 11 |
-
* - Study Pack (Summary, Key Points, Glossary, Flashcards, Quiz + AnswerKey JSON)
|
| 12 |
-
* - Streaming Chat via SSE with typing indicator
|
| 13 |
-
* - Quiz grading with rubric (JSON in/JSON out)
|
| 14 |
-
* - Download Study Pack as .md
|
| 15 |
-
* - RTL Hebrew UI, persistent chat history (localStorage)
|
| 16 |
-
* - Safety first: school-only topics, gentle tone, avoid unsafe content
|
| 17 |
-
* - Security: helmet + rate limit; simple input sanitization
|
| 18 |
-
*
|
| 19 |
-
* ENV:
|
| 20 |
-
* OPENAI_API_KEY=sk-...
|
| 21 |
-
* (optional) OPENAI_MODEL=gpt-4o-mini (or another chat-capable model)
|
| 22 |
-
*
|
| 23 |
-
* RUN:
|
| 24 |
-
* node app.js (default port 3000)
|
| 25 |
-
* open http://localhost:3000
|
| 26 |
-
*/
|
| 27 |
-
|
| 28 |
-
require('dotenv').config();
|
| 29 |
-
const path = require('path');
|
| 30 |
-
const fs = require('fs');
|
| 31 |
-
const os = require('os');
|
| 32 |
-
const express = require('express');
|
| 33 |
-
const cors = require('cors');
|
| 34 |
-
const bodyParser = require('body-parser');
|
| 35 |
-
const multer = require('multer');
|
| 36 |
-
const pdfParse = require('pdf-parse');
|
| 37 |
-
const mammoth = require('mammoth');
|
| 38 |
-
const { v4: uuidv4 } = require('uuid');
|
| 39 |
-
const helmet = require('helmet');
|
| 40 |
-
const rateLimit = require('express-rate-limit');
|
| 41 |
-
|
| 42 |
-
const OpenAI = require('openai');
|
| 43 |
-
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
|
| 44 |
-
const MODEL = process.env.OPENAI_MODEL || 'gpt-4o-mini';
|
| 45 |
-
|
| 46 |
-
const app = express();
|
| 47 |
-
|
| 48 |
-
// ---- Security & basics ----
|
| 49 |
-
app.use(helmet({ contentSecurityPolicy: false }));
|
| 50 |
-
app.use(cors());
|
| 51 |
-
app.use(bodyParser.json({ limit: '12mb' }));
|
| 52 |
-
const limiter = rateLimit({ windowMs: 60_000, max: 120 }); // 120 req/min/IP
|
| 53 |
-
app.use(limiter);
|
| 54 |
-
|
| 55 |
-
// ---- Uploads temp ----
|
| 56 |
-
const upload = multer({
|
| 57 |
-
dest: path.join(os.tmpdir(), 'deepstudypro_uploads'),
|
| 58 |
-
limits: { fileSize: 30 * 1024 * 1024 } // 30MB
|
| 59 |
-
});
|
| 60 |
-
|
| 61 |
-
// ---- Utils ----
|
| 62 |
-
const sanitize = (s) => (s || '').toString().replace(/\u0000/g, '').trim();
|
| 63 |
-
const clamp = (n, min, max) => Math.max(min, Math.min(max, n));
|
| 64 |
-
|
| 65 |
-
function approxTokenLen(str) {
|
| 66 |
-
// גס: 1 טוקן ~ 3.5 תווים לטיניים; בעברית קרוב ~ 2-3. נלך על 3.
|
| 67 |
-
return Math.ceil((str || '').length / 3);
|
| 68 |
-
}
|
| 69 |
-
|
| 70 |
-
function chunkText(str, targetTokens = 1200, overlapTokens = 120) {
|
| 71 |
-
// פיצול קשיח באורך תווים (בערך), עם חפיפה קלה בין מקטעים לשמירה על הקשר
|
| 72 |
-
const tokenSize = clamp(targetTokens, 400, 2000);
|
| 73 |
-
const overlap = clamp(overlapTokens, 0, Math.floor(tokenSize / 3));
|
| 74 |
-
|
| 75 |
-
const charsPerToken = 3; // הערכה
|
| 76 |
-
const chunkChars = tokenSize * charsPerToken;
|
| 77 |
-
const overlapChars = overlap * charsPerToken;
|
| 78 |
-
|
| 79 |
-
const text = sanitize(str);
|
| 80 |
-
const chunks = [];
|
| 81 |
-
let i = 0, idx = 0;
|
| 82 |
-
while (i < text.length) {
|
| 83 |
-
const end = Math.min(text.length, i + chunkChars);
|
| 84 |
-
const slice = text.slice(i, end);
|
| 85 |
-
chunks.push({ id: `S${idx + 1}`, text: slice });
|
| 86 |
-
idx++;
|
| 87 |
-
i = end - overlapChars;
|
| 88 |
-
if (i < 0) i = 0;
|
| 89 |
-
if (i >= text.length) break;
|
| 90 |
-
}
|
| 91 |
-
return chunks;
|
| 92 |
-
}
|
| 93 |
-
|
| 94 |
-
async function extractText(filePath, originalName) {
|
| 95 |
-
const ext = (path.extname(originalName || '').toLowerCase() || '').replace('.', '');
|
| 96 |
-
if (ext === 'pdf') {
|
| 97 |
-
const data = await pdfParse(fs.readFileSync(filePath));
|
| 98 |
-
return sanitize(data.text);
|
| 99 |
-
} else if (ext === 'docx') {
|
| 100 |
-
const { value } = await mammoth.extractRawText({ path: filePath });
|
| 101 |
-
return sanitize(value);
|
| 102 |
-
} else if (ext === 'txt' || ext === '') {
|
| 103 |
-
return sanitize(fs.readFileSync(filePath, 'utf8'));
|
| 104 |
-
}
|
| 105 |
-
throw new Error(`סוג קובץ לא נתמך עדיין: ${ext} (נתמך: PDF/DOCX/TXT)`);
|
| 106 |
-
}
|
| 107 |
-
|
| 108 |
-
async function chat(messages, system = 'You are a careful, school-safe Hebrew tutor for middle/high-school. Avoid unsafe content. Prefer Hebrew, keep answers clear & kind.') {
|
| 109 |
-
const resp = await openai.chat.completions.create({
|
| 110 |
-
model: MODEL,
|
| 111 |
-
temperature: 0.35,
|
| 112 |
-
messages: [{ role: 'system', content: system }, ...messages]
|
| 113 |
-
});
|
| 114 |
-
return resp.choices?.[0]?.message?.content?.trim() || '';
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
function safeParseJSON(s) {
|
| 118 |
-
try { return JSON.parse(s); } catch { return null; }
|
| 119 |
-
}
|
| 120 |
-
|
| 121 |
-
// ---- Study Pack Map-Reduce prompts ----
|
| 122 |
-
function mapPrompt(goal, sectionId, sectionText) {
|
| 123 |
-
return [
|
| 124 |
-
{ role: 'user', content:
|
| 125 |
-
`קלט: מקטע לימודי מסומן "${sectionId}" מתוך חומר רחב.
|
| 126 |
-
מטרה פדגוגית: ${sanitize(goal) || 'הכנה לבוחן קצר + הבנה משמעותית.'}
|
| 127 |
-
|
| 128 |
-
טקסט המקטע:
|
| 129 |
-
"""
|
| 130 |
-
${sectionText}
|
| 131 |
-
"""
|
| 132 |
-
|
| 133 |
-
הפק נא תקציר ממוקד למקטע זה בלבד (5–8 משפטים), נקודות מפתח קצרות, ומושגים מרכזיים.
|
| 134 |
-
פורמט (Markdown):
|
| 135 |
-
## ${sectionId}
|
| 136 |
-
### תקציר
|
| 137 |
-
- ...
|
| 138 |
-
### נקודות מפתח
|
| 139 |
-
- ...
|
| 140 |
-
### מושגים
|
| 141 |
-
- מושג | הסבר קצר
|
| 142 |
-
- ...
|
| 143 |
-
דגשים: עברית פשוטה, גיל חט"ב/ת��כון, ללא תוכן לא ראוי.`}
|
| 144 |
-
];
|
| 145 |
-
}
|
| 146 |
-
|
| 147 |
-
function reducePrompt(goal, mappedMarkdown, mergedText) {
|
| 148 |
-
return [
|
| 149 |
-
{ role: 'user', content:
|
| 150 |
-
`יש לנו סט סיכומים ממקטעים שונים (להלן). איחד אותם ל"חבילת לימוד" אחודה בעברית, עם שמירה על עקביות והפניות למקטעים (לציטוטים).
|
| 151 |
-
מטרה: ${sanitize(goal) || 'הבנה + הכנה למבחן קצר'}
|
| 152 |
-
|
| 153 |
-
סיכומי המקטעים (Map):
|
| 154 |
-
"""
|
| 155 |
-
${mappedMarkdown}
|
| 156 |
-
"""
|
| 157 |
-
|
| 158 |
-
(לרשותך גם הטקסט הגולמי המאוחד לקריאת הקשר, אם נדרש):
|
| 159 |
-
"""
|
| 160 |
-
${mergedText.slice(0, 20000)}
|
| 161 |
-
"""
|
| 162 |
-
|
| 163 |
-
הפק פורמט Markdown מדויק:
|
| 164 |
-
|
| 165 |
-
# סיכום (8–12 משפטים) עם אזכורי מקטעים (למשל [S2], [S5])
|
| 166 |
-
# נקודות מפתח (בולטים קצרים) כולל [Si] כאשר רלוונטי
|
| 167 |
-
# מילון מושגים (טבלה: מושג | הסבר קצר | מקטעים)
|
| 168 |
-
# כרטיסיות (8–14) — כל שורה: שאלה → תשובה קצרה
|
| 169 |
-
# מבחן לדוגמה (10–14 שאלות מעורבות)
|
| 170 |
-
- בסוף החזר מקטע JSON תקני עם מפתח תשובות בלבד:
|
| 171 |
-
{"answerKey":[{"qid":"Q1","correct":"B","explanation":"..."}, ...]}
|
| 172 |
-
|
| 173 |
-
כללי בטיחות: אין תכנים פוגעניים/מסוכנים. התאמה לגיל. שמירה על בהירות.`}
|
| 174 |
-
];
|
| 175 |
-
}
|
| 176 |
-
|
| 177 |
-
async function gradeRubric(questions, userAnswers, answerKey) {
|
| 178 |
-
const payload = { questions, userAnswers, answerKey };
|
| 179 |
-
const messages = [
|
| 180 |
-
{ role: 'user', content:
|
| 181 |
-
`You are a deterministic grader for school practice quizzes.
|
| 182 |
-
Return STRICT JSON only:
|
| 183 |
-
{
|
| 184 |
-
"perQuestion":[{"qid":"Q1","correct":true,"feedback":"..."}, ...],
|
| 185 |
-
"score":{"correct":N,"total":T,"percent":P}
|
| 186 |
-
}
|
| 187 |
-
Be concise and supportive.`},
|
| 188 |
-
{ role: 'user', content: JSON.stringify(payload) }
|
| 189 |
-
];
|
| 190 |
-
const out = await chat(messages, 'You are a fair JSON-only grader. No chatter — JSON only.');
|
| 191 |
-
const parsed = safeParseJSON(out);
|
| 192 |
-
if (parsed?.perQuestion && parsed?.score) return parsed;
|
| 193 |
-
|
| 194 |
-
// Fallback strict compare
|
| 195 |
-
const keyMap = {};
|
| 196 |
-
(answerKey?.answerKey || []).forEach(k => keyMap[String(k.qid)] = String(k.correct).trim().toLowerCase());
|
| 197 |
-
const per = (questions || []).map((q, i) => {
|
| 198 |
-
const qid = q.qid || `Q${i+1}`;
|
| 199 |
-
const ua = String(userAnswers?.[qid] ?? '').trim().toLowerCase();
|
| 200 |
-
const ca = keyMap[qid] || '';
|
| 201 |
-
const correct = !!ua && ua === ca;
|
| 202 |
-
return { qid, correct, feedback: correct ? 'נכון — מעולה!' : `שגוי. התשובה: ${answerKey?.answerKey?.find(x=>String(x.qid)===qid)?.correct ?? '—'}` };
|
| 203 |
-
});
|
| 204 |
-
const c = per.filter(p => p.correct).length;
|
| 205 |
-
return { perQuestion: per, score: { correct: c, total: per.length, percent: per.length ? Math.round(100*c/per.length) : 0 } };
|
| 206 |
-
}
|
| 207 |
-
|
| 208 |
-
// ------------- API -------------
|
| 209 |
-
|
| 210 |
-
app.get('/api/health', (_req, res) => res.json({ ok: true, model: MODEL }));
|
| 211 |
-
|
| 212 |
-
// Study Pack Pro: upload OR raw text, chunk → map → reduce
|
| 213 |
-
app.post('/api/study-pack', upload.array('files', 6), async (req, res) => {
|
| 214 |
-
const cleanup = () => (req.files || []).forEach(f => fs.existsSync(f.path) && fs.unlinkSync(f.path));
|
| 215 |
-
try {
|
| 216 |
-
const goal = sanitize(req.body.goal).slice(0, 600);
|
| 217 |
-
let merged = '';
|
| 218 |
-
|
| 219 |
-
if (req.files?.length) {
|
| 220 |
-
for (const f of req.files) {
|
| 221 |
-
const t = await extractText(f.path, f.originalname);
|
| 222 |
-
merged += `\n\n===== ${f.originalname} =====\n${t}\n`;
|
| 223 |
-
}
|
| 224 |
-
}
|
| 225 |
-
const rawText = sanitize(req.body.text || '');
|
| 226 |
-
if (rawText) merged += `\n\n===== Pasted Text =====\n${rawText}\n`;
|
| 227 |
-
|
| 228 |
-
if (!merged.trim()) return res.status(400).json({ ok: false, error: 'לא התקבל טקסט. העלו קבצים או הדביקו טקסט.' });
|
| 229 |
-
|
| 230 |
-
// Chunking
|
| 231 |
-
const chunks = chunkText(merged, 1200, 120);
|
| 232 |
-
// Map
|
| 233 |
-
const mappedParts = [];
|
| 234 |
-
for (const ch of chunks) {
|
| 235 |
-
const md = await chat(mapPrompt(goal, ch.id, ch.text));
|
| 236 |
-
mappedParts.push(md);
|
| 237 |
-
}
|
| 238 |
-
const mappedMarkdown = mappedParts.join('\n\n');
|
| 239 |
-
|
| 240 |
-
// Reduce
|
| 241 |
-
const reduced = await chat(reducePrompt(goal, mappedMarkdown, merged));
|
| 242 |
-
|
| 243 |
-
// Try extracting the last JSON block as answerKey
|
| 244 |
-
let answerKey = { answerKey: [] };
|
| 245 |
-
const matches = reduced.match(/\{[\s\S]*\}/g) || [];
|
| 246 |
-
for (let i = matches.length - 1; i >= 0; i--) {
|
| 247 |
-
const candidate = safeParseJSON(matches[i]);
|
| 248 |
-
if (candidate?.answerKey) { answerKey = candidate; break; }
|
| 249 |
-
}
|
| 250 |
-
|
| 251 |
-
res.json({ ok: true, studyPackMarkdown: reduced, mappedMarkdown, answerKey, sections: chunks.map(c => c.id) });
|
| 252 |
-
} catch (e) {
|
| 253 |
-
console.error(e);
|
| 254 |
-
res.status(500).json({ ok: false, error: 'שגיאה ביצירת החבילה', details: String(e.message || e) });
|
| 255 |
-
} finally { cleanup(); }
|
| 256 |
-
});
|
| 257 |
-
|
| 258 |
-
// Quiz grading
|
| 259 |
-
app.post('/api/grade', async (req, res) => {
|
| 260 |
-
try {
|
| 261 |
-
const { questions, userAnswers, answerKey } = req.body || {};
|
| 262 |
-
if (!questions || !userAnswers || !answerKey) {
|
| 263 |
-
return res.status(400).json({ ok: false, error: 'חסר שאלות/תשובות/מפתח.' });
|
| 264 |
-
}
|
| 265 |
-
const result = await gradeRubric(questions, userAnswers, answerKey);
|
| 266 |
-
res.json({ ok: true, result });
|
| 267 |
-
} catch (e) {
|
| 268 |
-
console.error(e);
|
| 269 |
-
res.status(500).json({ ok: false, error: 'שגיאה בבדיקה', details: String(e.message || e) });
|
| 270 |
-
}
|
| 271 |
-
});
|
| 272 |
-
|
| 273 |
-
// Streaming Chat via SSE
|
| 274 |
-
app.get('/api/chat/stream', async (req, res) => {
|
| 275 |
-
// Simple, non-chunk streaming (simulate typing via SSE) — robust for DeepSite proxies.
|
| 276 |
-
res.set({
|
| 277 |
-
'Cache-Control': 'no-cache',
|
| 278 |
-
'Content-Type': 'text/event-stream',
|
| 279 |
-
Connection: 'keep-alive'
|
| 280 |
-
});
|
| 281 |
-
res.flushHeaders();
|
| 282 |
-
|
| 283 |
-
const history = safeParseJSON(req.query.history || '[]') || [];
|
| 284 |
-
const message = sanitize(req.query.message || '');
|
| 285 |
-
const msgs = [];
|
| 286 |
-
for (const m of history) {
|
| 287 |
-
const role = m.role === 'assistant' ? 'assistant' : 'user';
|
| 288 |
-
msgs.push({ role, content: String(m.content || '') });
|
| 289 |
-
}
|
| 290 |
-
msgs.push({ role: 'user', content: message });
|
| 291 |
-
|
| 292 |
-
try {
|
| 293 |
-
const reply = await chat(
|
| 294 |
-
msgs,
|
| 295 |
-
'You are a friendly Hebrew tutor. Keep it short unless asked for more, include one example, avoid unsafe topics.'
|
| 296 |
-
);
|
| 297 |
-
// simulate streaming: send in slices
|
| 298 |
-
const parts = reply.match(/.{1,60}/g) || [reply];
|
| 299 |
-
for (const p of parts) {
|
| 300 |
-
res.write(`data: ${JSON.stringify({ chunk: p })}\n\n`);
|
| 301 |
-
await new Promise(r => setTimeout(r, 20));
|
| 302 |
-
}
|
| 303 |
-
res.write(`data: ${JSON.stringify({ done: true })}\n\n`);
|
| 304 |
-
res.end();
|
| 305 |
-
} catch (e) {
|
| 306 |
-
res.write(`data: ${JSON.stringify({ error: 'שגיאה בצ׳אט' })}\n\n`);
|
| 307 |
-
res.end();
|
| 308 |
-
}
|
| 309 |
-
});
|
| 310 |
-
|
| 311 |
-
// -------- Frontend (one HTML) --------
|
| 312 |
-
app.get('/', (_req, res) => {
|
| 313 |
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
| 314 |
-
res.end(`<!doctype html>
|
| 315 |
<html lang="he" dir="rtl">
|
| 316 |
<head>
|
| 317 |
-
<meta charset="
|
| 318 |
-
<meta name="viewport" content="width=device-width,initial-scale=1"
|
| 319 |
-
<title>
|
| 320 |
-
<style>
|
| 321 |
-
|
| 322 |
-
*{box-sizing:border-box} body{margin:0;background:radial-gradient(1200px 600px at 60% -200px,#0e1849,transparent),linear-gradient(180deg,#060a1a 0%,#0b1027 100%);color:var(--ink);font-family:Inter,Segoe UI,system-ui,Arial}
|
| 323 |
-
.wrap{max-width:1100px;margin:40px auto;padding:0 16px}
|
| 324 |
-
h1{margin:0 0 8px;font-size:32px;font-weight:900;letter-spacing:.2px}
|
| 325 |
-
.sub{margin:0 0 22px;color:var(--muted)}
|
| 326 |
-
.grid{display:grid;grid-template-columns:1.25fr .75fr;gap:16px}
|
| 327 |
-
@media(max-width:950px){.grid{grid-template-columns:1fr}}
|
| 328 |
-
.card{background:rgba(11,17,48,.7);backdrop-filter:blur(8px);border:1px solid var(--line);border-radius:18px;padding:16px;box-shadow:0 12px 30px rgba(0,0,0,.3)}
|
| 329 |
-
h3{margin:0 0 10px}
|
| 330 |
-
.row{display:flex;gap:8px;align-items:center;margin:10px 0}
|
| 331 |
-
input[type="text"],textarea{width:100%;padding:12px 14px;border-radius:12px;border:1px solid var(--line);background:#0a1030;color:var(--ink);resize:vertical}
|
| 332 |
-
textarea{min-height:110px}
|
| 333 |
-
input[type="file"]{border:1px dashed var(--line);padding:10px;border-radius:12px;width:100%;color:var(--muted)}
|
| 334 |
-
.btn{cursor:pointer;border:0;border-radius:12px;padding:11px 16px;font-weight:800;background:linear-gradient(90deg,var(--acc),var(--acc2));color:#03122a}
|
| 335 |
-
.btn.sec{background:transparent;color:var(--acc);border:1px solid var(--acc)}
|
| 336 |
-
.pill{display:inline-flex;align-items:center;gap:8px;background:#0a143b;padding:5px 10px;border:1px solid var(--line);border-radius:999px;font-size:12px;color:var(--muted)}
|
| 337 |
-
.hr{height:1px;background:linear-gradient(90deg,transparent,var(--line),transparent);margin:16px 0}
|
| 338 |
-
.out{white-space:pre-wrap;line-height:1.6}
|
| 339 |
-
.chat{height:420px;overflow:auto;background:#080f2b;border-radius:14px;padding:12px;border:1px solid var(--line)}
|
| 340 |
-
.bubble{padding:10px 12px;border-radius:12px;margin:8px 0;max-width:85%}
|
| 341 |
-
.me{background:#0f1c4e;margin-left:auto}
|
| 342 |
-
.bot{background:#0b1540;border:1px solid var(--line)}
|
| 343 |
-
.small{font-size:12px;color:var(--muted)}
|
| 344 |
-
.actions{display:flex;gap:8px;flex-wrap:wrap}
|
| 345 |
-
kbd{background:#09122e;border:1px solid var(--line);padding:2px 6px;border-radius:6px;font-size:12px}
|
| 346 |
-
table{border-collapse:collapse;width:100%}
|
| 347 |
-
td,th{border:1px solid var(--line);padding:6px}
|
| 348 |
-
.toast{position:fixed;bottom:16px;left:50%;transform:translateX(-50%);background:#0b1438;border:1px solid var(--line);color:var(--ink);padding:10px 12px;border-radius:10px;display:none}
|
| 349 |
-
</style>
|
| 350 |
</head>
|
| 351 |
<body>
|
| 352 |
-
<
|
| 353 |
-
|
| 354 |
-
|
| 355 |
-
|
| 356 |
-
|
| 357 |
-
|
| 358 |
-
|
| 359 |
-
|
| 360 |
-
|
| 361 |
-
|
| 362 |
-
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
-
|
| 368 |
-
|
| 369 |
-
|
| 370 |
-
|
| 371 |
-
|
| 372 |
-
|
| 373 |
-
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
|
| 379 |
-
|
| 380 |
-
|
| 381 |
-
|
| 382 |
-
|
| 383 |
-
|
| 384 |
-
|
| 385 |
-
|
| 386 |
-
|
| 387 |
-
|
| 388 |
-
|
| 389 |
-
|
| 390 |
-
|
| 391 |
-
|
| 392 |
-
|
| 393 |
-
|
| 394 |
-
|
| 395 |
-
|
| 396 |
-
<
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 406 |
-
|
| 407 |
-
|
| 408 |
-
|
| 409 |
-
|
| 410 |
-
|
| 411 |
-
function toast(txt) {
|
| 412 |
-
toastEl.textContent = txt;
|
| 413 |
-
toastEl.style.display = 'block';
|
| 414 |
-
setTimeout(()=> toastEl.style.display='none', 2200);
|
| 415 |
-
}
|
| 416 |
-
|
| 417 |
-
function addBubble(txt, who='bot'){
|
| 418 |
-
const b = document.createElement('div');
|
| 419 |
-
b.className = 'bubble ' + (who==='me'?'me':'bot');
|
| 420 |
-
b.textContent = txt;
|
| 421 |
-
chatEl.appendChild(b);
|
| 422 |
-
chatEl.scrollTop = chatEl.scrollHeight;
|
| 423 |
-
}
|
| 424 |
-
|
| 425 |
-
function renderHistory(){
|
| 426 |
-
chatEl.innerHTML = '';
|
| 427 |
-
chatHistory.forEach(m => addBubble(m.content, m.role==='user'?'me':'bot'));
|
| 428 |
-
}
|
| 429 |
-
renderHistory();
|
| 430 |
-
|
| 431 |
-
async function health() {
|
| 432 |
-
try {
|
| 433 |
-
const r = await fetch('/api/health');
|
| 434 |
-
const j = await r.json();
|
| 435 |
-
if (j.ok) healthEl.textContent = 'מודל: ' + (j.model || '—');
|
| 436 |
-
} catch {}
|
| 437 |
-
}
|
| 438 |
-
health();
|
| 439 |
-
|
| 440 |
-
sendBtn.onclick = async () => {
|
| 441 |
-
const m = msgEl.value.trim();
|
| 442 |
-
if (!m) return;
|
| 443 |
-
lastUserMsg = m;
|
| 444 |
-
addBubble(m, 'me');
|
| 445 |
-
msgEl.value = '';
|
| 446 |
-
chatHistory.push({ role:'user', content:m });
|
| 447 |
-
localStorage.setItem('dsp_chat', JSON.stringify(chatHistory));
|
| 448 |
-
|
| 449 |
-
const src = new EventSource('/api/chat/stream?'+new URLSearchParams({
|
| 450 |
-
message: m,
|
| 451 |
-
history: JSON.stringify(chatHistory.slice(-20)) // שומרים על הקשר קצר
|
| 452 |
-
}));
|
| 453 |
-
|
| 454 |
-
let acc = '';
|
| 455 |
-
const typing = document.createElement('div');
|
| 456 |
-
typing.className = 'bubble bot';
|
| 457 |
-
typing.textContent = 'המורה מקלידה…';
|
| 458 |
-
chatEl.appendChild(typing);
|
| 459 |
-
chatEl.scrollTop = chatEl.scrollHeight;
|
| 460 |
-
|
| 461 |
-
src.onmessage = (e) => {
|
| 462 |
-
const data = JSON.parse(e.data || '{}');
|
| 463 |
-
if (data.chunk) {
|
| 464 |
-
acc += data.chunk;
|
| 465 |
-
typing.textContent = acc;
|
| 466 |
-
}
|
| 467 |
-
if (data.done) {
|
| 468 |
-
chatHistory.push({ role:'assistant', content:acc });
|
| 469 |
-
localStorage.setItem('dsp_chat', JSON.stringify(chatHistory));
|
| 470 |
-
src.close();
|
| 471 |
-
}
|
| 472 |
-
if (data.error) {
|
| 473 |
-
typing.textContent = 'שגיאה בצ׳אט.';
|
| 474 |
-
src.close();
|
| 475 |
-
}
|
| 476 |
-
};
|
| 477 |
-
};
|
| 478 |
-
|
| 479 |
-
document.addEventListener('keydown', (ev)=>{
|
| 480 |
-
if (ev.key === 'ArrowUp' && document.activeElement === msgEl && !msgEl.value) {
|
| 481 |
-
msgEl.value = lastUserMsg;
|
| 482 |
-
}
|
| 483 |
-
});
|
| 484 |
-
|
| 485 |
-
buildBtn.onclick = async () => {
|
| 486 |
-
studyPackOut.textContent = '⏳ מעבד קבצים, מפצל למקטעים, ומריץ סי��ומים חכמים...';
|
| 487 |
-
const fd = new FormData();
|
| 488 |
-
if (goalEl.value.trim()) fd.append('goal', goalEl.value.trim());
|
| 489 |
-
if (rawTextEl.value.trim()) fd.append('text', rawTextEl.value.trim());
|
| 490 |
-
for (const f of filesEl.files) fd.append('files', f);
|
| 491 |
-
|
| 492 |
-
const r = await fetch('/api/study-pack', { method:'POST', body: fd });
|
| 493 |
-
const j = await r.json();
|
| 494 |
-
if (!j.ok) {
|
| 495 |
-
studyPackOut.textContent = '❌ ' + (j.error || 'שגיאה');
|
| 496 |
-
if (j.details) studyPackOut.textContent += '\\n' + j.details;
|
| 497 |
-
return;
|
| 498 |
-
}
|
| 499 |
-
|
| 500 |
-
latestMarkdown = j.studyPackMarkdown || '';
|
| 501 |
-
latestAnswerKey = j.answerKey || { answerKey: [] };
|
| 502 |
-
studyPackOut.textContent = latestMarkdown;
|
| 503 |
-
|
| 504 |
-
// דוגמאות JSON בשדות בדיקה
|
| 505 |
-
if (!questionsJSONEl.value.trim()) {
|
| 506 |
-
questionsJSONEl.value = JSON.stringify([
|
| 507 |
-
{"qid":"Q1","type":"mcq","question":"דוגמה: מהי אנרגיה קינטית?","choices":["A","B","C","D"]},
|
| 508 |
-
{"qid":"Q2","type":"tf","question":"דוגמה: לחץ מִדָּה סקלרית. (True/False)"},
|
| 509 |
-
{"qid":"Q3","type":"short","question":"דוגמה: כתבי את החוק הראשון של ניוטון במשפט אחד."}
|
| 510 |
-
], null, 2);
|
| 511 |
-
}
|
| 512 |
-
if (!userAnswersJSONEl.value.trim()) {
|
| 513 |
-
userAnswersJSONEl.value = JSON.stringify({"Q1":"B","Q2":"True","Q3":"גוף שומר על מצבו..."}, null, 2);
|
| 514 |
-
}
|
| 515 |
-
toast('החבילה מוכנה ✔');
|
| 516 |
-
};
|
| 517 |
-
|
| 518 |
-
downloadBtn.onclick = () => {
|
| 519 |
-
if (!latestMarkdown.trim()) return toast('אין תוכן להוריד עדיין');
|
| 520 |
-
const blob = new Blob([latestMarkdown], { type:'text/markdown;charset=utf-8' });
|
| 521 |
-
const a = document.createElement('a');
|
| 522 |
-
a.href = URL.createObjectURL(blob);
|
| 523 |
-
a.download = 'deepstudy_pack.md';
|
| 524 |
-
a.click();
|
| 525 |
-
URL.revokeObjectURL(a.href);
|
| 526 |
-
};
|
| 527 |
-
|
| 528 |
-
gradeBtn.onclick = async () => {
|
| 529 |
-
gradeOut.textContent = 'בודק...';
|
| 530 |
-
let questions=null, userAnswers=null;
|
| 531 |
-
try { questions = JSON.parse(questionsJSONEl.value); } catch { return gradeOut.textContent='JSON לא תקין בשדה שאלות'; }
|
| 532 |
-
try { userAnswers = JSON.parse(userAnswersJSONEl.value); } catch { return gradeOut.textContent='JSON לא תקין בשדה תשובות'; }
|
| 533 |
-
|
| 534 |
-
const r = await fetch('/api/grade', {
|
| 535 |
-
method:'POST', headers:{'Content-Type':'application/json'},
|
| 536 |
-
body: JSON.stringify({ questions, userAnswers, answerKey: latestAnswerKey })
|
| 537 |
-
});
|
| 538 |
-
const j = await r.json();
|
| 539 |
-
if (!j.ok) return gradeOut.textContent = 'שגיאה בבדיקה.';
|
| 540 |
-
const g = j.result;
|
| 541 |
-
const lines = [];
|
| 542 |
-
lines.push('תוצאה: ' + g.score.correct + '/' + g.score.total + ' (' + g.score.percent + '%)');
|
| 543 |
-
lines.push('');
|
| 544 |
-
(g.perQuestion||[]).forEach(p => lines.push(`${p.qid}: ${p.correct?'✓ נכון':'✗ שגוי'} — ${p.feedback||''}`));
|
| 545 |
-
gradeOut.textContent = lines.join('\\n');
|
| 546 |
-
};
|
| 547 |
-
|
| 548 |
-
refineBtn.onclick = async () => {
|
| 549 |
-
const sel = prompt('איזה נושא לשדרג/להרחיב (למשל: "חוק שני של ניוטון")?');
|
| 550 |
-
if (!sel) return;
|
| 551 |
-
const ask = 'שדרג/י והרחיבי במיוחד את הנושא: ' + sel + '\\nכללי: הסבר מדורג + דוגמה מספרית + תרגיל קצר עם פתרון.';
|
| 552 |
-
msgEl.value = ask;
|
| 553 |
-
sendBtn.click();
|
| 554 |
-
};
|
| 555 |
-
</script>
|
| 556 |
</body>
|
| 557 |
-
</html
|
| 558 |
-
});
|
| 559 |
-
|
| 560 |
-
// ---- Start ----
|
| 561 |
-
const PORT = process.env.PORT || 3000;
|
| 562 |
-
if (!process.env.OPENAI_API_KEY) {
|
| 563 |
-
console.warn('⚠️ חסר OPENAI_API_KEY — הגדירי משתנה סביבה לפני ההרצה.');
|
| 564 |
-
}
|
| 565 |
-
app.listen(PORT, () => {
|
| 566 |
-
console.log('DeepStudy Pro up on http://localhost:'+PORT);
|
| 567 |
-
});
|
|
|
|
| 1 |
+
<!DOCTYPE html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 2 |
<html lang="he" dir="rtl">
|
| 3 |
<head>
|
| 4 |
+
<meta charset="UTF-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 6 |
+
<title>StudyMate - עוזר לימודי חכם</title>
|
| 7 |
+
<link rel="stylesheet" href="style.css">
|
| 8 |
+
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
</head>
|
| 10 |
<body>
|
| 11 |
+
<custom-navbar></custom-navbar>
|
| 12 |
+
|
| 13 |
+
<main class="container">
|
| 14 |
+
<section class="hero">
|
| 15 |
+
<h1>StudyMate - הפיכת הלמידה לחכמה יותר</h1>
|
| 16 |
+
<p>העלה חומרי לימוד וקבל סיכומים, מבחנים וכרטיסיות לימוד - הכל אוטומטי!</p>
|
| 17 |
+
</section>
|
| 18 |
+
|
| 19 |
+
<div class="study-tools">
|
| 20 |
+
<div class="tool-card">
|
| 21 |
+
<h2><i class="fas fa-file-upload"></i> העלאת קבצים</h2>
|
| 22 |
+
<div class="upload-area" id="dropZone">
|
| 23 |
+
<input type="file" id="fileInput" multiple accept=".pdf,.docx,.txt">
|
| 24 |
+
<label for="fileInput" class="upload-btn">
|
| 25 |
+
<i class="fas fa-cloud-upload-alt"></i> גרור קבצים או לחץ לבחירה
|
| 26 |
+
</label>
|
| 27 |
+
<div class="file-list" id="fileList"></div>
|
| 28 |
+
</div>
|
| 29 |
+
<textarea id="studyGoal" placeholder="מה המטרה הלימודית? (למשל: הכנה למבחן בתנ״ך)"></textarea>
|
| 30 |
+
<button class="btn primary" id="processBtn">
|
| 31 |
+
<i class="fas fa-magic"></i> צור חומרי לימוד
|
| 32 |
+
</button>
|
| 33 |
+
</div>
|
| 34 |
+
|
| 35 |
+
<div class="tool-card">
|
| 36 |
+
<h2><i class="fas fa-book"></i> חומרי הלימוד שלי</h2>
|
| 37 |
+
<div class="tabs">
|
| 38 |
+
<button class="tab-btn active" data-tab="notes">סיכומים</button>
|
| 39 |
+
<button class="tab-btn" data-tab="flashcards">כרטיסיות</button>
|
| 40 |
+
<button class="tab-btn" data-tab="exams">מבחנים</button>
|
| 41 |
+
</div>
|
| 42 |
+
<div class="tab-content active" id="notes">
|
| 43 |
+
<div class="empty-state">
|
| 44 |
+
<i class="fas fa-book-open"></i>
|
| 45 |
+
<p>עדיין אין סיכומים. העלה קבצים כדי להתחיל!</p>
|
| 46 |
+
</div>
|
| 47 |
+
</div>
|
| 48 |
+
<div class="tab-content" id="flashcards">
|
| 49 |
+
<div class="empty-state">
|
| 50 |
+
<i class="fas fa-layer-group"></i>
|
| 51 |
+
<p>עדיין אין כרטיסיות. העלה קבצים כדי להתחיל!</p>
|
| 52 |
+
</div>
|
| 53 |
+
</div>
|
| 54 |
+
<div class="tab-content" id="exams">
|
| 55 |
+
<div class="empty-state">
|
| 56 |
+
<i class="fas fa-file-alt"></i>
|
| 57 |
+
<p>עדיין אין מבחנים. העלה קבצים כדי להתחיל!</p>
|
| 58 |
+
</div>
|
| 59 |
+
</div>
|
| 60 |
+
</div>
|
| 61 |
+
</div>
|
| 62 |
+
</main>
|
| 63 |
+
|
| 64 |
+
<custom-footer></custom-footer>
|
| 65 |
+
|
| 66 |
+
<script src="components/navbar.js"></script>
|
| 67 |
+
<script src="components/footer.js"></script>
|
| 68 |
+
<script src="script.js"></script>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 69 |
</body>
|
| 70 |
+
</html>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
script.js
CHANGED
|
@@ -1,56 +1,150 @@
|
|
| 1 |
-
|
| 2 |
-
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
//
|
| 18 |
-
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
e.preventDefault();
|
| 22 |
-
const submitBtn = form.querySelector('button[type="submit"]');
|
| 23 |
-
const originalText = submitBtn.textContent;
|
| 24 |
-
|
| 25 |
-
try {
|
| 26 |
-
submitBtn.disabled = true;
|
| 27 |
-
submitBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> מע מעבד...';
|
| 28 |
-
|
| 29 |
-
// Simulate form submission
|
| 30 |
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
| 31 |
-
|
| 32 |
-
showToast('ההפעולה בוצעה בהצלחה!', 'success');
|
| 33 |
-
form.reset();
|
| 34 |
-
} catch (error) {
|
| 35 |
-
showToast('אירעה שגיאה. נסה שוב.', 'danger');
|
| 36 |
-
console.error(error);
|
| 37 |
-
} finally {
|
| 38 |
-
submitBtn.disabled = false;
|
| 39 |
-
submitBtn.textContent = originalText;
|
| 40 |
-
}
|
| 41 |
-
});
|
| 42 |
});
|
| 43 |
-
}
|
| 44 |
|
| 45 |
-
//
|
| 46 |
-
|
| 47 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
```javascript
|
| 2 |
+
document.addEventListener('DOMContentLoaded', () => {
|
| 3 |
+
// File upload handling
|
| 4 |
+
const fileInput = document.getElementById('fileInput');
|
| 5 |
+
const fileList = document.getElementById('fileList');
|
| 6 |
+
const dropZone = document.getElementById('dropZone');
|
| 7 |
+
const processBtn = document.getElementById('processBtn');
|
| 8 |
+
|
| 9 |
+
// Tab switching
|
| 10 |
+
const tabBtns = document.querySelectorAll('.tab-btn');
|
| 11 |
+
tabBtns.forEach(btn => {
|
| 12 |
+
btn.addEventListener('click', () => {
|
| 13 |
+
// Remove active class from all buttons and tabs
|
| 14 |
+
document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
|
| 15 |
+
document.querySelectorAll('.tab-content').forEach(t => t.classList.remove('active'));
|
| 16 |
+
|
| 17 |
+
// Add active class to clicked button and corresponding tab
|
| 18 |
+
btn.classList.add('active');
|
| 19 |
+
const tabId = btn.getAttribute('data-tab');
|
| 20 |
+
document.getElementById(tabId).classList.add('active');
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 21 |
});
|
| 22 |
+
});
|
| 23 |
|
| 24 |
+
// Drag and drop functionality
|
| 25 |
+
['dragenter', 'dragover', 'dragleave', 'drop'].forEach(eventName => {
|
| 26 |
+
dropZone.addEventListener(eventName, preventDefaults, false);
|
| 27 |
+
});
|
| 28 |
+
|
| 29 |
+
function preventDefaults(e) {
|
| 30 |
+
e.preventDefault();
|
| 31 |
+
e.stopPropagation();
|
| 32 |
+
}
|
| 33 |
+
|
| 34 |
+
['dragenter', 'dragover'].forEach(eventName => {
|
| 35 |
+
dropZone.addEventListener(eventName, highlight, false);
|
| 36 |
+
});
|
| 37 |
+
|
| 38 |
+
['dragleave', 'drop'].forEach(eventName => {
|
| 39 |
+
dropZone.addEventListener(eventName, unhighlight, false);
|
| 40 |
+
});
|
| 41 |
+
|
| 42 |
+
function highlight() {
|
| 43 |
+
dropZone.classList.add('drag-over');
|
| 44 |
+
}
|
| 45 |
+
|
| 46 |
+
function unhighlight() {
|
| 47 |
+
dropZone.classList.remove('drag-over');
|
| 48 |
+
}
|
| 49 |
+
|
| 50 |
+
dropZone.addEventListener('drop', handleDrop, false);
|
| 51 |
+
|
| 52 |
+
function handleDrop(e) {
|
| 53 |
+
const dt = e.dataTransfer;
|
| 54 |
+
const files = dt.files;
|
| 55 |
+
handleFiles(files);
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
fileInput.addEventListener('change', function() {
|
| 59 |
+
handleFiles(this.files);
|
| 60 |
+
});
|
| 61 |
+
|
| 62 |
+
function handleFiles(files) {
|
| 63 |
+
fileList.innerHTML = '';
|
| 64 |
+
if (files.length === 0) return;
|
| 65 |
|
| 66 |
+
for (let i = 0; i < files.length; i++) {
|
| 67 |
+
const file = files[i];
|
| 68 |
+
if (!file.type.match('application/pdf|application/vnd.openxmlformats-officedocument.wordprocessingml.document|text/plain')) {
|
| 69 |
+
continue;
|
| 70 |
+
}
|
| 71 |
+
|
| 72 |
+
const fileItem = document.createElement('div');
|
| 73 |
+
fileItem.className = 'file-item';
|
| 74 |
+
fileItem.innerHTML = `
|
| 75 |
+
<span>${file.name}</span>
|
| 76 |
+
<i class="fas fa-times" data-index="${i}"></i>
|
| 77 |
+
`;
|
| 78 |
+
fileList.appendChild(fileItem);
|
| 79 |
+
|
| 80 |
+
// Add remove file functionality
|
| 81 |
+
fileItem.querySelector('i').addEventListener('click', function() {
|
| 82 |
+
fileItem.remove();
|
| 83 |
+
// In a real app, you would remove the file from the upload list
|
| 84 |
+
});
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
if (fileList.children.length > 0) {
|
| 88 |
+
processBtn.disabled = false;
|
| 89 |
+
} else {
|
| 90 |
+
processBtn.disabled = true;
|
| 91 |
+
}
|
| 92 |
+
}
|
| 93 |
+
|
| 94 |
+
// Process files button
|
| 95 |
+
processBtn.addEventListener('click', async function() {
|
| 96 |
+
if (fileList.children.length === 0) {
|
| 97 |
+
alert('אנא העלה לפחות קובץ אחד');
|
| 98 |
+
return;
|
| 99 |
+
}
|
| 100 |
+
|
| 101 |
+
const goal = document.getElementById('studyGoal').value.trim();
|
| 102 |
+
if (!goal) {
|
| 103 |
+
alert('אנא הזן מטרה לימודית');
|
| 104 |
+
return;
|
| 105 |
+
}
|
| 106 |
+
|
| 107 |
+
// Show loading state
|
| 108 |
+
const originalText = processBtn.innerHTML;
|
| 109 |
+
processBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> מעבד...';
|
| 110 |
+
processBtn.disabled = true;
|
| 111 |
+
|
| 112 |
+
try {
|
| 113 |
+
// Simulate processing (in a real app, this would be an API call)
|
| 114 |
+
await new Promise(resolve => setTimeout(resolve, 3000));
|
| 115 |
+
|
| 116 |
+
// Generate sample study materials
|
| 117 |
+
generateSampleMaterials();
|
| 118 |
+
|
| 119 |
+
// Show success message
|
| 120 |
+
alert('חומרי הלימוד נוצרו בהצלחה!');
|
| 121 |
+
|
| 122 |
+
// Switch to notes tab
|
| 123 |
+
document.querySelector('.tab-btn[data-tab="notes"]').click();
|
| 124 |
+
|
| 125 |
+
} catch (error) {
|
| 126 |
+
console.error('Error processing files:', error);
|
| 127 |
+
alert('אירעה שגיאה בעיבוד הקבצים. נסה שוב.');
|
| 128 |
+
} finally {
|
| 129 |
+
processBtn.innerHTML = originalText;
|
| 130 |
+
processBtn.disabled = false;
|
| 131 |
+
}
|
| 132 |
+
});
|
| 133 |
+
|
| 134 |
+
function generateSampleMaterials() {
|
| 135 |
+
// Generate sample notes
|
| 136 |
+
const notesTab = document.getElementById('notes');
|
| 137 |
+
notesTab.innerHTML = `
|
| 138 |
+
<div class="study-material">
|
| 139 |
+
<h3>סיכום המסמך</h3>
|
| 140 |
+
<div class="content">
|
| 141 |
+
<p>המסמך דן בנושאים מרכזיים בתחום הלימוד. להלן עיקרי הדברים:</p>
|
| 142 |
+
<ul>
|
| 143 |
+
<li>הקדמה היסטורית להתפתחות התחום</li>
|
| 144 |
+
<li>הגדרות יסוד ומושגי מפתח</li>
|
| 145 |
+
<li>תיאוריות מרכזיות והשוואה ביניהן</li>
|
| 146 |
+
<li>יישומים מעשיים ורלוונטיות לתקופה הנוכחית</li>
|
| 147 |
+
</ul>
|
| 148 |
+
<p>הסיכום מבוסס על ניתוח של 3 מסמכים שהעלית.</p>
|
| 149 |
+
</div>
|
| 150 |
+
<div class
|
style.css
CHANGED
|
@@ -1,378 +1,221 @@
|
|
| 1 |
-
|
| 2 |
:root {
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
--radius-md: 12px;
|
| 19 |
-
--radius-lg: 16px;
|
| 20 |
-
--rtl: rtl;
|
| 21 |
-
--transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
|
| 22 |
}
|
|
|
|
| 23 |
body {
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
direction: var(--rtl);
|
| 29 |
-
background-color: var(--light-bg);
|
| 30 |
}
|
| 31 |
|
| 32 |
.container {
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
}
|
| 37 |
|
| 38 |
.hero {
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
}
|
| 46 |
|
| 47 |
.hero h1 {
|
| 48 |
-
|
| 49 |
-
|
| 50 |
}
|
| 51 |
|
| 52 |
.hero p {
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
}
|
| 56 |
-
|
| 57 |
-
.cta-buttons {
|
| 58 |
-
display: flex;
|
| 59 |
-
justify-content: center;
|
| 60 |
-
gap: 20px;
|
| 61 |
-
}
|
| 62 |
-
.btn {
|
| 63 |
-
padding: 12px 24px;
|
| 64 |
-
border-radius: var(--radius-sm);
|
| 65 |
-
text-decoration: none;
|
| 66 |
-
font-weight: 600;
|
| 67 |
-
transition: var(--transition);
|
| 68 |
-
display: inline-flex;
|
| 69 |
-
align-items: center;
|
| 70 |
-
justify-content: center;
|
| 71 |
-
gap: 8px;
|
| 72 |
-
cursor: pointer;
|
| 73 |
-
border: none;
|
| 74 |
}
|
| 75 |
|
| 76 |
-
.
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
.btn.secondary {
|
| 82 |
-
background-color: transparent;
|
| 83 |
-
color: var(--primary-color);
|
| 84 |
-
border: 2px solid var(--primary-color);
|
| 85 |
-
}
|
| 86 |
-
|
| 87 |
-
.btn.success {
|
| 88 |
-
background-color: var(--success-color);
|
| 89 |
-
color: var(--light-text);
|
| 90 |
-
}
|
| 91 |
-
|
| 92 |
-
.btn.warning {
|
| 93 |
-
background-color: var(--warning-color);
|
| 94 |
-
color: var(--light-text);
|
| 95 |
}
|
| 96 |
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
|
| 100 |
-
}
|
| 101 |
-
|
| 102 |
-
.btn:hover {
|
| 103 |
-
transform: translateY(-2px);
|
| 104 |
-
box-shadow: var(--shadow-md);
|
| 105 |
-
}
|
| 106 |
-
|
| 107 |
-
.btn:active {
|
| 108 |
-
transform: translateY(0);
|
| 109 |
-
box-shadow: var(--shadow-sm);
|
| 110 |
-
}
|
| 111 |
-
|
| 112 |
-
.btn:disabled {
|
| 113 |
-
opacity: 0.6;
|
| 114 |
-
cursor: not-allowed;
|
| 115 |
-
}
|
| 116 |
-
|
| 117 |
-
.btn i {
|
| 118 |
-
font-size: 1rem;
|
| 119 |
-
}
|
| 120 |
-
.features {
|
| 121 |
-
display: grid;
|
| 122 |
-
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
| 123 |
-
gap: 30px;
|
| 124 |
-
margin-bottom: 40px;
|
| 125 |
-
}
|
| 126 |
-
|
| 127 |
-
.feature-card {
|
| 128 |
-
background: white;
|
| 129 |
-
padding: 30px;
|
| 130 |
-
border-radius: 10px;
|
| 131 |
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| 132 |
-
text-align: center;
|
| 133 |
-
transition: transform 0.3s ease;
|
| 134 |
-
}
|
| 135 |
-
|
| 136 |
-
.feature-card:hover {
|
| 137 |
-
transform: translateY(-10px);
|
| 138 |
-
}
|
| 139 |
-
|
| 140 |
-
.feature-card i {
|
| 141 |
-
font-size: 3rem;
|
| 142 |
-
color: var(--accent-color);
|
| 143 |
-
margin-bottom: 20px;
|
| 144 |
-
}
|
| 145 |
-
.feature-card h3 {
|
| 146 |
-
font-size: 1.5rem;
|
| 147 |
-
margin-bottom: 15px;
|
| 148 |
-
}
|
| 149 |
-
|
| 150 |
-
/* Authentication Forms */
|
| 151 |
-
.auth-form {
|
| 152 |
-
max-width: 500px;
|
| 153 |
-
margin: 0 auto;
|
| 154 |
-
background: white;
|
| 155 |
-
padding: 30px;
|
| 156 |
-
border-radius: 10px;
|
| 157 |
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| 158 |
-
}
|
| 159 |
-
|
| 160 |
-
.auth-form h1 {
|
| 161 |
-
text-align: center;
|
| 162 |
-
margin-bottom: 30px;
|
| 163 |
-
}
|
| 164 |
-
|
| 165 |
-
.form-group {
|
| 166 |
-
margin-bottom: 20px;
|
| 167 |
-
}
|
| 168 |
-
|
| 169 |
-
.form-group label {
|
| 170 |
-
display: block;
|
| 171 |
-
margin-bottom: 8px;
|
| 172 |
-
font-weight: bold;
|
| 173 |
-
}
|
| 174 |
-
|
| 175 |
-
.form-group input {
|
| 176 |
-
width: 100%;
|
| 177 |
-
padding: 12px;
|
| 178 |
-
border: 1px solid #ddd;
|
| 179 |
-
border-radius: 5px;
|
| 180 |
-
font-size: 1rem;
|
| 181 |
-
}
|
| 182 |
-
|
| 183 |
-
/* Chat Page */
|
| 184 |
-
.chat-container {
|
| 185 |
-
max-width: 800px;
|
| 186 |
-
margin: 0 auto;
|
| 187 |
-
}
|
| 188 |
-
|
| 189 |
-
.chat-box {
|
| 190 |
-
border: 1px solid #ddd;
|
| 191 |
-
border-radius: 10px;
|
| 192 |
-
overflow: hidden;
|
| 193 |
-
}
|
| 194 |
-
|
| 195 |
-
.messages {
|
| 196 |
-
height: 500px;
|
| 197 |
-
padding: 20px;
|
| 198 |
-
overflow-y: auto;
|
| 199 |
-
background: #f9f9f9;
|
| 200 |
-
}
|
| 201 |
-
|
| 202 |
-
.message {
|
| 203 |
-
margin-bottom: 15px;
|
| 204 |
-
padding: 10px 15px;
|
| 205 |
-
border-radius: 10px;
|
| 206 |
-
max-width: 70%;
|
| 207 |
-
}
|
| 208 |
-
|
| 209 |
-
.message.bot {
|
| 210 |
-
background: #e3f2fd;
|
| 211 |
-
margin-left: auto;
|
| 212 |
-
}
|
| 213 |
-
|
| 214 |
-
.message.user {
|
| 215 |
-
background: #bbdefb;
|
| 216 |
-
margin-right: auto;
|
| 217 |
-
}
|
| 218 |
-
|
| 219 |
-
.input-area {
|
| 220 |
-
display: flex;
|
| 221 |
-
padding: 15px;
|
| 222 |
-
background: white;
|
| 223 |
-
border-top: 1px solid #ddd;
|
| 224 |
}
|
| 225 |
|
| 226 |
-
.
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
}
|
| 232 |
|
| 233 |
-
.
|
| 234 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 235 |
}
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
| 239 |
-
|
| 240 |
-
|
| 241 |
-
|
|
|
|
|
|
|
|
|
|
| 242 |
}
|
| 243 |
|
| 244 |
-
.
|
| 245 |
-
|
| 246 |
-
padding: 30px;
|
| 247 |
-
border-radius: 10px;
|
| 248 |
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| 249 |
-
text-align: center;
|
| 250 |
}
|
| 251 |
|
| 252 |
-
.
|
| 253 |
-
|
| 254 |
-
|
| 255 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 256 |
}
|
| 257 |
|
| 258 |
-
|
| 259 |
-
|
| 260 |
-
|
| 261 |
-
|
|
|
|
|
|
|
|
|
|
| 262 |
}
|
| 263 |
|
| 264 |
-
.
|
| 265 |
-
|
| 266 |
-
|
| 267 |
-
border-radius: 10px;
|
| 268 |
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| 269 |
-
margin-bottom: 20px;
|
| 270 |
}
|
| 271 |
|
| 272 |
-
.
|
| 273 |
-
|
| 274 |
-
|
| 275 |
-
margin-bottom: 10px;
|
| 276 |
}
|
| 277 |
|
| 278 |
-
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
|
|
|
|
|
|
| 284 |
}
|
| 285 |
|
| 286 |
-
.
|
| 287 |
-
|
|
|
|
| 288 |
}
|
| 289 |
|
| 290 |
-
|
| 291 |
-
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
|
|
|
|
|
|
|
|
|
|
| 295 |
}
|
| 296 |
|
| 297 |
-
|
| 298 |
-
|
| 299 |
-
|
| 300 |
-
|
| 301 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 302 |
}
|
| 303 |
|
| 304 |
-
.
|
| 305 |
-
|
| 306 |
-
|
| 307 |
-
border-radius: 10px;
|
| 308 |
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| 309 |
}
|
| 310 |
|
| 311 |
-
.
|
| 312 |
-
|
| 313 |
-
color: var(--accent-color);
|
| 314 |
}
|
| 315 |
|
| 316 |
-
.
|
| 317 |
-
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
|
| 321 |
}
|
| 322 |
|
| 323 |
-
.
|
| 324 |
-
|
| 325 |
-
|
| 326 |
-
|
| 327 |
-
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
}
|
| 331 |
-
/* Toast Notifications */
|
| 332 |
-
.toast {
|
| 333 |
-
position: fixed;
|
| 334 |
-
bottom: 20px;
|
| 335 |
-
right: 20px;
|
| 336 |
-
padding: 12px 24px;
|
| 337 |
-
border-radius: var(--radius-sm);
|
| 338 |
-
color: white;
|
| 339 |
-
font-weight: 500;
|
| 340 |
-
transform: translateY(100px);
|
| 341 |
-
opacity: 0;
|
| 342 |
-
transition: var(--transition);
|
| 343 |
-
z-index: 9999;
|
| 344 |
}
|
| 345 |
|
| 346 |
-
.
|
| 347 |
-
|
| 348 |
-
opacity: 1;
|
| 349 |
}
|
| 350 |
|
| 351 |
-
.
|
| 352 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 353 |
}
|
| 354 |
|
| 355 |
-
.
|
| 356 |
-
|
| 357 |
}
|
| 358 |
|
| 359 |
-
.
|
| 360 |
-
|
| 361 |
}
|
| 362 |
|
| 363 |
-
|
| 364 |
-
|
| 365 |
-
|
| 366 |
-
|
| 367 |
}
|
| 368 |
|
| 369 |
-
.
|
| 370 |
-
|
|
|
|
|
|
|
| 371 |
}
|
| 372 |
|
| 373 |
-
/*
|
| 374 |
-
|
| 375 |
-
|
| 376 |
-
|
| 377 |
-
|
| 378 |
-
}
|
|
|
|
|
|
|
| 1 |
:root {
|
| 2 |
+
--primary: #4f46e5;
|
| 3 |
+
--primary-light: #6366f1;
|
| 4 |
+
--secondary: #7c3aed;
|
| 5 |
+
--dark: #1e293b;
|
| 6 |
+
--light: #f8fafc;
|
| 7 |
+
--gray: #94a3b8;
|
| 8 |
+
--success: #10b981;
|
| 9 |
+
--warning: #f59e0b;
|
| 10 |
+
--danger: #ef4444;
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
* {
|
| 14 |
+
box-sizing: border-box;
|
| 15 |
+
margin: 0;
|
| 16 |
+
padding: 0;
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
}
|
| 18 |
+
|
| 19 |
body {
|
| 20 |
+
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 21 |
+
background-color: #f1f5f9;
|
| 22 |
+
color: var(--dark);
|
| 23 |
+
direction: rtl;
|
|
|
|
|
|
|
| 24 |
}
|
| 25 |
|
| 26 |
.container {
|
| 27 |
+
max-width: 1200px;
|
| 28 |
+
margin: 0 auto;
|
| 29 |
+
padding: 20px;
|
| 30 |
}
|
| 31 |
|
| 32 |
.hero {
|
| 33 |
+
text-align: center;
|
| 34 |
+
padding: 60px 20px;
|
| 35 |
+
background: linear-gradient(135deg, var(--primary), var(--secondary));
|
| 36 |
+
color: white;
|
| 37 |
+
border-radius: 16px;
|
| 38 |
+
margin-bottom: 40px;
|
| 39 |
}
|
| 40 |
|
| 41 |
.hero h1 {
|
| 42 |
+
font-size: 2.5rem;
|
| 43 |
+
margin-bottom: 20px;
|
| 44 |
}
|
| 45 |
|
| 46 |
.hero p {
|
| 47 |
+
font-size: 1.2rem;
|
| 48 |
+
opacity: 0.9;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 49 |
}
|
| 50 |
|
| 51 |
+
.study-tools {
|
| 52 |
+
display: grid;
|
| 53 |
+
grid-template-columns: 1fr 1fr;
|
| 54 |
+
gap: 20px;
|
| 55 |
+
margin-bottom: 40px;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
}
|
| 57 |
|
| 58 |
+
@media (max-width: 768px) {
|
| 59 |
+
.study-tools {
|
| 60 |
+
grid-template-columns: 1fr;
|
| 61 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 62 |
}
|
| 63 |
|
| 64 |
+
.tool-card {
|
| 65 |
+
background: white;
|
| 66 |
+
border-radius: 16px;
|
| 67 |
+
padding: 25px;
|
| 68 |
+
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
| 69 |
}
|
| 70 |
|
| 71 |
+
.tool-card h2 {
|
| 72 |
+
margin-bottom: 20px;
|
| 73 |
+
display: flex;
|
| 74 |
+
align-items: center;
|
| 75 |
+
gap: 10px;
|
| 76 |
+
color: var(--primary);
|
| 77 |
}
|
| 78 |
|
| 79 |
+
.upload-area {
|
| 80 |
+
border: 2px dashed var(--gray);
|
| 81 |
+
border-radius: 12px;
|
| 82 |
+
padding: 30px;
|
| 83 |
+
text-align: center;
|
| 84 |
+
margin-bottom: 20px;
|
| 85 |
+
transition: all 0.3s;
|
| 86 |
+
position: relative;
|
| 87 |
}
|
| 88 |
|
| 89 |
+
.upload-area:hover {
|
| 90 |
+
border-color: var(--primary);
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
}
|
| 92 |
|
| 93 |
+
.upload-area input[type="file"] {
|
| 94 |
+
position: absolute;
|
| 95 |
+
width: 100%;
|
| 96 |
+
height: 100%;
|
| 97 |
+
opacity: 0;
|
| 98 |
+
top: 0;
|
| 99 |
+
right: 0;
|
| 100 |
+
cursor: pointer;
|
| 101 |
}
|
| 102 |
|
| 103 |
+
.upload-btn {
|
| 104 |
+
display: flex;
|
| 105 |
+
flex-direction: column;
|
| 106 |
+
align-items: center;
|
| 107 |
+
gap: 10px;
|
| 108 |
+
color: var(--gray);
|
| 109 |
+
cursor: pointer;
|
| 110 |
}
|
| 111 |
|
| 112 |
+
.upload-btn i {
|
| 113 |
+
font-size: 2rem;
|
| 114 |
+
color: var(--primary);
|
|
|
|
|
|
|
|
|
|
| 115 |
}
|
| 116 |
|
| 117 |
+
.file-list {
|
| 118 |
+
margin-top: 15px;
|
| 119 |
+
text-align: right;
|
|
|
|
| 120 |
}
|
| 121 |
|
| 122 |
+
.file-item {
|
| 123 |
+
display: flex;
|
| 124 |
+
justify-content: space-between;
|
| 125 |
+
align-items: center;
|
| 126 |
+
padding: 8px 12px;
|
| 127 |
+
background: #f1f5f9;
|
| 128 |
+
border-radius: 8px;
|
| 129 |
+
margin-bottom: 8px;
|
| 130 |
}
|
| 131 |
|
| 132 |
+
.file-item i {
|
| 133 |
+
color: var(--danger);
|
| 134 |
+
cursor: pointer;
|
| 135 |
}
|
| 136 |
|
| 137 |
+
#studyGoal {
|
| 138 |
+
width: 100%;
|
| 139 |
+
padding: 12px;
|
| 140 |
+
border: 1px solid #ddd;
|
| 141 |
+
border-radius: 8px;
|
| 142 |
+
margin-bottom: 20px;
|
| 143 |
+
min-height: 80px;
|
| 144 |
+
font-family: inherit;
|
| 145 |
}
|
| 146 |
|
| 147 |
+
.btn {
|
| 148 |
+
padding: 12px 24px;
|
| 149 |
+
border-radius: 8px;
|
| 150 |
+
border: none;
|
| 151 |
+
font-weight: bold;
|
| 152 |
+
cursor: pointer;
|
| 153 |
+
transition: all 0.3s;
|
| 154 |
+
display: inline-flex;
|
| 155 |
+
align-items: center;
|
| 156 |
+
gap: 8px;
|
| 157 |
}
|
| 158 |
|
| 159 |
+
.btn.primary {
|
| 160 |
+
background: var(--primary);
|
| 161 |
+
color: white;
|
|
|
|
|
|
|
| 162 |
}
|
| 163 |
|
| 164 |
+
.btn.primary:hover {
|
| 165 |
+
background: var(--primary-light);
|
|
|
|
| 166 |
}
|
| 167 |
|
| 168 |
+
.tabs {
|
| 169 |
+
display: flex;
|
| 170 |
+
border-bottom: 1px solid #ddd;
|
| 171 |
+
margin-bottom: 20px;
|
|
|
|
| 172 |
}
|
| 173 |
|
| 174 |
+
.tab-btn {
|
| 175 |
+
padding: 8px 16px;
|
| 176 |
+
background: none;
|
| 177 |
+
border: none;
|
| 178 |
+
cursor: pointer;
|
| 179 |
+
position: relative;
|
| 180 |
+
color: var(--gray);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
}
|
| 182 |
|
| 183 |
+
.tab-btn.active {
|
| 184 |
+
color: var(--primary);
|
|
|
|
| 185 |
}
|
| 186 |
|
| 187 |
+
.tab-btn.active::after {
|
| 188 |
+
content: '';
|
| 189 |
+
position: absolute;
|
| 190 |
+
bottom: -1px;
|
| 191 |
+
right: 0;
|
| 192 |
+
width: 100%;
|
| 193 |
+
height: 3px;
|
| 194 |
+
background: var(--primary);
|
| 195 |
}
|
| 196 |
|
| 197 |
+
.tab-content {
|
| 198 |
+
display: none;
|
| 199 |
}
|
| 200 |
|
| 201 |
+
.tab-content.active {
|
| 202 |
+
display: block;
|
| 203 |
}
|
| 204 |
|
| 205 |
+
.empty-state {
|
| 206 |
+
text-align: center;
|
| 207 |
+
padding: 40px 20px;
|
| 208 |
+
color: var(--gray);
|
| 209 |
}
|
| 210 |
|
| 211 |
+
.empty-state i {
|
| 212 |
+
font-size: 3rem;
|
| 213 |
+
margin-bottom: 20px;
|
| 214 |
+
color: #ddd;
|
| 215 |
}
|
| 216 |
|
| 217 |
+
/* Drag and drop styles */
|
| 218 |
+
.drag-over {
|
| 219 |
+
background: rgba(79, 70, 229, 0.1);
|
| 220 |
+
border-color: var(--primary);
|
| 221 |
+
}
|
|
|