itamarlifshitz commited on
Commit
2a1f177
ยท
verified ยท
1 Parent(s): 9882ee8

make the site look bettrer and every button working and doing its job

Browse files
Files changed (6) hide show
  1. chat.html +3 -3
  2. components/navbar.js +6 -4
  3. index.html +322 -44
  4. login.html +6 -4
  5. script.js +56 -0
  6. style.css +103 -16
chat.html CHANGED
@@ -20,10 +20,10 @@
20
  </div>
21
  </div>
22
  <div class="input-area">
23
- <input type="text" placeholder="ื›ืชื•ื‘ ืืช ืฉืืœืชืš ื›ืืŸ...">
24
- <button class="btn primary"><i class="fas fa-paper-plane"></i></button>
25
  </div>
26
- </div>
27
  </section>
28
  </main>
29
 
 
20
  </div>
21
  </div>
22
  <div class="input-area">
23
+ <input type="text" id="chatInput" placeholder="ื›ืชื•ื‘ ืืช ืฉืืœืชืš ื›ืืŸ...">
24
+ <button class="btn primary" id="sendMessage"><i class="fas fa-paper-plane"></i> ืฉืœ ืฉืœื—</button>
25
  </div>
26
+ </div>
27
  </section>
28
  </main>
29
 
components/navbar.js CHANGED
@@ -5,15 +5,17 @@ class CustomNavbar extends HTMLElement {
5
  <style>
6
  nav {
7
  background-color: var(--primary-color);
8
- color: white;
9
  padding: 1rem 2rem;
10
  display: flex;
11
  justify-content: space-between;
12
  align-items: center;
13
- box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
 
 
 
14
  }
15
-
16
- .logo {
17
  display: flex;
18
  align-items: center;
19
  font-weight: bold;
 
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;
index.html CHANGED
@@ -1,49 +1,327 @@
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>SmartStudy BuddyHub - ื‘ื™ืช ื”ืกืคืจ ื™ืฆื—ืง ืฉืžื™ืจ</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>ื‘ืจื•ื›ื™ื ื”ื‘ืื™ื ืœ-SmartStudy BuddyHub</h1>
16
- <p>ื”ืขื•ื–ืจ ื”ืœื™ืžื•ื“ื™ ื”ื—ื›ื ืฉืœืš! ืฉืืœ ืฉืืœื•ืช, ืงื‘ืœ ืชืฉื•ื‘ื•ืช ื•ื”ืฉืืจ ืžืขื•ื“ื›ืŸ ื‘ื”ื›ืจื–ื•ืช ื‘ื™ืช ื”ืกืคืจ.</p>
17
- <div class="cta-buttons">
18
- <a href="/login.html" class="btn primary">ื”ืชื—ื‘ืจื•ืช</a>
19
- <a href="/register.html" class="btn secondary">๏ฟฝ๏ฟฝืจืฉืžื”</a>
20
- </div>
21
- </section>
22
-
23
- <section class="features">
24
- <div class="feature-card">
25
- <i class="fas fa-robot"></i>
26
- <h3>ืขื•ื–ืจ ืœื™ืžื•ื“ื™ AI</h3>
27
- <p>ืงื‘ืœ ืชืฉื•ื‘ื•ืช ื—ื›ืžื•ืช ื”ืžื‘ื•ืกืกื•ืช ืขืœ ื—ื•ืžืจื™ ื”ืœื™ืžื•ื“ ืฉืœืš</p>
28
- </div>
29
- <div class="feature-card">
30
- <i class="fas fa-bullhorn"></i>
31
- <h3>ื”ื›ืจื–ื•ืช ื‘ื™ืช ืกืคืจ</h3>
32
- <p>ื”ื™ืฉืืจ ืžืขื•ื“ื›ืŸ ื‘ื›ืœ ื”ื”ื›ืจื–ื•ืช ื”ื—ืฉื•ื‘ื•ืช</p>
33
- </div>
34
- <div class="feature-card">
35
- <i class="fas fa-book"></i>
36
- <h3>ื ื™ื”ื•ืœ ื—ื•ืžืจื™ื</h3>
37
- <p>ืื—ืกืŸ ื•ืกื“ืจ ืืช ื›ืœ ื—ื•ืžืจื™ ื”ืœื™ืžื•ื“ ื‘ืžืงื•ื ืื—ื“</p>
38
- </div>
39
- </section>
40
- </main>
41
-
42
- <custom-footer></custom-footer>
43
-
44
- <script src="components/navbar.js"></script>
45
- <script src="components/footer.js"></script>
46
- <script src="script.js"></script>
47
- <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
48
  </body>
49
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // server.mjs
2
+ // One-file MVP: Node server + HTML chat UI (RTL Hebrew) + AI proxy
3
+ // ===== HOW TO RUN =====
4
+ // 1) Save this file as server.mjs
5
+ // 2) In terminal set env vars (PowerShell example):
6
+ // $env:AI_API_KEY="YOUR_KEY_HERE"
7
+ // $env:AI_BASE_URL="https://ai.gateway.lovable.dev/v1" # optional, default set below
8
+ // $env:AI_MODEL="google/gemini-2.5-flash" # optional
9
+ // node server.mjs
10
+ // 3) Open http://localhost:5173
11
+ //
12
+ // Notes:
13
+ // - Your API key stays on the server (safe). The browser never sees it.
14
+ // - You can paste your own study text ("context") so the bot answers based on it.
15
+ // - Context is saved in localStorage, and also sent per-message to the server as part of the system prompt.
16
+
17
+ import http from "node:http";
18
+ import { readFile } from "node:fs/promises";
19
+ import { fileURLToPath } from "node:url";
20
+ import { dirname, join } from "node:path";
21
+
22
+ // ====== CONFIG ======
23
+ const PORT = process.env.PORT ? Number(process.env.PORT) : 5173;
24
+ const AI_BASE_URL = process.env.AI_BASE_URL || "https://ai.gateway.lovable.dev/v1";
25
+ const AI_MODEL = process.env.AI_MODEL || "google/gemini-2.5-flash";
26
+ const AI_API_KEY = process.env.AI_API_KEY || ""; // MUST be set!
27
+
28
+ // ====== MINI ASSETS (single-file, inline CSS/JS) ======
29
+ const HTML = `<!doctype html>
30
  <html lang="he" dir="rtl">
31
  <head>
32
+ <meta charset="utf-8" />
33
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
34
+ <title>ืขื•ื–ืจ ืœื™ืžื•ื“ื™ื ื‘ื™ืช-ืกืคืจื™ โ€” MVP</title>
35
+ <style>
36
+ :root{
37
+ --bg: hsl(210 40% 98%);
38
+ --card: #fff;
39
+ --border: hsl(220 13% 91%);
40
+ --text: hsl(220 15% 20%);
41
+ --muted: hsl(220 15% 55%);
42
+ --primary: hsl(195 85% 45%);
43
+ --primary2: hsl(195 85% 65%);
44
+ --shadow: 0 8px 24px rgba(14,165,233,.12);
45
+ --radius: 16px;
46
+ }
47
+ *{box-sizing:border-box}
48
+ html{font-family:system-ui,-apple-system,Segoe UI,Rubik,Arial,sans-serif;background:var(--bg);color:var(--text)}
49
+ .container{max-width:980px;margin:0 auto;padding:16px}
50
+ header{background:var(--card);border-bottom:1px solid var(--border)}
51
+ .head{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 0}
52
+ .brand{display:flex;align-items:center;gap:10px}
53
+ .logo{width:44px;height:44px;border-radius:10px;background:linear-gradient(135deg,var(--primary),var(--primary2));display:grid;place-items:center;box-shadow:var(--shadow)}
54
+ .logo svg{fill:#fff;opacity:.9}
55
+ .title{font-weight:700}
56
+ .sub{font-size:12px;color:var(--muted)}
57
+ nav{display:flex;gap:8px}
58
+ .btn{display:inline-flex;align-items:center;justify-content:center;background:var(--primary);color:#fff;border:none;padding:10px 14px;border-radius:12px;cursor:pointer;box-shadow:var(--shadow)}
59
+ .btn:hover{filter:brightness(1.05)}
60
+ .btn-ghost{background:#fff;color:var(--primary);border:1px solid var(--primary)}
61
+ .btn-ghost:hover{background:rgba(14,165,233,.06)}
62
+ .grid{display:grid;gap:16px}
63
+ .card{background:var(--card);border:1px solid var(--border);border-radius:var(--radius);box-shadow:var(--shadow);padding:16px}
64
+ .row{display:grid;grid-template-columns:1fr 2fr;gap:16px}
65
+ @media (max-width:800px){.row{grid-template-columns:1fr}}
66
+ label{font-size:14px;color:var(--muted);display:block;margin-bottom:6px}
67
+ input,textarea,select{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:12px;background:#fff;outline:none}
68
+ textarea{min-height:120px;resize:vertical}
69
+ .muted{color:var(--muted);font-size:13px}
70
+ .chat{min-height:50vh;max-height:70vh;overflow:auto;display:flex;flex-direction:column;gap:10px}
71
+ .bubble{max-width:76%;border:1px solid var(--border);border-radius:14px;padding:10px 12px;background:#fff}
72
+ .me{margin-left:auto;background:rgba(14,165,233,.08);border-color:rgba(14,165,233,.25)}
73
+ .bot .who,.me .who{font-size:12px;color:var(--muted);margin-bottom:4px}
74
+ .toolbar{display:flex;gap:8px}
75
+ .badge{display:inline-flex;align-items:center;font-size:12px;color:var(--muted);border:1px solid var(--border);padding:2px 8px;border-radius:999px}
76
+ .row-compact{display:flex;gap:8px}
77
+ </style>
78
  </head>
79
  <body>
80
+ <header>
81
+ <div class="container head">
82
+ <div class="brand">
83
+ <div class="logo" title="ื™ืฆื—ืง ืฉืžื™ืจ โ€” ืกืžืœ ืžืจื•ื‘ืข, ืœืœื ืขื™ื’ื•ืœ ืคื™ื ื•ืช">
84
+ <svg viewBox="0 0 24 24" width="22" height="22"><path d="M12 3l7 4v6c0 3.866-3.582 7-8 7s-8-3.134-8-7V7l9-4z"/></svg>
85
+ </div>
86
+ <div>
87
+ <div class="title">ืขื•ื–ืจ ืœื™ืžื•ื“ื™ื ื‘ื™ืช-ืกืคืจื™</div>
88
+ <div class="sub">MVP โ€ข ืฆืณืื˜ + ื”ืงืฉืจ ืœื™ืžื•ื“ื™ (RTL)</div>
89
+ </div>
90
+ </div>
91
+ <nav>
92
+ <a class="btn-ghost" href="#context">ื—ื•ืžืจื™ ืœื™ืžื•ื“</a>
93
+ <a class="btn" href="#chat">ืฆืณืื˜</a>
94
+ </nav>
95
+ </div>
96
+ </header>
97
+
98
+ <main class="container grid" id="app">
99
+ <!-- Context / Materials -->
100
+ <section id="context" class="card">
101
+ <h2 style="margin:0 0 8px 0;">๐Ÿ“š ื—ื•ืžืจื™ ืœื™ืžื•ื“ / ื”ืงืฉืจ</h2>
102
+ <p class="muted" style="margin-top:0">ื”ื“ื‘ื™ืงื• ื›ืืŸ ืชืงืฆื™ืจ/ื˜ืงืกื˜ ืžื”ืžื—ื‘ืจืช/ื’ื•ื’ืœ ื“ื•ืงืก/ืืชืจ. ื”ื‘ื•ื˜ ื™ืชื‘ืกืก ืขืœื™ื• ื‘ืชืฉื•ื‘ื•ืช.</p>
103
+ <div class="row">
104
+ <div>
105
+ <label>ืฉื โ€œืžืจื—ื‘โ€ (ืœืžืฉืœ: ืžืชืžื˜ื™ืงื” โ€” ืคืจืง ื—ื–ืงื•ืช)</label>
106
+ <input id="spaceName" placeholder="ืฉื ื”ืžืจื—ื‘" />
107
+ <div style="height:8px"></div>
108
+ <label>ืฉืคืช ื”ื‘ื•ื˜</label>
109
+ <select id="botLang">
110
+ <option value="he">ืขื‘ืจื™ืช</option>
111
+ <option value="en">English</option>
112
+ </select>
113
+ <div style="height:8px"></div>
114
+ <span class="badge" id="stats">0 ืชื•ื•ื™ื ื‘ืงื•ื ื˜ืงืกื˜</span>
115
+ </div>
116
+ <div>
117
+ <label>ื˜ืงืกื˜ ื”ืงืฉืจ (ื ืฉืžืจ ื‘ื“ืคื“ืคืŸ, ื ืฉืœื— ืœื‘ื•ื˜ ื›ืชืžื™ื›ืช ื”ืงืฉืจ)</label>
118
+ <textarea id="contextText" placeholder="ื”ื“ื‘ื™ืงื• ื›ืืŸ ื˜ืงืกื˜ ื—ืฉื•ื‘ ืžื”ื—ื•ืžืจ..."></textarea>
119
+ <div style="height:8px"></div>
120
+ <div class="row-compact">
121
+ <button class="btn" id="saveCtx">ืฉืžื™ืจื”</button>
122
+ <button class="btn-ghost" id="clearCtx">ื ื™ืงื•ื™</button>
123
+ </div>
124
+ </div>
125
+ </div>
126
+ </section>
127
+
128
+ <!-- Chat -->
129
+ <section id="chat" class="card">
130
+ <h2 style="margin-top:0;">๐Ÿ’ฌ ืฆืณืื˜</h2>
131
+ <div class="row">
132
+ <div>
133
+ <label>ื”ื•ืจืื•ืช ืžืขืจื›ืช (ืื•ืคืฆื™ื•ื ืœื™)</label>
134
+ <textarea id="systemHint" placeholder="ืœืžืฉืœ: ืขื ื” ื‘ืงืฆืจื”, ืฆื™ื™ืŸ ืฉืœื‘ื™ื, ื”ืฉืชืžืฉ ื‘ื“ื•ื’ืžืื•ืช ืคืฉื•ื˜ื•ืช."></textarea>
135
+ <div style="height:8px"></div>
136
+ <label>ืžื•ื“ืœ (ืื•ืคืฆื™ื•ื ืœื™)</label>
137
+ <input id="model" placeholder="google/gemini-2.5-flash (ื‘ืจื™ืจืช ืžื—ื“ืœ ื‘ืฉืจืช)" />
138
+ <div style="height:8px"></div>
139
+ <div class="muted">ื”-API key ื ืฉืืจ ืขืœ ื”ืฉืจืช โ€” ื‘ื˜ื•ื—. ืื™ืŸ ืฆื•ืจืš ืœืฉื™ื ืื•ืชื• ื‘ื“ืคื“ืคืŸ.</div>
140
+ </div>
141
+ <div>
142
+ <div id="chatBox" class="chat" style="border:1px solid var(--border);border-radius:12px;padding:10px;background:#fff"></div>
143
+ <div style="height:8px"></div>
144
+ <div class="row-compact">
145
+ <input id="msg" placeholder="ืฉืืœ/ื™ ืฉืืœื” ืขืœ ืกืžืš ื”ื—ื•ืžืจ..." />
146
+ <button class="btn" id="send">ืฉืœื™ื—ื”</button>
147
+ </div>
148
+ </div>
149
+ </div>
150
+ </section>
151
+ </main>
152
+
153
+ <script>
154
+ const LS_SPACE = "mvp_space_name";
155
+ const LS_TEXT = "mvp_context_text";
156
+ const LS_LANG = "mvp_bot_lang";
157
+ const chatBox = document.getElementById("chatBox");
158
+ const msgInput = document.getElementById("msg");
159
+ const sendBtn = document.getElementById("send");
160
+ const stats = document.getElementById("stats");
161
+ const spaceName = document.getElementById("spaceName");
162
+ const contextEl = document.getElementById("contextText");
163
+ const saveCtx = document.getElementById("saveCtx");
164
+ const clearCtx = document.getElementById("clearCtx");
165
+ const botLang = document.getElementById("botLang");
166
+ const systemEl = document.getElementById("systemHint");
167
+ const modelEl = document.getElementById("model");
168
+
169
+ // Load persisted context
170
+ spaceName.value = localStorage.getItem(LS_SPACE) || "";
171
+ contextEl.value = localStorage.getItem(LS_TEXT) || "";
172
+ botLang.value = localStorage.getItem(LS_LANG) || "he";
173
+ stats.textContent = (contextEl.value.length || 0) + " ืชื•ื•ื™ื ื‘ืงื•ื ื˜ืงืกื˜";
174
+
175
+ contextEl.addEventListener("input", () => {
176
+ stats.textContent = (contextEl.value.length || 0) + " ืชื•ื•ื™ื ื‘ืงื•ื ื˜ืงืกื˜";
177
+ });
178
+
179
+ saveCtx.addEventListener("click", () => {
180
+ localStorage.setItem(LS_SPACE, spaceName.value || "");
181
+ localStorage.setItem(LS_TEXT, contextEl.value || "");
182
+ localStorage.setItem(LS_LANG, botLang.value || "he");
183
+ alert("ื ืฉืžืจ ื‘ื”ืฆืœื—ื”");
184
+ });
185
+ clearCtx.addEventListener("click", () => {
186
+ spaceName.value = "";
187
+ contextEl.value = "";
188
+ localStorage.removeItem(LS_SPACE);
189
+ localStorage.removeItem(LS_TEXT);
190
+ stats.textContent = "0 ืชื•ื•ื™ื ื‘ืงื•ื ื˜ืงืกื˜";
191
+ });
192
+
193
+ function appendBubble(role, text){
194
+ const wrap = document.createElement("div");
195
+ wrap.className = "bubble " + (role === "user" ? "me" : "bot");
196
+ const who = document.createElement("div");
197
+ who.className = "who";
198
+ who.textContent = role === "user" ? "ืืช/ื”" : "ื‘ื•ื˜";
199
+ const body = document.createElement("div");
200
+ body.textContent = text;
201
+ wrap.appendChild(who);
202
+ wrap.appendChild(body);
203
+ chatBox.appendChild(wrap);
204
+ chatBox.scrollTop = chatBox.scrollHeight;
205
+ }
206
+
207
+ async function ask(){
208
+ const q = (msgInput.value || "").trim();
209
+ if(!q) return;
210
+ appendBubble("user", q);
211
+ msgInput.value = "";
212
+ const ctx = contextEl.value || "";
213
+ const space = spaceName.value || "ื‘ืจื™ืจืช ืžื—ื“ืœ";
214
+ const lang = botLang.value || "he";
215
+ const system = (systemEl.value || "") + "\\n" +
216
+ (lang === "he"
217
+ ? \`ืขื ื” ื‘ืขื‘ืจื™ืช, ื‘ืงืฆืจื” ื•ื‘ื‘ื”ื™ืจื•ืช. ื”ืฉืชืžืฉ ืงื•ื“ื ื›ื•ืœ ื‘ืชื•ื›ืŸ ื”ืงืฉืจ ืกื‘ื™ืจ ืœื”ืœืŸ.\`
218
+ : \`Answer briefly and clearly. Prioritize the provided study context.\`) +
219
+ "\\n" +
220
+ \`ืฉื ื”ืžืจื—ื‘: "\${space}".\\n\\nื”ืงืฉืจ/ื—ื•ืžืจื™ ืœื™ืžื•ื“:\\n\${ctx}\`;
221
+
222
+ try{
223
+ const resp = await fetch("/api/chat", {
224
+ method: "POST",
225
+ headers: {"Content-Type":"application/json"},
226
+ body: JSON.stringify({ message: q, system, model: (modelEl.value || undefined) })
227
+ });
228
+ const j = await resp.json();
229
+ if(!resp.ok || !j.ok){
230
+ appendBubble("assistant", (j.error || "ืฉื’ื™ืื” ื‘ืฉื™ืจื•ืช ื”ื‘ื™ื ื”"));
231
+ return;
232
+ }
233
+ appendBubble("assistant", j.text || (lang==="he"?"ืื™ืŸ ืชืฉื•ื‘ื”":"No response"));
234
+ }catch(e){
235
+ appendBubble("assistant", (e?.message || "ืฉื’ื™ืืช ืจืฉืช"));
236
+ }
237
+ }
238
+
239
+ sendBtn.addEventListener("click", ask);
240
+ msgInput.addEventListener("keydown", (e)=>{ if(e.key==="Enter") ask(); });
241
+
242
+ // Warm greeting
243
+ if(!contextEl.value){
244
+ appendBubble("assistant", "ื”ื™ื™! ื”ื•ืกื™ืคื• ื˜ืงืกื˜ ื”ืงืฉืจ (ืœืžืฉืœ ืชืงืฆื™ืจ ื—ื•ืžืจ ืœื™ืžื•ื“), ื•ืื– ืฉืืœื• ืื•ืชื™ ืฉืืœื•ืช ืขืœื™ื•.");
245
+ } else {
246
+ appendBubble("assistant", "ืžืขื•ืœื”! ื™ืฉ ืœื™ ื”ืงืฉืจ ืœื™ืžื•ื“ื™. ืืคืฉืจ ืœืฉืื•ืœ ื›ืœ ืฉืืœื”.");
247
+ }
248
+ </script>
249
  </body>
250
+ </html>`;
251
+
252
+ const INDEX = Buffer.from(HTML);
253
+
254
+ // ====== SERVER (static + /api/chat) ======
255
+ const server = http.createServer(async (req, res) => {
256
+ try {
257
+ // Simple router
258
+ if (req.method === "GET" && (req.url === "/" || req.url?.startsWith("/#"))) {
259
+ res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
260
+ res.end(INDEX);
261
+ return;
262
+ }
263
+
264
+ if (req.method === "POST" && req.url === "/api/chat") {
265
+ if (!AI_API_KEY) {
266
+ const msg = "Missing AI_API_KEY env var";
267
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
268
+ res.end(JSON.stringify({ error: msg }));
269
+ return;
270
+ }
271
+
272
+ const chunks = [];
273
+ for await (const c of req) chunks.push(c);
274
+ const bodyStr = Buffer.concat(chunks).toString("utf-8");
275
+ const payload = JSON.parse(bodyStr || "{}");
276
+
277
+ const model = payload.model || AI_MODEL;
278
+ const system = payload.system || "Answer briefly and clearly in Hebrew.";
279
+ const user = payload.message || "";
280
+
281
+ // OpenAI-compatible /chat/completions
282
+ const resp = await fetch(`${AI_BASE_URL}/chat/completions`, {
283
+ method: "POST",
284
+ headers: {
285
+ "Authorization": `Bearer ${AI_API_KEY}`,
286
+ "Content-Type": "application/json",
287
+ },
288
+ body: JSON.stringify({
289
+ model,
290
+ messages: [
291
+ { role: "system", content: system },
292
+ { role: "user", content: user }
293
+ ],
294
+ })
295
+ });
296
+
297
+ if (!resp.ok) {
298
+ const text = await resp.text();
299
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
300
+ res.end(JSON.stringify({ error: `AI error ${resp.status}`, details: text }));
301
+ return;
302
+ }
303
+
304
+ const data = await resp.json();
305
+ const text = data?.choices?.[0]?.message?.content ?? "";
306
+ res.writeHead(200, { "Content-Type": "application/json; charset=utf-8" });
307
+ res.end(JSON.stringify({ ok: true, text }));
308
+ return;
309
+ }
310
+
311
+ // 404
312
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
313
+ res.end("Not found");
314
+ } catch (e) {
315
+ res.writeHead(500, { "Content-Type": "application/json; charset=utf-8" });
316
+ res.end(JSON.stringify({ error: e?.message || "server error" }));
317
+ }
318
+ });
319
+
320
+ server.listen(PORT, () => {
321
+ console.log(`โœ… Study Assistant MVP running on http://localhost:${PORT}`);
322
+ if (!AI_API_KEY) {
323
+ console.warn("โš ๏ธ AI_API_KEY is missing. Set it before chatting.");
324
+ } else {
325
+ console.log("๐Ÿ” AI key loaded. Ready to chat.");
326
+ }
327
+ });
login.html CHANGED
@@ -13,8 +13,8 @@
13
  <main class="container">
14
  <section class="auth-form">
15
  <h1>ื”ืชื—ื‘ืจื•ืช</h1>
16
- <form>
17
- <div class="form-group">
18
  <label for="email">ืื™ืžื™ื™ืœ</label>
19
  <input type="email" id="email" required>
20
  </div>
@@ -22,8 +22,10 @@
22
  <label for="password">ืกื™ืกืžื”</label>
23
  <input type="password" id="password" required>
24
  </div>
25
- <button type="submit" class="btn primary">ื”ืชื—ื‘ืจ</button>
26
- </form>
 
 
27
  <p>ืื™ืŸ ืœืš ื—ืฉื‘ื•ืŸ? <a href="/register.html">ื”ื™ืจืฉื ืขื›ืฉื™ื•</a></p>
28
  </section>
29
  </main>
 
13
  <main class="container">
14
  <section class="auth-form">
15
  <h1>ื”ืชื—ื‘ืจื•ืช</h1>
16
+ <form id="loginForm">
17
+ <div class="form-group">
18
  <label for="email">ืื™ืžื™ื™ืœ</label>
19
  <input type="email" id="email" required>
20
  </div>
 
22
  <label for="password">ืกื™ืกืžื”</label>
23
  <input type="password" id="password" required>
24
  </div>
25
+ <button type="submit" class="btn primary">
26
+ <i class="fas fa-sign-in-alt"></i> ื”ืชื—ื‘ืจ
27
+ </button>
28
+ </form>
29
  <p>ืื™ืŸ ืœืš ื—ืฉื‘ื•ืŸ? <a href="/register.html">ื”ื™ืจืฉื ืขื›ืฉื™ื•</a></p>
30
  </section>
31
  </main>
script.js ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
+ });
style.css CHANGED
@@ -1,12 +1,25 @@
 
