lss9566 commited on
Commit
8c44c8d
·
verified ·
1 Parent(s): 5137528

Upload 6 files

Browse files
Files changed (6) hide show
  1. README.md +70 -0
  2. app.py +99 -0
  3. index.html +712 -0
  4. questions.json +198 -0
  5. requirements.txt +2 -0
  6. simple_quiz.html +337 -0
README.md ADDED
@@ -0,0 +1,70 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: 인적자원관리 퀴즈
3
+ emoji: 📚
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: "4.0.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ # 📚 인적자원관리 퀴즈
13
+
14
+ 스마트폰에서도 편리하게 학습할 수 있는 인적자원관리 웹 퀴즈입니다.
15
+
16
+ ## ✨ 주요 기능
17
+
18
+ - **📱 모바일 최적화**: 스마트폰에서도 완벽한 사용자 경험
19
+ - **🎯 다중 정답 지원**: 체크박스를 통한 다중 선택 문제
20
+ - **🎨 현대적 디자인**: 글래스모피즘과 그라데이션을 활용한 세련된 UI
21
+ - **📊 실시간 점수**: 진행률과 점수를 실시간으로 확인
22
+ - **💡 상세한 해설**: 정답과 함께 제공되는 학습 해설
23
+
24
+ ## 🎮 사용 방법
25
+
26
+ 1. **문제 풀이**: 선택지를 클릭하여 답을 선택
27
+ 2. **다중 정답**: 체크박스가 있는 문제는 여러 개 선택 가능
28
+ 3. **해설 확인**: "다음" 버튼을 눌러 정답과 해설 확인
29
+ 4. **진행**: 이전/다음 버튼으로 문항 간 이동
30
+ 5. **완료**: 모든 문항 완료 후 최종 점수 확인
31
+
32
+ ## 📋 문제 구성
33
+
34
+ 총 **16개 문항**의 인적자원관리 관련 문제로 구성되어 있습니다:
35
+
36
+ - 전략적 인적자원관리
37
+ - 직무분석 및 직무설계
38
+ - 인력계획 및 채용
39
+ - 교육훈련 및 경력개발
40
+ - 인사평가 및 면접
41
+ - 기타 인적자원관리 이론
42
+
43
+ ## 🛠️ 기술 스택
44
+
45
+ - **Frontend**: HTML5, CSS3, JavaScript (Vanilla)
46
+ - **Backend**: Python, Gradio
47
+ - **Deployment**: Hugging Face Spaces
48
+ - **Design**: 글래스모피즘, 반응형 웹 디자인
49
+
50
+ ## 📱 모바일 지원
51
+
52
+ 이 퀴즈는 다양한 디바이스에서 최적화되어 작동합니다:
53
+
54
+ - 📱 스마트폰 (iOS, Android)
55
+ - 💻 태블릿
56
+ - 🖥️ 데스크톱
57
+
58
+ ## 🎯 학습 목표
59
+
60
+ - 인적자원관리의 핵심 개념 이해
61
+ - 실무 적용 가능한 지식 습득
62
+ - 체계적인 학습을 통한 전문성 향상
63
+
64
+ ## 📞 문의사항
65
+
66
+ 문제나 개선사항이 있으시면 언제든지 연락주세요!
67
+
68
+ ---
69
+
70
+ **💡 팁**: 틀린 문제는 해설을 꼼꼼히 읽어보세요. 반복 학습을 통해 더 나은 결과를 얻을 수 있습니다!
app.py ADDED
@@ -0,0 +1,99 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import json
3
+ from pathlib import Path
4
+ import re
5
+
6
+ # 현재 디렉토리 설정
7
+ current_dir = Path(__file__).parent
8
+
9
+ def create_quiz_app():
10
+ """Gradio 앱 생성"""
11
+
12
+ # simple_quiz.html 파일 읽기 (테스트용 전체 문서)
13
+ html_file = current_dir / "simple_quiz.html"
14
+ if html_file.exists():
15
+ with open(html_file, 'r', encoding='utf-8') as f:
16
+ full_html = f.read()
17
+ else:
18
+ full_html = """
19
+ <!DOCTYPE html>
20
+ <html lang=\"ko\">
21
+ <head><meta charset=\"UTF-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\"><title>퀴즈</title></head>
22
+ <body><h1>퀴즈 파일을 찾을 수 없습니다.</h1></body>
23
+ </html>
24
+ """
25
+
26
+ # questions.json 파일 읽기 (webquiz/ 하위 우선, 없으면 루트 fallback)
27
+ questions_file = current_dir / "webquiz" / "questions.json"
28
+ if not questions_file.exists():
29
+ fallback = current_dir / "questions.json"
30
+ if fallback.exists():
31
+ questions_file = fallback
32
+
33
+ if questions_file.exists():
34
+ with open(questions_file, 'r', encoding='utf-8') as f:
35
+ questions_data = json.load(f)
36
+ # JavaScript에서 questions 데이터를 직접 사용할 수 있도록 수정
37
+ questions_js = json.dumps(questions_data, ensure_ascii=False)
38
+
39
+ # 기존 문서 내에 샘플 데이터가 있다면 교체, 없으면 주입
40
+ if 'const questions = [' in full_html:
41
+ full_html = full_html.replace(
42
+ 'const questions = [',
43
+ f'const questions = {questions_js}; const _old_questions = ['
44
+ )
45
+ else:
46
+ # <body> 바로 뒤에 questions 변수를 주입
47
+ full_html = re.sub(
48
+ r"<body[^>]*>",
49
+ lambda m: f"{m.group(0)}\n<script>const questions = {questions_js};</script>",
50
+ full_html,
51
+ flags=re.IGNORECASE
52
+ )
53
+
54
+ # srcdoc에 안전하게 삽입하기 위해 작은따옴표를 HTML 엔티티로 치환
55
+ srcdoc_html = full_html.replace("'", "&#39;")
56
+
57
+ # iframe(srcdoc)로 전체 문서를 로드하여 스크립트가 확실히 실행되도록 함
58
+ iframe_html = f"""
59
+ <div style=\"width:100%; height:100vh;\">
60
+ <iframe id=\"quiz_iframe\" style=\"width:100%; height:100%; border:none;\" srcdoc='{srcdoc_html}'></iframe>
61
+ </div>
62
+ """
63
+
64
+ with gr.Blocks(
65
+ title="인적자원관리 퀴즈",
66
+ theme=gr.themes.Soft(),
67
+ css="""
68
+ .gradio-container {
69
+ max-width: none !important;
70
+ padding: 0 !important;
71
+ }
72
+ .main {
73
+ padding: 0 !important;
74
+ }
75
+ .block {
76
+ border: none !important;
77
+ box-shadow: none !important;
78
+ margin: 0 !important;
79
+ }
80
+ """
81
+ ) as app:
82
+
83
+ # 퀴즈 인터페이스를 HTML로 직접 렌더링 (iframe 사용)
84
+ gr.HTML(
85
+ value=iframe_html,
86
+ elem_id="quiz_interface"
87
+ )
88
+
89
+ return app
90
+
91
+ # Gradio 앱 생성 및 실행
92
+ if __name__ == "__main__":
93
+ app = create_quiz_app()
94
+ app.launch(
95
+ server_name="0.0.0.0",
96
+ server_port=7860,
97
+ share=False,
98
+ show_error=True
99
+ )
index.html ADDED
@@ -0,0 +1,712 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!doctype html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="utf-8"/>
5
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
6
+ <title>웹 퀴즈</title>
7
+ <style>
8
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
9
+
10
+ * {
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
16
+ line-height: 1.6;
17
+ margin: 0;
18
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
19
+ color: #1a202c;
20
+ min-height: 100vh;
21
+ padding: 20px 0;
22
+ }
23
+
24
+ .container {
25
+ max-width: 800px;
26
+ margin: 0 auto;
27
+ padding: 0 20px;
28
+ }
29
+
30
+ .quiz-header {
31
+ background: rgba(255, 255, 255, 0.95);
32
+ backdrop-filter: blur(20px);
33
+ border-radius: 20px;
34
+ padding: 30px;
35
+ margin-bottom: 25px;
36
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
37
+ border: 1px solid rgba(255, 255, 255, 0.2);
38
+ }
39
+
40
+ .quiz-title {
41
+ font-size: 28px;
42
+ font-weight: 700;
43
+ margin: 0 0 15px 0;
44
+ background: linear-gradient(135deg, #667eea, #764ba2);
45
+ -webkit-background-clip: text;
46
+ -webkit-text-fill-color: transparent;
47
+ background-clip: text;
48
+ text-align: center;
49
+ }
50
+
51
+ .quiz-controls {
52
+ display: flex;
53
+ gap: 12px;
54
+ justify-content: center;
55
+ flex-wrap: wrap;
56
+ }
57
+
58
+ .btn {
59
+ background: linear-gradient(135deg, #667eea, #764ba2);
60
+ color: white;
61
+ border: none;
62
+ border-radius: 12px;
63
+ padding: 12px 24px;
64
+ cursor: pointer;
65
+ font-weight: 500;
66
+ font-size: 14px;
67
+ transition: all 0.3s ease;
68
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
69
+ }
70
+
71
+ .btn:hover:not(:disabled) {
72
+ transform: translateY(-2px);
73
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
74
+ }
75
+
76
+ .btn:disabled {
77
+ opacity: 0.5;
78
+ cursor: not-allowed;
79
+ transform: none;
80
+ box-shadow: 0 2px 8px rgba(102, 126, 234, 0.2);
81
+ }
82
+
83
+ .status-bar {
84
+ background: rgba(255, 255, 255, 0.9);
85
+ backdrop-filter: blur(10px);
86
+ border-radius: 15px;
87
+ padding: 15px 25px;
88
+ margin-bottom: 20px;
89
+ display: flex;
90
+ justify-content: space-between;
91
+ align-items: center;
92
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
93
+ border: 1px solid rgba(255, 255, 255, 0.3);
94
+ }
95
+
96
+ .progress-info {
97
+ font-weight: 500;
98
+ color: #4a5568;
99
+ }
100
+
101
+ .score-display {
102
+ background: linear-gradient(135deg, #48bb78, #38a169);
103
+ color: white;
104
+ padding: 8px 16px;
105
+ border-radius: 20px;
106
+ font-weight: 600;
107
+ font-size: 14px;
108
+ box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
109
+ }
110
+
111
+ .message-area {
112
+ min-height: 24px;
113
+ text-align: center;
114
+ font-weight: 500;
115
+ color: #e53e3e;
116
+ margin-bottom: 15px;
117
+ }
118
+
119
+ .card {
120
+ background: rgba(255, 255, 255, 0.95);
121
+ backdrop-filter: blur(20px);
122
+ border-radius: 20px;
123
+ padding: 30px;
124
+ margin: 20px 0;
125
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
126
+ border: 1px solid rgba(255, 255, 255, 0.2);
127
+ transition: all 0.3s ease;
128
+ }
129
+
130
+ .card:hover {
131
+ transform: translateY(-2px);
132
+ box-shadow: 0 25px 50px rgba(0, 0, 0, 0.15);
133
+ }
134
+
135
+ .question-text {
136
+ font-size: 18px;
137
+ font-weight: 500;
138
+ line-height: 1.7;
139
+ margin-bottom: 25px;
140
+ color: #2d3748;
141
+ }
142
+
143
+ .choice-wrapper {
144
+ display: flex;
145
+ align-items: center;
146
+ margin: 12px 0;
147
+ }
148
+
149
+ .choice {
150
+ display: block;
151
+ border: 2px solid #e2e8f0;
152
+ border-radius: 12px;
153
+ padding: 16px 20px;
154
+ margin: 0;
155
+ background: white;
156
+ cursor: pointer;
157
+ transition: all 0.3s ease;
158
+ font-weight: 500;
159
+ color: #4a5568;
160
+ flex: 1;
161
+ }
162
+
163
+ .choice:hover {
164
+ border-color: #667eea;
165
+ background: #f7fafc;
166
+ transform: translateX(4px);
167
+ }
168
+
169
+ .choice.selected {
170
+ border-color: #667eea;
171
+ background: linear-gradient(135deg, #ebf4ff, #e6fffa);
172
+ color: #2b6cb0;
173
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
174
+ }
175
+
176
+ .choice.correct {
177
+ border-color: #48bb78;
178
+ background: linear-gradient(135deg, #f0fff4, #c6f6d5);
179
+ color: #22543d;
180
+ box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
181
+ }
182
+
183
+ .choice.wrong {
184
+ border-color: #f56565;
185
+ background: linear-gradient(135deg, #fff5f5, #fed7d7);
186
+ color: #742a2a;
187
+ box-shadow: 0 4px 12px rgba(245, 101, 101, 0.3);
188
+ }
189
+
190
+ .choice:disabled {
191
+ cursor: not-allowed;
192
+ }
193
+
194
+ .checkbox-input {
195
+ margin-right: 12px;
196
+ width: 18px;
197
+ height: 18px;
198
+ accent-color: #667eea;
199
+ }
200
+
201
+ .result-card {
202
+ background: rgba(255, 255, 255, 0.95);
203
+ backdrop-filter: blur(20px);
204
+ border-radius: 20px;
205
+ padding: 30px;
206
+ margin-top: 20px;
207
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
208
+ border: 1px solid rgba(255, 255, 255, 0.2);
209
+ }
210
+
211
+ .result-status {
212
+ font-size: 20px;
213
+ font-weight: 700;
214
+ margin-bottom: 15px;
215
+ text-align: center;
216
+ padding: 15px;
217
+ border-radius: 12px;
218
+ }
219
+
220
+ .result-correct {
221
+ background: linear-gradient(135deg, #c6f6d5, #9ae6b4);
222
+ color: #22543d;
223
+ }
224
+
225
+ .result-wrong {
226
+ background: linear-gradient(135deg, #fed7d7, #fbb6ce);
227
+ color: #742a2a;
228
+ }
229
+
230
+ .answer-display {
231
+ font-size: 16px;
232
+ font-weight: 600;
233
+ margin: 15px 0;
234
+ padding: 15px;
235
+ background: #f7fafc;
236
+ border-radius: 10px;
237
+ border-left: 4px solid #667eea;
238
+ }
239
+
240
+ .explanation {
241
+ color: #718096;
242
+ font-size: 14px;
243
+ line-height: 1.6;
244
+ margin-top: 15px;
245
+ padding: 15px;
246
+ background: #f8f9fa;
247
+ border-radius: 10px;
248
+ border-left: 4px solid #a0aec0;
249
+ }
250
+
251
+ .meta-info {
252
+ color: #a0aec0;
253
+ font-size: 12px;
254
+ font-weight: 500;
255
+ margin-top: 20px;
256
+ text-align: center;
257
+ text-transform: uppercase;
258
+ letter-spacing: 0.5px;
259
+ }
260
+
261
+ .completion-card {
262
+ text-align: center;
263
+ background: linear-gradient(135deg, #667eea, #764ba2);
264
+ color: white;
265
+ border-radius: 20px;
266
+ padding: 40px;
267
+ box-shadow: 0 25px 50px rgba(102, 126, 234, 0.3);
268
+ }
269
+
270
+ .completion-title {
271
+ font-size: 32px;
272
+ font-weight: 700;
273
+ margin-bottom: 15px;
274
+ }
275
+
276
+ .completion-score {
277
+ font-size: 18px;
278
+ opacity: 0.9;
279
+ margin-bottom: 25px;
280
+ }
281
+
282
+ .restart-btn {
283
+ background: rgba(255, 255, 255, 0.2);
284
+ border: 2px solid rgba(255, 255, 255, 0.3);
285
+ color: white;
286
+ padding: 15px 30px;
287
+ border-radius: 12px;
288
+ font-weight: 600;
289
+ cursor: pointer;
290
+ transition: all 0.3s ease;
291
+ }
292
+
293
+ .restart-btn:hover {
294
+ background: rgba(255, 255, 255, 0.3);
295
+ transform: translateY(-2px);
296
+ }
297
+
298
+ @media (max-width: 768px) {
299
+ .container {
300
+ padding: 0 10px;
301
+ }
302
+
303
+ .quiz-header {
304
+ padding: 20px 15px;
305
+ margin-bottom: 15px;
306
+ }
307
+
308
+ .quiz-title {
309
+ font-size: 22px;
310
+ }
311
+
312
+ .quiz-controls {
313
+ gap: 8px;
314
+ }
315
+
316
+ .btn {
317
+ padding: 10px 18px;
318
+ font-size: 13px;
319
+ }
320
+
321
+ .status-bar {
322
+ padding: 12px 20px;
323
+ flex-direction: column;
324
+ gap: 10px;
325
+ text-align: center;
326
+ }
327
+
328
+ .card {
329
+ padding: 20px 15px;
330
+ margin: 15px 0;
331
+ }
332
+
333
+ .question-text {
334
+ font-size: 16px;
335
+ line-height: 1.6;
336
+ }
337
+
338
+ .choice-wrapper {
339
+ margin: 10px 0;
340
+ }
341
+
342
+ .choice {
343
+ padding: 14px 16px;
344
+ font-size: 14px;
345
+ line-height: 1.5;
346
+ }
347
+
348
+ .checkbox-input {
349
+ margin-right: 10px;
350
+ width: 16px;
351
+ height: 16px;
352
+ }
353
+
354
+ .result-card {
355
+ padding: 20px 15px;
356
+ }
357
+
358
+ .result-status {
359
+ font-size: 18px;
360
+ padding: 12px;
361
+ }
362
+
363
+ .answer-display {
364
+ font-size: 15px;
365
+ padding: 12px;
366
+ }
367
+
368
+ .explanation {
369
+ font-size: 13px;
370
+ padding: 12px;
371
+ }
372
+
373
+ .completion-card {
374
+ padding: 30px 20px;
375
+ }
376
+
377
+ .completion-title {
378
+ font-size: 26px;
379
+ }
380
+
381
+ .completion-score {
382
+ font-size: 16px;
383
+ }
384
+ }
385
+
386
+ @media (max-width: 480px) {
387
+ body {
388
+ padding: 10px 0;
389
+ }
390
+
391
+ .container {
392
+ padding: 0 8px;
393
+ }
394
+
395
+ .quiz-header {
396
+ padding: 15px 12px;
397
+ }
398
+
399
+ .quiz-title {
400
+ font-size: 20px;
401
+ }
402
+
403
+ .btn {
404
+ padding: 8px 14px;
405
+ font-size: 12px;
406
+ }
407
+
408
+ .status-bar {
409
+ padding: 10px 15px;
410
+ }
411
+
412
+ .card {
413
+ padding: 15px 12px;
414
+ }
415
+
416
+ .question-text {
417
+ font-size: 15px;
418
+ }
419
+
420
+ .choice {
421
+ padding: 12px 14px;
422
+ font-size: 13px;
423
+ }
424
+
425
+ .checkbox-input {
426
+ width: 14px;
427
+ height: 14px;
428
+ }
429
+
430
+ .completion-title {
431
+ font-size: 24px;
432
+ }
433
+
434
+ .completion-score {
435
+ font-size: 15px;
436
+ }
437
+ }
438
+ </style>
439
+ </head>
440
+ <body>
441
+ <div class="container">
442
+ <div class="quiz-header">
443
+ <h1 class="quiz-title">인적자원관리 퀴즈</h1>
444
+ <div class="quiz-controls">
445
+ <button id="prev" class="btn">← 이전</button>
446
+ <button id="next" class="btn">다음 →</button>
447
+ </div>
448
+ </div>
449
+
450
+ <div class="status-bar">
451
+ <div id="status" class="progress-info">문항 1/16</div>
452
+ <div class="score-display">점수: <span id="score-text">0</span></div>
453
+ </div>
454
+
455
+ <div id="message" class="message-area"></div>
456
+ <div id="quiz"></div>
457
+ </div>
458
+ <script>
459
+ let questions=[], idx=0, score=0, state=new Map(), finished=false;
460
+ function getState(i){ if(!state.has(i)) state.set(i,{selected:null, correct:false, revealed:false, scored:false}); return state.get(i); }
461
+ async function load(){
462
+ // 허깅페이스 환경에서는 이 함수가 호출되지 않을 수 있음
463
+ console.log('load() 함수가 호출되었습니다');
464
+ try{
465
+ const res=await fetch('questions.json',{cache:'no-store'});
466
+ if(!res.ok) throw new Error('HTTP '+res.status);
467
+ questions=await res.json();
468
+ }catch(e){
469
+ console.error('questions.json 로드 실패:', e);
470
+ const msg=document.getElementById('message');
471
+ if(msg) msg.textContent='문항 로드 실패: '+ (e && e.message ? e.message : e);
472
+ return;
473
+ }
474
+ idx=0; score=0; state.clear(); finished=false; render();
475
+ }
476
+
477
+ // 페이지 로드 완료 시 자동 실행
478
+ document.addEventListener('DOMContentLoaded', function() {
479
+ console.log('DOM 로드 완료');
480
+ if (typeof questions === 'undefined' || questions.length === 0) {
481
+ console.log('questions가 정의되지 않았거나 비어있음, load() 호출');
482
+ load();
483
+ } else {
484
+ console.log('questions 이미 정의됨, render() 호출');
485
+ render();
486
+ }
487
+ });
488
+ function normalize(s){return (s||'').toString().replace(/[\s ]+/g,'').toLowerCase();}
489
+ function escapeHtml(s){ const div=document.createElement('div'); div.textContent = (s ?? ''); return div.innerHTML; }
490
+ function render(){
491
+ const quiz=document.getElementById('quiz'); quiz.innerHTML='';
492
+ if(finished){
493
+ const done=document.createElement('div');
494
+ done.className='completion-card';
495
+ done.innerHTML = `
496
+ <div class="completion-title">퀴즈 완료! 🎉</div>
497
+ <div class="completion-score">최종 점수: ${score}/${questions.length} (${Math.round(score/questions.length*100)}%)</div>
498
+ <button class="restart-btn" onclick="restartQuiz()">다시 시작하기</button>
499
+ `;
500
+ quiz.appendChild(done);
501
+
502
+ const nextBtn=document.getElementById('next');
503
+ const prevBtn=document.getElementById('prev');
504
+ document.getElementById('status').textContent=`완료`;
505
+ document.getElementById('score-text').textContent=`${score}/${questions.length}`;
506
+ prevBtn.disabled = true;
507
+ nextBtn.textContent = '처음으로';
508
+ return;
509
+ }
510
+ const q=questions[idx]; const st=getState(idx);
511
+
512
+ const card=document.createElement('div'); card.className='card';
513
+ const questionDiv=document.createElement('div');
514
+ questionDiv.className='question-text';
515
+ questionDiv.textContent=q.prompt;
516
+ card.appendChild(questionDiv);
517
+
518
+ if(q.choices && q.choices.length){
519
+ const isMultiple = q.qtype === 'multiple';
520
+
521
+ q.choices.forEach((c,i)=>{
522
+ const wrapper=document.createElement('div');
523
+ wrapper.className='choice-wrapper';
524
+
525
+ if(isMultiple){
526
+ // 체크박스 방식
527
+ const checkbox=document.createElement('input');
528
+ checkbox.type='checkbox'; checkbox.id=`choice_${i}`; checkbox.value=c;
529
+ checkbox.className='checkbox-input';
530
+
531
+ const label=document.createElement('label');
532
+ label.htmlFor=`choice_${i}`; label.className='choice';
533
+ label.textContent=(i+1)+'. '+c;
534
+
535
+ if(!st.revealed){
536
+ checkbox.onchange=()=>selectMultiple(c, checkbox.checked);
537
+ if(st.selected && st.selected.includes && st.selected.includes(c)) {
538
+ checkbox.checked=true; label.classList.add('selected');
539
+ }
540
+ }else{
541
+ // 해설 단계: 정답/오답 시각화
542
+ const isCorrect = Array.isArray(q.answer) ? q.answer.includes(c) : normalize(c)===normalize(q.answer);
543
+ if(isCorrect) label.classList.add('correct');
544
+ if(st.selected && st.selected.includes && st.selected.includes(c) && !isCorrect) label.classList.add('wrong');
545
+ checkbox.disabled=true;
546
+ }
547
+
548
+ wrapper.appendChild(checkbox); wrapper.appendChild(label);
549
+ }else{
550
+ // 기존 라디오 버튼 방식
551
+ const btn=document.createElement('button'); btn.className='choice'; btn.textContent=(i+1)+'. '+c;
552
+ if(!st.revealed){
553
+ btn.onclick=()=>select(c);
554
+ if(st.selected===c) btn.classList.add('selected');
555
+ }else{
556
+ // 해설 단계: 정답/오답 시각화
557
+ const isCorrect = Array.isArray(q.answer) ? q.answer.includes(c) : normalize(c)===normalize(q.answer);
558
+ if(isCorrect) btn.classList.add('correct');
559
+ if(st.selected===c && !isCorrect) btn.classList.add('wrong');
560
+ btn.disabled=true;
561
+ }
562
+ wrapper.appendChild(btn);
563
+ }
564
+
565
+ card.appendChild(wrapper);
566
+ });
567
+ }else{
568
+ const input=document.createElement('input');
569
+ input.placeholder='정답 입력';
570
+ input.style='width:100%;padding:15px;border:2px solid #e2e8f0;border-radius:12px;margin-top:15px;font-size:16px;';
571
+ input.value = st.selected||'';
572
+ const check=document.createElement('button');
573
+ check.className='btn';
574
+ check.style='margin-top:15px;width:100%;';
575
+ check.textContent= st.revealed? '제출됨' : '제출';
576
+ if(!st.revealed){
577
+ check.onclick=()=>select(input.value.trim());
578
+ }else{
579
+ check.disabled=true;
580
+ }
581
+ card.appendChild(input); card.appendChild(check);
582
+ }
583
+
584
+ // 해설 영역 (선택 후 다음을 누르면 표시)
585
+ if(st.revealed){
586
+ const resultCard=document.createElement('div');
587
+ resultCard.className='result-card';
588
+
589
+ // 정답/오답 표시
590
+ const resultDiv = document.createElement('div');
591
+ resultDiv.className = st.correct ? 'result-status result-correct' : 'result-status result-wrong';
592
+ resultDiv.textContent = st.correct ? '정답입니다! ✅' : '틀렸습니다! ❌';
593
+ resultCard.appendChild(resultDiv);
594
+
595
+ // 정답 번호 표시
596
+ let answerText = '';
597
+ if(Array.isArray(q.answer)){
598
+ // 다�� 정답: 번호로 표시
599
+ const answerNumbers = [];
600
+ q.answer.forEach(ans => {
601
+ const index = q.choices.findIndex(choice => normalize(choice) === normalize(ans));
602
+ if(index !== -1) answerNumbers.push(index + 1);
603
+ });
604
+ answerText = answerNumbers.join(', ');
605
+ }else{
606
+ // 단일 정답: 번호로 표시
607
+ const index = q.choices.findIndex(choice => normalize(choice) === normalize(q.answer));
608
+ answerText = index !== -1 ? (index + 1).toString() : '1';
609
+ }
610
+
611
+ const answerDiv = document.createElement('div');
612
+ answerDiv.className = 'answer-display';
613
+ answerDiv.innerHTML = `<strong>정답</strong>: ${answerText}`;
614
+ resultCard.appendChild(answerDiv);
615
+
616
+ // 해설 표시
617
+ if(q.explanation && q.explanation.toString().trim()){
618
+ const explanationDiv = document.createElement('div');
619
+ explanationDiv.className = 'explanation';
620
+ explanationDiv.innerHTML = `<strong>해설:</strong> ${escapeHtml(q.explanation.toString())}`;
621
+ resultCard.appendChild(explanationDiv);
622
+ }
623
+
624
+ card.appendChild(resultCard);
625
+ }
626
+
627
+ const meta=document.createElement('div');
628
+ meta.className='meta-info';
629
+ meta.textContent='유형: '+q.qtype.toUpperCase();
630
+ card.appendChild(meta);
631
+
632
+ quiz.appendChild(card);
633
+
634
+ const nextBtn=document.getElementById('next');
635
+ const prevBtn=document.getElementById('prev');
636
+ document.getElementById('status').textContent=`문항 ${idx+1}/${questions.length}`;
637
+ document.getElementById('score-text').textContent=`${score}/${questions.length}`;
638
+ prevBtn.disabled = idx===0;
639
+ nextBtn.textContent = st.revealed ? (idx===questions.length-1 ? '완료' : '다음 →') : '다음 →';
640
+ }
641
+ function selectMultiple(val, checked){
642
+ const q=questions[idx]; const st=getState(idx);
643
+ if(st.revealed) return; // 해설 단계에서는 선택 잠금
644
+
645
+ if(!st.selected) st.selected = [];
646
+ if(!Array.isArray(st.selected)) st.selected = [];
647
+
648
+ if(checked){
649
+ if(!st.selected.includes(val)) st.selected.push(val);
650
+ }else{
651
+ st.selected = st.selected.filter(item => item !== val);
652
+ }
653
+
654
+ document.getElementById('message').textContent='';
655
+ render(); // 시각적 반영
656
+ }
657
+ function select(val){
658
+ const q=questions[idx]; const st=getState(idx);
659
+ if(st.revealed) return; // 해설 단계에서는 선택 잠금
660
+ st.selected = val;
661
+ st.correct = normalize(val)===normalize(q.answer);
662
+ document.getElementById('message').textContent='';
663
+ render(); // 시각적 반영
664
+ }
665
+ function restartQuiz(){
666
+ idx=0; score=0; state.clear(); finished=false;
667
+ document.getElementById('message').textContent='';
668
+ render();
669
+ }
670
+ document.getElementById('prev').onclick=()=>{ if(finished) return; if(idx>0){ idx--; render(); }};
671
+ document.getElementById('next').onclick=()=>{
672
+ if(finished){ restartQuiz(); return; }
673
+ const st=getState(idx);
674
+ const q=questions[idx];
675
+ const msg=document.getElementById('message');
676
+
677
+ if(!st.revealed){
678
+ if(st.selected==null || (Array.isArray(st.selected) && st.selected.length===0)){
679
+ msg.textContent='먼저 답을 선택하세요.'; return;
680
+ }
681
+
682
+ // 해설 표시 단계로 전환하며 점수 반영(최초 1회)
683
+ st.revealed=true;
684
+
685
+ // 채점 로직
686
+ let isCorrect = false;
687
+ if(q.qtype === 'multiple'){
688
+ // 다중 정답: 선택한 답과 정답이 정확히 일치해야 함
689
+ if(Array.isArray(q.answer) && Array.isArray(st.selected)){
690
+ const sortedAnswer = [...q.answer].sort();
691
+ const sortedSelected = [...st.selected].sort();
692
+ isCorrect = sortedAnswer.length === sortedSelected.length &&
693
+ sortedAnswer.every((ans, i) => normalize(ans) === normalize(sortedSelected[i]));
694
+ }
695
+ }else{
696
+ // 단일 정답
697
+ isCorrect = Array.isArray(q.answer) ?
698
+ q.answer.some(ans => normalize(st.selected) === normalize(ans)) :
699
+ normalize(st.selected) === normalize(q.answer);
700
+ }
701
+
702
+ st.correct = isCorrect;
703
+ if(st.correct && !st.scored){ score++; st.scored=true; }
704
+ render();
705
+ }else{
706
+ if(idx<questions.length-1){ idx++; render(); }
707
+ else { finished=true; render(); }
708
+ }
709
+ };
710
+ load();
711
+ </script>
712
+ </body></html>
questions.json ADDED
@@ -0,0 +1,198 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ [
2
+ {
3
+ "qtype": "mcq",
4
+ "prompt": "조직이 더욱 커져 해외로 진출하거나 글로벌 지점을 늘리는 등 운영 범위가 확장될 때 나타납니다.해외 사업장의 법인장이나 임원들을 관리해야 할 필요성이 생기면서, 인사 담당 임원 (전무, 부사장, 상무 등)을 조직에 보직합니다. 이와 같은 인적자원관리의 유형을 무엇이라 하는가?",
5
+ "choices": [
6
+ "원시적인사관리(PPM)",
7
+ "인사관리(PM)",
8
+ "인적자원관리(HRM)",
9
+ "전략적인적자원관리(SHRM)"
10
+ ],
11
+ "answer": "전략적인적자원관리(SHRM)"
12
+ },
13
+ {
14
+ "qtype": "mcq",
15
+ "prompt": "다음 중 직무분석에 활용에 대해 틀린 내용을 고르시오.",
16
+ "choices": [
17
+ "직무분석을 기초로 직무가치를 산정하고 보상체계에 반영할 수 있다.",
18
+ "직무 분석은 조직 내에 존재하는 각 직무의 구체적인 내용, 요건, 역할과 책임을 체계적으로 분석하고 정의하는 활동. 인사 관리의 기초.",
19
+ "직무 기술서는 직무 분석을 통해 도출된 결과물로, 특정 직무를 구성하는 일의 전체 내용(과업, 절차, 책임 등)을 기술한 문서. 채용, 평가, 교육 등의 기준으로 활용.",
20
+ "경력개발을 위한 직무수행 요구조건(지식, 기술 등) 정립 및 이동, 승진을 위한 필요한 자료 제공 및 교육훈련 정보 제공"
21
+ ],
22
+ "answer": "직무분석을 기초로 직무가치를 산정하고 보상체계에 반영할 수 있다."
23
+ },
24
+ {
25
+ "qtype": "mcq",
26
+ "prompt": "다음 중 직무개념에 대하여 틀린 설명을 고르시오.",
27
+ "choices": [
28
+ "직위 (Position)는 한 개인이 수행하는 하나 혹은 그 이상의 의무로 구성 ,특정 개인이 수행하는 모든 과업의 집단",
29
+ "의무 및 책임(Duty & Responsibility)은 특정 개인이 수행하는 것으로 여러 가지 유사한 과업으로 이루어짐",
30
+ "요소 (要所/Element)는 업무의 가장 작은 단위이며 전화를 받는다던가, 기록을 하는 등의 작은 단위업무",
31
+ "과업(Task)은 업무량 분석의 단위가 된다."
32
+ ],
33
+ "answer": "과업(Task)은 업무량 분석의 단위가 된다."
34
+ },
35
+ {
36
+ "qtype": "multiple",
37
+ "prompt": "직무분석 절차에 따른 설명이다. 틀린 설명을 고르시오.",
38
+ "choices": [
39
+ "직무정보수집방법에는 면접법, 관찰법, 설문지법(조사표법)이 있다.",
40
+ "TFT 등 일시적 활동은 기재하지 않음",
41
+ "작성원칙에 인원/시간 부족 등으로 수행하지 못하는 일이지만 수행이 필요한 일은 기록",
42
+ "직무조사표 작성시에 조직내 참여한 활동(예, TFT 활동)들을 모두 포함하도록 함.",
43
+ "직무담당기간이 짧은 직무담당자의 경우 전임자에게 작성하도록 함."
44
+ ],
45
+ "answer": [
46
+ "직무조사표 작성시에 조직내 참여한 활동(예, TFT 활동)들을 모두 포함하도록 함.",
47
+ "직무담당기간이 짧은 직무담당자의 경우 전임자에게 작성하도록 함."
48
+ ],
49
+ "explanation": "작성원칙 -원칙적으로 직무담당자가 작성(경험이 짧은 경우, 전임자나 차상위자 조언) -TFT 등 일시적 활동은 기재하지 않음 -인원/시간 부족 등으로 수행하지 못하는 일지만 수행이 필요한 일은 기록"
50
+ },
51
+ {
52
+ "qtype": "mcq",
53
+ "prompt": "인력계획의 의의와 중요성에 대한 설명 중 틀린 것을 고르시오.",
54
+ "choices": [
55
+ "기업의 전략적 목표를 달성하기 위하여 각 직무, 과업별로 적정자격을 보유한 인력을 규명하고,적정한 인력 수를 확보하여야 함.",
56
+ "인력계획은 현재 및 장래의 각 시점에서 기업이 필요로 하는 특성을 지닌 인원의 수를 예측하고, 사내, 사외에서 공급 가능한 인력을 고려하여 인력 의 수급을 조정하는 활동.",
57
+ "정원계획이란 현재의 시점에서 정태적인 인력계획을 말한다.",
58
+ "미래 적정한 기간동안(예, 5개년) 중장기 인력계획을 수립하기도 한다.",
59
+ "인력계획은 외부노동시장의 상황을 고려하여 현재 조직내부에서가용할 수 있은 인원의 수를 파악하는 것이다."
60
+ ],
61
+ "answer": "인력계획은 외부노동시장의 상황을 고려하여 현재 조직내부에서가용할 수 있은 인원의 수를 파악하는 것이다."
62
+ },
63
+ {
64
+ "qtype": "mcq",
65
+ "prompt": "인력산정시 근로시간 기준에 대한 설명이다. 틀린 것을 고르시오.",
66
+ "choices": [
67
+ "ILO 여유율은 여유시간에 업무준비, 수리 등등 용변, 휴식 등도 포함.",
68
+ "생산직, 육체근로자 여유시간 많아야 한다.",
69
+ "팀별 적정인원 산정시 여유율을 고려해야 한다. ILO에서는 약 15%를 ���유율 기준으로 권고하고 있다.",
70
+ "남성 보다 여성의 여유시간 더 많아야 한다. 남성 9%, 여성 11%"
71
+ ],
72
+ "answer": "팀별 적정인원 산정시 여유율을 고려해야 한다. ILO에서는 약 15%를 여유율 기준으로 권고하고 있다."
73
+ },
74
+ {
75
+ "qtype": "mcq",
76
+ "prompt": "다음 중 경력에 관한 내용으로 올바르지 않은 것을 고르시오.",
77
+ "choices": [
78
+ "한 개인이 입사로부터 퇴직에 이르기까지 경력경로를 개인과 조직이 함께 계획하고 실천하는 활동",
79
+ "경력개발이란 구성원 자신이 성공적인 조직생활을 하겠다는 의지와 경영자들이 인재를 육성하겠다는 의도를 포괄하는 개념",
80
+ "기업내 전 종업원, 특정 부류 직책에 있는 사원을 대상으로 개개인의 장기적인 직무수행 능력과 적성을 평가하고 개발하여 적성에 맞는 직무를 보임하는 계획적 활동",
81
+ "조기퇴직, 조기이탈을 막기 위한 방안 경력개발활동 등을 지원하기 위한 다양한 제도를 도입한다."
82
+ ],
83
+ "answer": "조기퇴직, 조기이탈을 막기 위한 방안 경력개발활동 등을 지원하기 위한 다양한 제도를 도입한다."
84
+ },
85
+ {
86
+ "qtype": "mcq",
87
+ "prompt": "역량 면접(Competency-based Interview)이 미래의 성과를 예측하기 위해 주로 무엇을 하는지 틀린 설명을 고르시오",
88
+ "choices": [
89
+ "역량 면접이 미래의 성과를 예측하는 가장 중요한 근거는 과거의 행동이 미래의 행동을 예측한다는 기본 원칙에 있다.",
90
+ "면접에서는 지원자의 과거 행동을 파악한다. 이를 위하여 탐색질문(Probing Question)을 한다.",
91
+ "지원자가 경험한 모든 것이 미래에 반복된다는 것이 전제이다(행동의 전이가능성), 면접에서 이를 체크하는 것.",
92
+ "지원자의 지원에 생각이나 미래 상황에 대한 의견을 묻고 이를 중심으로 평가한다."
93
+ ],
94
+ "answer": "지원자의 지원에 생각이나 미래 상황에 대한 의견을 묻고 이를 중심으로 평가한다."
95
+ },
96
+ {
97
+ "qtype": "multiple",
98
+ "prompt": "다양한 면접에 관한 내용 중 틀린 것을 고르시오.",
99
+ "choices": [
100
+ "1:1 면접 -면접관 1, 지원자 1 -시간과 비용 소요 -임원면접",
101
+ "Presentation 면접 -5~10분간 PT -면접관 3~4명 -사전에 문제와 30분 제공",
102
+ "Role Play -지원자와 면접관 -지원자 多 -금융권 활용(금융상품 설명)",
103
+ "Assessment Center -위 모든 면접을 종합 -가장 객관적 평가 -임원 승진 시 활용",
104
+ "역량면접은 경험이나 상황을 중심으로 1:1 심층면접으로 진행해야 한다.",
105
+ "패널면접은 피면접자 다수, 면접관 다수로 구성되는 면접으로 면접 시간을 절약할 수 있는 장점이 있다.",
106
+ "Panel 면접 -면접관 4, 지원자 1 -지원자 당황 -실무팀장 면접",
107
+ "Group Discussion -지원자 多 -찬반 토론 등 다양 -대기업 신입사원 전형"
108
+ ],
109
+ "answer": [
110
+ "역량면접은 경험이나 상황을 중심으로 1:1 심층면접으로 진행해야 한다.",
111
+ "패널면접은 피면접자 다수, 면접관 다수로 구성되는 면접으로 면접 시간을 절약할 수 있는 장점이 있다."
112
+ ]
113
+ },
114
+ {
115
+ "qtype": "mcq",
116
+ "prompt": "다음은 인사평가요소에 대한 설명이다. 바르지 않은 내용을 고르시오.",
117
+ "choices": [
118
+ "개인별 직무수행 결과와 관련된 업적평가, 성공적인 직무 수행방법과 관련된 역량평가라는 두 개의 축으로 설계.",
119
+ "업적평가(What)의 평가지표는 계량지표(정량지표)와 비계량 지표(정성지표)이다.",
120
+ "역량평가(How)의 평가지표는 역량 모델링을 통한 역량 및 행동지표 도출, 역량별 평가지표(Behavior Indicator) 활용",
121
+ "역량평가(How)의 평가방법은 BOS(Behavioral Observation Scales) 방식의 평정 척도법이다.",
122
+ "전통적 인사고과-평가요소는 업적, 역량이다"
123
+ ],
124
+ "answer": "전통적 인사고과-평가요소는 업적, 역량이다"
125
+ },
126
+ {
127
+ "qtype": "mcq",
128
+ "prompt": "사내모집과 사외모집의 장점과 단점에 관한 내용이다. 틀린 것을 고르시오.",
129
+ "choices": [
130
+ "사내모집 - 성장기의 인력수요를 충족시킬 수 있다.",
131
+ "사내모집 - 능력이 충분히 검증된 사람을 확보할 수 있다.",
132
+ "사내모집 - 성장기에는 유자격자를 충분히 공급하지 못함",
133
+ "사외모집 - 새로운 아이디어와 견해가 유인됨."
134
+ ],
135
+ "answer": "사내모집 - 성장기의 인력수요를 충족시킬 수 있다."
136
+ },
137
+ {
138
+ "qtype": "mcq",
139
+ "prompt": "다음 Calibration Meeting에 관한 설명 중 틀린 것을 고르시오.",
140
+ "choices": [
141
+ "캘리브레이션 미팅은 평가과정에서 평가의 공정성과 일관성을 확보하기 위해 매우 중요한 과정.",
142
+ "평가대상자의 상사와 다른 상사(또는 차 상위 상사)가 모여서 평가대상자의 평가결과를 조정하고 평가등급을 확인",
143
+ "Calibration Meeting은 상대평가에 따른 우수한 집단의 평가상의 불이익을 방지하기 위한 제도",
144
+ "주요 목적은 ‘평가 일관성 확보’, ‘편견의 최소화’, 그리고 ‘성과관리 강화’이다."
145
+ ],
146
+ "answer": "Calibration Meeting은 상대평가에 따른 우수한 집단의 평가상의 불이익을 방지하기 위한 제도"
147
+ },
148
+ {
149
+ "qtype": "mcq",
150
+ "prompt": "버디의 선정요건 중 틀린 것을 고르시오.",
151
+ "choices": [
152
+ "신입 사원의 질문에 친절하고 꼼꼼하게 답변할 수 있는 사람",
153
+ "신입 사원의 업무와 관련된 경험 및 지식을 갖춘 사람",
154
+ "신입사원의 성공적 조직적응을 위하여 같은 경력목표를 가진 사람",
155
+ "신입 사원과 친밀한 관계를 형성할 수 있는 성격의 사람"
156
+ ],
157
+ "answer": "신입사원의 성공적 조직적응을 위하여 같은 경력목표를 가진 사람"
158
+ },
159
+ {
160
+ "qtype": "mcq",
161
+ "prompt": "목표설정 원칙 중 틀린 내용을 고르시오.",
162
+ "choices": [
163
+ "Time-based (달성 시한) - Time-bound (목표를 달성하는 데 필요한 기간은?)",
164
+ "Action-Oriented (행동 지향) - Attainable (무엇을 어떻게 하면 목표를 달성할 수 있는지 알 수 있는가?)",
165
+ "Attainable: 달성 가능",
166
+ "Realistic (현실적) - Relevant (부서 또는 개인의 노력과 역량으로 달성할 수 있는가?)"
167
+ ],
168
+ "answer": "Attainable: 달성 가능"
169
+ },
170
+ {
171
+ "qtype": "mcq",
172
+ "prompt": "다음은 교육의 종류에 대한 설명이다. 틀린 내용을 고르시오.",
173
+ "choices": [
174
+ "액션 러닝은 교육에서 배운 지식(Learning)이 실제 행동(Action)으로 이어지지 않는 문제를 해결하기 위한 교육 방식.",
175
+ "OJT – 직장 내에서 자기 직무를 수행하면서 받는 informal learning이다. 이는 현장실습이 어려운 사무직 직원의 능력개발에 유용.",
176
+ "토론은 교육대상자에게 주제를 주어 각자의 의견발표를 통해 스스로 문제를 해결하도록 하는 방법",
177
+ "강의는 여러 사람을 대상으로 강사가 일방적 정보와 지식을 전달하는 교육"
178
+ ],
179
+ "answer": "OJT – 직장 내에서 자기 직무를 수행하면서 받는 informal learning이다. 이는 현장실습이 어려운 사무직 직원의 능력개발에 유용.",
180
+ "explanation": "OJT(현장훈련): 직무를 수행하면서 상사로부터 지도를 받는 비공식적 교육으로 직장내 교육이라 함 최근 ‘S-OJT’ 구체화되고, PBL 방식 기반으로 한 문제해결형 교육으로 추진되고 있음"
181
+ },
182
+ {
183
+ "qtype": "multiple",
184
+ "prompt": "샤인(Schein) 교수의 Career Anchor에 대한 설명으로 틀린 것을 모두 고르시오.",
185
+ "choices": [
186
+ "안정성 추구형 : 정해진 일정, 일을 추구",
187
+ "E. Schein 교수는 시대변화와 사회의 다원화 등을 고려하여 Career Anchor를 9가지로 다시 정의하였다.",
188
+ "도전 추구형: 도전, 문제해결, 장애극복 일",
189
+ "삶의 질 추구형: 경력목표 낮음, 개인의 삶 중시",
190
+ "전문가 추구형 – 도전, 문제해결 추구"
191
+ ],
192
+ "answer": [
193
+ "E. Schein 교수는 시대변화와 사회의 다원화 등을 고려하여 Career Anchor를 9가지로 다시 정의하였다.",
194
+ "전문가 추구형 – 도전, 문제해결 추구"
195
+ ],
196
+ "explanation": "개인이 경력을 선택할 때 포기하기 어려운 핵심적인 가치관, 동기, 역량의 조합. 샤인(Schein) 교수는 전문가, 리더십, 자율성, 안정성 등 8가지 유형을 제시했다."
197
+ }
198
+ ]
requirements.txt ADDED
@@ -0,0 +1,2 @@
 
 
 
1
+ gradio>=4.0.0
2
+ pathlib
simple_quiz.html ADDED
@@ -0,0 +1,337 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>인적자원관리 퀴즈</title>
7
+ <style>
8
+ body {
9
+ font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
10
+ line-height: 1.6;
11
+ margin: 0;
12
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
13
+ color: #1a202c;
14
+ min-height: 100vh;
15
+ padding: 20px;
16
+ }
17
+ .container {
18
+ max-width: 800px;
19
+ margin: 0 auto;
20
+ padding: 0 20px;
21
+ }
22
+ .quiz-header {
23
+ background: rgba(255, 255, 255, 0.95);
24
+ backdrop-filter: blur(20px);
25
+ border-radius: 20px;
26
+ padding: 30px;
27
+ margin-bottom: 25px;
28
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
29
+ border: 1px solid rgba(255, 255, 255, 0.2);
30
+ text-align: center;
31
+ }
32
+ .quiz-title {
33
+ font-size: 28px;
34
+ font-weight: 700;
35
+ margin: 0 0 15px 0;
36
+ background: linear-gradient(135deg, #667eea, #764ba2);
37
+ -webkit-background-clip: text;
38
+ -webkit-text-fill-color: transparent;
39
+ background-clip: text;
40
+ }
41
+ .card {
42
+ background: rgba(255, 255, 255, 0.95);
43
+ backdrop-filter: blur(20px);
44
+ border-radius: 20px;
45
+ padding: 30px;
46
+ margin: 20px 0;
47
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
48
+ border: 1px solid rgba(255, 255, 255, 0.2);
49
+ }
50
+ .question-text {
51
+ font-size: 18px;
52
+ font-weight: 500;
53
+ line-height: 1.7;
54
+ margin-bottom: 25px;
55
+ color: #2d3748;
56
+ }
57
+ .choice {
58
+ display: block;
59
+ border: 2px solid #e2e8f0;
60
+ border-radius: 12px;
61
+ padding: 16px 20px;
62
+ margin: 12px 0;
63
+ background: white;
64
+ cursor: pointer;
65
+ transition: all 0.3s ease;
66
+ font-weight: 500;
67
+ color: #4a5568;
68
+ width: 100%;
69
+ text-align: left;
70
+ }
71
+ .choice:hover {
72
+ border-color: #667eea;
73
+ background: #f7fafc;
74
+ transform: translateX(4px);
75
+ }
76
+ .choice.selected {
77
+ border-color: #667eea;
78
+ background: linear-gradient(135deg, #ebf4ff, #e6fffa);
79
+ color: #2b6cb0;
80
+ box-shadow: 0 4px 12px rgba(102, 126, 234, 0.2);
81
+ }
82
+ .btn {
83
+ background: linear-gradient(135deg, #667eea, #764ba2);
84
+ color: white;
85
+ border: none;
86
+ border-radius: 12px;
87
+ padding: 12px 24px;
88
+ cursor: pointer;
89
+ font-weight: 500;
90
+ font-size: 14px;
91
+ transition: all 0.3s ease;
92
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
93
+ margin: 10px 5px;
94
+ }
95
+ .btn:hover:not(:disabled) {
96
+ transform: translateY(-2px);
97
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.4);
98
+ }
99
+ .btn:disabled {
100
+ opacity: 0.5;
101
+ cursor: not-allowed;
102
+ }
103
+ .status-bar {
104
+ background: rgba(255, 255, 255, 0.9);
105
+ backdrop-filter: blur(10px);
106
+ border-radius: 15px;
107
+ padding: 15px 25px;
108
+ margin-bottom: 20px;
109
+ display: flex;
110
+ justify-content: space-between;
111
+ align-items: center;
112
+ box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
113
+ border: 1px solid rgba(255, 255, 255, 0.3);
114
+ }
115
+ .progress-info {
116
+ font-weight: 500;
117
+ color: #4a5568;
118
+ }
119
+ .score-display {
120
+ background: linear-gradient(135deg, #48bb78, #38a169);
121
+ color: white;
122
+ padding: 8px 16px;
123
+ border-radius: 20px;
124
+ font-weight: 600;
125
+ font-size: 14px;
126
+ box-shadow: 0 4px 12px rgba(72, 187, 120, 0.3);
127
+ }
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <div class="container">
132
+ <div class="quiz-header">
133
+ <h1 class="quiz-title">인적자원관리 퀴즈</h1>
134
+ </div>
135
+
136
+ <div class="status-bar">
137
+ <div id="status" class="progress-info">문항 1/16</div>
138
+ <div class="score-display">점수: <span id="score-text">0/16</span></div>
139
+ </div>
140
+
141
+ <div id="quiz"></div>
142
+
143
+ <div style="text-align: center; margin-top: 20px;">
144
+ <button id="prev" class="btn">← 이전</button>
145
+ <button id="next" class="btn">다음 →</button>
146
+ </div>
147
+ </div>
148
+
149
+ <script>
150
+ // 문제 데이터
151
+ const questions = [
152
+ {
153
+ "qtype": "mcq",
154
+ "prompt": "조직이 더욱 커져 해외로 진출하거나 글로벌 지점을 늘리는 등 운영 범위가 확장될 때 나타납니다.해외 사업장의 법인장이나 임원들을 관리해야 할 필요성이 생기면서, 인사 담당 임원 (전무, 부사장, 상무 등)을 조직에 보직합니다. 이와 같은 인적자원관리의 유형을 무엇이라 하는가?",
155
+ "choices": [
156
+ "원시적인사관리(PPM)",
157
+ "인사관리(PM)",
158
+ "인적자원관리(HRM)",
159
+ "전략적인적자원관리(SHRM)"
160
+ ],
161
+ "answer": "전략적인적자원관리(SHRM)"
162
+ },
163
+ {
164
+ "qtype": "mcq",
165
+ "prompt": "다음 중 직무분석에 활용에 대해 틀린 내용을 고르시오.",
166
+ "choices": [
167
+ "직무분석을 기초로 직무가치를 산정하고 보상체계에 반영할 수 있다.",
168
+ "직무 분석은 조직 내에 존재하는 각 직무의 구체적인 내용, 요건, 역할과 책임을 체계적으로 분석하고 정의하는 활동. 인사 관리의 기초.",
169
+ "직무 기술서는 직무 분석을 통해 도출된 결과물로, 특정 직무를 구성하는 일의 전체 내용(과업, 절차, 책임 등)을 기술한 문서. 채용, 평가, 교육 등의 기준으로 활용.",
170
+ "경력개발을 위한 직무수행 요구조건(지식, 기술 등) 정립 및 이동, 승진을 위한 필요한 자료 제공 및 교육훈련 정보 제공"
171
+ ],
172
+ "answer": "직무분석을 기초로 직무가치를 산정하고 보상체계에 반영할 수 있다."
173
+ }
174
+ ];
175
+
176
+ let idx = 0;
177
+ let score = 0;
178
+ let state = new Map();
179
+ let finished = false;
180
+
181
+ function getState(i) {
182
+ if (!state.has(i)) {
183
+ state.set(i, {selected: null, correct: false, revealed: false, scored: false});
184
+ }
185
+ return state.get(i);
186
+ }
187
+
188
+ function normalize(s) {
189
+ return (s || '').toString().replace(/[\s ]+/g, '').toLowerCase();
190
+ }
191
+
192
+ function select(val) {
193
+ const q = questions[idx];
194
+ const st = getState(idx);
195
+ if (st.revealed) return;
196
+ st.selected = val;
197
+ st.correct = normalize(val) === normalize(q.answer);
198
+ render();
199
+ }
200
+
201
+ function render() {
202
+ const quiz = document.getElementById('quiz');
203
+ quiz.innerHTML = '';
204
+
205
+ if (finished) {
206
+ const done = document.createElement('div');
207
+ done.className = 'card';
208
+ done.innerHTML = `
209
+ <div style="text-align: center;">
210
+ <h2>퀴즈 완료! 🎉</h2>
211
+ <p>최종 점수: ${score}/${questions.length} (${Math.round(score/questions.length*100)}%)</p>
212
+ <button class="btn" onclick="restart()">다시 시작하기</button>
213
+ </div>
214
+ `;
215
+ quiz.appendChild(done);
216
+ return;
217
+ }
218
+
219
+ const q = questions[idx];
220
+ const st = getState(idx);
221
+
222
+ const card = document.createElement('div');
223
+ card.className = 'card';
224
+
225
+ const questionDiv = document.createElement('div');
226
+ questionDiv.className = 'question-text';
227
+ questionDiv.textContent = q.prompt;
228
+ card.appendChild(questionDiv);
229
+
230
+ if (q.choices && q.choices.length) {
231
+ q.choices.forEach((c, i) => {
232
+ const btn = document.createElement('button');
233
+ btn.className = 'choice';
234
+ btn.textContent = (i + 1) + '. ' + c;
235
+
236
+ if (!st.revealed) {
237
+ btn.onclick = () => select(c);
238
+ if (st.selected === c) btn.classList.add('selected');
239
+ } else {
240
+ btn.disabled = true;
241
+ if (normalize(c) === normalize(q.answer)) {
242
+ btn.style.backgroundColor = '#c6f6d5';
243
+ btn.style.borderColor = '#48bb78';
244
+ }
245
+ if (st.selected === c && !st.correct) {
246
+ btn.style.backgroundColor = '#fed7d7';
247
+ btn.style.borderColor = '#f56565';
248
+ }
249
+ }
250
+ card.appendChild(btn);
251
+ });
252
+ }
253
+
254
+ if (st.revealed) {
255
+ const resultDiv = document.createElement('div');
256
+ resultDiv.style.cssText = 'margin-top: 20px; padding: 15px; border-radius: 10px; text-align: center; font-weight: 600;';
257
+ if (st.correct) {
258
+ resultDiv.textContent = '정답입니다! ✅';
259
+ resultDiv.style.backgroundColor = '#c6f6d5';
260
+ resultDiv.style.color = '#22543d';
261
+ } else {
262
+ resultDiv.textContent = '틀렸습니다! ❌';
263
+ resultDiv.style.backgroundColor = '#fed7d7';
264
+ resultDiv.style.color = '#742a2a';
265
+ }
266
+ card.appendChild(resultDiv);
267
+
268
+ const answerDiv = document.createElement('div');
269
+ answerDiv.style.cssText = 'margin-top: 10px; padding: 10px; background: #f7fafc; border-radius: 8px;';
270
+ const answerIndex = q.choices.findIndex(choice => normalize(choice) === normalize(q.answer));
271
+ answerDiv.innerHTML = `<strong>정답:</strong> ${answerIndex + 1}`;
272
+ card.appendChild(answerDiv);
273
+ }
274
+
275
+ quiz.appendChild(card);
276
+
277
+ // 상태 업데이트
278
+ document.getElementById('status').textContent = `문항 ${idx + 1}/${questions.length}`;
279
+ document.getElementById('score-text').textContent = `${score}/${questions.length}`;
280
+
281
+ const prevBtn = document.getElementById('prev');
282
+ const nextBtn = document.getElementById('next');
283
+ prevBtn.disabled = idx === 0;
284
+ nextBtn.textContent = st.revealed ? (idx === questions.length - 1 ? '완료' : '다음 →') : '다음 →';
285
+ }
286
+
287
+ function restart() {
288
+ idx = 0;
289
+ score = 0;
290
+ state.clear();
291
+ finished = false;
292
+ render();
293
+ }
294
+
295
+ // 이벤트 리스너
296
+ document.getElementById('prev').onclick = () => {
297
+ if (finished) return;
298
+ if (idx > 0) {
299
+ idx--;
300
+ render();
301
+ }
302
+ };
303
+
304
+ document.getElementById('next').onclick = () => {
305
+ if (finished) {
306
+ restart();
307
+ return;
308
+ }
309
+
310
+ const st = getState(idx);
311
+ if (!st.revealed) {
312
+ if (st.selected == null) {
313
+ alert('먼저 답을 선택하세요.');
314
+ return;
315
+ }
316
+ st.revealed = true;
317
+ if (st.correct && !st.scored) {
318
+ score++;
319
+ st.scored = true;
320
+ }
321
+ render();
322
+ } else {
323
+ if (idx < questions.length - 1) {
324
+ idx++;
325
+ render();
326
+ } else {
327
+ finished = true;
328
+ render();
329
+ }
330
+ }
331
+ };
332
+
333
+ // 초기 렌더링
334
+ render();
335
+ </script>
336
+ </body>
337
+ </html>