itamarlifshitz commited on
Commit
33b5019
·
verified ·
1 Parent(s): ed666cb

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
Files changed (5) hide show
  1. components/footer.js +2 -2
  2. components/navbar.js +20 -20
  3. index.html +65 -562
  4. script.js +147 -53
  5. 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-color: var(--secondary-color);
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 SmartStudy BuddyHub - כל הזכויות שמורות
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-color: var(--primary-color);
8
- color: var(--light-text);
9
  padding: 1rem 2rem;
10
  display: flex;
11
  justify-content: space-between;
12
  align-items: center;
13
- box-shadow: var(--shadow-md);
14
  position: sticky;
15
  top: 0;
16
  z-index: 1000;
17
  }
18
- .logo {
 
19
  display: flex;
20
  align-items: center;
21
  font-weight: bold;
22
- font-size: 1.2rem;
 
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: 5px;
40
- transition: background-color 0.3s;
41
  }
42
 
43
  .nav-links a:hover {
44
- background-color: rgba(255, 255, 255, 0.2);
 
 
 
 
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="School Logo">
67
- <span>בית ספר יצחק שמיר</span>
68
  </div>
69
  <div class="nav-links">
70
- <a href="/">בית</a>
71
- <a href="/chat.html">צ'אט AI</a>
72
- <a href="/materials.html">חומרי לימוד</a>
73
- <a href="/announcements.html">הכרזות</a>
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
- npm i express multer openai pdf-parse mammoth cors dotenv uuid body-parser helmet express-rate-limit
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="utf-8" />
318
- <meta name="viewport" content="width=device-width,initial-scale=1" />
319
- <title>DeepStudy Pro deepsite GPT לתלמידים</title>
320
- <style>
321
- :root{--bg:#060a1a;--ink:#eef1ff;--muted:#b9c3ff;--card:#0b1130;--line:rgba(255,255,255,.12);--acc:#8eb1ff;--acc2:#7affe1}
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
- <div class="wrap">
353
- <h1>DeepStudy Pro — עוזר לימודי משוגע טוב</h1>
354
- <p class="sub">מעלים חומר → מקבלים חבילת לימוד מפורטת (סיכום, מושגים, כרטיסיות, מבחן + מפתח) + צ׳אט מורה זורם. נבנה במיוחד ל־DeepSite.</p>
355
-
356
- <div class="grid">
357
- <div class="card">
358
- <h3>1) יצירת חבילת לימוד (Pro)</h3>
359
- <div class="row"><input id="goal" type="text" placeholder="מטרה (למשל: 'להיערך למבחן בגאוגרפיה על אקלים')"></div>
360
- <div class="row"><input id="files" type="file" multiple accept=".pdf,.docx,.txt"></div>
361
- <div class="row"><textarea id="rawText" placeholder="או הדביקו טקסט במקום קבצים"></textarea></div>
362
- <div class="actions">
363
- <button class="btn" id="buildBtn">צור חבילת לימוד</button>
364
- <button class="btn sec" id="downloadBtn" title="מוריד את החבילה האחרונה כ-Markdown">הורדת Markdown</button>
365
- <span class="pill" id="health">מודל: —</span>
366
- </div>
367
- <div class="hr"></div>
368
- <div class="out" id="studyPackOut">—</div>
369
- <div class="hr"></div>
370
-
371
- <h3>בדיקת מבחן</h3>
372
- <p class="small">אם נוצר מפתח תשובות JSON — מלאו תשובות וקבלו ציון.</p>
373
- <textarea id="questionsJSON" placeholder='לדוגמה: [{"qid":"Q1","type":"mcq","question":"...","choices":["A","B","C","D"]}]'></textarea>
374
- <textarea id="userAnswersJSON" placeholder='לדוגמה: {"Q1":"B","Q2":"True"}'></textarea>
375
- <div class="actions">
376
- <button class="btn sec" id="gradeBtn">בדיקה וציון</button>
377
- <button class="btn sec" id="refineBtn" title="בקשו הרחבה/חידוד לנושא ספציפי">שדרוג נושא</button>
378
- </div>
379
- <div class="out" id="gradeOut"></div>
380
- </div>
381
-
382
- <div class="card">
383
- <h3>2) צ׳אט מורה (SSE)</h3>
384
- <div class="chat" id="chat"></div>
385
- <div class="row">
386
- <input id="msg" type="text" placeholder="שאלה למורה... (למשל: 'תסבירי את חוק אום עם דוגמה')">
387
- <button class="btn" id="send">שלח</button>
388
- </div>
389
- <p class="small">טיפ: <kbd>↑</kbd> מחזיר הודעה אחרונה. ההיסטוריה נשמרת אוטומטית (localStorage).</p>
390
- </div>
391
- </div>
392
- </div>
393
-
394
- <div class="toast" id="toast"></div>
395
-
396
- <script>
397
- const $ = (id) => document.getElementById(id);
398
- const chatEl = $('chat'), msgEl = $('msg'), sendBtn = $('send');
399
- const studyPackOut = $('studyPackOut'), healthEl = $('health');
400
- const filesEl = $('files'), rawTextEl = $('rawText'), goalEl = $('goal');
401
- const buildBtn = $('buildBtn'), downloadBtn = $('downloadBtn');
402
- const questionsJSONEl = $('questionsJSON'), userAnswersJSONEl = $('userAnswersJSON');
403
- const gradeBtn = $('gradeBtn'), gradeOut = $('gradeOut'), refineBtn = $('refineBtn');
404
- const toastEl = $('toast');
405
-
406
- let chatHistory = JSON.parse(localStorage.getItem('dsp_chat') || '[]');
407
- let lastUserMsg = '';
408
- let latestMarkdown = '';
409
- let latestAnswerKey = { answerKey: [] };
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
- // Utility functions
2
- function showToast(message, type = 'success') {
3
- const toast = document.createElement('div');
4
- toast.className = `toast toast-${type}`;
5
- toast.textContent = message;
6
- document.body.appendChild(toast);
7
-
8
- setTimeout(() => {
9
- toast.classList.add('show');
10
- setTimeout(() => {
11
- toast.classList.remove('show');
12
- setTimeout(() => toast.remove(), 300);
13
- }, 3000);
14
- }, 100);
15
- }
16
-
17
- // Form handling
18
- function setupForms() {
19
- document.querySelectorAll('form').forEach(form => {
20
- form.addEventListener('submit', async (e) => {
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
- // Initialize when DOM is loaded
46
- document.addEventListener('DOMContentLoaded', () => {
47
- setupForms();
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
 
49
- // Set active nav link
50
- const currentPath = window.location.pathname;
51
- document.querySelectorAll('.nav-links a').forEach(link => {
52
- if (link.getAttribute('href') === currentPath) {
53
- link.classList.add('active');
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
- --primary-color: #4361ee;
4
- --secondary-color: #3a0ca3;
5
- --accent-color: #4cc9f0;
6
- --success-color: #4caf50;
7
- --warning-color: #ff9800;
8
- --danger-color: #f44336;
9
- --text-color: #2b2d42;
10
- --light-text: #f8f9fa;
11
- --light-bg: #f8f9fa;
12
- --card-bg: #ffffff;
13
- --border-color: #e9ecef;
14
- --shadow-sm: 0 1px 3px rgba(0,0,0,0.12);
15
- --shadow-md: 0 4px 6px rgba(0,0,0,0.1);
16
- --shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
17
- --radius-sm: 8px;
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
- font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
25
- margin: 0;
26
- padding: 0;
27
- color: var(--text-color);
28
- direction: var(--rtl);
29
- background-color: var(--light-bg);
30
  }
31
 
32
  .container {
33
- max-width: 1200px;
34
- margin: 0 auto;
35
- padding: 20px;
36
  }
37
 
38
  .hero {
39
- text-align: center;
40
- padding: 60px 20px;
41
- background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
42
- color: white;
43
- border-radius: 10px;
44
- margin-bottom: 40px;
45
  }
46
 
47
  .hero h1 {
48
- font-size: 2.5rem;
49
- margin-bottom: 20px;
50
  }
51
 
52
  .hero p {
53
- font-size: 1.2rem;
54
- margin-bottom: 30px;
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
- .btn.primary {
77
- background-color: var(--primary-color);
78
- color: var(--light-text);
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
- .btn.danger {
98
- background-color: var(--danger-color);
99
- color: var(--light-text);
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
- .input-area input {
227
- flex-grow: 1;
228
- padding: 12px;
229
- border: 1px solid #ddd;
230
- border-radius: 5px 0 0 5px;
231
  }
232
 
233
- .input-area button {
234
- border-radius: 0 5px 5px 0;
 
 
 
 
235
  }
236
 
237
- /* Materials Page */
238
- .materials-grid {
239
- display: grid;
240
- grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
241
- gap: 20px;
 
 
 
242
  }
243
 
244
- .material-card {
245
- background: white;
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
- .material-card i {
253
- font-size: 2.5rem;
254
- color: var(--accent-color);
255
- margin-bottom: 15px;
 
 
 
 
256
  }
257
 
258
- /* Announcements Page */
259
- .announcements-list {
260
- max-width: 800px;
261
- margin: 0 auto;
 
 
 
262
  }
263
 
264
- .announcement-card {
265
- background: white;
266
- padding: 20px;
267
- border-radius: 10px;
268
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
269
- margin-bottom: 20px;
270
  }
271
 
272
- .announcement-card .date {
273
- color: #666;
274
- font-size: 0.9rem;
275
- margin-bottom: 10px;
276
  }
277
 
278
- /* About Page */
279
- .team-grid {
280
- display: grid;
281
- grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
282
- gap: 30px;
283
- margin-top: 30px;
 
 
284
  }
285
 
286
- .team-member {
287
- text-align: center;
 
288
  }
289
 
290
- .team-member img {
291
- width: 100%;
292
- height: auto;
293
- border-radius: 50%;
294
- margin-bottom: 15px;
 
 
 
295
  }
296
 
297
- /* Contact Page */
298
- .contact-content {
299
- display: grid;
300
- grid-template-columns: 1fr 2fr;
301
- gap: 40px;
 
 
 
 
 
302
  }
303
 
304
- .contact-info {
305
- background: white;
306
- padding: 30px;
307
- border-radius: 10px;
308
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
309
  }
310
 
311
- .contact-info i {
312
- margin-left: 10px;
313
- color: var(--accent-color);
314
  }
315
 
316
- .contact-form {
317
- background: white;
318
- padding: 30px;
319
- border-radius: 10px;
320
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.05);
321
  }
322
 
323
- .contact-form textarea {
324
- width: 100%;
325
- padding: 12px;
326
- border: 1px solid #ddd;
327
- border-radius: 5px;
328
- font-size: 1rem;
329
- font-family: inherit;
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
- .toast.show {
347
- transform: translateY(0);
348
- opacity: 1;
349
  }
350
 
351
- .toast-success {
352
- background-color: var(--success-color);
 
 
 
 
 
 
353
  }
354
 
355
- .toast-danger {
356
- background-color: var(--danger-color);
357
  }
358
 
359
- .toast-warning {
360
- background-color: var(--warning-color);
361
  }
362
 
363
- /* Loading Spinner */
364
- @keyframes spin {
365
- 0% { transform: rotate(0deg); }
366
- 100% { transform: rotate(360deg); }
367
  }
368
 
369
- .fa-spin {
370
- animation: spin 1s linear infinite;
 
 
371
  }
372
 
373
- /* Responsive Adjustments */
374
- @media (max-width: 768px) {
375
- .contact-content {
376
- grid-template-columns: 1fr;
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
+ }