OpenLab-NLP commited on
Commit
b070fc7
Β·
verified Β·
1 Parent(s): 829fc79

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +115 -128
index.html CHANGED
@@ -2,196 +2,183 @@
2
  <html lang="ko">
3
  <head>
4
  <meta charset="UTF-8">
5
- <title>Huiucl Precision Dictionary v2</title>
6
  <style>
7
- :root { --primary: #2c3e50; --accent: #e67e22; --bg: #f0f2f5; --card: #ffffff; }
8
- body { font-family: 'Malgun Gothic', sans-serif; background: var(--bg); padding: 30px; color: var(--primary); line-height: 1.6; }
9
- .container { max-width: 1000px; margin: 0 auto; }
10
 
11
- /* 헀더 및 파일 λ‘œλ” */
12
- .header { background: var(--primary); color: white; padding: 20px; border-radius: 12px; margin-bottom: 20px; text-align: center; }
13
- .file-upload { background: #fff; padding: 15px; border-radius: 8px; border: 2px dashed #bdc3c7; margin-bottom: 20px; text-align: center; }
14
 
15
- /* 검색창 */
16
- .search-area { position: sticky; top: 10px; z-index: 100; background: var(--bg); padding-bottom: 10px; }
17
- .search-box { width: 100%; padding: 18px; font-size: 20px; border: 3px solid #ddd; border-radius: 10px; box-sizing: border-box; outline: none; transition: border-color 0.3s; }
18
- .search-box:focus { border-color: var(--accent); }
19
- .search-box:disabled { background: #eee; cursor: not-allowed; }
20
-
21
- /* κ²°κ³Ό μ•„μ΄ν…œ */
22
- .result-item { background: var(--card); border-radius: 12px; padding: 30px; margin-bottom: 25px; box-shadow: 0 5px 15px rgba(0,0,0,0.08); position: relative; }
23
- .exact-match { border: 3px solid var(--accent); }
24
- .exact-badge { position: absolute; top: -15px; right: 20px; background: var(--accent); color: white; padding: 5px 15px; border-radius: 20px; font-weight: bold; font-size: 13px; }
25
-
26
- .word-title { font-size: 28px; font-weight: 800; color: #2980b9; margin: 0 0 10px 0; border-bottom: 2px solid #eee; padding-bottom: 5px; }
27
- .category { font-size: 13px; color: #7f8c8d; background: #ecf0f1; padding: 3px 10px; border-radius: 5px; margin-left: 10px; }
28
 
29
- .meaning-section { font-size: 19px; margin: 15px 0; font-weight: 500; }
 
 
 
 
 
 
 
 
30
 
31
- .data-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 20px; }
32
- .data-box { background: #f8f9fa; padding: 15px; border-radius: 8px; border: 1px solid #e9ecef; }
33
- .box-title { font-weight: bold; font-size: 14px; color: #34495e; margin-bottom: 10px; border-left: 4px solid #3498db; padding-left: 8px; }
34
 
35
- .info-list { list-style: none; padding: 0; margin: 0; font-size: 14px; }
36
- .info-list li { margin-bottom: 5px; }
37
- .key-text { font-weight: bold; color: #2c3e50; width: 100px; display: inline-block; }
38
-
39
- .desc-section { margin-top: 15px; font-size: 14px; color: #555; border-top: 1px solid #eee; padding-top: 15px; }
40
- .hidden-search-tag { display: none; } /* 검색엔 μ•ˆκ±Έλ¦¬μ§€λ§Œ λ³΄μ—¬μ£ΌκΈ°λ§Œ 함 */
 
 
 
 
 
 
 
 
 
 
 
41
  </style>
42
  </head>
43
  <body>
44
 
45
  <div class="container">
46
- <div class="header">
47
- <h1>Huiucl Precision Search Engine</h1>
48
- <p>쑰사, λŒ€λͺ…사, νŒŒμƒμ–΄λ₯Ό 예문 κ°„μ„­ 없이 μ •λ°€ν•˜κ²Œ μ°Ύμ•„μ€λ‹ˆλ‹€.</p>
49
- </div>
50
-
51
- <div class="file-upload">
52
- <strong>1. JSON 파일 λ‘œλ“œ:</strong>
53
- <input type="file" id="fileInput" accept=".json">
54
  </div>
55
 
56
- <div class="search-area">
57
- <input type="text" id="searchInput" class="search-box" placeholder="νŒŒμΌμ„ λ¨Όμ € λ‘œλ“œν•΄μ£Όμ„Έμš”..." disabled onkeyup="doSearch()">
58
  </div>
59
 
60
- <div id="status" style="margin-bottom: 15px; font-weight: bold; color: #2980b9;"></div>
61
  <div id="results"></div>
62
  </div>
63
 
64
  <script>
65
  let lexicon = {};
66
 
67
- // [λ‘œλ“œ κΈ°λŠ₯] 파일 읽기
68
- document.getElementById('fileInput').addEventListener('change', function(e) {
69
- const file = e.target.files[0];
70
- if (!file) return;
71
-
72
- const reader = new FileReader();
73
- reader.onload = function(e) {
74
- try {
75
- const jsonData = JSON.parse(e.target.result);
76
- processData(jsonData);
77
- document.getElementById('searchInput').disabled = false;
78
- document.getElementById('searchInput').placeholder = "단어 λ˜λŠ” λœ»μ„ μž…λ ₯ν•˜μ„Έμš” (예: su, a, κ΄€μ°°)...";
79
- document.getElementById('status').innerText = `βœ… λ‘œλ“œ μ™„λ£Œ: ${Object.keys(lexicon).length}개의 ν•­λͺ©μ„ μΈλ±μ‹±ν–ˆμŠ΅λ‹ˆλ‹€.`;
80
- } catch (err) {
81
- alert("JSON νŒŒμ‹± 쀑 였λ₯˜κ°€ λ°œμƒν–ˆμŠ΅λ‹ˆλ‹€: " + err.message);
82
- }
83
- };
84
- reader.readAsText(file);
85
- });
86
 
87
- // [데이터 가곡] μ •λ°€ 인덱싱
88
- function processData(obj, parentKey = null, currentCat = null) {
89
  if (typeof obj !== 'object' || obj === null) return;
90
 
91
- // λŒ€λͺ…사 μ„Ήμ…˜ 특수 처리
92
- const pronounSects = ["1st_Person", "2nd_Person", "3rd_Person", "Demonstratives"];
93
- if (pronounSects.includes(parentKey)) {
94
- for (const [pKey, pVal] of Object.entries(obj)) {
95
- lexicon[pKey] = {
96
- meaning_ko: pVal,
97
- _category: `Pronouns (${parentKey})`,
98
- _search_target: `${pKey} ${pVal}`.toLowerCase() // 단어와 뜻만 검색
99
  };
100
  }
101
  return;
102
  }
103
 
104
- const meaningKeys = ['meaning_ko', 'ko', 'definition', 'morpheme', 'meaning_en', 'en'];
105
- const hasMeaning = meaningKeys.some(k => k in obj);
 
106
 
107
- if (hasMeaning && parentKey) {
108
  let entry = { ...obj };
109
- if (entry.definition && typeof entry.definition === 'object') {
110
- Object.assign(entry, entry.definition);
111
- }
112
 
113
- // [μ •λ°€ 검색 μ„€μ •] 단어λͺ…κ³Ό 뜻만 검색 λŒ€μƒμ— 포함 (usage, example μ œμ™Έ)
114
- let searchParts = [parentKey];
115
- meaningKeys.forEach(k => { if (entry[k]) searchParts.push(entry[k]); });
116
 
117
  entry._category = currentCat;
118
- entry._search_target = searchParts.join(" ").toLowerCase();
119
  lexicon[parentKey] = entry;
120
  }
121
 
122
  for (const [k, v] of Object.entries(obj)) {
123
  if (typeof v === 'object') {
124
  const nextCat = (parentKey === null) ? k : currentCat;
125
- if (k !== 'Settings') processData(v, k, nextCat);
126
  }
127
  }
128
  }
129
 
130
- // [검색 κΈ°λŠ₯] μ •λ°€ λ§€μΉ­
131
- function doSearch() {
132
- const query = document.getElementById('searchInput').value.trim().toLowerCase();
133
- const resultsArea = document.getElementById('results');
134
- resultsArea.innerHTML = '';
135
-
136
- if (!query) return;
137
 
138
- let results = [];
139
- // ν•œ κΈ€μž 검색 μ‹œμ—λ„ 정확도λ₯Ό μœ μ§€ν•˜κΈ° μœ„ν•œ 독립 단어 경계 체크용
140
- const regex = new RegExp(`(^|[^a-zA-Zκ°€-힣])${query}($|[^a-zA-Zκ°€-힣])`, 'i');
141
 
142
  for (const [word, info] of Object.entries(lexicon)) {
143
- const wordLower = word.toLowerCase();
144
- const searchTarget = info._search_target || "";
145
 
146
- if (query === wordLower) {
147
- results.push({ word, info, score: 2 }); // μ™„μ „ 일치
148
- } else if (regex.test(searchTarget) || (query.length > 1 && searchTarget.includes(query))) {
149
- results.push({ word, info, score: 1 }); // 슀마트 일치
150
- }
151
  }
152
 
153
- results.sort((a, b) => b.score - a.score);
154
- render(results);
155
- }
156
-
157
- function render(items) {
158
- const area = document.getElementById('results');
159
- items.forEach(item => {
160
  const info = item.info;
161
- const div = document.createElement('div');
162
- div.className = `result-item ${item.score === 2 ? 'exact-match' : ''}`;
163
-
164
- let subHtml = "";
165
-
166
- // νŒŒμƒμ–΄
167
  const derivs = info.derivations || info.derivation;
168
  if (derivs) {
169
- subHtml += `<div class="data-box"><div class="box-title">πŸ“‚ 기둝된 νŒŒμƒμ–΄</div><ul class="info-list">`;
170
- for(const [dw, dm] of Object.entries(derivs)) subHtml += `<li><span class="deriv-word">${dw}</span> : ${dm}</li>`;
171
- subHtml += `</ul></div>`;
172
  }
173
 
174
- // λ³€μ΄ν˜•
175
  let vars = {};
176
  ["tense_variants", "aspect_voice", "variants"].forEach(k => { if(info[k]) Object.assign(vars, info[k]); });
177
  if (Object.keys(vars).length > 0) {
178
- subHtml += `<div class="data-box"><div class="box-title">πŸ“‚ μ‹œμ œ 및 변이</div><ul class="info-list">`;
179
- for(const [vw, vi] of Object.entries(vars)) subHtml += `<li><span class="deriv-word">${vw}</span> : ${vi.meaning_ko || vi.ko || ''}</li>`;
180
- subHtml += `</ul></div>`;
181
  }
182
 
183
- div.innerHTML = `
184
- ${item.score === 2 ? '<div class="exact-badge">μ •ν™•ν•œ 일치</div>' : ''}
185
- <h2 class="word-title">${item.word.toUpperCase()} <span class="category">${info._category}</span></h2>
186
- <div class="meaning-section">${info.meaning_ko || info.ko || info.morpheme || ''} | <span style="color:#7f8c8d">${info.meaning_en || info.en || ''}</span></div>
187
- <div class="data-grid">${subHtml}</div>
188
- <div class="desc-section">
189
- ${info.usage ? `<div><span class="detail-label">πŸ“ μš©λ²•:</span> ${typeof info.usage === 'object' ? info.usage.ko : info.usage}</div>` : ''}
190
- ${info.note ? `<div><span class="detail-label">πŸ’‘ μ°Έκ³ :</span> ${info.note}</div>` : ''}
191
- ${info.example ? `<div style="margin-top:8px; color:#2980b9;"><span class="detail-label">πŸ“– 예문:</span> ${info.example}</div>` : ''}
 
 
 
 
192
  </div>
193
  `;
194
- area.appendChild(div);
195
  });
196
  }
197
  </script>
 
2
  <html lang="ko">
3
  <head>
4
  <meta charset="UTF-8">
5
+ <title>Huiucl Auto-Load Dictionary</title>
6
  <style>
7
+ :root { --primary: #2c3e50; --accent: #e67e22; --bg: #f8f9fa; --card: #ffffff; }
8
+ body { font-family: 'Malgun Gothic', 'Apple SD Gothic Neo', sans-serif; background: var(--bg); padding: 20px; color: var(--primary); }
9
+ .container { max-width: 900px; margin: 0 auto; }
10
 
11
+ /* λ‘œλ”© μƒνƒœ ν‘œμ‹œ */
12
+ #loader { text-align: center; padding: 20px; font-weight: bold; color: #2980b9; }
 
13
 
14
+ /* 헀더 μ„Ήμ…˜ */
15
+ .header-panel { background: var(--primary); color: white; padding: 30px; border-radius: 15px; margin-bottom: 25px; box-shadow: 0 4px 15px rgba(0,0,0,0.2); }
16
+ .header-panel h1 { margin: 0; font-size: 32px; letter-spacing: 1px; }
 
 
 
 
 
 
 
 
 
 
17
 
18
+ /* 검색창 */
19
+ .search-container { position: sticky; top: 15px; z-index: 1000; margin-bottom: 30px; }
20
+ .search-input { width: 100%; padding: 20px 25px; font-size: 22px; border: 4px solid #ddd; border-radius: 50px; box-sizing: border-box; outline: none; box-shadow: 0 10px 25px rgba(0,0,0,0.1); transition: all 0.3s ease; }
21
+ .search-input:focus { border-color: var(--accent); transform: translateY(-2px); }
22
+
23
+ /* κ²°κ³Ό μΉ΄λ“œ */
24
+ .card { background: var(--card); border-radius: 15px; padding: 30px; margin-bottom: 25px; box-shadow: 0 5px 20px rgba(0,0,0,0.05); border-left: 8px solid #dcdde1; transition: transform 0.2s; }
25
+ .card:hover { transform: scale(1.01); }
26
+ .card.exact { border-left-color: var(--accent); }
27
 
28
+ .badge { display: inline-block; padding: 4px 12px; border-radius: 20px; font-size: 12px; font-weight: bold; margin-bottom: 10px; }
29
+ .badge-cat { background: #f1f2f6; color: #7f8c8d; }
30
+ .badge-exact { background: var(--accent); color: white; margin-left: 10px; }
31
 
32
+ .word-row { display: flex; align-items: baseline; gap: 15px; flex-wrap: wrap; }
33
+ .word-text { font-size: 32px; font-weight: 900; color: #2980b9; text-transform: uppercase; }
34
+ .meaning-text { font-size: 20px; color: #2c3e50; font-weight: 600; }
35
+ .meaning-en { color: #95a5a6; font-size: 16px; font-style: italic; }
36
+
37
+ /* 데이터 κ·Έλ¦¬λ“œ (νŒŒμƒμ–΄, λ³€μ΄ν˜•) */
38
+ .grid-container { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 15px; margin-top: 20px; }
39
+ .data-list { background: #fdfdfd; border: 1px solid #f1f1f1; border-radius: 10px; padding: 15px; }
40
+ .list-title { font-size: 14px; font-weight: bold; color: #34495e; border-bottom: 2px solid #3498db; padding-bottom: 5px; margin-bottom: 10px; }
41
+ .item-row { display: flex; font-size: 14px; margin-bottom: 4px; }
42
+ .item-key { font-weight: bold; width: 110px; color: #e67e22; }
43
+
44
+ /* 상세 정보 (검색 μ œμ™Έ ꡬ역) */
45
+ .details { margin-top: 20px; padding-top: 15px; border-top: 1px dashed #eee; font-size: 14px; color: #555; }
46
+ .detail-item { margin-bottom: 5px; }
47
+ .label { font-weight: bold; color: #7f8c8d; margin-right: 5px; }
48
+ .example-box { color: #2980b9; background: #ebf5fb; padding: 10px; border-radius: 5px; margin-top: 5px; }
49
  </style>
50
  </head>
51
  <body>
52
 
53
  <div class="container">
54
+ <div class="header-panel">
55
+ <h1>Huiucl Engine <small style="font-size: 14px; opacity: 0.8;">v3.0 Auto-Load</small></h1>
56
+ <div id="loader">βš™οΈ 데이터λ₯Ό λΆˆλŸ¬μ˜€λŠ” 쀑...</div>
 
 
 
 
 
57
  </div>
58
 
59
+ <div class="search-container">
60
+ <input type="text" id="query" class="search-input" placeholder="검색어λ₯Ό μž…λ ₯ν•˜μ„Έμš” (su, a, mi, μ œμž‘ν•˜λ‹€...)" onkeyup="search()">
61
  </div>
62
 
 
63
  <div id="results"></div>
64
  </div>
65
 
66
  <script>
67
  let lexicon = {};
68
 
69
+ // [μžλ™ λ‘œλ“œ] νŽ˜μ΄μ§€ λ‘œλ“œ μ‹œ Huiucl.json νŒŒμΌμ„ μ¦‰μ‹œ μ½μ–΄μ˜΄
70
+ window.onload = async function() {
71
+ try {
72
+ const response = await fetch('Huiucl.json');
73
+ if (!response.ok) throw new Error('νŒŒμΌμ„ 찾을 수 μ—†μŠ΅λ‹ˆλ‹€.');
74
+ const data = await response.json();
75
+
76
+ process(data);
77
+
78
+ document.getElementById('loader').innerText = `βœ… ${Object.keys(lexicon).length}개 ν•­λͺ© λ‘œλ“œ μ™„λ£Œ. λ°”λ‘œ 검색 κ°€λŠ₯ν•©λ‹ˆλ‹€.`;
79
+ setTimeout(() => document.getElementById('loader').style.display = 'none', 3000);
80
+ } catch (err) {
81
+ document.getElementById('loader').innerHTML = `❌ 였λ₯˜: ${err.message}<br><small>Huiucl.json 파일이 같은 폴더에 μžˆλŠ”μ§€ ν™•μΈν•˜μ„Έμš”.</small>`;
82
+ }
83
+ };
 
 
 
 
84
 
85
+ function process(obj, parentKey = null, currentCat = null) {
 
86
  if (typeof obj !== 'object' || obj === null) return;
87
 
88
+ // 1. λŒ€λͺ…사 μ„Ήμ…˜ (a, i, u λ“± ν•œ κΈ€μž λŒ€λͺ…사 μ™„λ²½ λ‘œλ“œ)
89
+ const pSects = ["1st_Person", "2nd_Person", "3rd_Person", "Demonstratives"];
90
+ if (pSects.includes(parentKey)) {
91
+ for (const [pk, pv] of Object.entries(obj)) {
92
+ lexicon[pk] = {
93
+ meaning_ko: pv,
94
+ _category: `Pronoun (${parentKey})`,
95
+ _search_target: `${pk} ${pv}`.toLowerCase()
96
  };
97
  }
98
  return;
99
  }
100
 
101
+ // 2. 일반 단어 처리
102
+ const keys = ['meaning_ko', 'ko', 'definition', 'morpheme', 'meaning_en', 'en'];
103
+ const hasInfo = keys.some(k => k in obj);
104
 
105
+ if (hasInfo && parentKey) {
106
  let entry = { ...obj };
107
+ if (entry.definition && typeof entry.definition === 'object') Object.assign(entry, entry.definition);
 
 
108
 
109
+ // [μ€‘μš”] 검색 νƒ€κ²Ÿμ—μ„œ usage, example은 제거 (μ˜€λ‘œμ§€ 단어λͺ…κ³Ό 뜻만)
110
+ let targets = [parentKey];
111
+ keys.forEach(k => { if (entry[k]) targets.push(entry[k]); });
112
 
113
  entry._category = currentCat;
114
+ entry._search_target = targets.join(" ").toLowerCase();
115
  lexicon[parentKey] = entry;
116
  }
117
 
118
  for (const [k, v] of Object.entries(obj)) {
119
  if (typeof v === 'object') {
120
  const nextCat = (parentKey === null) ? k : currentCat;
121
+ if (k !== 'Settings') process(v, k, nextCat);
122
  }
123
  }
124
  }
125
 
126
+ function search() {
127
+ const q = document.getElementById('query').value.trim().toLowerCase();
128
+ const resDiv = document.getElementById('results');
129
+ resDiv.innerHTML = '';
130
+ if (!q) return;
 
 
131
 
132
+ let found = [];
133
+ // μ •ν™•ν•œ 독립 ν˜•νƒœμ†Œ 인지λ₯Ό μœ„ν•œ μ •κ·œμ‹
134
+ const re = new RegExp(`(^|[^a-zA-Zκ°€-힣])${q}($|[^a-zA-Zκ°€-힣])`, 'i');
135
 
136
  for (const [word, info] of Object.entries(lexicon)) {
137
+ const wordL = word.toLowerCase();
138
+ const target = info._search_target || "";
139
 
140
+ if (q === wordL) found.push({ word, info, score: 2 });
141
+ else if (re.test(target) || (q.length > 1 && target.includes(q))) found.push({ word, info, score: 1 });
 
 
 
142
  }
143
 
144
+ found.sort((a, b) => b.score - a.score).forEach(item => {
 
 
 
 
 
 
145
  const info = item.info;
146
+ const card = document.createElement('div');
147
+ card.className = `card ${item.score === 2 ? 'exact' : ''}`;
148
+
149
+ // νŒŒμƒμ–΄/λ³€μ΄ν˜• κ·Έλ¦¬λ“œ 생성
150
+ let gridHtml = '';
 
151
  const derivs = info.derivations || info.derivation;
152
  if (derivs) {
153
+ gridHtml += `<div class="data-list"><div class="list-title">πŸ“‚ 기둝된 νŒŒμƒμ–΄</div>`;
154
+ for(const [dw, dm] of Object.entries(derivs)) gridHtml += `<div class="item-row"><span class="item-key">${dw}</span> <span>${dm}</span></div>`;
155
+ gridHtml += `</div>`;
156
  }
157
 
 
158
  let vars = {};
159
  ["tense_variants", "aspect_voice", "variants"].forEach(k => { if(info[k]) Object.assign(vars, info[k]); });
160
  if (Object.keys(vars).length > 0) {
161
+ gridHtml += `<div class="data-list"><div class="list-title">πŸ“‚ μ‹œμ œ 및 변이</div>`;
162
+ for(const [vw, vi] of Object.entries(vars)) gridHtml += `<div class="item-row"><span class="item-key">${vw}</span> <span>${vi.meaning_ko || vi.ko || ''}</span></div>`;
163
+ gridHtml += `</div>`;
164
  }
165
 
166
+ card.innerHTML = `
167
+ <span class="badge badge-cat">${info._category}</span>
168
+ ${item.score === 2 ? '<span class="badge badge-exact">μ •ν™•ν•œ 일치</span>' : ''}
169
+ <div class="word-row">
170
+ <span class="word-text">${item.word}</span>
171
+ <span class="meaning-text">${info.meaning_ko || info.ko || info.morpheme || ''}</span>
172
+ <span class="meaning-en">${info.meaning_en || info.en || ''}</span>
173
+ </div>
174
+ <div class="grid-container">${gridHtml}</div>
175
+ <div class="details">
176
+ ${info.usage ? `<div class="detail-item"><span class="label">πŸ“ μš©λ²•:</span> ${typeof info.usage === 'object' ? info.usage.ko : info.usage}</div>` : ''}
177
+ ${info.note ? `<div class="detail-item"><span class="label">πŸ’‘ μ°Έκ³ :</span> ${info.note}</div>` : ''}
178
+ ${info.example ? `<div class="example-box"><span class="label">πŸ“– 예문:</span> ${info.example}</div>` : ''}
179
  </div>
180
  `;
181
+ resDiv.appendChild(card);
182
  });
183
  }
184
  </script>