ssboost commited on
Commit
23fa4e3
·
verified ·
1 Parent(s): 51b3ca6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +600 -124
app.py CHANGED
@@ -1,6 +1,6 @@
1
  import gradio as gr
2
 
3
- # MBTI 질문 데이터 (각 차원당 5개씩 총 20개)
4
  questions = [
5
  # E vs I (외향 vs 내향)
6
  {"text": "새로운 사람들을 만나는 것이 즐겁고 에너지가 충전된다.", "dimension": "EI", "direction": "E"},
@@ -34,118 +34,512 @@ questions = [
34
  # MBTI 유형별 설명
35
  mbti_descriptions = {
36
  "INTJ": {
37
- "title": "전략가 (Architect)",
 
 
38
  "description": "상상력이 풍부하고 전략적인 사고를 가진 완벽주의자입니다.",
39
- "strengths": "논리적, 독립적, 결단력 있음, 창의적 문제 해결",
40
- "weaknesses": "지나치게 비판적, 감정 표현 어려움, 고집스러움"
 
41
  },
42
  "INTP": {
43
- "title": "논리술사 (Logician)",
 
 
44
  "description": "혁신적인 발명가로 끊임없는 지식의 갈증을 가지고 있습니다.",
45
- "strengths": "분석적, 객관적, 창의적, 열린 마음",
46
- "weaknesses": "실용성 부족, 감정 둔감, 우유부단함"
 
47
  },
48
  "ENTJ": {
49
- "title": "통솔자 (Commander)",
 
 
50
  "description": "대담하고 상상력이 풍부한 강력한 지도자입니다.",
51
- "strengths": "리더십, 효율적, 자신감, 전략적 사고",
52
- "weaknesses": "참을성 부족, 오만함, 감정 무시"
 
53
  },
54
  "ENTP": {
55
- "title": "변론가 (Debater)",
 
 
56
  "description": "똑똑하고 호기심 많은 사색가로 지적 도전을 즐깁니다.",
57
- "strengths": "창의적, 카리스마, 에너지 넘침, 논쟁 능력",
58
- "weaknesses": "논쟁적, 무감각, 집중력 부족"
 
59
  },
60
  "INFJ": {
61
- "title": "옹호자 (Advocate)",
 
 
62
  "description": "조용하고 신비로우며 사람들에게 영감을 주는 이상주의자입니다.",
63
- "strengths": "통찰력, 결단력, 이타적, 창의적",
64
- "weaknesses": "민감함, 완벽주의, 소진되기 쉬움"
 
65
  },
66
  "INFP": {
67
- "title": "중재자 (Mediator)",
 
 
68
  "description": "항상 선을 행할 준비가 되어 있는 이타적이고 부드러운 사람입니다.",
69
- "strengths": "공감 능력, 창의적, 열정적, 헌신적",
70
- "weaknesses": "비현실적, 자기비판적, 감정적"
 
71
  },
72
  "ENFJ": {
73
- "title": "선도자 (Protagonist)",
 
 
74
  "description": "카리스마 있고 영감을 주는 지도자로 청중을 사로잡습니다.",
75
- "strengths": "카리스마, 이타적, 영감을 줌, 자연스러운 리더",
76
- "weaknesses": "지나치게 이상적, 자기희생적, 민감함"
 
77
  },
78
  "ENFP": {
79
- "title": "활동가 (Campaigner)",
 
 
80
  "description": "열정적이고 창의적이며 긍정적인 자유로운 영혼입니다.",
81
- "strengths": "열정적, 창의적, 사교적, 낙관적",
82
- "weaknesses": "산만함, 스트레스 취약, 지나친 감정 이입"
 
83
  },
84
  "ISTJ": {
85
- "title": "현실주의자 (Logistician)",
 
 
86
  "description": "실용적이고 사실에 기반한 믿음직한 사람입니다.",
87
- "strengths": "책임감, 정직함, 실용적, 차분함",
88
- "weaknesses": "고집스러움, 둔감함, 규칙 고수"
 
89
  },
90
  "ISFJ": {
91
- "title": "수호자 (Defender)",
 
 
92
  "description": "헌신적이고 따뜻한 수호자로 소중한 사람들을 지킵니다.",
93
- "strengths": "지지적, 신뢰할 있음, 인내심, 실용적",
94
- "weaknesses": "겸손함, 변화 회피, 과부하"
 
95
  },
96
  "ESTJ": {
97
- "title": "경영자 (Executive)",
 
 
98
  "description": "뛰어난 관리자로 사람들이나 일을 관리하는 데 타고났습니다.",
99
- "strengths": "조직력, 정직함, 헌신적, 결단력",
100
- "weaknesses": "완고함, 감정 무시, 비판적"
 
101
  },
102
  "ESFJ": {
103
- "title": "집정관 (Consul)",
 
 
104
  "description": "배려심이 많고 사교적이며 인기가 많은 사람입니다.",
105
- "strengths": "지지적, 책임감, 충성스러움, 민감함",
106
- "weaknesses": "걱정 많음, 비판에 취약, 자기주장 어려움"
 
107
  },
108
  "ISTP": {
109
- "title": "장인 (Virtuoso)",
 
 
110
  "description": "대담하고 실용적인 실험가로 모든 도구를 다룹니다.",
111
- "strengths": "낙관적, 창의적, 실용적, 자발적",
112
- "weaknesses": "완고함, 둔감함, 쉽게 지루함"
 
113
  },
114
  "ISFP": {
115
- "title": "모험가 (Adventurer)",
 
 
116
  "description": "유연하고 매력적인 예술가로 새로운 것을 탐험할 준비가 되어 있습니다.",
117
- "strengths": "매력적, 민감함, 상상력, 호기심",
118
- "weaknesses": "섬세함, 독립적 과함, 예측 불가능"
 
119
  },
120
  "ESTP": {
121
- "title": "사업가 (Entrepreneur)",
 
 
122
  "description": "영리하고 에너지 넘치며 매우 지각적인 사람입니다.",
123
- "strengths": "대담함, 실용적, 직관적, 사교적",
124
- "weaknesses": "무감각함, 참을성 없음, 위험 감수"
 
125
  },
126
  "ESFP": {
127
- "title": "연예인 (Entertainer)",
 
 
128
  "description": "자발적이고 에너지 넘치며 열정적인 엔터테이너입니다.",
129
- "strengths": "대담함, 독창적, 실용적, 관찰력",
130
- "weaknesses": "민감함, 갈등 회피, 쉽게 지루함"
 
131
  }
132
  }
133
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
  def calculate_mbti(answers):
135
  """답변을 바탕으로 MBTI 유형 계산"""
136
  scores = {"E": 0, "I": 0, "S": 0, "N": 0, "T": 0, "F": 0, "J": 0, "P": 0}
137
 
138
  for i, answer in enumerate(answers):
139
- if answer is None:
140
  continue
141
  question = questions[i]
142
  dimension = question["dimension"]
143
  direction = question["direction"]
144
-
145
- # 5점 척도: 1(전혀 아니다) ~ 5(매우 그렇다)
146
  scores[direction] += answer
147
 
148
- # MBTI 유형 결정
149
  mbti_type = ""
150
  mbti_type += "E" if scores["E"] > scores["I"] else "I"
151
  mbti_type += "S" if scores["S"] > scores["N"] else "N"
@@ -154,86 +548,168 @@ def calculate_mbti(answers):
154
 
155
  return mbti_type, scores
156
 
157
- def format_result(mbti_type, scores):
158
- """결과를 포맷팅"""
159
  info = mbti_descriptions[mbti_type]
 
160
 
161
- result = f"""
162
- # 🎯 당신의 MBTI 유형: {mbti_type}
163
-
164
- ## {info['title']}
165
-
166
- ### 📝 설명
167
- {info['description']}
168
-
169
- ### 강점
170
- {info['strengths']}
171
-
172
- ### ⚠️ 약점
173
- {info['weaknesses']}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
174
 
175
- ### 📊 세부 점수
176
- - **외향(E) vs 내향(I)**: E={scores['E']}, I={scores['I']} → **{'E' if scores['E'] > scores['I'] else 'I'}**
177
- - **감각(S) vs 직관(N)**: S={scores['S']}, N={scores['N']} → **{'S' if scores['S'] > scores['N'] else 'N'}**
178
- - **사고(T) vs 감정(F)**: T={scores['T']}, F={scores['F']} → **{'T' if scores['T'] > scores['F'] else 'F'}**
179
- - **판단(J) vs 인식(P)**: J={scores['J']}, P={scores['P']} **{'J' if scores['J'] > scores['P'] else 'P'}**
180
- """
181
- return result
 
182
 
183
  def submit_test(*answers):
184
- """테스트 제출 및 결과 계산"""
185
- if None in answers:
186
- return "⚠️ 모든 질문에 답변해주세요!"
187
 
188
  mbti_type, scores = calculate_mbti(answers)
189
- return format_result(mbti_type, scores)
190
 
191
- # Gradio 인터페이스 구성
192
- with gr.Blocks(title="MBTI 성격 유형 테스트", theme=gr.themes.Soft()) as demo:
193
- gr.Markdown("""
194
- # 🧠 MBTI 성격 유형 테스트
195
-
196
- 질문에 대해 자신의 성향을 솔직하게 평가해주세요.
197
-
198
- **평가 기준:**
199
- - 1 = 전혀 아니다
200
- - 2 = 아니다
201
- - 3 = 보통이다
202
- - 4 = 그렇다
203
- - 5 = 매우 그렇다
204
- """)
205
-
206
- # 질문 슬라이더 생성
207
- sliders = []
208
- for i, q in enumerate(questions):
209
- with gr.Row():
210
- slider = gr.Slider(
211
- minimum=1,
212
- maximum=5,
213
- step=1,
214
- value=3,
215
- label=f"질문 {i+1}: {q['text']}",
216
- info="1=전혀 아니다, 5=매우 그렇다"
 
 
 
217
  )
218
- sliders.append(slider)
219
-
220
- # 제출 버튼
221
- submit_btn = gr.Button("🎯 결과 확인", variant="primary", size="lg")
222
-
223
- # 결과 표시
224
- result_output = gr.Markdown(label="테스트 결과")
225
-
226
- # 버튼 클릭 이벤트
227
- submit_btn.click(
228
- fn=submit_test,
229
- inputs=sliders,
230
- outputs=result_output
231
- )
232
-
233
- gr.Markdown("""
234
- ---
235
- 💡 **참고**: 이 테스트는 간단한 MBTI 성향 파악을 위한 것이며, 공식 MBTI 검사를 대체하지 않습니다.
236
- """)
237
 
238
  if __name__ == "__main__":
239
  demo.launch(share=True)
 
1
  import gradio as gr
2
 
3
+ # MBTI 질문 데이터
4
  questions = [
5
  # E vs I (외향 vs 내향)
6
  {"text": "새로운 사람들을 만나는 것이 즐겁고 에너지가 충전된다.", "dimension": "EI", "direction": "E"},
 
34
  # MBTI 유형별 설명
35
  mbti_descriptions = {
36
  "INTJ": {
37
+ "title": "전략가",
38
+ "subtitle": "Architect",
39
+ "emoji": "🏛️",
40
  "description": "상상력이 풍부하고 전략적인 사고를 가진 완벽주의자입니다.",
41
+ "strengths": ["논리적 사고", "독립적 성향", "결단력", "창의적 문제 해결"],
42
+ "weaknesses": ["지나친 비판", "감정 표현 어려움", "고집스러움"],
43
+ "color": "#6B46C1"
44
  },
45
  "INTP": {
46
+ "title": "논리술사",
47
+ "subtitle": "Logician",
48
+ "emoji": "🔬",
49
  "description": "혁신적인 발명가로 끊임없는 지식의 갈증을 가지고 있습니다.",
50
+ "strengths": ["분석적 사고", "객관적 판단", "창의성", "열린 마음"],
51
+ "weaknesses": ["실용성 부족", "감정 둔감", "우유부단함"],
52
+ "color": "#805AD5"
53
  },
54
  "ENTJ": {
55
+ "title": "통솔자",
56
+ "subtitle": "Commander",
57
+ "emoji": "⚔️",
58
  "description": "대담하고 상상력이 풍부한 강력한 지도자입니다.",
59
+ "strengths": ["타고난 리더십", "효율성", "자신감", "전략적 사고"],
60
+ "weaknesses": ["참을성 부족", "오만함", "감정 무시"],
61
+ "color": "#D97706"
62
  },
63
  "ENTP": {
64
+ "title": "변론가",
65
+ "subtitle": "Debater",
66
+ "emoji": "💡",
67
  "description": "똑똑하고 호기심 많은 사색가로 지적 도전을 즐깁니다.",
68
+ "strengths": ["창의적 사고", "카리스마", "에너지", "뛰어난 논쟁력"],
69
+ "weaknesses": ["논쟁적 성향", "무감각함", "집중력 부족"],
70
+ "color": "#F59E0B"
71
  },
72
  "INFJ": {
73
+ "title": "옹호자",
74
+ "subtitle": "Advocate",
75
+ "emoji": "🌟",
76
  "description": "조용하고 신비로우며 사람들에게 영감을 주는 이상주의자입니다.",
77
+ "strengths": ["통찰력", "결단력", "이타심", "창의성"],
78
+ "weaknesses": ["과도한 민감함", "완벽주의", "쉽게 소진됨"],
79
+ "color": "#059669"
80
  },
81
  "INFP": {
82
+ "title": "중재자",
83
+ "subtitle": "Mediator",
84
+ "emoji": "🌈",
85
  "description": "항상 선을 행할 준비가 되어 있는 이타적이고 부드러운 사람입니다.",
86
+ "strengths": ["뛰어난 공감 능력", "창의성", "열정", "헌신"],
87
+ "weaknesses": ["비현실적", "자기비판", "감정적"],
88
+ "color": "#10B981"
89
  },
90
  "ENFJ": {
91
+ "title": "선도자",
92
+ "subtitle": "Protagonist",
93
+ "emoji": "✨",
94
  "description": "카리스마 있고 영감을 주는 지도자로 청중을 사로잡습니다.",
95
+ "strengths": ["카리스마", "이타심", "영감 제공", "자연스러운 리더십"],
96
+ "weaknesses": ["지나친 이상주의", "자기희생", "민감함"],
97
+ "color": "#3B82F6"
98
  },
99
  "ENFP": {
100
+ "title": "활동가",
101
+ "subtitle": "Campaigner",
102
+ "emoji": "🎨",
103
  "description": "열정적이고 창의적이며 긍정적인 자유로운 영혼입니다.",
104
+ "strengths": ["열정", "창의성", "사교성", "낙관주의"],
105
+ "weaknesses": ["산만함", "스트레스 취약", "과도한 감정이입"],
106
+ "color": "#06B6D4"
107
  },
108
  "ISTJ": {
109
+ "title": "현실주의자",
110
+ "subtitle": "Logistician",
111
+ "emoji": "📊",
112
  "description": "실용적이고 사실에 기반한 믿음직한 사람입니다.",
113
+ "strengths": ["책임감", "정직함", "실용성", "차분함"],
114
+ "weaknesses": ["고집", "둔감함", "규칙 고수"],
115
+ "color": "#475569"
116
  },
117
  "ISFJ": {
118
+ "title": "수호자",
119
+ "subtitle": "Defender",
120
+ "emoji": "🛡️",
121
  "description": "헌신적이고 따뜻한 수호자로 소중한 사람들을 지킵니다.",
122
+ "strengths": ["지지적", "신뢰성", "인내심", "실용성"],
123
+ "weaknesses": ["과도한 겸손", "변화 회피", "과부하"],
124
+ "color": "#64748B"
125
  },
126
  "ESTJ": {
127
+ "title": "경영자",
128
+ "subtitle": "Executive",
129
+ "emoji": "💼",
130
  "description": "뛰어난 관리자로 사람들이나 일을 관리하는 데 타고났습니다.",
131
+ "strengths": ["조직력", "정직함", "헌신", "결단력"],
132
+ "weaknesses": ["완고함", "감정 무시", "비판적"],
133
+ "color": "#DC2626"
134
  },
135
  "ESFJ": {
136
+ "title": "집정관",
137
+ "subtitle": "Consul",
138
+ "emoji": "🤝",
139
  "description": "배려심이 많고 사교적이며 인기가 많은 사람입니다.",
140
+ "strengths": ["지지적", "책임감", "충성심", "민감함"],
141
+ "weaknesses": ["걱정 과다", "비판에 취약", "자기주장 어려움"],
142
+ "color": "#EF4444"
143
  },
144
  "ISTP": {
145
+ "title": "장인",
146
+ "subtitle": "Virtuoso",
147
+ "emoji": "🔧",
148
  "description": "대담하고 실용적인 실험가로 모든 도구를 다룹니다.",
149
+ "strengths": ["낙관적", "창의적", "실용적", "자발적"],
150
+ "weaknesses": ["완고함", "둔감함", "쉽게 지루함"],
151
+ "color": "#78716C"
152
  },
153
  "ISFP": {
154
+ "title": "모험가",
155
+ "subtitle": "Adventurer",
156
+ "emoji": "🎭",
157
  "description": "유연하고 매력적인 예술가로 새로운 것을 탐험할 준비가 되어 있습니다.",
158
+ "strengths": ["매력적", "민감함", "상상력", "호기심"],
159
+ "weaknesses": ["섬세함", "과도한 독립성", "예측 불가"],
160
+ "color": "#A78BFA"
161
  },
162
  "ESTP": {
163
+ "title": "사업가",
164
+ "subtitle": "Entrepreneur",
165
+ "emoji": "🚀",
166
  "description": "영리하고 에너지 넘치며 매우 지각적인 사람입니다.",
167
+ "strengths": ["대담함", "실용성", "직관력", "사교성"],
168
+ "weaknesses": ["무감각", "참을성 부족", "위험 감수"],
169
+ "color": "#F97316"
170
  },
171
  "ESFP": {
172
+ "title": "연예인",
173
+ "subtitle": "Entertainer",
174
+ "emoji": "🎪",
175
  "description": "자발적이고 에너지 넘치며 열정적인 엔터테이너입니다.",
176
+ "strengths": ["대담함", "독창성", "실용성", "관찰력"],
177
+ "weaknesses": ["민감함", "갈등 회피", "쉽게 지루함"],
178
+ "color": "#EC4899"
179
  }
180
  }
181
 
182
+ # 커스텀 CSS
183
+ custom_css = """
184
+ @import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@300;400;500;700;900&display=swap');
185
+
186
+ * {
187
+ font-family: 'Noto Sans KR', sans-serif !important;
188
+ }
189
+
190
+ body {
191
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
192
+ }
193
+
194
+ .gradio-container {
195
+ max-width: 900px !important;
196
+ margin: 0 auto !important;
197
+ background: transparent !important;
198
+ }
199
+
200
+ #main-container {
201
+ background: white;
202
+ border-radius: 24px;
203
+ padding: 48px;
204
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
205
+ margin: 40px 0;
206
+ }
207
+
208
+ .header-section {
209
+ text-align: center;
210
+ margin-bottom: 48px;
211
+ padding-bottom: 32px;
212
+ border-bottom: 2px solid #f0f0f0;
213
+ }
214
+
215
+ .main-title {
216
+ font-size: 48px;
217
+ font-weight: 900;
218
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
219
+ -webkit-background-clip: text;
220
+ -webkit-text-fill-color: transparent;
221
+ background-clip: text;
222
+ margin-bottom: 16px;
223
+ letter-spacing: -1px;
224
+ }
225
+
226
+ .subtitle {
227
+ font-size: 18px;
228
+ color: #64748b;
229
+ font-weight: 400;
230
+ line-height: 1.6;
231
+ }
232
+
233
+ .progress-container {
234
+ background: #f8fafc;
235
+ border-radius: 16px;
236
+ padding: 24px;
237
+ margin-bottom: 32px;
238
+ border: 1px solid #e2e8f0;
239
+ }
240
+
241
+ .progress-text {
242
+ font-size: 14px;
243
+ color: #64748b;
244
+ margin-bottom: 12px;
245
+ font-weight: 500;
246
+ }
247
+
248
+ .progress-bar-container {
249
+ width: 100%;
250
+ height: 8px;
251
+ background: #e2e8f0;
252
+ border-radius: 999px;
253
+ overflow: hidden;
254
+ }
255
+
256
+ .progress-bar {
257
+ height: 100%;
258
+ background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
259
+ border-radius: 999px;
260
+ transition: width 0.3s ease;
261
+ }
262
+
263
+ .question-card {
264
+ background: white;
265
+ border: 2px solid #e2e8f0;
266
+ border-radius: 16px;
267
+ padding: 28px;
268
+ margin-bottom: 20px;
269
+ transition: all 0.3s ease;
270
+ }
271
+
272
+ .question-card:hover {
273
+ border-color: #667eea;
274
+ box-shadow: 0 8px 24px rgba(102, 126, 234, 0.12);
275
+ transform: translateY(-2px);
276
+ }
277
+
278
+ .question-number {
279
+ display: inline-block;
280
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
281
+ color: white;
282
+ font-weight: 700;
283
+ padding: 6px 14px;
284
+ border-radius: 20px;
285
+ font-size: 13px;
286
+ margin-bottom: 12px;
287
+ }
288
+
289
+ .question-text {
290
+ font-size: 17px;
291
+ font-weight: 500;
292
+ color: #1e293b;
293
+ line-height: 1.6;
294
+ margin-bottom: 20px;
295
+ }
296
+
297
+ .radio-group {
298
+ display: flex;
299
+ gap: 12px;
300
+ justify-content: space-between;
301
+ flex-wrap: wrap;
302
+ }
303
+
304
+ .radio-option {
305
+ flex: 1;
306
+ min-width: 80px;
307
+ }
308
+
309
+ .radio-option input[type="radio"] {
310
+ display: none;
311
+ }
312
+
313
+ .radio-option label {
314
+ display: flex;
315
+ flex-direction: column;
316
+ align-items: center;
317
+ padding: 16px 8px;
318
+ background: #f8fafc;
319
+ border: 2px solid #e2e8f0;
320
+ border-radius: 12px;
321
+ cursor: pointer;
322
+ transition: all 0.2s ease;
323
+ text-align: center;
324
+ }
325
+
326
+ .radio-option input[type="radio"]:checked + label {
327
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
328
+ border-color: #667eea;
329
+ color: white;
330
+ }
331
+
332
+ .radio-option label:hover {
333
+ border-color: #667eea;
334
+ transform: translateY(-2px);
335
+ }
336
+
337
+ .option-number {
338
+ font-size: 20px;
339
+ font-weight: 700;
340
+ margin-bottom: 4px;
341
+ }
342
+
343
+ .option-text {
344
+ font-size: 11px;
345
+ font-weight: 500;
346
+ }
347
+
348
+ .submit-button {
349
+ width: 100%;
350
+ padding: 20px;
351
+ font-size: 18px;
352
+ font-weight: 700;
353
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
354
+ color: white;
355
+ border: none;
356
+ border-radius: 16px;
357
+ cursor: pointer;
358
+ transition: all 0.3s ease;
359
+ margin-top: 32px;
360
+ box-shadow: 0 10px 30px rgba(102, 126, 234, 0.3);
361
+ }
362
+
363
+ .submit-button:hover {
364
+ transform: translateY(-2px);
365
+ box-shadow: 0 15px 40px rgba(102, 126, 234, 0.4);
366
+ }
367
+
368
+ .submit-button:active {
369
+ transform: translateY(0);
370
+ }
371
+
372
+ .result-container {
373
+ background: white;
374
+ border-radius: 24px;
375
+ padding: 48px;
376
+ margin-top: 32px;
377
+ box-shadow: 0 20px 60px rgba(0,0,0,0.15);
378
+ }
379
+
380
+ .result-header {
381
+ text-align: center;
382
+ margin-bottom: 40px;
383
+ }
384
+
385
+ .result-emoji {
386
+ font-size: 80px;
387
+ margin-bottom: 20px;
388
+ }
389
+
390
+ .result-mbti {
391
+ font-size: 56px;
392
+ font-weight: 900;
393
+ margin-bottom: 12px;
394
+ letter-spacing: 8px;
395
+ }
396
+
397
+ .result-title {
398
+ font-size: 32px;
399
+ font-weight: 700;
400
+ margin-bottom: 8px;
401
+ color: #1e293b;
402
+ }
403
+
404
+ .result-subtitle {
405
+ font-size: 18px;
406
+ color: #64748b;
407
+ font-weight: 400;
408
+ }
409
+
410
+ .result-description {
411
+ font-size: 18px;
412
+ line-height: 1.8;
413
+ color: #475569;
414
+ text-align: center;
415
+ margin: 32px 0;
416
+ padding: 28px;
417
+ background: #f8fafc;
418
+ border-radius: 16px;
419
+ }
420
+
421
+ .result-section {
422
+ margin-top: 32px;
423
+ }
424
+
425
+ .section-title {
426
+ font-size: 20px;
427
+ font-weight: 700;
428
+ color: #1e293b;
429
+ margin-bottom: 16px;
430
+ display: flex;
431
+ align-items: center;
432
+ gap: 8px;
433
+ }
434
+
435
+ .badge-container {
436
+ display: flex;
437
+ flex-wrap: wrap;
438
+ gap: 10px;
439
+ }
440
+
441
+ .badge {
442
+ display: inline-flex;
443
+ align-items: center;
444
+ padding: 10px 18px;
445
+ background: #f1f5f9;
446
+ border-radius: 999px;
447
+ font-size: 14px;
448
+ font-weight: 500;
449
+ color: #334155;
450
+ }
451
+
452
+ .badge.strength {
453
+ background: #dcfce7;
454
+ color: #166534;
455
+ }
456
+
457
+ .badge.weakness {
458
+ background: #fee2e2;
459
+ color: #991b1b;
460
+ }
461
+
462
+ .dimension-grid {
463
+ display: grid;
464
+ grid-template-columns: repeat(2, 1fr);
465
+ gap: 16px;
466
+ margin-top: 32px;
467
+ }
468
+
469
+ .dimension-card {
470
+ background: #f8fafc;
471
+ border-radius: 16px;
472
+ padding: 20px;
473
+ border: 2px solid #e2e8f0;
474
+ }
475
+
476
+ .dimension-header {
477
+ font-size: 14px;
478
+ color: #64748b;
479
+ margin-bottom: 12px;
480
+ font-weight: 600;
481
+ }
482
+
483
+ .dimension-result {
484
+ font-size: 32px;
485
+ font-weight: 900;
486
+ margin-bottom: 8px;
487
+ }
488
+
489
+ .dimension-bar {
490
+ width: 100%;
491
+ height: 8px;
492
+ background: #e2e8f0;
493
+ border-radius: 999px;
494
+ overflow: hidden;
495
+ margin-bottom: 8px;
496
+ }
497
+
498
+ .dimension-fill {
499
+ height: 100%;
500
+ border-radius: 999px;
501
+ transition: width 0.5s ease;
502
+ }
503
+
504
+ .dimension-scores {
505
+ display: flex;
506
+ justify-content: space-between;
507
+ font-size: 12px;
508
+ color: #64748b;
509
+ }
510
+
511
+ @media (max-width: 768px) {
512
+ #main-container {
513
+ padding: 24px;
514
+ }
515
+
516
+ .main-title {
517
+ font-size: 36px;
518
+ }
519
+
520
+ .dimension-grid {
521
+ grid-template-columns: 1fr;
522
+ }
523
+
524
+ .result-mbti {
525
+ font-size: 42px;
526
+ letter-spacing: 4px;
527
+ }
528
+ }
529
+ """
530
+
531
  def calculate_mbti(answers):
532
  """답변을 바탕으로 MBTI 유형 계산"""
533
  scores = {"E": 0, "I": 0, "S": 0, "N": 0, "T": 0, "F": 0, "J": 0, "P": 0}
534
 
535
  for i, answer in enumerate(answers):
536
+ if answer is None or answer == 0:
537
  continue
538
  question = questions[i]
539
  dimension = question["dimension"]
540
  direction = question["direction"]
 
 
541
  scores[direction] += answer
542
 
 
543
  mbti_type = ""
544
  mbti_type += "E" if scores["E"] > scores["I"] else "I"
545
  mbti_type += "S" if scores["S"] > scores["N"] else "N"
 
548
 
549
  return mbti_type, scores
550
 
551
+ def create_result_html(mbti_type, scores):
552
+ """결과를 HTML로 생성"""
553
  info = mbti_descriptions[mbti_type]
554
+ color = info['color']
555
 
556
+ # 차원별 퍼센티지 계산
557
+ ei_total = scores['E'] + scores['I']
558
+ sn_total = scores['S'] + scores['N']
559
+ tf_total = scores['T'] + scores['F']
560
+ jp_total = scores['J'] + scores['P']
561
+
562
+ ei_pct = (scores['E'] / ei_total * 100) if ei_total > 0 else 50
563
+ sn_pct = (scores['S'] / sn_total * 100) if sn_total > 0 else 50
564
+ tf_pct = (scores['T'] / tf_total * 100) if tf_total > 0 else 50
565
+ jp_pct = (scores['J'] / jp_total * 100) if jp_total > 0 else 50
566
+
567
+ result_html = f"""
568
+ <div class="result-container">
569
+ <div class="result-header">
570
+ <div class="result-emoji">{info['emoji']}</div>
571
+ <div class="result-mbti" style="color: {color};">{mbti_type}</div>
572
+ <div class="result-title">{info['title']}</div>
573
+ <div class="result-subtitle">{info['subtitle']}</div>
574
+ </div>
575
+
576
+ <div class="result-description">
577
+ {info['description']}
578
+ </div>
579
+
580
+ <div class="result-section">
581
+ <div class="section-title">
582
+ ✨ 주요 강점
583
+ </div>
584
+ <div class="badge-container">
585
+ {''.join([f'<span class="badge strength">{s}</span>' for s in info['strengths']])}
586
+ </div>
587
+ </div>
588
+
589
+ <div class="result-section">
590
+ <div class="section-title">
591
+ ⚠️ 개선 포인트
592
+ </div>
593
+ <div class="badge-container">
594
+ {''.join([f'<span class="badge weakness">{w}</span>' for w in info['weaknesses']])}
595
+ </div>
596
+ </div>
597
+
598
+ <div class="dimension-grid">
599
+ <div class="dimension-card">
600
+ <div class="dimension-header">외향 vs 내향</div>
601
+ <div class="dimension-result" style="color: {color};">{'E' if scores['E'] > scores['I'] else 'I'}</div>
602
+ <div class="dimension-bar">
603
+ <div class="dimension-fill" style="width: {ei_pct}%; background: {color};"></div>
604
+ </div>
605
+ <div class="dimension-scores">
606
+ <span>E: {scores['E']}</span>
607
+ <span>I: {scores['I']}</span>
608
+ </div>
609
+ </div>
610
+
611
+ <div class="dimension-card">
612
+ <div class="dimension-header">감각 vs 직관</div>
613
+ <div class="dimension-result" style="color: {color};">{'S' if scores['S'] > scores['N'] else 'N'}</div>
614
+ <div class="dimension-bar">
615
+ <div class="dimension-fill" style="width: {sn_pct}%; background: {color};"></div>
616
+ </div>
617
+ <div class="dimension-scores">
618
+ <span>S: {scores['S']}</span>
619
+ <span>N: {scores['N']}</span>
620
+ </div>
621
+ </div>
622
+
623
+ <div class="dimension-card">
624
+ <div class="dimension-header">사고 vs 감정</div>
625
+ <div class="dimension-result" style="color: {color};">{'T' if scores['T'] > scores['F'] else 'F'}</div>
626
+ <div class="dimension-bar">
627
+ <div class="dimension-fill" style="width: {tf_pct}%; background: {color};"></div>
628
+ </div>
629
+ <div class="dimension-scores">
630
+ <span>T: {scores['T']}</span>
631
+ <span>F: {scores['F']}</span>
632
+ </div>
633
+ </div>
634
+
635
+ <div class="dimension-card">
636
+ <div class="dimension-header">판단 vs 인식</div>
637
+ <div class="dimension-result" style="color: {color};">{'J' if scores['J'] > scores['P'] else 'P'}</div>
638
+ <div class="dimension-bar">
639
+ <div class="dimension-fill" style="width: {jp_pct}%; background: {color};"></div>
640
+ </div>
641
+ <div class="dimension-scores">
642
+ <span>J: {scores['J']}</span>
643
+ <span>P: {scores['P']}</span>
644
+ </div>
645
+ </div>
646
+ </div>
647
+ </div>
648
+ """
649
+
650
+ return result_html
651
 
652
+ def create_question_html(index, question_text):
653
+ """질문 카드 HTML 생성"""
654
+ return f"""
655
+ <div class="question-card">
656
+ <div class="question-number">질문 {index + 1}</div>
657
+ <div class="question-text">{question_text}</div>
658
+ </div>
659
+ """
660
 
661
  def submit_test(*answers):
662
+ """테스트 제출"""
663
+ if 0 in answers or None in answers:
664
+ return "<div style='text-align: center; padding: 40px; color: #dc2626; font-size: 18px; font-weight: 600;'>⚠️ 모든 질문에 답변해주세요!</div>"
665
 
666
  mbti_type, scores = calculate_mbti(answers)
667
+ return create_result_html(mbti_type, scores)
668
 
669
+ # Gradio 인터페이스
670
+ with gr.Blocks(css=custom_css, title="MBTI 성격 유형 테스트") as demo:
671
+ with gr.Column(elem_id="main-container"):
672
+ # 헤더
673
+ gr.HTML("""
674
+ <div class="header-section">
675
+ <div class="main-title">MBTI 성격 유형 테스트</div>
676
+ <div class="subtitle">
677
+ 20개의 질문을 통해 당신의 성격 유형을 발견하세요<br>
678
+ 질문에 솔직하게 답변하시면 더 정확한 결과를 얻을 수 있습니다
679
+ </div>
680
+ </div>
681
+ """)
682
+
683
+ # 질문들
684
+ radios = []
685
+ for i, q in enumerate(questions):
686
+ gr.HTML(create_question_html(i, q['text']))
687
+ radio = gr.Radio(
688
+ choices=[
689
+ ("1️⃣ 전혀 아니다", 1),
690
+ ("2️⃣ 아니다", 2),
691
+ ("3️⃣ 보통이다", 3),
692
+ ("4️⃣ 그렇다", 4),
693
+ ("5️⃣ 매우 그렇다", 5)
694
+ ],
695
+ value=None,
696
+ show_label=False,
697
+ container=False
698
  )
699
+ radios.append(radio)
700
+
701
+ # 제출 버튼
702
+ submit_btn = gr.Button("🎯 결과 확인하기", elem_classes="submit-button")
703
+
704
+ # 결과 영역
705
+ result_html = gr.HTML()
706
+
707
+ # 이벤트 핸들러
708
+ submit_btn.click(
709
+ fn=submit_test,
710
+ inputs=radios,
711
+ outputs=result_html
712
+ )
 
 
 
 
 
713
 
714
  if __name__ == "__main__":
715
  demo.launch(share=True)