Hma47 commited on
Commit
945d5b8
·
verified ·
1 Parent(s): 688c6a1

Upload 4 files

Browse files
Apache License.txt ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ Copyright 2025 Shift Mind AI Labs
6
+
7
+ Licensed under the Apache License, Version 2.0 (the "License");
8
+ you may not use this file except in compliance with the License.
9
+ You may obtain a copy of the License at
10
+
11
+ http://www.apache.org/licenses/LICENSE-2.0
12
+
13
+ Unless required by applicable law or agreed to in writing, software
14
+ distributed under the License is distributed on an "AS IS" BASIS,
15
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ See the License for the specific language governing permissions and
17
+ limitations under the License.
README.md ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: "🛡️ EthicsGuard Pro – AI Education Ethics Assessment Platform"
3
+ emoji: "🛡️"
4
+ colorFrom: "blue"
5
+ colorTo: "purple"
6
+ sdk: "static"
7
+ sdk_version: "0.1.0"
8
+ app_file: "index.html"
9
+ pinned: false
10
+ ---
11
+
12
+ # 🛡️ EthicsGuard Pro – AI Education Ethics Assessment Platform
13
+ **Developed by Shift Mind AI Labs**
14
+
15
+ EthicsGuard Pro is an open-source, privacy-first assessment platform that generates personalized, multilingual quizzes on **AI ethics in education**. Instantly create, take, and receive feedback on 10-question MCQ tests—aligned to the latest responsible AI, data privacy, and digital citizenship standards. All language processing is local and privacy-protected.
16
+
17
+ ---
18
+
19
+ ## 🚀 Features
20
+
21
+ - **Personalized AI Ethics Quizzes:**
22
+ Instantly generate and take 10-question MCQ tests on AI and ethics in education.
23
+ - **Immediate, Actionable Feedback:**
24
+ Receive automated scoring and detailed, question-by-question feedback after submission.
25
+ - **Multilingual Support:**
26
+ UI and assessments available in 80+ languages (OpenAI API required; 100% in-browser).
27
+ - **Modern, Accessible UI:**
28
+ Mobile responsive, RTL/LTR support, accessible color palette, progress indicators, and cache control.
29
+ - **Data Privacy:**
30
+ API keys and all content **never leave your browser**.
31
+
32
+ ---
33
+
34
+ ## 🛠 How to Use
35
+
36
+ 1. **Open the tool** in your browser (local file or Hugging Face Space).
37
+ 2. **Select your language** and enter your OpenAI API key (never leaves your device).
38
+ 3. **Generate Test:**
39
+ - Click “🧠 Generate Test” to create a new AI ethics quiz.
40
+ 4. **Take the Test:**
41
+ - Answer the 10 questions and submit.
42
+ 5. **Review Feedback:**
43
+ - Instantly see your score and detailed feedback for every question.
44
+ 6. **Repeat as Needed:**
45
+ - Click “Generate New Test” to try another randomized quiz or practice in a new language.
46
+
47
+ ---
48
+
49
+ ## 👩‍🏫 For Teachers
50
+
51
+ - **Professional Development:**
52
+ Assess your own or colleagues’ understanding of AI ethics, bias, privacy, and responsible use in education.
53
+ - **Classroom Use:**
54
+ Assign as a formative or summative quiz for students in digital citizenship, computer science, or ethics modules.
55
+ - **Discussion Starter:**
56
+ Use feedback explanations as prompts for class debates or group projects on AI ethics.
57
+ - **Multilingual Assessment:**
58
+ Instantly provide assessments in native languages for diverse or international classes.
59
+
60
+ ---
61
+
62
+ ## 👨‍🎓 For Students
63
+
64
+ - **Knowledge Check:**
65
+ Practice and test your understanding of ethical AI, bias, consent, and safe tech use in school.
66
+ - **Revision:**
67
+ Use as self-paced quizzes to prep for assessments or projects.
68
+ - **Peer Learning:**
69
+ Challenge classmates or work in teams—compare results and discuss ethical dilemmas.
70
+
71
+ ---
72
+
73
+ ## 🔐 Data Privacy
74
+
75
+ - Your OpenAI API key, responses, and test data **never leave your browser**.
76
+ - No data is transmitted to Shift Mind AI Labs, Hugging Face, or third parties.
77
+
78
+ ---
79
+
80
+ ## 📄 License
81
+
82
+ Licensed under the [Apache License 2.0](./LICENSE).
83
+
84
+ ---
85
+
86
+ ## 🧠 About Shift Mind AI Labs
87
+
88
+ Shift Mind AI Labs builds open-source, ethical AI tools for global education and responsible technology use.
89
+
90
+ 🌐 https://www.shiftmind.io
91
+ ✉️ info@shiftmind.io
92
+
93
+ ---
94
+
95
+ ## 🙌 Contributing
96
+
97
+ Pull requests, suggestions, and partnerships are welcome!
98
+ For training, research pilots, or district/school rollouts: **info@shiftmind.io**
99
+
100
+ ---
101
+
102
+ *Empowering educators and students everywhere to understand and practice ethical AI in education—securely, equitably, and with real impact.*
index.html ADDED
@@ -0,0 +1,2016 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en" dir="ltr">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>EthicsGuard Pro - AI Education Ethics Assessment Platform</title>
7
+ <style>
8
+ :root {
9
+ --primary: #1e40af;
10
+ --secondary: #7c3aed;
11
+ --accent: #3b82f6;
12
+ --background: #f8fafc;
13
+ --surface: #ffffff;
14
+ --text: #1e293b;
15
+ --text-secondary: #64748b;
16
+ --border: #e2e8f0;
17
+ --success: #059669;
18
+ --warning: #ea580c;
19
+ --error: #dc2626;
20
+ --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
21
+ --shadow-lg: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
22
+ }
23
+
24
+ * {
25
+ box-sizing: border-box;
26
+ margin: 0;
27
+ padding: 0;
28
+ }
29
+
30
+ body {
31
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
32
+ background: linear-gradient(135deg, #f1f5f9 0%, #e2e8f0 100%);
33
+ color: var(--text);
34
+ line-height: 1.6;
35
+ min-height: 100vh;
36
+ padding: 15px;
37
+ transition: all 0.3s ease;
38
+ }
39
+
40
+ /* RTL Support */
41
+ [dir="rtl"] {
42
+ text-align: right;
43
+ }
44
+
45
+ [dir="rtl"] .container {
46
+ direction: rtl;
47
+ }
48
+
49
+ [dir="rtl"] .api-key-section {
50
+ margin: 15px 15px 15px auto;
51
+ }
52
+
53
+ [dir="rtl"] .language-switcher {
54
+ right: auto;
55
+ left: 15px;
56
+ }
57
+
58
+ /* Language Selection Landing */
59
+ .language-landing {
60
+ display: block;
61
+ max-width: 600px;
62
+ margin: 50px auto;
63
+ background: var(--surface);
64
+ border-radius: 16px;
65
+ box-shadow: var(--shadow-lg);
66
+ padding: 40px;
67
+ text-align: center;
68
+ }
69
+
70
+ .language-landing h1 {
71
+ font-size: 2.5rem;
72
+ font-weight: 800;
73
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
74
+ -webkit-background-clip: text;
75
+ -webkit-text-fill-color: transparent;
76
+ background-clip: text;
77
+ margin-bottom: 15px;
78
+ }
79
+
80
+ .language-landing p {
81
+ font-size: 1.1rem;
82
+ color: var(--text-secondary);
83
+ margin-bottom: 30px;
84
+ }
85
+
86
+ .language-selector {
87
+ margin-bottom: 25px;
88
+ }
89
+
90
+ .language-selector label {
91
+ display: block;
92
+ margin-bottom: 10px;
93
+ font-weight: 600;
94
+ color: var(--text);
95
+ font-size: 1.1rem;
96
+ }
97
+
98
+ .language-selector select {
99
+ width: 100%;
100
+ padding: 15px 20px;
101
+ border: 2px solid var(--border);
102
+ border-radius: 12px;
103
+ font-size: 1.1rem;
104
+ font-family: inherit;
105
+ background: var(--surface);
106
+ transition: all 0.2s ease;
107
+ margin-bottom: 20px;
108
+ }
109
+
110
+ .language-selector select:focus {
111
+ outline: none;
112
+ border-color: var(--primary);
113
+ box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
114
+ }
115
+
116
+ .api-key-landing {
117
+ margin-bottom: 25px;
118
+ }
119
+
120
+ .api-key-landing label {
121
+ display: block;
122
+ margin-bottom: 10px;
123
+ font-weight: 600;
124
+ color: var(--text);
125
+ font-size: 1.1rem;
126
+ }
127
+
128
+ .api-key-landing input {
129
+ width: 100%;
130
+ padding: 15px 20px;
131
+ border: 2px solid var(--border);
132
+ border-radius: 12px;
133
+ font-size: 1.1rem;
134
+ background: var(--surface);
135
+ transition: all 0.2s ease;
136
+ font-family: 'Courier New', monospace;
137
+ }
138
+
139
+ .api-key-landing input:focus {
140
+ outline: none;
141
+ border-color: var(--primary);
142
+ box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
143
+ }
144
+
145
+ .start-btn {
146
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
147
+ color: white;
148
+ padding: 18px 40px;
149
+ border: none;
150
+ border-radius: 12px;
151
+ font-size: 1.2rem;
152
+ font-weight: 700;
153
+ cursor: pointer;
154
+ transition: all 0.3s ease;
155
+ width: 100%;
156
+ }
157
+
158
+ .start-btn:hover {
159
+ transform: translateY(-2px);
160
+ box-shadow: 0 12px 24px -8px rgba(30, 64, 175, 0.4);
161
+ }
162
+
163
+ .start-btn:disabled {
164
+ opacity: 0.7;
165
+ cursor: not-allowed;
166
+ transform: none;
167
+ }
168
+
169
+ /* Cache Management Section */
170
+ .cache-management {
171
+ background: rgba(30, 64, 175, 0.05);
172
+ border: 1px solid rgba(30, 64, 175, 0.1);
173
+ border-radius: 12px;
174
+ padding: 20px;
175
+ margin-bottom: 25px;
176
+ text-align: center;
177
+ }
178
+
179
+ .cache-management h3 {
180
+ color: var(--primary);
181
+ margin-bottom: 15px;
182
+ font-size: 1.1rem;
183
+ font-weight: 600;
184
+ }
185
+
186
+ .cache-status-display {
187
+ background: rgba(5, 150, 105, 0.1);
188
+ border: 1px solid rgba(5, 150, 105, 0.2);
189
+ color: var(--success);
190
+ padding: 12px 16px;
191
+ border-radius: 8px;
192
+ margin-bottom: 15px;
193
+ font-size: 0.9rem;
194
+ font-weight: 500;
195
+ }
196
+
197
+ .cache-status-display.no-cache {
198
+ background: rgba(100, 116, 139, 0.1);
199
+ border-color: rgba(100, 116, 139, 0.2);
200
+ color: var(--text-secondary);
201
+ }
202
+
203
+ .clear-cache-btn {
204
+ background: linear-gradient(135deg, var(--warning) 0%, #f59e0b 100%);
205
+ color: white;
206
+ padding: 12px 24px;
207
+ border: none;
208
+ border-radius: 8px;
209
+ font-size: 0.9rem;
210
+ font-weight: 600;
211
+ cursor: pointer;
212
+ transition: all 0.3s ease;
213
+ }
214
+
215
+ .clear-cache-btn:hover {
216
+ transform: translateY(-1px);
217
+ box-shadow: 0 6px 12px -4px rgba(234, 88, 12, 0.4);
218
+ }
219
+
220
+ .clear-cache-btn:disabled {
221
+ opacity: 0.5;
222
+ cursor: not-allowed;
223
+ transform: none;
224
+ }
225
+
226
+ /* Cache Status Indicator */
227
+ .cache-status {
228
+ background: rgba(5, 150, 105, 0.1);
229
+ border: 1px solid rgba(5, 150, 105, 0.2);
230
+ color: var(--success);
231
+ padding: 8px 12px;
232
+ border-radius: 6px;
233
+ margin-bottom: 15px;
234
+ font-size: 0.85rem;
235
+ text-align: center;
236
+ font-weight: 500;
237
+ display: none;
238
+ }
239
+
240
+ .cache-status.cached {
241
+ display: block;
242
+ }
243
+
244
+ .cache-status.translating {
245
+ background: rgba(30, 64, 175, 0.1);
246
+ border-color: rgba(30, 64, 175, 0.2);
247
+ color: var(--primary);
248
+ }
249
+
250
+ /* Translation Loading Overlay */
251
+ .translation-overlay {
252
+ position: fixed;
253
+ top: 0;
254
+ left: 0;
255
+ width: 100%;
256
+ height: 100%;
257
+ background: rgba(30, 64, 175, 0.9);
258
+ display: none;
259
+ justify-content: center;
260
+ align-items: center;
261
+ z-index: 10000;
262
+ color: white;
263
+ text-align: center;
264
+ }
265
+
266
+ .translation-content {
267
+ background: rgba(255, 255, 255, 0.1);
268
+ padding: 40px;
269
+ border-radius: 16px;
270
+ backdrop-filter: blur(10px);
271
+ }
272
+
273
+ .translation-spinner {
274
+ width: 50px;
275
+ height: 50px;
276
+ border: 4px solid rgba(255, 255, 255, 0.3);
277
+ border-radius: 50%;
278
+ border-top-color: white;
279
+ animation: spin 1s ease-in-out infinite;
280
+ margin: 0 auto 20px;
281
+ }
282
+
283
+ /* Main Application (Hidden Initially) */
284
+ .main-app {
285
+ display: none;
286
+ }
287
+
288
+ .container {
289
+ max-width: 1200px;
290
+ margin: 0 auto;
291
+ background: var(--surface);
292
+ border-radius: 16px;
293
+ box-shadow: var(--shadow-lg);
294
+ overflow: hidden;
295
+ position: relative;
296
+ }
297
+
298
+ /* API Key Section - Static Position Top Left */
299
+ .api-key-section {
300
+ background: rgba(30, 64, 175, 0.05);
301
+ border: 1px solid rgba(30, 64, 175, 0.1);
302
+ border-radius: 8px;
303
+ padding: 12px;
304
+ margin: 15px;
305
+ max-width: 280px;
306
+ }
307
+
308
+ .api-key-section label {
309
+ font-size: 12px;
310
+ font-weight: 600;
311
+ color: var(--primary);
312
+ margin-bottom: 6px;
313
+ display: block;
314
+ }
315
+
316
+ .api-key-section input {
317
+ width: 100%;
318
+ padding: 8px 12px;
319
+ border: 1px solid var(--border);
320
+ border-radius: 6px;
321
+ font-size: 14px;
322
+ background: var(--surface);
323
+ transition: all 0.2s ease;
324
+ font-family: 'Courier New', monospace;
325
+ }
326
+
327
+ .api-key-section input:focus {
328
+ outline: none;
329
+ border-color: var(--primary);
330
+ box-shadow: 0 0 0 3px rgba(30, 64, 175, 0.1);
331
+ }
332
+
333
+ /* Language Switcher in Main App */
334
+ .language-switcher {
335
+ position: absolute;
336
+ top: 15px;
337
+ right: 15px;
338
+ display: flex;
339
+ gap: 10px;
340
+ align-items: center;
341
+ }
342
+
343
+ .language-switch-btn {
344
+ background: rgba(30, 64, 175, 0.05);
345
+ border: 1px solid rgba(30, 64, 175, 0.1);
346
+ border-radius: 8px;
347
+ padding: 8px 12px;
348
+ font-size: 12px;
349
+ cursor: pointer;
350
+ transition: all 0.2s ease;
351
+ color: var(--primary);
352
+ font-weight: 500;
353
+ }
354
+
355
+ .language-switch-btn:hover {
356
+ background: rgba(30, 64, 175, 0.1);
357
+ }
358
+
359
+ .mini-clear-cache {
360
+ background: rgba(234, 88, 12, 0.1);
361
+ border: 1px solid rgba(234, 88, 12, 0.2);
362
+ color: var(--warning);
363
+ padding: 6px 10px;
364
+ border-radius: 6px;
365
+ font-size: 11px;
366
+ cursor: pointer;
367
+ transition: all 0.2s ease;
368
+ font-weight: 500;
369
+ }
370
+
371
+ .mini-clear-cache:hover {
372
+ background: rgba(234, 88, 12, 0.2);
373
+ }
374
+
375
+ /* Main Content */
376
+ .main-content {
377
+ padding: 20px 30px 30px;
378
+ }
379
+
380
+ /* Header */
381
+ .header {
382
+ text-align: center;
383
+ margin-bottom: 25px;
384
+ padding-bottom: 20px;
385
+ border-bottom: 2px solid var(--border);
386
+ }
387
+
388
+ .header h1 {
389
+ font-size: 2.5rem;
390
+ font-weight: 800;
391
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
392
+ -webkit-background-clip: text;
393
+ -webkit-text-fill-color: transparent;
394
+ background-clip: text;
395
+ margin-bottom: 8px;
396
+ }
397
+
398
+ .header .subtitle {
399
+ font-size: 1.1rem;
400
+ color: var(--text-secondary);
401
+ font-weight: 500;
402
+ }
403
+
404
+ .header .description {
405
+ font-size: 1rem;
406
+ color: var(--text-secondary);
407
+ margin-top: 12px;
408
+ max-width: 700px;
409
+ margin-left: auto;
410
+ margin-right: auto;
411
+ }
412
+
413
+ /* Progress Bar */
414
+ .progress-container {
415
+ background: rgba(30, 64, 175, 0.05);
416
+ border: 1px solid rgba(30, 64, 175, 0.1);
417
+ border-radius: 12px;
418
+ padding: 20px;
419
+ margin-bottom: 25px;
420
+ }
421
+
422
+ .progress-label {
423
+ display: flex;
424
+ justify-content: space-between;
425
+ align-items: center;
426
+ margin-bottom: 15px;
427
+ font-size: 0.9rem;
428
+ font-weight: 600;
429
+ color: var(--primary);
430
+ }
431
+
432
+ .progress-bar-track {
433
+ width: 100%;
434
+ height: 8px;
435
+ background: var(--border);
436
+ border-radius: 4px;
437
+ overflow: hidden;
438
+ }
439
+
440
+ .progress-bar-fill {
441
+ height: 100%;
442
+ background: linear-gradient(90deg, var(--primary), var(--secondary));
443
+ border-radius: 4px;
444
+ transition: width 0.3s ease;
445
+ width: 0%;
446
+ }
447
+
448
+ /* Section Containers */
449
+ .section {
450
+ background: var(--surface);
451
+ border-radius: 16px;
452
+ padding: 30px;
453
+ box-shadow: var(--shadow);
454
+ margin-bottom: 25px;
455
+ transition: all 0.3s ease;
456
+ }
457
+
458
+ .section:hover {
459
+ box-shadow: var(--shadow-lg);
460
+ }
461
+
462
+ .section-title {
463
+ font-size: 1.5rem;
464
+ font-weight: 700;
465
+ color: var(--primary);
466
+ margin-bottom: 20px;
467
+ text-align: center;
468
+ }
469
+
470
+ /* Generate Button */
471
+ .generate-button {
472
+ background: linear-gradient(135deg, var(--primary) 0%, var(--secondary) 100%);
473
+ color: white;
474
+ padding: 18px 40px;
475
+ border: none;
476
+ border-radius: 12px;
477
+ font-size: 1.2rem;
478
+ font-weight: 700;
479
+ cursor: pointer;
480
+ transition: all 0.3s ease;
481
+ display: block;
482
+ margin: 30px auto;
483
+ min-width: 320px;
484
+ position: relative;
485
+ overflow: hidden;
486
+ }
487
+
488
+ .generate-button:hover {
489
+ transform: translateY(-2px);
490
+ box-shadow: 0 12px 24px -8px rgba(30, 64, 175, 0.4);
491
+ }
492
+
493
+ .generate-button:disabled {
494
+ opacity: 0.7;
495
+ cursor: not-allowed;
496
+ transform: none;
497
+ }
498
+
499
+ /* Loading Spinner */
500
+ .loading-spinner {
501
+ display: none;
502
+ width: 20px;
503
+ height: 20px;
504
+ border: 2px solid rgba(255, 255, 255, 0.3);
505
+ border-radius: 50%;
506
+ border-top-color: white;
507
+ animation: spin 1s ease-in-out infinite;
508
+ margin-right: 8px;
509
+ }
510
+
511
+ [dir="rtl"] .loading-spinner {
512
+ margin-right: 0;
513
+ margin-left: 8px;
514
+ }
515
+
516
+ @keyframes spin {
517
+ to { transform: rotate(360deg); }
518
+ }
519
+
520
+ /* Question Cards */
521
+ .questions-container {
522
+ display: flex;
523
+ flex-direction: column;
524
+ gap: 20px;
525
+ }
526
+
527
+ .question-card {
528
+ background: var(--surface);
529
+ border: 1px solid var(--border);
530
+ border-radius: 12px;
531
+ padding: 25px;
532
+ box-shadow: var(--shadow);
533
+ transition: all 0.3s ease;
534
+ }
535
+
536
+ .question-card:hover {
537
+ box-shadow: var(--shadow-lg);
538
+ border-color: var(--accent);
539
+ }
540
+
541
+ .question-number {
542
+ display: inline-flex;
543
+ align-items: center;
544
+ justify-content: center;
545
+ width: 32px;
546
+ height: 32px;
547
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
548
+ color: white;
549
+ border-radius: 50%;
550
+ font-weight: 600;
551
+ font-size: 0.875rem;
552
+ margin-bottom: 15px;
553
+ }
554
+
555
+ .question-text {
556
+ font-size: 1.125rem;
557
+ font-weight: 500;
558
+ color: var(--text);
559
+ margin-bottom: 20px;
560
+ line-height: 1.5;
561
+ }
562
+
563
+ .options-container {
564
+ display: flex;
565
+ flex-direction: column;
566
+ gap: 12px;
567
+ }
568
+
569
+ .option-item {
570
+ position: relative;
571
+ }
572
+
573
+ .option-input {
574
+ position: absolute;
575
+ opacity: 0;
576
+ cursor: pointer;
577
+ }
578
+
579
+ .option-label {
580
+ display: flex;
581
+ align-items: center;
582
+ padding: 15px;
583
+ border: 2px solid var(--border);
584
+ border-radius: 8px;
585
+ cursor: pointer;
586
+ transition: all 0.2s ease;
587
+ background: var(--background);
588
+ font-weight: 500;
589
+ }
590
+
591
+ .option-label:hover {
592
+ border-color: var(--accent);
593
+ background: var(--surface);
594
+ box-shadow: var(--shadow);
595
+ }
596
+
597
+ .option-input:checked + .option-label {
598
+ border-color: var(--primary);
599
+ background: linear-gradient(135deg, rgba(30, 64, 175, 0.1), rgba(124, 58, 237, 0.1));
600
+ box-shadow: var(--shadow);
601
+ }
602
+
603
+ .option-indicator {
604
+ width: 20px;
605
+ height: 20px;
606
+ border: 2px solid var(--border);
607
+ border-radius: 50%;
608
+ margin-right: 15px;
609
+ transition: all 0.2s ease;
610
+ position: relative;
611
+ }
612
+
613
+ [dir="rtl"] .option-indicator {
614
+ margin-right: 0;
615
+ margin-left: 15px;
616
+ }
617
+
618
+ .option-input:checked + .option-label .option-indicator {
619
+ border-color: var(--primary);
620
+ background: var(--primary);
621
+ }
622
+
623
+ .option-input:checked + .option-label .option-indicator::after {
624
+ content: '';
625
+ position: absolute;
626
+ top: 50%;
627
+ left: 50%;
628
+ transform: translate(-50%, -50%);
629
+ width: 8px;
630
+ height: 8px;
631
+ background: white;
632
+ border-radius: 50%;
633
+ }
634
+
635
+ .option-text {
636
+ flex: 1;
637
+ color: var(--text);
638
+ }
639
+
640
+ /* Action Buttons */
641
+ .action-buttons {
642
+ display: flex;
643
+ gap: 15px;
644
+ justify-content: center;
645
+ margin-top: 30px;
646
+ }
647
+
648
+ .btn {
649
+ display: inline-flex;
650
+ align-items: center;
651
+ gap: 8px;
652
+ padding: 15px 30px;
653
+ font-size: 1rem;
654
+ font-weight: 600;
655
+ border: none;
656
+ border-radius: 12px;
657
+ cursor: pointer;
658
+ transition: all 0.3s ease;
659
+ text-decoration: none;
660
+ }
661
+
662
+ .btn-primary {
663
+ color: white;
664
+ background: linear-gradient(135deg, var(--primary), var(--secondary));
665
+ box-shadow: var(--shadow);
666
+ }
667
+
668
+ .btn-primary:hover:not(:disabled) {
669
+ transform: translateY(-1px);
670
+ box-shadow: var(--shadow-lg);
671
+ }
672
+
673
+ .btn-secondary {
674
+ color: var(--primary);
675
+ background: var(--surface);
676
+ border: 2px solid var(--primary);
677
+ }
678
+
679
+ .btn-secondary:hover {
680
+ background: var(--primary);
681
+ color: white;
682
+ }
683
+
684
+ /* Results Section */
685
+ .results-container {
686
+ text-align: center;
687
+ }
688
+
689
+ .score-display {
690
+ background: linear-gradient(135deg, var(--success), #10b981);
691
+ color: white;
692
+ padding: 40px;
693
+ border-radius: 16px;
694
+ margin-bottom: 30px;
695
+ box-shadow: var(--shadow-lg);
696
+ }
697
+
698
+ .score-number {
699
+ font-size: 3rem;
700
+ font-weight: 700;
701
+ margin-bottom: 8px;
702
+ }
703
+
704
+ .score-label {
705
+ font-size: 1.25rem;
706
+ opacity: 0.9;
707
+ }
708
+
709
+ .feedback-container {
710
+ text-align: left;
711
+ background: var(--background);
712
+ border-radius: 12px;
713
+ padding: 25px;
714
+ margin-bottom: 25px;
715
+ }
716
+
717
+ .feedback-title {
718
+ font-size: 1.25rem;
719
+ font-weight: 600;
720
+ color: var(--text);
721
+ margin-bottom: 20px;
722
+ }
723
+
724
+ .feedback-item {
725
+ padding: 20px;
726
+ border-left: 4px solid var(--border);
727
+ margin-bottom: 20px;
728
+ background: var(--surface);
729
+ border-radius: 0 8px 8px 0;
730
+ }
731
+
732
+ [dir="rtl"] .feedback-item {
733
+ border-left: none;
734
+ border-right: 4px solid var(--border);
735
+ border-radius: 8px 0 0 8px;
736
+ }
737
+
738
+ .feedback-item.correct {
739
+ border-left-color: var(--success);
740
+ }
741
+
742
+ .feedback-item.incorrect {
743
+ border-left-color: var(--error);
744
+ }
745
+
746
+ [dir="rtl"] .feedback-item.correct {
747
+ border-right-color: var(--success);
748
+ }
749
+
750
+ [dir="rtl"] .feedback-item.incorrect {
751
+ border-right-color: var(--error);
752
+ }
753
+
754
+ .feedback-question {
755
+ font-weight: 600;
756
+ margin-bottom: 8px;
757
+ }
758
+
759
+ .feedback-answers {
760
+ font-size: 0.875rem;
761
+ color: var(--text-secondary);
762
+ margin-bottom: 8px;
763
+ }
764
+
765
+ .feedback-explanation {
766
+ color: var(--text);
767
+ line-height: 1.5;
768
+ }
769
+
770
+ /* Error Messages */
771
+ .error-message {
772
+ background: rgba(220, 38, 38, 0.1);
773
+ border: 1px solid rgba(220, 38, 38, 0.2);
774
+ color: var(--error);
775
+ padding: 12px 16px;
776
+ border-radius: 8px;
777
+ margin: 10px 0;
778
+ font-size: 0.9rem;
779
+ display: none;
780
+ }
781
+
782
+ /* Status Messages */
783
+ .status-message {
784
+ background: rgba(30, 64, 175, 0.1);
785
+ border: 1px solid rgba(30, 64, 175, 0.2);
786
+ color: var(--primary);
787
+ padding: 12px 16px;
788
+ border-radius: 8px;
789
+ margin: 10px 0;
790
+ font-size: 0.9rem;
791
+ text-align: center;
792
+ font-weight: 500;
793
+ display: none;
794
+ }
795
+
796
+ /* Hidden sections */
797
+ .hidden {
798
+ display: none !important;
799
+ }
800
+
801
+ /* Footer */
802
+ .footer {
803
+ text-align: center;
804
+ padding: 20px;
805
+ background: rgba(30, 64, 175, 0.05);
806
+ border-top: 1px solid var(--border);
807
+ color: var(--text-secondary);
808
+ font-size: 0.9rem;
809
+ }
810
+
811
+ /* Responsive Design */
812
+ @media (max-width: 768px) {
813
+ body {
814
+ padding: 10px;
815
+ }
816
+
817
+ .language-landing {
818
+ margin: 20px auto;
819
+ padding: 30px 20px;
820
+ }
821
+
822
+ .api-key-section {
823
+ margin: 10px;
824
+ max-width: none;
825
+ }
826
+
827
+ .language-switcher {
828
+ position: relative;
829
+ top: auto;
830
+ right: auto;
831
+ margin-bottom: 15px;
832
+ justify-content: center;
833
+ }
834
+
835
+ .main-content {
836
+ padding: 15px 20px 20px;
837
+ }
838
+
839
+ .header h1 {
840
+ font-size: 2rem;
841
+ }
842
+
843
+ .section {
844
+ padding: 20px 15px;
845
+ }
846
+
847
+ .action-buttons {
848
+ flex-direction: column;
849
+ align-items: center;
850
+ }
851
+
852
+ .btn {
853
+ width: 100%;
854
+ justify-content: center;
855
+ }
856
+
857
+ .generate-button {
858
+ width: 100%;
859
+ margin: 25px 0;
860
+ padding: 16px 32px;
861
+ font-size: 1.1rem;
862
+ }
863
+
864
+ .score-number {
865
+ font-size: 2.5rem;
866
+ }
867
+ }
868
+
869
+ @media (max-width: 480px) {
870
+ .language-landing h1 {
871
+ font-size: 2rem;
872
+ }
873
+
874
+ .header h1 {
875
+ font-size: 1.8rem;
876
+ }
877
+ }
878
+
879
+ /* Dark mode support */
880
+ @media (prefers-color-scheme: dark) {
881
+ :root {
882
+ --background: #0f172a;
883
+ --surface: #1e293b;
884
+ --text: #f1f5f9;
885
+ --text-secondary: #94a3b8;
886
+ --border: #334155;
887
+ }
888
+
889
+ body {
890
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
891
+ }
892
+ }
893
+ </style>
894
+ </head>
895
+ <body>
896
+ <!-- Translation Loading Overlay -->
897
+ <div class="translation-overlay" id="translationOverlay">
898
+ <div class="translation-content">
899
+ <div class="translation-spinner"></div>
900
+ <h2 id="translationTitle">Translating Interface...</h2>
901
+ <p id="translationMessage">Please wait while we translate the interface to your selected language.</p>
902
+ </div>
903
+ </div>
904
+
905
+ <!-- Language Selection Landing Page -->
906
+ <div class="language-landing" id="languageLanding">
907
+ <h1 data-translate="app_title">EthicsGuard Pro</h1>
908
+ <p data-translate="welcome_message">Welcome! Please select your preferred language and enter your API key to get started with AI ethics assessment.</p>
909
+
910
+ <!-- Cache Status Indicator -->
911
+ <div class="cache-status" id="cacheStatus">
912
+ 💾 Translations cached - instant loading!
913
+ </div>
914
+
915
+ <!-- Cache Management Section -->
916
+ <div class="cache-management" id="cacheManagement">
917
+ <h3 data-translate="cache_management_title">🗂️ Translation Cache Management</h3>
918
+ <div class="cache-status-display" id="cacheStatusDisplay">
919
+ <span data-translate="cache_status_checking">Checking cache status...</span>
920
+ </div>
921
+ <button class="clear-cache-btn" id="clearCacheBtn" data-translate="clear_cache_button">
922
+ 🗑️ Clear All Cached Translations
923
+ </button>
924
+ </div>
925
+
926
+ <div class="language-selector">
927
+ <label for="languageSelect" data-translate="select_language">🌐 Select Language</label>
928
+ <select id="languageSelect">
929
+ <option value="en">🇺🇸 English</option>
930
+ <option value="es">🇪🇸 Español (Spanish)</option>
931
+ <option value="fr">🇫🇷 Français (French)</option>
932
+ <option value="de">🇩🇪 Deutsch (German)</option>
933
+ <option value="zh">🇨🇳 中文 (Chinese)</option>
934
+ <option value="ja">🇯🇵 日本語 (Japanese)</option>
935
+ <option value="ko">🇰🇷 한국어 (Korean)</option>
936
+ <option value="pt">🇵🇹 Português (Portuguese)</option>
937
+ <option value="it">🇮🇹 Italiano (Italian)</option>
938
+ <option value="ar">🇸🇦 العربية (Arabic)</option>
939
+ <option value="ru">🇷🇺 Русский (Russian)</option>
940
+ <option value="hi">🇮🇳 हिन्दी (Hindi)</option>
941
+ <option value="bn">🇧🇩 বাংলা (Bengali)</option>
942
+ <option value="ur">🇵🇰 اردو (Urdu)</option>
943
+ <option value="tr">🇹🇷 Türkçe (Turkish)</option>
944
+ <option value="pl">🇵🇱 Polski (Polish)</option>
945
+ <option value="nl">🇳🇱 Nederlands (Dutch)</option>
946
+ <option value="sv">🇸🇪 Svenska (Swedish)</option>
947
+ <option value="da">🇩🇰 Dansk (Danish)</option>
948
+ <option value="no">🇳🇴 Norsk (Norwegian)</option>
949
+ <option value="fi">🇫🇮 Suomi (Finnish)</option>
950
+ <option value="is">🇮🇸 Íslenska (Icelandic)</option>
951
+ <option value="cs">🇨🇿 Čeština (Czech)</option>
952
+ <option value="sk">🇸🇰 Slovenčina (Slovak)</option>
953
+ <option value="hu">🇭🇺 Magyar (Hungarian)</option>
954
+ <option value="ro">🇷🇴 Română (Romanian)</option>
955
+ <option value="bg">🇧🇬 Български (Bulgarian)</option>
956
+ <option value="hr">🇭🇷 Hrvatski (Croatian)</option>
957
+ <option value="sr">🇷🇸 Српски (Serbian)</option>
958
+ <option value="sl">🇸🇮 Slovenščina (Slovenian)</option>
959
+ <option value="mk">🇲🇰 Македонски (Macedonian)</option>
960
+ <option value="sq">🇦🇱 Shqip (Albanian)</option>
961
+ <option value="lv">🇱🇻 Latviešu (Latvian)</option>
962
+ <option value="lt">🇱🇹 Lietuvių (Lithuanian)</option>
963
+ <option value="et">🇪🇪 Eesti (Estonian)</option>
964
+ <option value="mt">🇲🇹 Malti (Maltese)</option>
965
+ <option value="ga">🇮🇪 Gaeilge (Irish)</option>
966
+ <option value="cy">🏴󠁧󠁢󠁷󠁬󠁳󠁿 Cymraeg (Welsh)</option>
967
+ <option value="eu">🏴󠁥󠁳󠁰󠁶󠁿 Euskera (Basque)</option>
968
+ <option value="ca">🏴󠁥󠁳󠁣󠁴󠁿 Català (Catalan)</option>
969
+ <option value="gl">🏴󠁥󠁳󠁧󠁡󠁿 Galego (Galician)</option>
970
+ <option value="el">🇬🇷 Ελληνικά (Greek)</option>
971
+ <option value="he">🇮🇱 עברית (Hebrew)</option>
972
+ <option value="fa">🇮🇷 فارسی (Persian)</option>
973
+ <option value="ps">🇦🇫 پښتو (Pashto)</option>
974
+ <option value="ku">🏴󠁩󠁱󠁫󠁲󠁿 کوردی (Kurdish)</option>
975
+ <option value="az">🇦🇿 Azərbaycan (Azerbaijani)</option>
976
+ <option value="kk">🇰🇿 Қазақша (Kazakh)</option>
977
+ <option value="ky">🇰🇬 Кыргызча (Kyrgyz)</option>
978
+ <option value="uz">🇺🇿 O'zbek (Uzbek)</option>
979
+ <option value="tk">🇹🇲 Türkmen (Turkmen)</option>
980
+ <option value="tg">🇹🇯 Тоҷикӣ (Tajik)</option>
981
+ <option value="mn">🇲🇳 Монгол (Mongolian)</option>
982
+ <option value="ka">🇬🇪 ��ართული (Georgian)</option>
983
+ <option value="hy">🇦🇲 Հայերեն (Armenian)</option>
984
+ <option value="th">🇹🇭 ไทย (Thai)</option>
985
+ <option value="vi">🇻🇳 Tiếng Việt (Vietnamese)</option>
986
+ <option value="lo">🇱🇦 ລາວ (Lao)</option>
987
+ <option value="km">🇰🇭 ខ្មែរ (Khmer)</option>
988
+ <option value="my">🇲🇲 မြန်မာ (Myanmar)</option>
989
+ <option value="si">🇱🇰 සිංහල (Sinhala)</option>
990
+ <option value="ta">🇱🇰 தமிழ் (Tamil)</option>
991
+ <option value="te">🇮🇳 తెలుగు (Telugu)</option>
992
+ <option value="kn">🇮🇳 ಕನ್ನಡ (Kannada)</option>
993
+ <option value="ml">🇮🇳 മലയാളം (Malayalam)</option>
994
+ <option value="gu">🇮🇳 ગુજરાતી (Gujarati)</option>
995
+ <option value="pa">🇮🇳 ਪੰਜਾਬੀ (Punjabi)</option>
996
+ <option value="or">🇮🇳 ଓଡ଼ିଆ (Odia)</option>
997
+ <option value="as">🇮🇳 অসমীয়া (Assamese)</option>
998
+ <option value="ne">🇳🇵 नेपाली (Nepali)</option>
999
+ <option value="dz">🇧🇹 རྫོང་ཁ (Dzongkha)</option>
1000
+ <option value="bo">🏔️ བོད་ཡིག (Tibetan)</option>
1001
+ <option value="id">🇮🇩 Bahasa Indonesia</option>
1002
+ <option value="ms">🇲🇾 Bahasa Melayu (Malay)</option>
1003
+ <option value="tl">🇵🇭 Filipino (Tagalog)</option>
1004
+ <option value="ceb">🇵🇭 Cebuano</option>
1005
+ <option value="haw">🏝️ ʻŌlelo Hawaiʻi (Hawaiian)</option>
1006
+ <option value="mi">🇳🇿 Te Reo Māori (Maori)</option>
1007
+ <option value="sm">🇼🇸 Gagana Samoa (Samoan)</option>
1008
+ <option value="to">🇹🇴 Lea Fakatonga (Tongan)</option>
1009
+ <option value="fj">🇫🇯 Na Vosa Vakaviti (Fijian)</option>
1010
+ <option value="mg">🇲🇬 Malagasy</option>
1011
+ <option value="sw">🇰🇪 Kiswahili (Swahili)</option>
1012
+ <option value="zu">🇿🇦 isiZulu (Zulu)</option>
1013
+ <option value="xh">🇿🇦 isiXhosa (Xhosa)</option>
1014
+ <option value="af">🇿🇦 Afrikaans</option>
1015
+ <option value="st">🇱🇸 Sesotho (Southern Sotho)</option>
1016
+ <option value="tn">🇧🇼 Setswana (Tswana)</option>
1017
+ <option value="ss">🇸🇿 siSwati (Swati)</option>
1018
+ <option value="ve">🇿🇦 Tshivenḓa (Venda)</option>
1019
+ <option value="ts">🇿🇦 Xitsonga (Tsonga)</option>
1020
+ <option value="nr">🇿🇦 isiNdebele (Southern Ndebele)</option>
1021
+ <option value="am">🇪🇹 አማርኛ (Amharic)</option>
1022
+ <option value="ti">🇪🇷 ትግርኛ (Tigrinya)</option>
1023
+ <option value="om">🇪🇹 Afaan Oromoo (Oromo)</option>
1024
+ <option value="so">🇸🇴 Soomaali (Somali)</option>
1025
+ <option value="ha">🇳🇬 Hausa</option>
1026
+ <option value="yo">🇳🇬 Yorùbá (Yoruba)</option>
1027
+ <option value="ig">🇳🇬 Igbo</option>
1028
+ <option value="ff">🇸🇳 Fulfulde (Fulani)</option>
1029
+ <option value="wo">🇸🇳 Wolof</option>
1030
+ <option value="bm">🇲🇱 Bamanankan (Bambara)</option>
1031
+ <option value="rn">🇧🇮 Kirundi (Rundi)</option>
1032
+ <option value="rw">🇷🇼 Kinyarwanda (Rwanda)</option>
1033
+ <option value="lg">🇺🇬 Luganda</option>
1034
+ <option value="ny">🇲🇼 Chichewa (Nyanja)</option>
1035
+ <option value="sn">🇿🇼 chiShona (Shona)</option>
1036
+ <option value="nd">🇿🇼 isiNdebele (Northern Ndebele)</option>
1037
+ </select>
1038
+ </div>
1039
+
1040
+ <div class="api-key-landing">
1041
+ <label for="apiKeyLanding" data-translate="api_key_label">🔑 OpenAI API Key</label>
1042
+ <input type="password" id="apiKeyLanding" placeholder="Enter your OpenAI API key" data-translate-placeholder="api_key_placeholder">
1043
+ </div>
1044
+
1045
+ <button class="start-btn" id="startBtn" data-translate="start_button">🚀 Start Ethics Assessment</button>
1046
+ </div>
1047
+
1048
+ <!-- Main Application -->
1049
+ <div class="main-app" id="mainApp">
1050
+ <div class="container">
1051
+ <!-- Language Switcher -->
1052
+ <div class="language-switcher" id="languageSwitcher">
1053
+ <div class="language-switch-btn" onclick="showLanguageLanding()">
1054
+ <span data-translate="change_language">🌐 Change Language</span>
1055
+ </div>
1056
+ <div class="mini-clear-cache" onclick="clearAllTranslationCache()" title="Clear translation cache">
1057
+ <span data-translate="clear_cache_mini">🗑️ Clear Cache</span>
1058
+ </div>
1059
+ </div>
1060
+
1061
+ <!-- API Key Section - Static Position Top Left -->
1062
+ <div class="api-key-section">
1063
+ <label for="apiKey" data-translate="api_key_short">🔑 API Key</label>
1064
+ <input type="password" id="apiKey" data-translate-placeholder="api_key_placeholder" autocomplete="off">
1065
+ </div>
1066
+
1067
+ <div class="main-content">
1068
+ <!-- Header -->
1069
+ <div class="header">
1070
+ <h1 data-translate="app_title">EthicsGuard Pro</h1>
1071
+ <p class="subtitle" data-translate="app_subtitle">AI Education Ethics Assessment Platform</p>
1072
+ <p class="description" data-translate="app_description">Test your knowledge of ethical considerations when using AI in educational settings with personalized assessments and detailed feedback</p>
1073
+ </div>
1074
+
1075
+ <!-- Progress Bar -->
1076
+ <div class="progress-container">
1077
+ <div class="progress-label">
1078
+ <span data-translate="test_progress">Test Progress</span>
1079
+ <span id="progressText">0%</span>
1080
+ </div>
1081
+ <div class="progress-bar-track">
1082
+ <div class="progress-bar-fill" id="progressBar"></div>
1083
+ </div>
1084
+ </div>
1085
+
1086
+ <!-- Section: Test Setup -->
1087
+ <section id="testSetup" class="section">
1088
+ <h2 class="section-title" data-translate="generate_test_title">Generate Your Ethics Test</h2>
1089
+ <p style="margin-bottom: 2rem; color: var(--text-secondary); text-align: center;" data-translate="generate_test_description">
1090
+ Click the button below to generate a personalized 10-question multiple-choice test on AI in Education Ethics.
1091
+ </p>
1092
+ <button id="generateTestBtn" class="generate-button">
1093
+ <span class="loading-spinner" id="loadingSpinner"></span>
1094
+ <span class="button-text" data-translate="generate_test_button">🧠 Generate Test</span>
1095
+ </button>
1096
+ </section>
1097
+
1098
+ <!-- Section: Test Questions -->
1099
+ <section id="testSection" class="section hidden">
1100
+ <h2 class="section-title" data-translate="test_questions_title">Test Questions</h2>
1101
+ <form id="testForm">
1102
+ <div id="questionsContainer" class="questions-container"></div>
1103
+ <div class="action-buttons">
1104
+ <button type="submit" class="btn btn-primary" data-translate="submit_test_button">📝 Submit Test</button>
1105
+ <button type="button" id="newTestBtn" class="btn btn-secondary" data-translate="new_test_button">🔄 New Test</button>
1106
+ </div>
1107
+ </form>
1108
+ </section>
1109
+
1110
+ <!-- Section: Test Results -->
1111
+ <section id="resultsSection" class="section hidden">
1112
+ <h2 class="section-title" data-translate="test_results_title">Test Results</h2>
1113
+ <div class="results-container">
1114
+ <div class="score-display">
1115
+ <div class="score-number" id="scoreNumber">0</div>
1116
+ <div class="score-label" data-translate="score_label">out of 10 questions correct</div>
1117
+ </div>
1118
+
1119
+ <div class="feedback-container">
1120
+ <h3 class="feedback-title" data-translate="feedback_title">📋 Detailed Feedback</h3>
1121
+ <div id="feedbackContent"></div>
1122
+ </div>
1123
+
1124
+ <div class="action-buttons">
1125
+ <button id="newTestBtn2" class="btn btn-primary" data-translate="generate_new_test_button">🔄 Generate New Test</button>
1126
+ </div>
1127
+ </div>
1128
+ </section>
1129
+
1130
+ <!-- Error and Status Messages -->
1131
+ <div class="error-message" id="errorMessage"></div>
1132
+ <div class="status-message" id="statusMessage"></div>
1133
+ </div>
1134
+
1135
+ <!-- Footer -->
1136
+ <div class="footer">
1137
+ Created by Shift Mind AI Labs
1138
+ </div>
1139
+ </div>
1140
+ </div>
1141
+
1142
+ <script>
1143
+ // RTL languages list
1144
+ const rtlLanguages = ['ar', 'he', 'fa', 'ur', 'ps', 'ku'];
1145
+
1146
+ // Current language and API key
1147
+ let currentLanguage = 'en';
1148
+ let currentApiKey = '';
1149
+
1150
+ // Language names mapping
1151
+ const languageNames = {
1152
+ en: 'English', es: 'Spanish', fr: 'French', de: 'German', zh: 'Chinese',
1153
+ ja: 'Japanese', ko: 'Korean', pt: 'Portuguese', it: 'Italian', ar: 'Arabic',
1154
+ ru: 'Russian', hi: 'Hindi', bn: 'Bengali', ur: 'Urdu', tr: 'Turkish',
1155
+ pl: 'Polish', nl: 'Dutch', sv: 'Swedish', da: 'Danish', no: 'Norwegian',
1156
+ fi: 'Finnish', is: 'Icelandic', cs: 'Czech', sk: 'Slovak', hu: 'Hungarian',
1157
+ ro: 'Romanian', bg: 'Bulgarian', hr: 'Croatian', sr: 'Serbian', sl: 'Slovenian',
1158
+ mk: 'Macedonian', sq: 'Albanian', lv: 'Latvian', lt: 'Lithuanian', et: 'Estonian',
1159
+ mt: 'Maltese', ga: 'Irish', cy: 'Welsh', eu: 'Basque', ca: 'Catalan',
1160
+ gl: 'Galician', el: 'Greek', he: 'Hebrew', fa: 'Persian', ps: 'Pashto',
1161
+ ku: 'Kurdish', az: 'Azerbaijani', kk: 'Kazakh', ky: 'Kyrgyz', uz: 'Uzbek',
1162
+ tk: 'Turkmen', tg: 'Tajik', mn: 'Mongolian', ka: 'Georgian', hy: 'Armenian',
1163
+ th: 'Thai', vi: 'Vietnamese', lo: 'Lao', km: 'Khmer', my: 'Myanmar',
1164
+ si: 'Sinhala', ta: 'Tamil', te: 'Telugu', kn: 'Kannada', ml: 'Malayalam',
1165
+ gu: 'Gujarati', pa: 'Punjabi', or: 'Odia', as: 'Assamese', ne: 'Nepali',
1166
+ dz: 'Dzongkha', bo: 'Tibetan', id: 'Indonesian', ms: 'Malay', tl: 'Filipino',
1167
+ ceb: 'Cebuano', haw: 'Hawaiian', mi: 'Maori', sm: 'Samoan', to: 'Tongan',
1168
+ fj: 'Fijian', mg: 'Malagasy', sw: 'Swahili', zu: 'Zulu', xh: 'Xhosa',
1169
+ af: 'Afrikaans', st: 'Southern Sotho', tn: 'Tswana', ss: 'Swati', ve: 'Venda',
1170
+ ts: 'Tsonga', nr: 'Southern Ndebele', am: 'Amharic', ti: 'Tigrinya', om: 'Oromo',
1171
+ so: 'Somali', ha: 'Hausa', yo: 'Yoruba', ig: 'Igbo', ff: 'Fulani',
1172
+ wo: 'Wolof', bm: 'Bambara', rn: 'Rundi', rw: 'Rwanda', lg: 'Luganda',
1173
+ ny: 'Chichewa', sn: 'Shona', nd: 'Northern Ndebele'
1174
+ };
1175
+
1176
+ // Translation cache management
1177
+ const CACHE_PREFIX = 'ethicsguard_translations_';
1178
+ const CACHE_VERSION = '1.0';
1179
+
1180
+ // App Configuration
1181
+ const AppConfig = {
1182
+ API_BASE_URL: 'https://api.openai.com/v1/chat/completions',
1183
+ MODEL: 'gpt-4o-mini',
1184
+ MAX_TOKENS: 1500,
1185
+ TEMPERATURE: 0.5
1186
+ };
1187
+
1188
+ // App State Management
1189
+ const AppState = {
1190
+ testData: null,
1191
+ isLoading: false,
1192
+ currentSection: 'setup',
1193
+
1194
+ setLoading(loading) {
1195
+ this.isLoading = loading;
1196
+ UIController.updateLoadingState(loading);
1197
+ }
1198
+ };
1199
+
1200
+ // Check if translations are cached for a language
1201
+ function isLanguageCached(language) {
1202
+ const cacheKey = CACHE_PREFIX + language;
1203
+ const cached = localStorage.getItem(cacheKey);
1204
+ return cached !== null;
1205
+ }
1206
+
1207
+ // Save translations to cache
1208
+ function saveTranslationsToCache(language, translations) {
1209
+ const cacheKey = CACHE_PREFIX + language;
1210
+ const cacheData = {
1211
+ version: CACHE_VERSION,
1212
+ timestamp: Date.now(),
1213
+ translations: translations
1214
+ };
1215
+ localStorage.setItem(cacheKey, JSON.stringify(cacheData));
1216
+ console.log(`Translations cached for ${language}`);
1217
+ }
1218
+
1219
+ // Load translations from cache
1220
+ function loadTranslationsFromCache(language) {
1221
+ const cacheKey = CACHE_PREFIX + language;
1222
+ const cached = localStorage.getItem(cacheKey);
1223
+
1224
+ if (cached) {
1225
+ try {
1226
+ const cacheData = JSON.parse(cached);
1227
+ if (cacheData.version === CACHE_VERSION) {
1228
+ console.log(`Translations loaded from cache for ${language}`);
1229
+ return cacheData.translations;
1230
+ }
1231
+ } catch (error) {
1232
+ console.error('Error parsing cached translations:', error);
1233
+ }
1234
+ }
1235
+ return null;
1236
+ }
1237
+
1238
+ // Get all cached languages
1239
+ function getCachedLanguages() {
1240
+ const cachedLanguages = [];
1241
+ for (let i = 0; i < localStorage.length; i++) {
1242
+ const key = localStorage.key(i);
1243
+ if (key && key.startsWith(CACHE_PREFIX)) {
1244
+ const language = key.replace(CACHE_PREFIX, '');
1245
+ cachedLanguages.push(language);
1246
+ }
1247
+ }
1248
+ return cachedLanguages;
1249
+ }
1250
+
1251
+ // Clear all translation cache
1252
+ function clearAllTranslationCache() {
1253
+ const cachedLanguages = getCachedLanguages();
1254
+
1255
+ if (cachedLanguages.length === 0) {
1256
+ alert('No cached translations to clear.');
1257
+ return;
1258
+ }
1259
+
1260
+ const languageList = cachedLanguages.map(lang => languageNames[lang] || lang).join(', ');
1261
+ const confirmMessage = `Are you sure you want to clear all cached translations?\n\nCached languages: ${languageList}\n\nThis will require re-downloading translations when switching languages.`;
1262
+
1263
+ if (confirm(confirmMessage)) {
1264
+ // Clear all translation caches
1265
+ cachedLanguages.forEach(language => {
1266
+ const cacheKey = CACHE_PREFIX + language;
1267
+ localStorage.removeItem(cacheKey);
1268
+ });
1269
+
1270
+ // Update cache status
1271
+ updateCacheStatus(currentLanguage);
1272
+ updateCacheStatusDisplay();
1273
+
1274
+ alert(`Cache cleared successfully!\n\n${cachedLanguages.length} language(s) removed from cache.`);
1275
+
1276
+ // Ask if user wants to reload current language translations
1277
+ if (currentLanguage !== 'en' && cachedLanguages.includes(currentLanguage)) {
1278
+ if (confirm('Would you like to reload the current language translations?')) {
1279
+ applyLanguage(currentLanguage);
1280
+ }
1281
+ }
1282
+ }
1283
+ }
1284
+
1285
+ // Update cache status indicator
1286
+ function updateCacheStatus(language) {
1287
+ const cacheStatus = document.getElementById('cacheStatus');
1288
+ const isCached = isLanguageCached(language);
1289
+
1290
+ if (language === 'en') {
1291
+ cacheStatus.classList.remove('cached', 'translating');
1292
+ return;
1293
+ }
1294
+
1295
+ if (isCached) {
1296
+ cacheStatus.textContent = '💾 Translations cached - instant loading!';
1297
+ cacheStatus.classList.add('cached');
1298
+ cacheStatus.classList.remove('translating');
1299
+ } else {
1300
+ cacheStatus.textContent = '🔄 First time translation - will be cached for future use';
1301
+ cacheStatus.classList.add('translating');
1302
+ cacheStatus.classList.remove('cached');
1303
+ }
1304
+ }
1305
+
1306
+ // Update cache status display in management section
1307
+ function updateCacheStatusDisplay() {
1308
+ const cacheStatusDisplay = document.getElementById('cacheStatusDisplay');
1309
+ const clearCacheBtn = document.getElementById('clearCacheBtn');
1310
+ const cachedLanguages = getCachedLanguages();
1311
+
1312
+ if (cachedLanguages.length === 0) {
1313
+ cacheStatusDisplay.textContent = '📭 No cached translations';
1314
+ cacheStatusDisplay.className = 'cache-status-display no-cache';
1315
+ clearCacheBtn.disabled = true;
1316
+ } else {
1317
+ const languageList = cachedLanguages.map(lang => languageNames[lang] || lang).join(', ');
1318
+ cacheStatusDisplay.textContent = `💾 ${cachedLanguages.length} language(s) cached: ${languageList}`;
1319
+ cacheStatusDisplay.className = 'cache-status-display';
1320
+ clearCacheBtn.disabled = false;
1321
+ }
1322
+ }
1323
+
1324
+ // Initialize the application
1325
+ function initializeApp() {
1326
+ // Load saved language and API key
1327
+ const savedLanguage = localStorage.getItem('ethicsguard_language') || 'en';
1328
+ const savedApiKey = localStorage.getItem('ethicsguard_api_key') || '';
1329
+
1330
+ currentLanguage = savedLanguage;
1331
+ currentApiKey = savedApiKey;
1332
+
1333
+ // Set language selector
1334
+ document.getElementById('languageSelect').value = currentLanguage;
1335
+ document.getElementById('apiKeyLanding').value = currentApiKey;
1336
+
1337
+ // Apply direction for current language
1338
+ applyDirection(currentLanguage);
1339
+
1340
+ // Update cache status
1341
+ updateCacheStatus(currentLanguage);
1342
+ updateCacheStatusDisplay();
1343
+
1344
+ // Show appropriate screen
1345
+ if (currentApiKey && currentLanguage) {
1346
+ showMainApp();
1347
+ } else {
1348
+ showLanguageLanding();
1349
+ }
1350
+ }
1351
+
1352
+ // Apply language direction
1353
+ function applyDirection(language) {
1354
+ currentLanguage = language;
1355
+
1356
+ // Set document language and direction
1357
+ document.documentElement.lang = language;
1358
+ document.documentElement.dir = rtlLanguages.includes(language) ? 'rtl' : 'ltr';
1359
+
1360
+ // Save language preference
1361
+ localStorage.setItem('ethicsguard_language', language);
1362
+
1363
+ // Update cache status
1364
+ updateCacheStatus(language);
1365
+ }
1366
+
1367
+ // API call function for translation
1368
+ async function translateText(text, targetLanguage) {
1369
+ if (!currentApiKey) {
1370
+ throw new Error('API key is required for translation');
1371
+ }
1372
+
1373
+ const languageName = languageNames[targetLanguage] || 'English';
1374
+
1375
+ const prompt = `Translate the following text to ${languageName}. Provide ONLY the exact translation without any explanations, additional information, or formatting:
1376
+
1377
+ "${text}"`;
1378
+
1379
+ const payload = {
1380
+ model: "gpt-4o-mini",
1381
+ messages: [{ role: "user", content: prompt }],
1382
+ max_tokens: 500,
1383
+ temperature: 0.1
1384
+ };
1385
+
1386
+ const response = await fetch("https://api.openai.com/v1/chat/completions", {
1387
+ method: "POST",
1388
+ headers: {
1389
+ "Content-Type": "application/json",
1390
+ "Authorization": `Bearer ${currentApiKey}`
1391
+ },
1392
+ body: JSON.stringify(payload)
1393
+ });
1394
+
1395
+ if (!response.ok) {
1396
+ const errorData = await response.json();
1397
+ throw new Error(errorData.error?.message || "Translation API request failed");
1398
+ }
1399
+
1400
+ const data = await response.json();
1401
+ return data.choices[0].message.content.trim();
1402
+ }
1403
+
1404
+ // Apply cached translations to UI
1405
+ function applyCachedTranslations(translations) {
1406
+ // Apply text translations
1407
+ Object.keys(translations.texts).forEach(originalText => {
1408
+ const translation = translations.texts[originalText];
1409
+ const elements = document.querySelectorAll(`[data-translate]`);
1410
+
1411
+ elements.forEach(element => {
1412
+ const originalElementText = element.getAttribute('data-original-text') || element.textContent;
1413
+ if (originalElementText === originalText) {
1414
+ element.textContent = translation;
1415
+ }
1416
+ });
1417
+ });
1418
+
1419
+ // Apply placeholder translations
1420
+ Object.keys(translations.placeholders).forEach(originalPlaceholder => {
1421
+ const translation = translations.placeholders[originalPlaceholder];
1422
+ const elements = document.querySelectorAll(`[data-translate-placeholder]`);
1423
+
1424
+ elements.forEach(element => {
1425
+ const originalElementPlaceholder = element.getAttribute('data-original-placeholder') || element.placeholder;
1426
+ if (originalElementPlaceholder === originalPlaceholder) {
1427
+ element.placeholder = translation;
1428
+ }
1429
+ });
1430
+ });
1431
+ }
1432
+
1433
+ // Translate all UI elements
1434
+ async function translateInterface(targetLanguage) {
1435
+ if (targetLanguage === 'en') {
1436
+ return;
1437
+ }
1438
+
1439
+ // Check if translations are cached
1440
+ const cachedTranslations = loadTranslationsFromCache(targetLanguage);
1441
+ if (cachedTranslations) {
1442
+ console.log('Using cached translations for', targetLanguage);
1443
+ applyCachedTranslations(cachedTranslations);
1444
+ return;
1445
+ }
1446
+
1447
+ // Need to translate via API
1448
+ showTranslationOverlay();
1449
+
1450
+ try {
1451
+ // Get all elements with data-translate attribute
1452
+ const elements = document.querySelectorAll('[data-translate]');
1453
+ const placeholderElements = document.querySelectorAll('[data-translate-placeholder]');
1454
+
1455
+ // Collect all texts to translate
1456
+ const textsToTranslate = [];
1457
+ const placeholdersToTranslate = [];
1458
+ const elementMap = new Map();
1459
+
1460
+ elements.forEach(element => {
1461
+ const originalText = element.getAttribute('data-original-text') || element.textContent;
1462
+ if (!element.getAttribute('data-original-text')) {
1463
+ element.setAttribute('data-original-text', originalText);
1464
+ }
1465
+ textsToTranslate.push(originalText);
1466
+ elementMap.set(originalText, element);
1467
+ });
1468
+
1469
+ placeholderElements.forEach(element => {
1470
+ const originalPlaceholder = element.getAttribute('data-original-placeholder') || element.placeholder;
1471
+ if (!element.getAttribute('data-original-placeholder')) {
1472
+ element.setAttribute('data-original-placeholder', originalPlaceholder);
1473
+ }
1474
+ placeholdersToTranslate.push(originalPlaceholder);
1475
+ elementMap.set(originalPlaceholder, element);
1476
+ });
1477
+
1478
+ // Prepare cache structure
1479
+ const translationsCache = {
1480
+ texts: {},
1481
+ placeholders: {}
1482
+ };
1483
+
1484
+ // Translate texts in batches
1485
+ const batchSize = 10;
1486
+ const allTexts = [...textsToTranslate, ...placeholdersToTranslate];
1487
+
1488
+ for (let i = 0; i < allTexts.length; i += batchSize) {
1489
+ const batch = allTexts.slice(i, i + batchSize);
1490
+
1491
+ // Update progress
1492
+ updateTranslationProgress(i, allTexts.length);
1493
+
1494
+ // Translate batch
1495
+ const translations = await Promise.all(
1496
+ batch.map(text => translateText(text, targetLanguage))
1497
+ );
1498
+
1499
+ // Apply translations and cache them
1500
+ batch.forEach((originalText, index) => {
1501
+ const element = elementMap.get(originalText);
1502
+ const translation = translations[index];
1503
+
1504
+ if (element.hasAttribute('data-translate')) {
1505
+ element.textContent = translation;
1506
+ translationsCache.texts[originalText] = translation;
1507
+ } else if (element.hasAttribute('data-translate-placeholder')) {
1508
+ element.placeholder = translation;
1509
+ translationsCache.placeholders[originalText] = translation;
1510
+ }
1511
+ });
1512
+ }
1513
+
1514
+ // Save translations to cache
1515
+ saveTranslationsToCache(targetLanguage, translationsCache);
1516
+
1517
+ // Update cache status
1518
+ updateCacheStatus(targetLanguage);
1519
+ updateCacheStatusDisplay();
1520
+
1521
+ } catch (error) {
1522
+ console.error('Translation error:', error);
1523
+ showError('Translation failed: ' + error.message);
1524
+ } finally {
1525
+ hideTranslationOverlay();
1526
+ }
1527
+ }
1528
+
1529
+ // Show translation overlay
1530
+ function showTranslationOverlay() {
1531
+ document.getElementById('translationOverlay').style.display = 'flex';
1532
+ }
1533
+
1534
+ // Hide translation overlay
1535
+ function hideTranslationOverlay() {
1536
+ document.getElementById('translationOverlay').style.display = 'none';
1537
+ }
1538
+
1539
+ // Update translation progress
1540
+ function updateTranslationProgress(current, total) {
1541
+ const percentage = Math.round((current / total) * 100);
1542
+ document.getElementById('translationMessage').textContent =
1543
+ `Translating interface... ${percentage}% complete (will be cached for future use)`;
1544
+ }
1545
+
1546
+ // Apply language with API translation or cache
1547
+ async function applyLanguage(language) {
1548
+ applyDirection(language);
1549
+
1550
+ if (language !== 'en') {
1551
+ await translateInterface(language);
1552
+ }
1553
+ }
1554
+
1555
+ // Show language landing page
1556
+ function showLanguageLanding() {
1557
+ document.getElementById('languageLanding').style.display = 'block';
1558
+ document.getElementById('mainApp').style.display = 'none';
1559
+ }
1560
+
1561
+ // Show main application
1562
+ function showMainApp() {
1563
+ document.getElementById('languageLanding').style.display = 'none';
1564
+ document.getElementById('mainApp').style.display = 'block';
1565
+
1566
+ // Set API key in main app
1567
+ document.getElementById('apiKey').value = currentApiKey;
1568
+ }
1569
+
1570
+ // Show error message
1571
+ function showError(message) {
1572
+ const errorDiv = document.getElementById('errorMessage');
1573
+ errorDiv.textContent = message;
1574
+ errorDiv.style.display = 'block';
1575
+ setTimeout(() => {
1576
+ errorDiv.style.display = 'none';
1577
+ }, 5000);
1578
+ }
1579
+
1580
+ // API Service Module
1581
+ const APIService = {
1582
+ async callOpenAI(prompt, apiKey) {
1583
+ const languageName = languageNames[currentLanguage] || 'English';
1584
+
1585
+ // Modify prompt to include language instruction
1586
+ const languagePrompt = currentLanguage !== 'en'
1587
+ ? `\n\nIMPORTANT: Generate all content (questions, options, feedback) in ${languageName} language.`
1588
+ : '';
1589
+
1590
+ const payload = {
1591
+ model: AppConfig.MODEL,
1592
+ messages: [{ role: 'system', content: prompt + languagePrompt }],
1593
+ max_tokens: AppConfig.MAX_TOKENS,
1594
+ temperature: AppConfig.TEMPERATURE
1595
+ };
1596
+
1597
+ const response = await fetch(AppConfig.API_BASE_URL, {
1598
+ method: 'POST',
1599
+ headers: {
1600
+ 'Content-Type': 'application/json',
1601
+ 'Authorization': `Bearer ${apiKey}`
1602
+ },
1603
+ body: JSON.stringify(payload)
1604
+ });
1605
+
1606
+ if (!response.ok) {
1607
+ const errorData = await response.json();
1608
+ throw new Error(errorData.error?.message || `HTTP ${response.status}: ${response.statusText}`);
1609
+ }
1610
+
1611
+ const data = await response.json();
1612
+ return data.choices[0].message.content;
1613
+ }
1614
+ };
1615
+
1616
+ // Utility Functions
1617
+ const Utils = {
1618
+ extractJSON(rawText, startMarker, endMarker) {
1619
+ const startIndex = rawText.indexOf(startMarker);
1620
+ const endIndex = rawText.indexOf(endMarker, startIndex);
1621
+ if (startIndex !== -1 && endIndex !== -1) {
1622
+ return rawText.substring(startIndex + startMarker.length, endIndex).trim();
1623
+ }
1624
+ return rawText;
1625
+ },
1626
+
1627
+ updateProgress(percent) {
1628
+ const progressBar = document.getElementById('progressBar');
1629
+ const progressText = document.getElementById('progressText');
1630
+ progressBar.style.width = percent + '%';
1631
+ progressText.textContent = percent + '%';
1632
+ }
1633
+ };
1634
+
1635
+ // UI Controller Module
1636
+ const UIController = {
1637
+ elements: {},
1638
+
1639
+ init() {
1640
+ this.elements = {
1641
+ apiKeyInput: document.getElementById('apiKey'),
1642
+ generateBtn: document.getElementById('generateTestBtn'),
1643
+ loadingSpinner: document.getElementById('loadingSpinner'),
1644
+ buttonText: document.querySelector('.button-text'),
1645
+ testSetup: document.getElementById('testSetup'),
1646
+ testSection: document.getElementById('testSection'),
1647
+ resultsSection: document.getElementById('resultsSection'),
1648
+ questionsContainer: document.getElementById('questionsContainer'),
1649
+ testForm: document.getElementById('testForm'),
1650
+ scoreNumber: document.getElementById('scoreNumber'),
1651
+ feedbackContent: document.getElementById('feedbackContent'),
1652
+ newTestBtn: document.getElementById('newTestBtn'),
1653
+ newTestBtn2: document.getElementById('newTestBtn2')
1654
+ };
1655
+
1656
+ this.setupEventListeners();
1657
+ },
1658
+
1659
+ setupEventListeners() {
1660
+ this.elements.generateBtn.addEventListener('click', () => {
1661
+ App.generateTest();
1662
+ });
1663
+
1664
+ this.elements.testForm.addEventListener('submit', (e) => {
1665
+ e.preventDefault();
1666
+ App.submitTest();
1667
+ });
1668
+
1669
+ this.elements.newTestBtn.addEventListener('click', () => {
1670
+ App.resetToSetup();
1671
+ });
1672
+
1673
+ this.elements.newTestBtn2.addEventListener('click', () => {
1674
+ App.resetToSetup();
1675
+ });
1676
+ },
1677
+
1678
+ updateLoadingState(isLoading) {
1679
+ if (isLoading) {
1680
+ this.elements.generateBtn.disabled = true;
1681
+ this.elements.buttonText.style.opacity = '0';
1682
+ this.elements.loadingSpinner.style.display = 'block';
1683
+ } else {
1684
+ this.elements.generateBtn.disabled = false;
1685
+ this.elements.buttonText.style.opacity = '1';
1686
+ this.elements.loadingSpinner.style.display = 'none';
1687
+ }
1688
+ },
1689
+
1690
+ showSection(sectionName) {
1691
+ // Hide all sections
1692
+ this.elements.testSetup.classList.add('hidden');
1693
+ this.elements.testSection.classList.add('hidden');
1694
+ this.elements.resultsSection.classList.add('hidden');
1695
+
1696
+ // Show target section
1697
+ switch (sectionName) {
1698
+ case 'setup':
1699
+ this.elements.testSetup.classList.remove('hidden');
1700
+ break;
1701
+ case 'test':
1702
+ this.elements.testSection.classList.remove('hidden');
1703
+ break;
1704
+ case 'results':
1705
+ this.elements.resultsSection.classList.remove('hidden');
1706
+ break;
1707
+ }
1708
+
1709
+ AppState.currentSection = sectionName;
1710
+ },
1711
+
1712
+ displayTest(testData) {
1713
+ this.elements.questionsContainer.innerHTML = '';
1714
+
1715
+ testData.questions.forEach((question, index) => {
1716
+ const questionNumber = index + 1;
1717
+ const questionCard = this.createQuestionCard(question, questionNumber);
1718
+ this.elements.questionsContainer.appendChild(questionCard);
1719
+ });
1720
+
1721
+ this.showSection('test');
1722
+ Utils.updateProgress(100);
1723
+ },
1724
+
1725
+ createQuestionCard(question, questionNumber) {
1726
+ const card = document.createElement('div');
1727
+ card.className = 'question-card';
1728
+
1729
+ card.innerHTML = `
1730
+ <div class="question-number">${questionNumber}</div>
1731
+ <div class="question-text">${question.question}</div>
1732
+ <div class="options-container">
1733
+ ${Object.entries(question.options).map(([key, value]) => `
1734
+ <div class="option-item">
1735
+ <input type="radio" id="q${questionNumber}_${key}" name="question${questionNumber}" value="${key}" class="option-input">
1736
+ <label for="q${questionNumber}_${key}" class="option-label">
1737
+ <div class="option-indicator"></div>
1738
+ <div class="option-text"><strong>${key}.</strong> ${value}</div>
1739
+ </label>
1740
+ </div>
1741
+ `).join('')}
1742
+ </div>
1743
+ `;
1744
+
1745
+ return card;
1746
+ },
1747
+
1748
+ displayResults(gradingResult) {
1749
+ this.elements.scoreNumber.textContent = gradingResult.score;
1750
+
1751
+ let feedbackHTML = '';
1752
+ gradingResult.results.forEach(item => {
1753
+ const isCorrect = item.userAnswer === item.correctAnswer;
1754
+ feedbackHTML += `
1755
+ <div class="feedback-item ${isCorrect ? 'correct' : 'incorrect'}">
1756
+ <div class="feedback-question">Question ${item.questionNumber}</div>
1757
+ <div class="feedback-answers">
1758
+ Your answer: <strong>${item.userAnswer || 'Not answered'}</strong> |
1759
+ Correct answer: <strong>${item.correctAnswer}</strong>
1760
+ </div>
1761
+ <div class="feedback-explanation">${item.feedback}</div>
1762
+ </div>
1763
+ `;
1764
+ });
1765
+
1766
+ this.elements.feedbackContent.innerHTML = feedbackHTML;
1767
+ this.showSection('results');
1768
+ },
1769
+
1770
+ showError(message) {
1771
+ const errorDiv = document.getElementById('errorMessage');
1772
+ errorDiv.textContent = message;
1773
+ errorDiv.style.display = 'block';
1774
+ setTimeout(() => {
1775
+ errorDiv.style.display = 'none';
1776
+ }, 5000);
1777
+ }
1778
+ };
1779
+
1780
+ // Error Handler Module
1781
+ const ErrorHandler = {
1782
+ handleError(error) {
1783
+ console.error('Application error:', error);
1784
+
1785
+ let userMessage = 'An unexpected error occurred. Please try again.';
1786
+
1787
+ if (error.message.includes('API key')) {
1788
+ userMessage = 'Invalid API key. Please check your OpenAI API key and try again.';
1789
+ } else if (error.message.includes('network') || error.message.includes('fetch')) {
1790
+ userMessage = 'Network error. Please check your internet connection and try again.';
1791
+ } else if (error.message.includes('rate limit')) {
1792
+ userMessage = 'Rate limit exceeded. Please wait a moment before trying again.';
1793
+ } else if (error.message.includes('quota')) {
1794
+ userMessage = 'API quota exceeded. Please check your OpenAI account usage.';
1795
+ }
1796
+
1797
+ UIController.showError(userMessage);
1798
+ }
1799
+ };
1800
+
1801
+ // Input Validator Module
1802
+ const InputValidator = {
1803
+ validateApiKey(apiKey) {
1804
+ if (!apiKey || apiKey.length < 10) {
1805
+ throw new Error('Please enter a valid OpenAI API key.');
1806
+ }
1807
+
1808
+ if (!apiKey.startsWith('sk-')) {
1809
+ throw new Error('OpenAI API key should start with "sk-".');
1810
+ }
1811
+
1812
+ return true;
1813
+ }
1814
+ };
1815
+
1816
+ // Main App Module
1817
+ const App = {
1818
+ init() {
1819
+ UIController.init();
1820
+ Utils.updateProgress(0);
1821
+ console.log('EthicsGuard Pro initialized');
1822
+ },
1823
+
1824
+ async generateTest() {
1825
+ if (AppState.isLoading) return;
1826
+
1827
+ try {
1828
+ const apiKey = currentApiKey;
1829
+
1830
+ // Validate API key
1831
+ InputValidator.validateApiKey(apiKey);
1832
+
1833
+ // Update state
1834
+ AppState.setLoading(true);
1835
+ Utils.updateProgress(10);
1836
+
1837
+ // Build prompt for test generation
1838
+ const prompt = `
1839
+ Generate a multiple-choice test on AI in Education Ethics. Provide exactly 10 questions.
1840
+ For each question, provide 4 answer choices labeled A, B, C, and D.
1841
+ Also provide an answer key that maps question numbers (1 to 10) to the correct option letter.
1842
+ Output exactly valid JSON in the following format with no extra commentary:
1843
+ <<<BEGIN TEST>>>
1844
+ {
1845
+ "questions": [
1846
+ {
1847
+ "question": "Question text here",
1848
+ "options": {
1849
+ "A": "Option A text",
1850
+ "B": "Option B text",
1851
+ "C": "Option C text",
1852
+ "D": "Option D text"
1853
+ }
1854
+ },
1855
+ ... (10 questions total)
1856
+ ],
1857
+ "answerKey": {
1858
+ "1": "B",
1859
+ "2": "D",
1860
+ ... (for all 10 questions)
1861
+ }
1862
+ }
1863
+ <<<END TEST>>>
1864
+ `;
1865
+
1866
+ Utils.updateProgress(30);
1867
+
1868
+ // Call API
1869
+ const result = await APIService.callOpenAI(prompt, apiKey);
1870
+
1871
+ Utils.updateProgress(70);
1872
+
1873
+ // Parse result
1874
+ const testJsonString = Utils.extractJSON(result, "<<<BEGIN TEST>>>", "<<<END TEST>>>");
1875
+ AppState.testData = JSON.parse(testJsonString);
1876
+
1877
+ Utils.updateProgress(90);
1878
+
1879
+ // Display test
1880
+ UIController.displayTest(AppState.testData);
1881
+
1882
+ } catch (error) {
1883
+ ErrorHandler.handleError(error);
1884
+ Utils.updateProgress(0);
1885
+ } finally {
1886
+ AppState.setLoading(false);
1887
+ }
1888
+ },
1889
+
1890
+ async submitTest() {
1891
+ try {
1892
+ // Collect user answers
1893
+ const userAnswers = {};
1894
+ AppState.testData.questions.forEach((q, index) => {
1895
+ const questionNumber = index + 1;
1896
+ const radios = document.getElementsByName(`question${questionNumber}`);
1897
+ for (let radio of radios) {
1898
+ if (radio.checked) {
1899
+ userAnswers[questionNumber] = radio.value;
1900
+ break;
1901
+ }
1902
+ }
1903
+ if (!userAnswers[questionNumber]) {
1904
+ userAnswers[questionNumber] = "";
1905
+ }
1906
+ });
1907
+
1908
+ // Build grading prompt
1909
+ const prompt = `
1910
+ You are an expert test grader. Given the following answer key and user answers for a 10-question multiple-choice test, calculate the score (number of correct answers) and provide a detailed feedback report.
1911
+
1912
+ Answer Key:
1913
+ <<<BEGIN KEY>>>
1914
+ ${JSON.stringify(AppState.testData.answerKey, null, 2)}
1915
+ <<<END KEY>>>
1916
+
1917
+ User Answers:
1918
+ <<<BEGIN ANSWERS>>>
1919
+ ${JSON.stringify(userAnswers, null, 2)}
1920
+ <<<END ANSWERS>>>
1921
+
1922
+ Output exactly valid JSON in the following format with no extra commentary:
1923
+ {
1924
+ "score": <number>,
1925
+ "results": [
1926
+ {
1927
+ "questionNumber": <number>,
1928
+ "userAnswer": "<letter>",
1929
+ "correctAnswer": "<letter>",
1930
+ "feedback": "<explanation>"
1931
+ },
1932
+ ... (for each question)
1933
+ ]
1934
+ }
1935
+ `;
1936
+
1937
+ // Call API for grading
1938
+ const result = await APIService.callOpenAI(prompt, currentApiKey);
1939
+ const gradingResult = JSON.parse(result);
1940
+
1941
+ // Display results
1942
+ UIController.displayResults(gradingResult);
1943
+
1944
+ } catch (error) {
1945
+ ErrorHandler.handleError(error);
1946
+ }
1947
+ },
1948
+
1949
+ resetToSetup() {
1950
+ AppState.testData = null;
1951
+ UIController.elements.questionsContainer.innerHTML = "";
1952
+ UIController.showSection('setup');
1953
+ Utils.updateProgress(0);
1954
+ }
1955
+ };
1956
+
1957
+ // Start button click handler
1958
+ document.getElementById('startBtn').addEventListener('click', async function() {
1959
+ const selectedLanguage = document.getElementById('languageSelect').value;
1960
+ const apiKey = document.getElementById('apiKeyLanding').value.trim();
1961
+
1962
+ if (!apiKey) {
1963
+ alert('Please enter your OpenAI API key');
1964
+ return;
1965
+ }
1966
+
1967
+ currentLanguage = selectedLanguage;
1968
+ currentApiKey = apiKey;
1969
+
1970
+ // Save API key
1971
+ localStorage.setItem('ethicsguard_api_key', apiKey);
1972
+
1973
+ // Apply language with translation (cached or API)
1974
+ await applyLanguage(selectedLanguage);
1975
+
1976
+ // Show main app
1977
+ showMainApp();
1978
+ });
1979
+
1980
+ // Language selector change handler
1981
+ document.getElementById('languageSelect').addEventListener('change', async function() {
1982
+ const selectedLanguage = this.value;
1983
+ updateCacheStatus(selectedLanguage);
1984
+
1985
+ if (currentApiKey) {
1986
+ await applyLanguage(selectedLanguage);
1987
+ } else {
1988
+ applyDirection(selectedLanguage);
1989
+ }
1990
+ });
1991
+
1992
+ // Clear cache button handler
1993
+ document.getElementById('clearCacheBtn').addEventListener('click', clearAllTranslationCache);
1994
+
1995
+ // API key sync between landing and main app
1996
+ document.getElementById('apiKeyLanding').addEventListener('input', function() {
1997
+ currentApiKey = this.value;
1998
+ localStorage.setItem('ethicsguard_api_key', this.value);
1999
+ document.getElementById('apiKey').value = this.value;
2000
+ });
2001
+
2002
+ document.getElementById('apiKey').addEventListener('input', function() {
2003
+ currentApiKey = this.value;
2004
+ localStorage.setItem('ethicsguard_api_key', this.value);
2005
+ document.getElementById('apiKeyLanding').value = this.value;
2006
+ });
2007
+
2008
+ // Initialize the application when page loads
2009
+ document.addEventListener('DOMContentLoaded', function() {
2010
+ initializeApp();
2011
+ App.init();
2012
+ });
2013
+ </script>
2014
+ </body>
2015
+ </html>
2016
+
📘 Teacher & Student Guide EthicsGu.txt ADDED
@@ -0,0 +1,84 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ 📘 Teacher & Student Guide: EthicsGuard Pro
2
+ 1. Purpose & Impact
3
+ EthicsGuard Pro helps educators and students build, test, and deepen their knowledge of AI ethics and responsible technology use in schools—through instant, interactive, and multilingual quizzes with actionable feedback.
4
+
5
+ 2. Step-by-Step Instructions
6
+ A. Teacher Workflow
7
+ Setup & Access
8
+
9
+ Open the HTML file or Hugging Face Space.
10
+
11
+ Select your preferred language and enter your OpenAI API key (never leaves your device).
12
+
13
+ Quiz Generation
14
+
15
+ Click “🧠 Generate Test” to create a fresh, randomized ethics quiz.
16
+
17
+ Each quiz contains 10 multiple-choice questions focused on AI in education, privacy, bias, and responsible use.
18
+
19
+ Delivery Options
20
+
21
+ Project the quiz for the whole class or share individual devices.
22
+
23
+ Have students take the quiz independently, in pairs, or as groups.
24
+
25
+ Feedback & Discussion
26
+
27
+ Review instant results and detailed, question-by-question feedback.
28
+
29
+ Use feedback explanations as discussion starters or prompts for further research.
30
+
31
+ Assessment & Reflection
32
+
33
+ Assign repeated quizzes over time to measure growth.
34
+
35
+ Encourage students to reflect on missed questions and research correct answers.
36
+
37
+ Multilingual Support
38
+
39
+ Instantly switch the quiz and feedback to any supported language—ideal for international classes or ELLs.
40
+
41
+ B. Student Workflow
42
+ Getting Started
43
+
44
+ Join the quiz as assigned by your teacher, or launch it yourself for practice.
45
+
46
+ Select your language and enter the provided OpenAI API key (if using a shared device).
47
+
48
+ Taking the Quiz
49
+
50
+ Read each question carefully and select the best answer.
51
+
52
+ Submit your answers when finished.
53
+
54
+ Reviewing Results
55
+
56
+ Instantly see your total score (out of 10) and get feedback on each question.
57
+
58
+ For incorrect answers, review the provided explanation to learn what to do differently next time.
59
+
60
+ Reattempt & Improve
61
+
62
+ Take new quizzes to keep improving your understanding of AI ethics in education.
63
+
64
+ Challenge classmates to see who can get the highest score.
65
+
66
+ 3. Classroom Best Practices
67
+ Use Case How to Integrate
68
+ Formative Assessment Use before or after a unit on AI ethics to gauge knowledge.
69
+ Class Discussions Use feedback explanations as debate or discussion starters.
70
+ Project-Based Learning Assign as research prompts—students explain ethical dilemmas.
71
+ Multilingual/ELL Support Switch languages for native-language assessment.
72
+ Professional Development Teachers/staff self-assess or run group sessions.
73
+
74
+ 4. Tips & Troubleshooting
75
+ Be patient if translation is slow (first use only—then cached for next time).
76
+
77
+ For privacy: All quiz data and API keys stay local.
78
+
79
+ If questions feel generic: Regenerate for new sets; coverage is broad and random.
80
+
81
+ Technical issues? Contact info@shiftmind.io or visit www.shiftmind.io.
82
+
83
+ EthicsGuard Pro enables every educator and learner to confidently practice, assess, and grow their understanding of responsible AI in education—anytime, anywhere, in any language.
84
+