-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html.backup2
More file actions
416 lines (383 loc) · 22.3 KB
/
index.html.backup2
File metadata and controls
416 lines (383 loc) · 22.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>조휘민 | Product Manager</title>
<!-- Font -->
<link rel="stylesheet" as="style" crossorigin href="https://cdn.jsdelivr.net/gh/orioncactus/pretendard@v1.3.9/dist/web/static/pretendard.min.css" />
<!-- React & Tailwind -->
<script crossorigin src="https://unpkg.com/react@18/umd/react.production.min.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@18/umd/react-dom.production.min.js"></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>
<script src="https://cdn.tailwindcss.com"></script>
<script src="https://unpkg.com/lucide@latest"></script>
<script>
tailwind.config = {
darkMode: 'class',
theme: {
extend: {
fontFamily: {
sans: ['Pretendard', '-apple-system', 'system-ui', 'sans-serif'],
},
colors: {
bg: "#ffffff",
text: "#111111",
muted: "#6B7280", // Gray 500
border: "#E5E7EB", // Gray 200
accent: "#0F766E", // Deep Teal (지적이고 신뢰감 있는 컬러)
light: "#F9FAFB", // Gray 50
},
animation: {
'fade-up': 'fadeUp 0.8s cubic-bezier(0.16, 1, 0.3, 1) forwards',
},
keyframes: {
fadeUp: {
'0%': { opacity: '0', transform: 'translateY(20px)' },
'100%': { opacity: '1', transform: 'translateY(0)' },
}
}
}
}
}
</script>
<style>
html { scroll-behavior: smooth; }
body {
background-color: #ffffff;
color: #111111;
-webkit-font-smoothing: antialiased;
word-break: keep-all;
}
/* Marker Effect */
.marker-highlight {
position: relative;
display: inline-block;
}
.marker-highlight::after {
content: "";
position: absolute;
left: -2px; right: -2px; bottom: 2px;
height: 8px;
background-color: rgba(15, 118, 110, 0.15);
z-index: -1;
transform: skewX(-10deg);
}
/* Custom Cursor */
@media (hover: hover) {
.cursor-dot {
width: 8px; height: 8px; background: #111; opacity: 0.4;
position: fixed; top: 0; left: 0; border-radius: 50%;
pointer-events: none; z-index: 9999; transform: translate(-50%, -50%);
}
.cursor-outline {
width: 32px; height: 32px; border: 1px solid rgba(0,0,0,0.2);
position: fixed; top: 0; left: 0; border-radius: 50%;
pointer-events: none; z-index: 9998; transform: translate(-50%, -50%);
transition: all 0.1s ease-out;
}
body.hovering .cursor-outline {
width: 50px; height: 50px; background: rgba(0,0,0,0.04); border-color: transparent;
}
}
</style>
</head>
<body class="antialiased">
<div id="root"></div>
<script type="text/babel">
const { useState, useEffect, useRef } = React;
// --- 데이터 구성: 스토리 흐름에 맞춰 재배치 ---
const data = {
profile: {
name: "조휘민",
role: "Product Manager",
school: "세종대학교 건설환경공학",
email: "jowheemin@gmail.com",
github: "https://github.com/wheemin1",
intro: "데이터로 문제를 정의하고,\n기술로 솔루션을 검증합니다.",
keywords: ["데이터 기반", "문제 해결", "직접 만드는"],
profileImage: "https://placehold.co/300x300/0F766E/FFFFFF?text=H.Cho" // 본인 사진 경로로 교체하세요
},
// [섹션 1] 하이라이트 프로젝트: 면접관이 가장 먼저 봐야 할 것들
highlightProjects: [
{
id: "romrom",
category: "기획력 · Planning",
title: "45개의 화면, 하나의 PRD.",
subtitle: "위치 기반 물물교환 플랫폼 '롬롬'",
desc: "추상적인 아이디어를 구체적인 문서로 만드는 데 강점이 있습니다. 개발자 4명, 디자이너 1명과 협업하기 위해 <span class='marker-highlight'>45장 분량의 상세 기획서(PRD)</span>를 작성하고 MVP 런칭을 리딩했습니다.",
tools: ["Figma", "Jira", "Notion"],
metrics: [
{ label: "기획 산출물", value: "45 Screens" },
{ label: "협업 인원", value: "6 Members" }
],
link: "https://github.com/TEAM-ROMROM"
},
{
id: "codebattle",
category: "비즈니스 감각 · Business",
title: "300명의 유저, 그리고 피벗.",
subtitle: "AI 코딩 배틀 플랫폼 '코드배틀'",
desc: "1인 개발로 <span class='marker-highlight'>300명의 유저</span>를 모았습니다. 운영 중 AI 비용 문제가 발생하자, 로그 데이터를 분석해 '경쟁' 중심의 건별 결제 모델로 <span class='marker-highlight'>피벗(Pivot)</span>하여 지속 가능성을 확보했습니다.",
tools: ["React", "Supabase", "OpenAI API", "Vercel"],
metrics: [
{ label: "가입 유저", value: "300+" },
{ label: "비용 절감", value: "-40%" }
],
link: "https://codebattle.site/"
},
{
id: "univquiz",
category: "문제해결력 · Quick Win",
title: "불편함을 해결하는 시간, 24시간.",
subtitle: "전공 시험 대비 퀴즈 웹",
desc: "시험 기간 '정답지가 없다'는 학우들의 불만(Pain Point)을 포착했습니다. 복잡한 기능은 모두 버리고, 핵심 기능만 담은 MVP를 <span class='marker-highlight'>24시간 만에 배포</span>해 학과 공식 툴로 만들었습니다.",
tools: ["HTML/CSS", "JavaScript", "Netlify"],
metrics: [
{ label: "개발 소요", value: "24 Hours" },
{ label: "사용률", value: "90% Active" }
],
link: "#"
}
],
// [섹션 2] 경력: 신뢰도 보강
experience: [
{
company: "DailySnap",
role: "Product Manager",
period: "2025.01 — 2025.09",
desc: "매일 새로운 키워드 사진 챌린지 앱 기획. 초기 리텐션 전략 수립 및 디자이너/개발자 협업 리딩."
},
{
company: "ROK Marine Corps",
role: "EHCT Vice Team Leader",
period: "2021 — 2023",
desc: "폭발물처리팀 부팀장. 예측 불가능한 환경에서의 위기관리 및 팀 운영 리더십."
}
],
// [섹션 3] 기타: 호기심 충족
playground: [
{ title: "TimeSync", desc: "글로벌 시차 변환 툴", link: "https://globalmeetingtime.netlify.app/" },
{ title: "MoCheck", desc: "웹 성능 시각화", link: "https://mocheck.netlify.app/" },
{ title: "SecureNote", desc: "GPS 메타데이터 제거", link: "https://securegps.netlify.app/" }
],
// [섹션 4] 스킬셋: 실무 투입 가능성 증명
skills: [
{ category: "Planning", items: ["PRD/정책서 작성", "Data Analysis (SQL)", "User Research"] },
{ category: "Tools", items: ["Figma", "Jira/Slack", "Amplitude", "Notion"] },
{ category: "Engineering", items: ["HTML/CSS", "React (Basic)", "Python"] }
]
};
// --- Components ---
const Cursor = () => {
const dot = useRef(null);
const outline = useRef(null);
useEffect(() => {
const move = (e) => {
if (dot.current) { dot.current.style.left = `${e.clientX}px`; dot.current.style.top = `${e.clientY}px`; }
if (outline.current) {
outline.current.animate({ left: `${e.clientX}px`, top: `${e.clientY}px` }, { duration: 500, fill: "forwards" });
}
}
const hover = () => document.body.classList.add('hovering');
const leave = () => document.body.classList.remove('hovering');
window.addEventListener('mousemove', move);
document.querySelectorAll('a, button').forEach(el => {
el.addEventListener('mouseenter', hover);
el.addEventListener('mouseleave', leave);
});
return () => window.removeEventListener('mousemove', move);
}, []);
return (
<>
<div ref={dot} className="cursor-dot hidden md:block"></div>
<div ref={outline} className="cursor-outline hidden md:block"></div>
</>
);
};
const Reveal = ({ children, delay = 0 }) => {
const ref = useRef(null);
const [isVisible, setIsVisible] = useState(false);
useEffect(() => {
const observer = new IntersectionObserver(([entry]) => {
if (entry.isIntersecting) { setIsVisible(true); observer.unobserve(entry.target); }
}, { threshold: 0.1 });
if (ref.current) observer.observe(ref.current);
}, []);
return (
<div ref={ref} className={`transition-all duration-700 ease-out transform ${isVisible ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-10'}`} style={{ transitionDelay: `${delay}ms` }}>
{children}
</div>
);
};
const Icon = ({ name, className }) => {
useEffect(() => lucide.createIcons(), [name]);
return <i data-lucide={name} className={className}></i>;
};
// --- Main App ---
const App = () => {
return (
<div className="min-h-screen bg-bg text-text relative">
<Cursor />
{/* Sticky Header */}
<nav className="fixed top-0 w-full z-50 bg-white/80 backdrop-blur-md border-b border-border/50">
<div className="max-w-3xl mx-auto px-6 h-16 flex items-center justify-between">
<span className="font-bold text-lg tracking-tight">H. Cho</span>
<div className="text-xs font-medium text-muted flex gap-4">
<a href={`mailto:${data.profile.email}`} className="hover:text-text transition-colors">Email</a>
<a href={data.profile.github} target="_blank" className="hover:text-text transition-colors">Github</a>
</div>
</div>
</nav>
<main className="max-w-4xl mx-auto px-6 pt-32 pb-32">
{/* 1. Intro Section: 짧고 강렬하게 */}
<Reveal>
<header className="mb-24 grid md:grid-cols-[1fr_auto] gap-12 items-center">
{/* Left: Text Content */}
<div>
<div className="flex gap-2 mb-6">
{data.profile.keywords.map(k => (
<span key={k} className="px-2 py-1 bg-light border border-border rounded text-[11px] font-bold uppercase tracking-wider text-muted">
{k}
</span>
))}
</div>
<h1 className="text-4xl md:text-5xl font-bold leading-[1.2] mb-6 whitespace-pre-line">
{data.profile.intro}
</h1>
<div className="text-muted text-lg font-medium">
<p>{data.profile.name} <span className="text-gray-300 mx-2">|</span> {data.profile.role}</p>
<p className="text-sm mt-1 opacity-70">{data.profile.school}</p>
</div>
</div>
{/* Right: Profile Image */}
<div className="hidden md:block">
<img
src={data.profile.profileImage}
alt={data.profile.name}
className="w-48 h-48 object-cover rounded-2xl border-2 border-border shadow-sm"
/>
</div>
</header>
</Reveal>
<div className="w-full h-px bg-border mb-20"></div>
{/* 2. Highlight Projects: 스토리 흐름의 핵심 */}
<section className="space-y-32 mb-32">
{data.highlightProjects.map((proj, i) => (
<Reveal key={i}>
<a href={proj.link} target="_blank" className="block group">
{/* Tag */}
<div className="mb-4">
<span className="text-accent font-bold text-xs uppercase tracking-widest border-b border-accent pb-1">
{proj.category}
</span>
</div>
{/* Title (The Hook) */}
<h2 className="text-3xl md:text-4xl font-bold mb-3 group-hover:text-accent transition-colors flex items-center gap-3">
{proj.title}
<Icon name="arrow-up-right" className="w-6 h-6 opacity-0 -translate-x-2 group-hover:opacity-100 group-hover:translate-x-0 transition-all duration-300" />
</h2>
{/* Subtitle */}
<p className="text-lg font-medium text-gray-800 mb-6">
{proj.subtitle}
</p>
{/* Description */}
<p className="text-muted text-lg leading-relaxed mb-6 max-w-2xl" dangerouslySetInnerHTML={{ __html: proj.desc }}></p>
{/* Tech Stack */}
{proj.tools && (
<div className="flex flex-wrap gap-2 mb-8">
{proj.tools.map((t, idx) => (
<span key={idx} className="text-xs font-medium text-muted bg-light px-2 py-1 rounded border border-border/50">
{t}
</span>
))}
</div>
)}
{/* Metrics Grid */}
<div className="grid grid-cols-2 sm:grid-cols-3 gap-6 border-l-2 border-light pl-6">
{proj.metrics.map((m, idx) => (
<div key={idx}>
<div className="text-2xl font-bold font-mono text-text">{m.value}</div>
<div className="text-xs text-muted uppercase font-bold tracking-wider mt-1">{m.label}</div>
</div>
))}
</div>
</a>
</Reveal>
))}
</section>
<div className="w-full h-px bg-border mb-20"></div>
{/* 3. Experience & Toolbox: 신뢰와 역량 */}
<div className="grid md:grid-cols-12 gap-16">
{/* Experience (왼쪽 7칸) */}
<div className="md:col-span-7">
<Reveal delay={100}>
<section>
<h3 className="text-xs font-bold text-muted uppercase tracking-widest mb-8">Career</h3>
<div className="space-y-10">
{data.experience.map((exp, i) => (
<div key={i}>
<h4 className="font-bold text-lg mb-1">{exp.company}</h4>
<div className="text-sm text-accent font-medium mb-2">{exp.role}</div>
<p className="text-sm text-muted leading-relaxed mb-2">{exp.desc}</p>
<div className="text-xs text-gray-400 font-mono">{exp.period}</div>
</div>
))}
</div>
</section>
</Reveal>
</div>
{/* Toolbox (오른쪽 5칸) */}
<div className="md:col-span-5">
<Reveal delay={200}>
<section>
<h3 className="text-xs font-bold text-muted uppercase tracking-widest mb-8">Toolbox</h3>
<div className="space-y-8">
{data.skills.map((skill, i) => (
<div key={i}>
<h4 className="text-sm font-bold text-text mb-3">{skill.category}</h4>
<ul className="space-y-2">
{skill.items.map((item, idx) => (
<li key={idx} className="flex items-center gap-2 text-sm text-muted">
<div className="w-1 h-1 rounded-full bg-accent/50"></div>
{item}
</li>
))}
</ul>
</div>
))}
</div>
</section>
</Reveal>
</div>
</div>
<div className="w-full h-px bg-border my-20"></div>
{/* 4. Playground: 사이드 프로젝트 */}
<Reveal delay={100}>
<section>
<h3 className="text-xs font-bold text-muted uppercase tracking-widest mb-8">Playground</h3>
<div className="grid md:grid-cols-3 gap-6">
{data.playground.map((proj, i) => (
<a key={i} href={proj.link} target="_blank" className="group block p-6 border border-border rounded-xl hover:border-accent transition-colors">
<div className="flex items-center justify-between mb-2">
<h4 className="font-bold group-hover:text-accent transition-colors">{proj.title}</h4>
<Icon name="external-link" className="w-4 h-4 text-muted group-hover:text-accent transition-colors" />
</div>
<p className="text-xs text-muted">{proj.desc}</p>
</a>
))}
</div>
</section>
</Reveal>
</main>
<footer className="py-8 border-t border-border text-center">
<p className="text-xs text-muted uppercase tracking-widest font-bold">© 2025 Hweemin Cho. All Rights Reserved.</p>
</footer>
</div>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
</script>
</body>
</html>