1
  :root {
2
- --primary-color: #4a6fa5;
3
- --secondary-color: #166088;
4
- --accent-color: #4fc3f7;
5
- --text-color: #333;
6
- --light-bg: #f5f9fc;
 
 
 
 
 
 
 
 
 
 
 
 
7
  --rtl: rtl;
 
8
  }
9
-
10
  body {
11
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
12
  margin: 0;
@@ -46,31 +59,64 @@ body {
46
  justify-content: center;
47
  gap: 20px;
48
  }
49
-
50
  .btn {
51
  padding: 12px 24px;
52
- border-radius: 5px;
53
  text-decoration: none;
54
- font-weight: bold;
55
- transition: all 0.3s ease;
 
 
 
 
 
 
56
  }
57
 
58
  .btn.primary {
59
- background-color: white;
60
- color: var(--primary-color);
61
  }
62
 
63
  .btn.secondary {
64
  background-color: transparent;
65
- color: white;
66
- border: 2px solid white;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
  }
68
 
69
  .btn:hover {
70
- transform: translateY(-3px);
71
- box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1);
 
 
 
 
 
 
 
 
 
 
72
  }
73
 
 
 
 
74
  .features {
75
  display: grid;
76
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
@@ -282,6 +328,47 @@ body {
282
  font-size: 1rem;
283
  font-family: inherit;
284
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
  /* Responsive Adjustments */
287
  @media (max-width: 768px) {
 
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;
 
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));
 
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) {