CVNSS commited on
Commit
bff1463
·
verified ·
1 Parent(s): ab0d448

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +350 -19
index.html CHANGED
@@ -1,19 +1,350 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="vi">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Từ Điển Thuật Ngữ Địa Lý</title>
7
+
8
+ <link rel="preconnect" href="https://fonts.googleapis.com">
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
10
+ <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
11
+
12
+ <style>
13
+ /* --- CSS VARIABLES & RESET --- */
14
+ :root {
15
+ --primary: #0ea5e9; /* Sky Blue */
16
+ --primary-dark: #0284c7;
17
+ --bg-body: #f8fafc;
18
+ --bg-card: #ffffff;
19
+ --text-main: #1e293b;
20
+ --text-muted: #64748b;
21
+ --border: #e2e8f0;
22
+ --shadow-sm: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
23
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
24
+ --radius: 12px;
25
+ }
26
+
27
+ * { margin: 0; padding: 0; box-sizing: border-box; font-family: 'Inter', sans-serif; }
28
+
29
+ body {
30
+ background-color: var(--bg-body);
31
+ color: var(--text-main);
32
+ line-height: 1.6;
33
+ padding-bottom: 40px;
34
+ }
35
+
36
+ /* --- LAYOUT --- */
37
+ .container {
38
+ max-width: 800px;
39
+ margin: 0 auto;
40
+ padding: 0 20px;
41
+ }
42
+
43
+ /* --- HEADER & SEARCH --- */
44
+ header {
45
+ background: linear-gradient(135deg, #0f172a 0%, #1e293b 100%);
46
+ color: white;
47
+ padding: 60px 0 40px;
48
+ text-align: center;
49
+ margin-bottom: 30px;
50
+ box-shadow: var(--shadow-md);
51
+ }
52
+
53
+ h1 {
54
+ font-size: 2.2rem;
55
+ font-weight: 700;
56
+ margin-bottom: 10px;
57
+ letter-spacing: -0.025em;
58
+ }
59
+
60
+ p.subtitle {
61
+ color: #94a3b8;
62
+ font-size: 0.95rem;
63
+ margin-bottom: 30px;
64
+ }
65
+
66
+ .search-box {
67
+ position: relative;
68
+ max-width: 600px;
69
+ margin: 0 auto;
70
+ }
71
+
72
+ .search-input {
73
+ width: 100%;
74
+ padding: 16px 24px 16px 50px;
75
+ border-radius: 50px;
76
+ border: none;
77
+ font-size: 1.1rem;
78
+ box-shadow: 0 10px 25px -5px rgba(0, 0, 0, 0.1), 0 8px 10px -6px rgba(0, 0, 0, 0.1);
79
+ outline: none;
80
+ transition: all 0.3s ease;
81
+ }
82
+
83
+ .search-input:focus {
84
+ box-shadow: 0 0 0 4px rgba(14, 165, 233, 0.3);
85
+ }
86
+
87
+ .search-icon {
88
+ position: absolute;
89
+ left: 20px;
90
+ top: 50%;
91
+ transform: translateY(-50%);
92
+ color: #94a3b8;
93
+ width: 20px;
94
+ height: 20px;
95
+ }
96
+
97
+ /* --- RESULTS LIST --- */
98
+ #results-area {
99
+ display: grid;
100
+ gap: 20px;
101
+ }
102
+
103
+ .term-card {
104
+ background: var(--bg-card);
105
+ padding: 24px;
106
+ border-radius: var(--radius);
107
+ border: 1px solid var(--border);
108
+ box-shadow: var(--shadow-sm);
109
+ transition: transform 0.2s ease, box-shadow 0.2s ease;
110
+ animation: fadeIn 0.4s ease-out;
111
+ }
112
+
113
+ .term-card:hover {
114
+ transform: translateY(-2px);
115
+ box-shadow: var(--shadow-md);
116
+ border-color: var(--primary);
117
+ }
118
+
119
+ .card-header {
120
+ display: flex;
121
+ justify-content: space-between;
122
+ align-items: center;
123
+ margin-bottom: 12px;
124
+ flex-wrap: wrap;
125
+ gap: 10px;
126
+ }
127
+
128
+ .term-title {
129
+ font-size: 1.25rem;
130
+ font-weight: 700;
131
+ color: var(--primary-dark);
132
+ }
133
+
134
+ .term-type {
135
+ font-size: 0.75rem;
136
+ text-transform: uppercase;
137
+ letter-spacing: 0.05em;
138
+ background-color: #f1f5f9;
139
+ color: var(--text-muted);
140
+ padding: 4px 10px;
141
+ border-radius: 6px;
142
+ font-weight: 600;
143
+ }
144
+
145
+ .term-def {
146
+ color: #334155;
147
+ font-size: 1rem;
148
+ text-align: justify;
149
+ }
150
+
151
+ /* --- STATES --- */
152
+ .loading, .error, .empty {
153
+ text-align: center;
154
+ padding: 40px;
155
+ color: var(--text-muted);
156
+ }
157
+
158
+ .error { color: #ef4444; }
159
+
160
+ .highlight {
161
+ background-color: #fef08a; /* Yellow highlight */
162
+ padding: 0 2px;
163
+ border-radius: 2px;
164
+ }
165
+
166
+ /* --- ANIMATION --- */
167
+ @keyframes fadeIn {
168
+ from { opacity: 0; transform: translateY(10px); }
169
+ to { opacity: 1; transform: translateY(0); }
170
+ }
171
+
172
+ /* --- RESPONSIVE --- */
173
+ @media (max-width: 600px) {
174
+ h1 { font-size: 1.8rem; }
175
+ .container { padding: 0 15px; }
176
+ header { padding: 40px 0 30px; border-radius: 0 0 20px 20px; }
177
+ }
178
+ </style>
179
+ </head>
180
+ <body>
181
+
182
+ <header>
183
+ <div class="container">
184
+ <h1>Sổ Tay Thuật Ngữ Địa Lý</h1>
185
+ <p class="subtitle">Tra cứu nhanh chóng, chính xác và tiện lợi</p>
186
+
187
+ <div class="search-box">
188
+ <svg class="search-icon" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke="currentColor">
189
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
190
+ </svg>
191
+ <input type="text" id="searchInput" class="search-input" placeholder="Nhập từ khoá (ví dụ: Bazan, Thủy triều, Khí hậu...)" autocomplete="off">
192
+ </div>
193
+ </div>
194
+ </header>
195
+
196
+ <main class="container">
197
+ <div id="count-display" style="margin-bottom: 15px; color: var(--text-muted); font-size: 0.9rem;"></div>
198
+ <div id="results-area">
199
+ </div>
200
+ </main>
201
+
202
+ <script>
203
+ /**
204
+ * LOGIC ỨNG DỤNG TRA CỨU
205
+ * Chuyên gia: AI Assistant (100 Years Exp Sim)
206
+ */
207
+
208
+ const resultsArea = document.getElementById('results-area');
209
+ const searchInput = document.getElementById('searchInput');
210
+ const countDisplay = document.getElementById('count-display');
211
+
212
+ let dictionaryData = [];
213
+
214
+ // 1. Hàm khởi tạo: Tải dữ liệu JSON
215
+ async function initApp() {
216
+ renderState('loading', 'Đang tải dữ liệu từ điển...');
217
+
218
+ try {
219
+ // Lưu ý: Cần chạy qua Live Server hoặc Local Server để tránh lỗi CORS khi fetch file local
220
+ const response = await fetch('tudien_dialy.json');
221
+
222
+ if (!response.ok) throw new Error("Không tìm thấy file tudien_dialy.json");
223
+
224
+ dictionaryData = await response.json();
225
+
226
+ // Sắp xếp theo bảng chữ cái
227
+ dictionaryData.sort((a, b) => a.tu_vung.localeCompare(b.tu_vung, 'vi'));
228
+
229
+ renderData(dictionaryData); // Hiển thị toàn bộ ban đầu
230
+ countDisplay.textContent = `Tổng số thuật ngữ: ${dictionaryData.length}`;
231
+
232
+ } catch (error) {
233
+ console.error(error);
234
+ renderState('error', `
235
+ <strong>Lỗi tải dữ liệu!</strong><br>
236
+ 1. Hãy chắc chắn file "tudien_dialy.json" nằm cùng thư mục.<br>
237
+ 2. Nếu mở trực tiếp file HTML, trình duyệt có thể chặn (CORS).<br>
238
+ Hãy dùng VS Code "Live Server" hoặc upload lên host.
239
+ `);
240
+ }
241
+ }
242
+
243
+ // 2. Hàm xử lý hiển thị (Render)
244
+ function renderData(data, keyword = '') {
245
+ resultsArea.innerHTML = '';
246
+
247
+ if (data.length === 0) {
248
+ renderState('empty', 'Không tìm thấy thuật ngữ nào phù hợp.');
249
+ return;
250
+ }
251
+
252
+ // Sử dụng DocumentFragment để tối ưu hiệu năng render
253
+ const fragment = document.createDocumentFragment();
254
+
255
+ data.forEach(item => {
256
+ const card = document.createElement('div');
257
+ card.className = 'term-card';
258
+
259
+ // Highlight từ khóa nếu đang tìm kiếm
260
+ let displayTitle = item.tu_vung;
261
+ if (keyword) {
262
+ const regex = new RegExp(`(${keyword})`, 'gi');
263
+ displayTitle = item.tu_vung.replace(regex, '<span class="highlight">$1</span>');
264
+ }
265
+
266
+ card.innerHTML = `
267
+ <div class="card-header">
268
+ <div class="term-title">${displayTitle}</div>
269
+ <span class="term-type">${item.phan_loai || 'Thuật ngữ'}</span>
270
+ </div>
271
+ <div class="term-def">
272
+ ${item.nghia}
273
+ </div>
274
+ `;
275
+ fragment.appendChild(card);
276
+ });
277
+
278
+ resultsArea.appendChild(fragment);
279
+
280
+ // Cập nhật số lượng
281
+ if(keyword) {
282
+ countDisplay.textContent = `Tìm thấy ${data.length} kết quả cho "${keyword}"`;
283
+ } else {
284
+ countDisplay.textContent = `Tổng số thuật ngữ: ${dictionaryData.length}`;
285
+ }
286
+ }
287
+
288
+ // 3. Hàm hiển thị trạng thái (Loading/Error)
289
+ function renderState(type, message) {
290
+ resultsArea.innerHTML = `<div class="${type}">${message}</div>`;
291
+ }
292
+
293
+ // 4. Hàm chuẩn hóa tiếng Việt để tìm kiếm thông minh
294
+ // Ví dụ: Nhập "khi hau" vẫn tìm ra "Khí hậu"
295
+ function removeVietnameseTones(str) {
296
+ str = str.replace(/à|á|ạ|ả|ã|â|ầ|ấ|ậ|ẩ|ẫ|ă|ằ|ắ|ặ|ẳ|ẵ/g,"a");
297
+ str = str.replace(/è|é|ẹ|ẻ|ẽ|ê|ề|ế|ệ|ể|ễ/g,"e");
298
+ str = str.replace(/ì|í|ị|ỉ|ĩ/g,"i");
299
+ str = str.replace(/ò|ó|ọ|ỏ|õ|ô|ồ|ố|ộ|ổ|ỗ|ơ|ờ|ớ|ợ|ở|ỡ/g,"o");
300
+ str = str.replace(/ù|ú|ụ|ủ|ũ|ư|ừ|ứ|ự|ử|ữ/g,"u");
301
+ str = str.replace(/ỳ|ý|ỵ|ỷ|ỹ/g,"y");
302
+ str = str.replace(/đ/g,"d");
303
+ str = str.replace(/À|Á|Ạ|Ả|Ã|Â|Ầ|Ấ|Ậ|Ẩ|Ẫ|Ă|Ằ|Ắ|Ặ|Ẳ|Ẵ/g, "A");
304
+ str = str.replace(/È|É|Ẹ|Ẻ|Ẽ|Ê|Ề|Ế|Ệ|Ể|Ễ/g, "E");
305
+ str = str.replace(/Ì|Í|Ị|Ỉ|Ĩ/g, "I");
306
+ str = str.replace(/Ò|Ó|Ọ|Ỏ|Õ|Ô|Ồ|Ố|Ộ|Ổ|Ỗ|Ơ|Ờ|Ớ|Ợ|Ở|Ỡ/g, "O");
307
+ str = str.replace(/Ù|Ú|Ụ|Ủ|Ũ|Ư|Ừ|Ứ|Ự|Ử|Ữ/g, "U");
308
+ str = str.replace(/Ỳ|Ý|Ỵ|Ỷ|Ỹ/g, "Y");
309
+ str = str.replace(/Đ/g, "D");
310
+ // Một số ký tự đặc biệt
311
+ str = str.replace(/\u0300|\u0301|\u0303|\u0309|\u0323/g, "");
312
+ return str;
313
+ }
314
+
315
+ // 5. Sự kiện tìm kiếm (Debounce nhẹ để mượt mà)
316
+ let timeout = null;
317
+ searchInput.addEventListener('input', (e) => {
318
+ clearTimeout(timeout);
319
+ timeout = setTimeout(() => {
320
+ const rawKeyword = e.target.value.trim();
321
+ const normalizeKeyword = removeVietnameseTones(rawKeyword).toLowerCase();
322
+
323
+ if (!rawKeyword) {
324
+ renderData(dictionaryData);
325
+ return;
326
+ }
327
+
328
+ // Lọc dữ liệu: So sánh cả từ gốc và từ đã bỏ dấu
329
+ const filtered = dictionaryData.filter(item => {
330
+ const rawTerm = item.tu_vung.toLowerCase();
331
+ const normTerm = removeVietnameseTones(item.tu_vung).toLowerCase();
332
+ const rawDef = item.nghia.toLowerCase();
333
+ const normDef = removeVietnameseTones(item.nghia).toLowerCase();
334
+
335
+ return rawTerm.includes(rawKeyword.toLowerCase()) ||
336
+ normTerm.includes(normalizeKeyword) ||
337
+ rawDef.includes(rawKeyword.toLowerCase()) ||
338
+ normDef.includes(normalizeKeyword);
339
+ });
340
+
341
+ renderData(filtered, rawKeyword);
342
+ }, 300); // Đợi 300ms sau khi ngừng gõ mới tìm
343
+ });
344
+
345
+ // Khởi chạy ứng dụng
346
+ initApp();
347
+
348
+ </script>
349
+ </body>
350
+ </html>