careermate 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (137) hide show
  1. package/AGENTS.md +33 -0
  2. package/INSTALL.md +230 -0
  3. package/LICENSE +21 -0
  4. package/README.md +532 -0
  5. package/THIRD_PARTY_NOTICES.md +57 -0
  6. package/bin/careermate.mjs +77 -0
  7. package/dist/career-os/README.md +78 -0
  8. package/dist/career-os/eop/cover-letter.md +46 -0
  9. package/dist/career-os/eop/fit-analysis.md +48 -0
  10. package/dist/career-os/eop/interview-prep.md +39 -0
  11. package/dist/career-os/eop/job-analysis.md +42 -0
  12. package/dist/career-os/eop/profile-extraction.md +37 -0
  13. package/dist/career-os/knowledge/ats.md +131 -0
  14. package/dist/career-os/knowledge/company-research.md +137 -0
  15. package/dist/career-os/knowledge/cover-letter.md +135 -0
  16. package/dist/career-os/knowledge/fit-matching.md +121 -0
  17. package/dist/career-os/knowledge/human-writing.md +118 -0
  18. package/dist/career-os/knowledge/interview-behavioral.md +132 -0
  19. package/dist/career-os/knowledge/interview-technical.md +126 -0
  20. package/dist/career-os/knowledge/linkedin-profile.md +152 -0
  21. package/dist/career-os/knowledge/networking-referrals.md +166 -0
  22. package/dist/career-os/knowledge/offer-evaluation-decision.md +121 -0
  23. package/dist/career-os/knowledge/onboarding-first-90-days.md +126 -0
  24. package/dist/career-os/knowledge/portfolio.md +121 -0
  25. package/dist/career-os/knowledge/recruiter-screen.md +105 -0
  26. package/dist/career-os/knowledge/rejection-triage-iteration.md +115 -0
  27. package/dist/career-os/knowledge/resume.md +123 -0
  28. package/dist/career-os/knowledge/salary-negotiation.md +123 -0
  29. package/dist/career-os/knowledge/verifiers/ats-compat.md +64 -0
  30. package/dist/career-os/knowledge/verifiers/consistency.md +61 -0
  31. package/dist/career-os/knowledge/verifiers/human-voice.md +82 -0
  32. package/dist/career-os/knowledge/verifiers/recency-staleness.md +72 -0
  33. package/dist/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
  34. package/dist/career-os/knowledge/verifiers/truthfulness.md +67 -0
  35. package/dist/doctor.mjs +79094 -0
  36. package/dist/mcp.mjs +96108 -0
  37. package/dist/migrate.mjs +4735 -0
  38. package/dist/pdf.worker.min.mjs +28 -0
  39. package/dist/public/app.js +150 -0
  40. package/dist/public/fonts/PretendardVariable.woff2 +0 -0
  41. package/dist/public/icon-180.png +0 -0
  42. package/dist/public/index.html +52 -0
  43. package/dist/public/lib.js +394 -0
  44. package/dist/public/pages/applications.js +107 -0
  45. package/dist/public/pages/documents.js +467 -0
  46. package/dist/public/pages/home.js +261 -0
  47. package/dist/public/pages/interview.js +227 -0
  48. package/dist/public/pages/jobs.js +493 -0
  49. package/dist/public/pages/profile.js +556 -0
  50. package/dist/public/pages/settings.js +240 -0
  51. package/dist/public/styles.css +498 -0
  52. package/dist/site/analytics.js +97 -0
  53. package/dist/site/favicon.svg +1 -0
  54. package/dist/site/fonts/PretendardVariable.woff2 +0 -0
  55. package/dist/site/icon-180.png +0 -0
  56. package/dist/site/icon-512.png +0 -0
  57. package/dist/site/index.html +598 -0
  58. package/dist/site/landing.js +54 -0
  59. package/dist/site/llms-install.txt +217 -0
  60. package/dist/site/robots.txt +4 -0
  61. package/dist/site/sitemap.xml +21 -0
  62. package/dist/site/start.html +326 -0
  63. package/dist/site/style.css +550 -0
  64. package/dist/site/vercel.json +20 -0
  65. package/dist/web.mjs +46852 -0
  66. package/docs/FAQ.md +101 -0
  67. package/docs/MCP_TOOLS.md +311 -0
  68. package/docs/README.md +28 -0
  69. package/docs/START_WORKFLOW.md +137 -0
  70. package/docs/SUPPORTED_AI_APPS.md +112 -0
  71. package/docs/WORKFLOWS.md +202 -0
  72. package/docs/career-os/README.md +78 -0
  73. package/docs/career-os/eop/cover-letter.md +46 -0
  74. package/docs/career-os/eop/fit-analysis.md +48 -0
  75. package/docs/career-os/eop/interview-prep.md +39 -0
  76. package/docs/career-os/eop/job-analysis.md +42 -0
  77. package/docs/career-os/eop/profile-extraction.md +37 -0
  78. package/docs/career-os/knowledge/ats.md +131 -0
  79. package/docs/career-os/knowledge/company-research.md +137 -0
  80. package/docs/career-os/knowledge/cover-letter.md +135 -0
  81. package/docs/career-os/knowledge/fit-matching.md +121 -0
  82. package/docs/career-os/knowledge/human-writing.md +118 -0
  83. package/docs/career-os/knowledge/interview-behavioral.md +132 -0
  84. package/docs/career-os/knowledge/interview-technical.md +126 -0
  85. package/docs/career-os/knowledge/linkedin-profile.md +152 -0
  86. package/docs/career-os/knowledge/networking-referrals.md +166 -0
  87. package/docs/career-os/knowledge/offer-evaluation-decision.md +121 -0
  88. package/docs/career-os/knowledge/onboarding-first-90-days.md +126 -0
  89. package/docs/career-os/knowledge/portfolio.md +121 -0
  90. package/docs/career-os/knowledge/recruiter-screen.md +105 -0
  91. package/docs/career-os/knowledge/rejection-triage-iteration.md +115 -0
  92. package/docs/career-os/knowledge/resume.md +123 -0
  93. package/docs/career-os/knowledge/salary-negotiation.md +123 -0
  94. package/docs/career-os/knowledge/verifiers/ats-compat.md +64 -0
  95. package/docs/career-os/knowledge/verifiers/consistency.md +61 -0
  96. package/docs/career-os/knowledge/verifiers/human-voice.md +82 -0
  97. package/docs/career-os/knowledge/verifiers/recency-staleness.md +72 -0
  98. package/docs/career-os/knowledge/verifiers/responsiveness-on-target.md +82 -0
  99. package/docs/career-os/knowledge/verifiers/truthfulness.md +67 -0
  100. package/package.json +89 -0
  101. package/packages/core/src/context.ts +74 -0
  102. package/packages/core/src/index.ts +8 -0
  103. package/packages/core/src/onboarding.ts +81 -0
  104. package/packages/core/src/services.ts +187 -0
  105. package/packages/core/src/summary.ts +104 -0
  106. package/packages/db/src/connection.ts +76 -0
  107. package/packages/db/src/index.ts +23 -0
  108. package/packages/db/src/paths.ts +52 -0
  109. package/packages/db/src/repositories.ts +891 -0
  110. package/packages/db/src/runtime.ts +58 -0
  111. package/packages/db/src/schema.ts +307 -0
  112. package/packages/exporters/src/html.ts +110 -0
  113. package/packages/exporters/src/index.ts +364 -0
  114. package/packages/exporters/src/markdown.ts +178 -0
  115. package/packages/knowledge/src/index.ts +168 -0
  116. package/packages/mcp-tools/src/bridge.ts +106 -0
  117. package/packages/mcp-tools/src/index.ts +17 -0
  118. package/packages/mcp-tools/src/result.ts +49 -0
  119. package/packages/mcp-tools/src/tools.ts +747 -0
  120. package/packages/mcp-tools/src/update.ts +330 -0
  121. package/packages/parsers/src/documents.ts +533 -0
  122. package/packages/parsers/src/html.ts +86 -0
  123. package/packages/parsers/src/index.ts +231 -0
  124. package/packages/parsers/src/keywords.ts +151 -0
  125. package/packages/prompts/src/humanize.ts +59 -0
  126. package/packages/prompts/src/index.ts +82 -0
  127. package/packages/prompts/src/install.ts +43 -0
  128. package/packages/prompts/src/onboarding.ts +35 -0
  129. package/packages/prompts/src/system.ts +57 -0
  130. package/packages/shared/src/build-info.ts +22 -0
  131. package/packages/shared/src/enums.ts +142 -0
  132. package/packages/shared/src/index.ts +19 -0
  133. package/packages/shared/src/schemas.ts +453 -0
  134. package/packages/workflows/src/definitions.ts +109 -0
  135. package/packages/workflows/src/index.ts +39 -0
  136. package/scripts/init.ts +371 -0
  137. package/tsconfig.json +32 -0
@@ -0,0 +1,598 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ko" data-theme="dark">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <!-- Resolve the saved theme before first paint (default: dark) to avoid a flash. -->
7
+ <script>
8
+ (function () {
9
+ try {
10
+ var t = window.localStorage.getItem('careermate-theme') || window.localStorage.getItem('cf-theme');
11
+ document.documentElement.setAttribute('data-theme', t === 'light' || t === 'dark' ? t : 'dark');
12
+ } catch (e) { document.documentElement.setAttribute('data-theme', 'dark'); }
13
+ })();
14
+ </script>
15
+ <title>CareerMate — 내 AI를 커리어 비서로</title>
16
+ <meta name="description" content="Claude Desktop·Claude Code·Codex 같은 로컬 AI 앱에 연결하세요. CareerMate가 이력서·자기소개서·지원 현황을 이 컴퓨터에만 저장하고 MCP로 당신의 AI에 연결합니다." />
17
+ <link rel="canonical" href="https://careermate.life/" />
18
+ <meta name="google-site-verification" content="MfU5uwl1RNXKklsJhXz2hPCRytjLpCjCXSa2ls5JQH0" />
19
+ <meta name="theme-color" content="#faf8f4" media="(prefers-color-scheme: light)" />
20
+ <meta name="theme-color" content="#14130f" media="(prefers-color-scheme: dark)" />
21
+
22
+ <!-- icons -->
23
+ <link rel="icon" href="favicon.svg" type="image/svg+xml" />
24
+ <link rel="apple-touch-icon" href="icon-180.png" />
25
+
26
+ <!-- Open Graph -->
27
+ <meta property="og:type" content="website" />
28
+ <meta property="og:site_name" content="CareerMate" />
29
+ <meta property="og:locale" content="ko_KR" />
30
+ <meta property="og:url" content="https://careermate.life/" />
31
+ <meta property="og:title" content="CareerMate — 내 AI를 커리어 비서로" />
32
+ <meta property="og:description" content="이력서·자기소개서·지원 현황을 이 컴퓨터 안에서 관리하세요. 분석·글쓰기는 평소 쓰던 AI가, 데이터 보관은 CareerMate가 맡습니다." />
33
+ <meta property="og:image" content="https://careermate.life/og.png" />
34
+ <meta property="og:image:width" content="1200" />
35
+ <meta property="og:image:height" content="630" />
36
+ <meta property="og:image:alt" content="CareerMate — 로컬 전용 MCP 커리어 워크스페이스" />
37
+
38
+ <!-- Twitter / X -->
39
+ <meta name="twitter:card" content="summary_large_image" />
40
+ <meta name="twitter:title" content="CareerMate — 내 AI를 커리어 비서로" />
41
+ <meta name="twitter:description" content="이력서·자기소개서·지원 현황을 이 컴퓨터 안에서. 데이터는 외부로 나가지 않습니다." />
42
+ <meta name="twitter:image" content="https://careermate.life/og.png" />
43
+
44
+ <link rel="preload" href="fonts/PretendardVariable.woff2" as="font" type="font/woff2" crossorigin />
45
+ <link rel="stylesheet" href="style.css" />
46
+ <!-- Privacy-friendly analytics for the marketing site only (never the local 127.0.0.1 app). -->
47
+ <script src="/analytics.js" defer></script>
48
+ </head>
49
+ <body>
50
+ <a class="skip-link" href="#top">본문으로 건너뛰기</a>
51
+ <!-- ============================================================ nav -->
52
+ <nav class="nav">
53
+ <a class="nav__brand" href="#top">
54
+ <span class="nav__logo" aria-hidden="true">C</span>
55
+ <span class="nav__name">CareerMate</span>
56
+ </a>
57
+ <div class="nav__links">
58
+ <a href="#how">동작 방식</a>
59
+ <a href="#features">기능</a>
60
+ <a href="#preview">화면</a>
61
+ <a href="#install">설치</a>
62
+ <a href="start.html">처음이세요?</a>
63
+ <a href="#privacy">개인정보</a>
64
+ </div>
65
+ <div class="nav__actions">
66
+ <button class="theme-toggle" id="theme-toggle" type="button" aria-label="다크 모드 전환" title="다크 모드 전환">
67
+ <svg class="i-sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>
68
+ <svg class="i-moon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"/></svg>
69
+ </button>
70
+ <a class="btn btn--ghost" href="#install">설치하기</a>
71
+ </div>
72
+ </nav>
73
+
74
+ <main id="top" tabindex="-1">
75
+
76
+ <!-- ========================================================== hero -->
77
+ <header class="hero">
78
+ <div class="hero__main">
79
+ <span class="pill-badge">
80
+ <span class="pill-badge__dot" aria-hidden="true"></span>
81
+ 로컬 전용 · 무료
82
+ </span>
83
+ <h1 class="hero__title">
84
+ 내가 쓰던 AI를<br />
85
+ 나만의 커리어 비서로
86
+ </h1>
87
+ <p class="hero__lede">
88
+ <strong>공고 분석 · 맞춤 자기소개서 · 지원 현황 추적</strong>을 평소 쓰던 AI에게 말로 시키면, 결과가 이 컴퓨터 안에 쌓입니다. 한 번 정리한 경력은 대화마다 다시 붙여넣지 않아도 돼요.
89
+ </p>
90
+
91
+ <ul class="trust-strip" aria-label="안심 포인트">
92
+ <li>내 컴퓨터에만 저장</li>
93
+ <li>외부 업로드 없음</li>
94
+ <li>계정·API 키 불필요</li>
95
+ <li>언제든 삭제</li>
96
+ </ul>
97
+
98
+ <div class="hero__cta">
99
+ <a class="btn btn--primary" href="start.html">처음이세요? — 내게 맞는 설치법 찾기</a>
100
+ <a class="btn btn--ghost" href="#install">
101
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
102
+ 바로 설치 방법 보기
103
+ </a>
104
+ </div>
105
+
106
+ <p class="hero__note">
107
+ CareerMate는 <strong>내 컴퓨터에서 도는 로컬 도구</strong>예요. 그래서 <span class="nowrap">Claude&nbsp;Desktop · Claude&nbsp;Code · Codex</span>처럼 내 컴퓨터의 프로그램을 띄울 수 있는 AI에서 동작합니다. 설치를 마쳤다면 대시보드 <code>http://127.0.0.1:4319</code>에서 내 데이터를 확인하세요.
108
+ </p>
109
+ </div>
110
+
111
+ <!-- signature element: CareerMate를 설치하는 방식 자체가 "내 AI에게 한 문장" — 그 순간을 보여준다.
112
+ 장식적 예시라 aria-hidden; 실제 복사 가능한 안내는 #install에 맥락과 함께 있다. -->
113
+ <aside class="hero__demo" aria-hidden="true">
114
+ <div class="chat">
115
+ <div class="chat__head">
116
+ <span class="chat__dot"></span>
117
+ 내 AI 앱
118
+ </div>
119
+ <div class="chat__msg chat__msg--me">
120
+ <span class="chat__who">나</span>
121
+ <p>careermate.life/llms-install.txt 읽고, 이 폴더에 CareerMate 설치하고 연결해줘.</p>
122
+ </div>
123
+ <div class="chat__msg chat__msg--ai">
124
+ <span class="chat__who">AI</span>
125
+ <p>설치했어요. 이제 이력서를 주거나 공고를 붙여넣어 보세요.</p>
126
+ <ul class="chat__steps">
127
+ <li>Node 확인 후 이 폴더에 설치</li>
128
+ <li>데이터는 <code>.cm/data</code>에만 저장</li>
129
+ <li>대시보드 <code>127.0.0.1:4319</code> 열림</li>
130
+ </ul>
131
+ </div>
132
+ </div>
133
+ <p class="hero__demo-note">내 AI 앱 대화창에 붙여넣으면 끝 — 터미널이 없어도 돼요.</p>
134
+ </aside>
135
+ </header>
136
+
137
+ <!-- ========================================================== how -->
138
+ <section id="how" class="section">
139
+ <div class="section__head">
140
+ <h2>당신의 AI가 두뇌, CareerMate는 커리어 서랍장</h2>
141
+ <p>CareerMate 안에는 AI가 들어 있지 않습니다. 생각하는 일은 당신의 AI가, 데이터 보관은 CareerMate가 맡습니다.</p>
142
+ </div>
143
+ <div class="cards-3">
144
+ <article class="feature">
145
+ <span class="feature__no">01</span>
146
+ <h3>당신의 AI와 대화</h3>
147
+ <p>평소 쓰던 Claude에게 말로 시키면 됩니다. 분석·글쓰기 같은 “생각”은 전부 당신의 AI가 합니다.</p>
148
+ </article>
149
+ <article class="feature">
150
+ <span class="feature__no">02</span>
151
+ <h3>안전하게 연결</h3>
152
+ <p>표준 연결 방식(MCP)이 AI와 CareerMate를 잇는 다리가 됩니다. AI가 당신의 커리어 기록을 직접 읽고 정리해 저장합니다.</p>
153
+ </article>
154
+ <article class="feature">
155
+ <span class="feature__no">03</span>
156
+ <h3>이 컴퓨터에 저장</h3>
157
+ <p>모든 데이터는 로컬 SQLite(<code>~/.careermate</code>)에만 저장됩니다. 외부 서버로 전송하지 않고, 대시보드에서 눈으로 볼 수 있습니다.</p>
158
+ </article>
159
+ </div>
160
+ </section>
161
+
162
+ <!-- ====================================================== features -->
163
+ <section id="features" class="section">
164
+ <div class="section__head">
165
+ <h2>지원 한 건을 끝까지 함께 처리</h2>
166
+ <p>프로필 정리부터 면접 준비까지, AI와 대화만으로 이어집니다.</p>
167
+ </div>
168
+ <div class="checks">
169
+ <div class="check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><div><strong>프로필·이력서 구조화</strong><span>업로드한 파일을 읽어 경력·프로젝트·스킬로 정리·보관.</span></div></div>
170
+ <div class="check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><div><strong>채용공고 파싱·저장</strong><span>붙여넣은 공고 텍스트에서 회사·직무·자격요건을 추출.</span></div></div>
171
+ <div class="check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><div><strong>적합도(핏) 분석</strong><span>내 경력과 공고를 비교해 강점·보완점·매칭 키워드 도출.</span></div></div>
172
+ <div class="check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><div><strong>맞춤 자소서 + 버전 관리</strong><span>공고별 자기소개서를 버전으로 쌓고 비교, 파일로 내보내기.</span></div></div>
173
+ <div class="check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><div><strong>지원 상태 8단계 관리</strong><span>작성 중 → 지원 → 서류 합격 → 면접 → 최종까지 칸반으로.</span></div></div>
174
+ <div class="check"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.4" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M20 6 9 17l-5-5"/></svg><div><strong>면접 준비</strong><span>서류 합격 시 예상 질문·STAR 답변·1분 자기소개 초안 생성.</span></div></div>
175
+ </div>
176
+ </section>
177
+
178
+ <!-- ======================================================= preview -->
179
+ <section id="preview" class="section">
180
+ <div class="section__head section__head--center">
181
+ <h2>내 데이터를 눈으로 확인</h2>
182
+ <p>모든 기록은 이 컴퓨터의 대시보드(<code>127.0.0.1:4319</code>)에 모입니다.<br>아래는 실제 화면 — 설치 없이 바로 둘러볼 수 있어요.</p>
183
+ <div class="section__cta">
184
+ <a class="btn btn--primary" href="demo/">데모 둘러보기 →</a>
185
+ <span class="section__cta-note">샘플 데이터로 클릭해 보는 미리보기 · 설치·로그인 불필요</span>
186
+ </div>
187
+ </div>
188
+
189
+ <div class="carousel" id="dash-carousel" role="group" aria-roledescription="캐러셀" aria-label="대시보드 화면 미리보기" tabindex="0">
190
+ <div class="carousel__viewport">
191
+ <div class="carousel__track" id="carousel-track">
192
+ <figure class="slide">
193
+ <div class="browser">
194
+ <div class="browser__bar">
195
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
196
+ <span class="browser__url">127.0.0.1:4319</span>
197
+ </div>
198
+ <div class="browser__body">
199
+ <img class="shot__img shot__img--light" src="shots/home-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 홈 대시보드 — 지원 파이프라인과 할 일, 최근 활동" />
200
+ <img class="shot__img shot__img--dark" src="shots/home-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 홈 대시보드 (다크) — 지원 파이프라인과 할 일, 최근 활동" />
201
+ </div>
202
+ </div>
203
+ <figcaption><strong>홈</strong> — 파이프라인·할 일·활동을 한 화면에</figcaption>
204
+ </figure>
205
+
206
+ <figure class="slide">
207
+ <div class="browser">
208
+ <div class="browser__bar">
209
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
210
+ <span class="browser__url">127.0.0.1:4319/#/applications</span>
211
+ </div>
212
+ <div class="browser__body">
213
+ <img class="shot__img shot__img--light" src="shots/applications-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 지원 현황 — 상태별로 정리된 지원 목록" />
214
+ <img class="shot__img shot__img--dark" src="shots/applications-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 지원 현황 (다크) — 상태별로 정리된 지원 목록" />
215
+ </div>
216
+ </div>
217
+ <figcaption><strong>지원 현황</strong> — 8단계 상태별로 한눈에</figcaption>
218
+ </figure>
219
+
220
+ <figure class="slide">
221
+ <div class="browser">
222
+ <div class="browser__bar">
223
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
224
+ <span class="browser__url">127.0.0.1:4319/#/jobs</span>
225
+ </div>
226
+ <div class="browser__body">
227
+ <img class="shot__img shot__img--light" src="shots/jobs-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 채용공고 — 적합도 점수와 진행 상태" />
228
+ <img class="shot__img shot__img--dark" src="shots/jobs-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 채용공고 (다크) — 적합도 점수와 진행 상태" />
229
+ </div>
230
+ </div>
231
+ <figcaption><strong>채용공고</strong> — 적합도 점수와 진행 상태</figcaption>
232
+ </figure>
233
+
234
+ <figure class="slide">
235
+ <div class="browser">
236
+ <div class="browser__bar">
237
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
238
+ <span class="browser__url">127.0.0.1:4319/#/documents</span>
239
+ </div>
240
+ <div class="browser__body">
241
+ <img class="shot__img shot__img--light" src="shots/documents-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 문서 — 자기소개서와 이력서를 버전과 함께 보관" />
242
+ <img class="shot__img shot__img--dark" src="shots/documents-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 문서 (다크) — 자기소개서와 이력서를 버전과 함께 보관" />
243
+ </div>
244
+ </div>
245
+ <figcaption><strong>문서</strong> — 자기소개서·이력서를 버전과 함께</figcaption>
246
+ </figure>
247
+
248
+ <figure class="slide">
249
+ <div class="browser">
250
+ <div class="browser__bar">
251
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
252
+ <span class="browser__url">127.0.0.1:4319/#/interview</span>
253
+ </div>
254
+ <div class="browser__body">
255
+ <img class="shot__img shot__img--light" src="shots/interview-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 면접 준비 — 예상 질문과 STAR, 1분 자기소개" />
256
+ <img class="shot__img shot__img--dark" src="shots/interview-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 면접 준비 (다크) — 예상 질문과 STAR, 1분 자기소개" />
257
+ </div>
258
+ </div>
259
+ <figcaption><strong>면접 준비</strong> — 예상 질문·STAR·1분 자기소개</figcaption>
260
+ </figure>
261
+
262
+ <figure class="slide">
263
+ <div class="browser">
264
+ <div class="browser__bar">
265
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
266
+ <span class="browser__url">127.0.0.1:4319/#/settings</span>
267
+ </div>
268
+ <div class="browser__body">
269
+ <img class="shot__img shot__img--light" src="shots/settings-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 설정 — 데이터 저장 위치와 연결 상태, 테마" />
270
+ <img class="shot__img shot__img--dark" src="shots/settings-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 설정 (다크) — 데이터 저장 위치와 연결 상태, 테마" />
271
+ </div>
272
+ </div>
273
+ <figcaption><strong>설정</strong> — 데이터 위치·내보내기·테마</figcaption>
274
+ </figure>
275
+ </div>
276
+ </div>
277
+
278
+ <button class="carousel__arrow carousel__arrow--prev" id="carousel-prev" type="button" aria-label="이전 화면">
279
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M15 18l-6-6 6-6"/></svg>
280
+ </button>
281
+ <button class="carousel__arrow carousel__arrow--next" id="carousel-next" type="button" aria-label="다음 화면">
282
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9 18l6-6-6-6"/></svg>
283
+ </button>
284
+ </div>
285
+ <div class="carousel__dots" id="carousel-dots" role="tablist" aria-label="화면 선택"></div>
286
+ </section>
287
+
288
+ <!-- ======================================================= install -->
289
+ <section id="install" class="section">
290
+ <div class="section__head section__head--center">
291
+ <h2>내 AI 앱에 연결하기</h2>
292
+ <p>빈 폴더 잡고 <strong>한 문장이면 끝.</strong><br />
293
+ 터미널이 익숙하면 한 줄 설치, 싫으면 파일 하나로 — <strong>무료도 있습니다.</strong></p>
294
+ </div>
295
+
296
+ <a class="note" href="start.html" style="display:block;text-decoration:none;border-left-color:var(--accent);background:var(--accent-soft)">
297
+ <strong style="color:var(--text)">완전 처음이세요?</strong> 터미널·구독이 뭔지 몰라도 괜찮아요 — 쓰는 AI와 컴퓨터만 고르면 <strong>맞는 길 하나</strong>를 알려드려요. <strong style="color:var(--accent)">길 찾기 →</strong>
298
+ </a>
299
+
300
+ <div class="note">
301
+ <strong>어떤 AI에서 되나요?</strong> 내 컴퓨터에서 프로그램을 띄울 수 있는 AI에서만 됩니다.<br />
302
+ <strong style="color:var(--accent)">✓</strong> Claude Desktop · Claude Code · Codex · Cursor 등 &nbsp;/&nbsp; <strong style="color:var(--warn-text)">✗</strong> ChatGPT·Gemini·Claude의 <strong>웹·모바일</strong>(클라우드라 내 컴퓨터 서버에 못 닿음).
303
+ </div>
304
+
305
+ <!-- track 1 · 앱에서 직접 설치 (비개발자 메인) -->
306
+ <details class="acc" open>
307
+ <summary><span class="track__tag">추천 · 앱에서 한 문장</span> 내 AI 앱에서 빈 폴더 잡고 한 문장</summary>
308
+ <div class="acc__body">
309
+ <p class="track-desc"><strong>Claude 데스크톱의 “Code” 탭</strong>처럼 폴더를 잡고 작동하는 AI 앱이면 <strong>별도 터미널 없이</strong> 앱 안에서 끝납니다(Codex는 <strong>CLI</strong> 권장 — 데스크톱 앱은 등록이 사라질 수 있습니다). 작업 폴더 하나를 정하고 아래 문장만 주면, 에이전트가 공개 안내(<code>careermate.life/llms-install.txt</code>)를 읽고 <strong>그 폴더 안에</strong> 설치·연결합니다. 데이터까지 그 폴더에 모여(<code>.cm/data</code>) 백업·이동이 폴더 하나로 끝.</p>
310
+ <ol class="steps">
311
+ <li class="step">
312
+ <div class="step__no">1</div>
313
+ <div class="step__body">
314
+ <h3>앱 열고 빈 폴더 선택</h3>
315
+ <p><strong>Claude 데스크톱</strong> → <strong>Code</strong> 탭에서 빈 폴더를 작업 폴더로 선택(<strong>Pro 이상</strong> 구독, <strong>Windows는 Git 먼저</strong>). <strong>Codex</strong>는 빈 폴더에서 <code>codex</code>(CLI, <strong>ChatGPT Plus 이상</strong>)로 실행하세요 — <strong>데스크톱 앱은 등록이 사라질 수 있어</strong> 권장하지 않습니다.</p>
316
+ </div>
317
+ </li>
318
+ <li class="step">
319
+ <div class="step__no">2</div>
320
+ <div class="step__body">
321
+ <h3>이 문장 붙여넣기</h3>
322
+ <p class="paste-note">💬 위에서 연 <strong>AI 앱의 대화창에</strong> 붙여넣으세요 — 터미널 명령이 아니에요. 이 문장은 공개 안내 페이지만 읽어 설치하며, <strong>당신의 이력서·개인정보는 외부로 올라가지 않아요.</strong></p>
323
+ <div class="code">
324
+ <button class="copy-btn" type="button" data-copy-target="code-prompt-agent">복사</button>
325
+ <pre id="code-prompt-agent">https://careermate.life/llms-install.txt 를 읽고, 그 안내대로 CareerMate를 이 폴더에 설치하고 연결해줘. 데이터도 이 폴더의 .cm/data 안에 저장하고, Node 설치처럼 시스템에 바꿀 게 있으면 실행 전에 먼저 알려줘. 설치가 끝나면 앱 재시작 방법과 이력서를 주는 법, 대시보드 주소(http://127.0.0.1:4319)도 알려줘.</pre>
326
+ </div>
327
+ <p class="step__tip">준비물은 <strong>Node.js 22.5 이상</strong> 하나뿐. 없으면 에이전트가 <strong>먼저 확인하고</strong> 설치를 도와줍니다. 데이터는 작업 폴더의 <code>.cm/data</code>에만 저장됩니다. (Codex CLI는 설치 단계에서 네트워크 허용을 한 번 물을 수 있어요 — 허용하면 됩니다.)</p>
328
+ </div>
329
+ </li>
330
+ <li class="step">
331
+ <div class="step__no">3</div>
332
+ <div class="step__body">
333
+ <h3>재시작 후 확인</h3>
334
+ <p>AI 앱을 완전히 종료했다 다시 켜고(처음 한 번 승인을 물으면 허용), <strong>“커리어메이트 연결됐는지 확인해줘”</strong>라고 말해 보세요. Codex는 <code>/mcp</code>로도 확인됩니다.</p>
335
+ </div>
336
+ </li>
337
+ </ol>
338
+ </div>
339
+ </details>
340
+
341
+ <!-- track 2 · npx 한 줄 -->
342
+ <details class="acc">
343
+ <summary><span class="track__tag">터미널 · 한 줄 설치</span> npx 한 줄로 자동 연결</summary>
344
+ <div class="acc__body">
345
+ <p class="track-desc">준비물은 <strong>Node.js 22.5 이상</strong> 하나. 감지된 AI 앱(Claude Desktop·Codex·Cursor)의 MCP 설정에 CareerMate를 자동 등록합니다.</p>
346
+ <ol class="steps">
347
+ <li class="step">
348
+ <div class="step__no">1</div>
349
+ <div class="step__body">
350
+ <h3>한 줄 실행</h3>
351
+ <p>터미널(Windows는 PowerShell)에 붙여넣고 실행하세요. 클론도 빌드도 필요 없습니다. <code>node --version</code>이 22.5 미만이면 <a href="https://nodejs.org/" rel="noopener" target="_blank">nodejs.org</a>에서 LTS를 먼저 설치하세요.</p>
352
+ <div class="code">
353
+ <button class="copy-btn" type="button" data-copy-target="code-npx">복사</button>
354
+ <pre id="code-npx">npx -y careermate init</pre>
355
+ </div>
356
+ <p class="step__tip">특정 클라이언트만 등록하려면 <code>npx -y careermate init --client cursor</code>, 설정 JSON·명령만 보고 직접 붙여넣으려면 <code>npx -y careermate init --print</code>. (클라이언트: <code>claude</code> · <code>claude-code</code> · <code>codex</code> · <code>cursor</code>)</p>
357
+ </div>
358
+ </li>
359
+ <li class="step">
360
+ <div class="step__no">2</div>
361
+ <div class="step__body">
362
+ <h3>AI 앱 재시작 후 확인</h3>
363
+ <p>연결한 AI 앱을 <strong>완전히 종료했다 다시 켜고</strong>, <strong>“커리어메이트 연결됐는지 확인해줘”</strong>라고 말해 결과가 돌아오면 정상입니다.</p>
364
+ </div>
365
+ </li>
366
+ <li class="step">
367
+ <div class="step__no">3</div>
368
+ <div class="step__body">
369
+ <h3>대시보드 열기 (선택)</h3>
370
+ <p>내 데이터를 눈으로 보려면 <code>npx -y careermate start</code> 실행 → <code>http://127.0.0.1:4319</code>. 종료는 <kbd>Ctrl</kbd>+<kbd>C</kbd>.</p>
371
+ </div>
372
+ </li>
373
+ </ol>
374
+ </div>
375
+ </details>
376
+
377
+ <!-- track 3 · .mcpb (데모트) -->
378
+ <details class="acc">
379
+ <summary><span class="track__tag">Claude Desktop · <code>.mcpb</code></span> 설치 파일로 추가 (터미널 없이)</summary>
380
+ <div class="acc__body">
381
+ <p class="track-desc">터미널·Node 없이 파일 하나로 끝나지만, <strong>Claude Desktop 내장 Node 버전에 따라 불안정할 수 있습니다.</strong> 잘 안 되면 위 <strong>에이전트가 직접 설치</strong> 또는 <strong>터미널 한 줄</strong>을 쓰세요.</p>
382
+ <ol class="steps">
383
+ <li class="step">
384
+ <div class="step__no">1</div>
385
+ <div class="step__body">
386
+ <h3>설치 파일 받기</h3>
387
+ <p>아래 버튼으로 <code>careermate.mcpb</code>를 내려받으세요. (약 2&nbsp;MB)</p>
388
+ <a class="btn btn--ghost" href="https://github.com/osntak/careermate/releases/latest/download/careermate.mcpb">
389
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><path d="M7 10l5 5 5-5"/><path d="M12 15V3"/></svg>
390
+ careermate.mcpb 다운로드
391
+ </a>
392
+ </div>
393
+ </li>
394
+ <li class="step">
395
+ <div class="step__no">2</div>
396
+ <div class="step__body">
397
+ <h3>Claude Desktop 설정에서 추가</h3>
398
+ <p>Claude Desktop → <strong>Settings(설정) → Extensions(확장)</strong>에서 <strong>Advanced settings(고급 설정)</strong>를 누르면 나오는 <strong>Install extension…</strong>으로 내려받은 <code>careermate.mcpb</code>를 선택하세요.</p>
399
+ <p class="step__tip">더블클릭·드래그는 Windows에서 파일 연결이 안 잡혀 <strong>반응이 없을 수 있으니</strong>, 위 설정 메뉴 경로를 쓰세요. (확장을 처음 설치하면 <strong>Advanced settings</strong>를 먼저 눌러야 버튼이 보입니다.)</p>
400
+ </div>
401
+ </li>
402
+ <li class="step">
403
+ <div class="step__no">3</div>
404
+ <div class="step__body">
405
+ <h3>재시작하고 확인</h3>
406
+ <p>Claude Desktop을 완전히 종료했다 다시 켜고, <strong>“커리어메이트 연결됐는지 확인해줘”</strong>라고 말해 보세요.</p>
407
+ </div>
408
+ </li>
409
+ </ol>
410
+ </div>
411
+ </details>
412
+
413
+ <!-- 수동 등록 · 설정 파일 위치 · 소스에서 실행 -->
414
+ <details class="acc" id="install-other">
415
+ <summary>수동 등록 · 설정 파일 위치 <span class="acc__hint">Cursor·Cline·Windsurf</span></summary>
416
+ <div class="acc__body">
417
+ <p class="step__tip"><code>npx -y careermate init --print</code>로 설정 JSON·명령만 출력해 클라이언트에 직접 추가할 수 있습니다. 클라이언트별 설정 파일 위치는 아래 탭을 참고하세요.</p>
418
+
419
+ <div class="tabs" role="tablist" aria-label="설정 파일 위치">
420
+ <button class="tab is-active" id="tab-claude" role="tab" aria-controls="panel-claude" aria-selected="true">Claude Desktop</button>
421
+ <button class="tab" id="tab-cursor" role="tab" aria-controls="panel-cursor" aria-selected="false" tabindex="-1">Cursor / 기타</button>
422
+ </div>
423
+ <div class="panel" id="panel-claude" role="tabpanel" aria-labelledby="tab-claude" data-active="true">
424
+ <p class="path-note">Windows: <code>%APPDATA%\Claude\claude_desktop_config.json</code> · macOS: <code>~/Library/Application Support/Claude/claude_desktop_config.json</code><br />(Settings → Developer → Edit Config 로도 열 수 있습니다. <code>npx -y careermate init</code>이 이 파일을 자동으로 수정합니다.)</p>
425
+ </div>
426
+ <div class="panel" id="panel-cursor" role="tabpanel" aria-labelledby="tab-cursor" data-active="false" hidden>
427
+ <p class="path-note">프로젝트별: <code>.cursor/mcp.json</code> · 전역: <code>~/.cursor/mcp.json</code> — Cursor·Cline·Windsurf 등 대부분 같은 형식입니다. 추가 후 클라이언트를 다시 시작하세요.</p>
428
+ </div>
429
+ </div>
430
+ </details>
431
+
432
+ <!-- 트러블슈팅 -->
433
+ <details class="acc">
434
+ <summary>문제가 생겼을 때 <span class="acc__hint">포트 충돌 · MCP 연결 실패 · 데이터 위치 · 초기화</span></summary>
435
+ <div class="acc__body">
436
+ <div class="trouble">
437
+ <div class="trouble__item">
438
+ <h4>포트가 이미 사용 중</h4>
439
+ <p>4319가 막히면 자동으로 다음 빈 포트로 옮겨갑니다. 터미널에 출력된 실제 주소를 사용하거나, <code>CAREERMATE_PORT</code> 환경변수로 고정하세요.</p>
440
+ </div>
441
+ <div class="trouble__item">
442
+ <h4>AI에 MCP가 연결되지 않음</h4>
443
+ <p>① <code>npx -y careermate init</code>이 정상 종료됐는지 ② <code>node --version</code>이 22.5 이상인지 ③ 클라이언트를 <strong>완전히 종료 후 재시작</strong>했는지 확인하세요. 폴더를 옮기거나 이름을 바꿨다면 등록된 경로가 깨질 수 있으니 init을 다시 실행하세요.</p>
444
+ </div>
445
+ <div class="trouble__item">
446
+ <h4>상태 점검</h4>
447
+ <p>진단을 실행하면 Node 버전·데이터 폴더·DB 상태를 점검합니다.</p>
448
+ <div class="code">
449
+ <button class="copy-btn" type="button" data-copy-target="code-doctor">복사</button>
450
+ <pre id="code-doctor">npx -y careermate doctor</pre>
451
+ </div>
452
+ </div>
453
+ <div class="trouble__item">
454
+ <h4>데이터 위치 / 초기화</h4>
455
+ <p>기본 위치는 <code>~/.careermate/careermate.sqlite</code>(Windows: <code>%USERPROFILE%\.careermate</code>). <code>CAREERMATE_DATA_DIR</code>로 변경 가능. 완전 초기화는 <code>careermate.sqlite</code> 삭제 후 재실행(백업 권장)입니다.</p>
456
+ </div>
457
+ </div>
458
+ </div>
459
+ </details>
460
+ </section>
461
+
462
+ <!-- ======================================================= privacy -->
463
+ <section id="privacy" class="section">
464
+ <div class="privacy-card">
465
+ <div class="privacy-card__icon" aria-hidden="true">
466
+ <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="M9 12l2 2 4-4"/></svg>
467
+ </div>
468
+ <div class="privacy-card__text">
469
+ <h2>당신의 데이터는 당신 컴퓨터를 떠나지 않습니다</h2>
470
+ <p>대시보드 서버는 <code>127.0.0.1</code>(이 컴퓨터)에만 바인딩되어 외부에서 접근할 수 없습니다.<br />MCP 서버가 외부로 내보내는 것은 (끌 수 있는) <strong>최신 버전 확인</strong>뿐이며, <strong>당신의 커리어 데이터는 전송하지 않습니다.</strong> 분석·글쓰기는 당신이 직접 고른 AI가 수행합니다.</p>
471
+ <ul class="privacy-card__list">
472
+ <li><strong>저장 위치</strong> <code>~/.careermate/careermate.sqlite</code></li>
473
+ <li><strong>내보내기</strong> 자기소개서 등을 <code>~/.careermate/exports</code>로</li>
474
+ <li><strong>삭제·관리</strong> 대시보드 <strong>설정(Settings)</strong>에서 직접</li>
475
+ </ul>
476
+ <p class="privacy-card__note">
477
+ 위 내용은 <strong>설치하는 앱</strong> 이야기예요. 지금 보고 계신 <strong>이 소개 웹사이트</strong>는 방문 통계를 보려고 쿠키 없는 익명 분석(Vercel Analytics)만 사용하며, 여기엔 어떤 커리어 데이터도 포함되지 않습니다.
478
+ </p>
479
+ </div>
480
+ </div>
481
+ </section>
482
+
483
+ </main>
484
+
485
+ <footer class="foot">
486
+ <div class="foot__brand"><span class="nav__logo" aria-hidden="true">C</span> CareerMate</div>
487
+ <p>로컬에서 동작하는 MCP 기반 커리어 관리 도구. 모든 데이터는 이 컴퓨터에만 저장됩니다.</p>
488
+ <div class="foot__links">
489
+ <a href="#top">맨 위로</a>
490
+ <a href="#features">기능</a>
491
+ <a href="#install">설치</a>
492
+ </div>
493
+ </footer>
494
+
495
+ <div class="sr-only" role="status" aria-live="polite" id="copy-status"></div>
496
+
497
+ <script src="landing.js"></script>
498
+ <script>
499
+ (function () {
500
+ 'use strict';
501
+
502
+ // --- config tabs -------------------------------------------------
503
+ var tablist = document.querySelector('.tabs[role="tablist"]');
504
+ if (tablist) {
505
+ var tabs = Array.prototype.slice.call(tablist.querySelectorAll('[role="tab"]'));
506
+ function selectTab(tab) {
507
+ tabs.forEach(function (t) {
508
+ var sel = t === tab;
509
+ t.setAttribute('aria-selected', sel ? 'true' : 'false');
510
+ t.classList.toggle('is-active', sel);
511
+ t.tabIndex = sel ? 0 : -1;
512
+ var panel = document.getElementById(t.getAttribute('aria-controls'));
513
+ if (panel) {
514
+ panel.setAttribute('data-active', sel ? 'true' : 'false');
515
+ if (sel) panel.removeAttribute('hidden'); else panel.setAttribute('hidden', '');
516
+ }
517
+ });
518
+ }
519
+ tabs.forEach(function (tab, i) {
520
+ tab.addEventListener('click', function () { selectTab(tab); });
521
+ tab.addEventListener('keydown', function (e) {
522
+ var idx = null;
523
+ if (e.key === 'ArrowRight') idx = (i + 1) % tabs.length;
524
+ else if (e.key === 'ArrowLeft') idx = (i - 1 + tabs.length) % tabs.length;
525
+ else if (e.key === 'Home') idx = 0;
526
+ else if (e.key === 'End') idx = tabs.length - 1;
527
+ if (idx !== null) { e.preventDefault(); tabs[idx].focus(); selectTab(tabs[idx]); }
528
+ });
529
+ });
530
+ }
531
+
532
+ // --- scroll reveal (fade-up on enter) ----------------------------
533
+ var reduceMotion = window.matchMedia && window.matchMedia('(prefers-reduced-motion: reduce)').matches;
534
+ if (!reduceMotion && 'IntersectionObserver' in window) {
535
+ var revealEls = document.querySelectorAll('.section__head, .feature, .check, .track__head, .step, .acc, .privacy-card');
536
+ revealEls.forEach(function (el) { el.classList.add('reveal'); });
537
+ var io = new IntersectionObserver(function (entries) {
538
+ entries.forEach(function (e) {
539
+ if (e.isIntersecting) { e.target.classList.add('is-visible'); io.unobserve(e.target); }
540
+ });
541
+ }, { threshold: 0.1, rootMargin: '0px 0px -48px 0px' });
542
+ revealEls.forEach(function (el) { io.observe(el); });
543
+ }
544
+
545
+ // --- dashboard carousel (prev/next + dots + keyboard + swipe) -----
546
+ var carousel = document.getElementById('dash-carousel');
547
+ var track = document.getElementById('carousel-track');
548
+ if (carousel && track) {
549
+ var slides = Array.prototype.slice.call(track.children);
550
+ var prevBtn = document.getElementById('carousel-prev');
551
+ var nextBtn = document.getElementById('carousel-next');
552
+ var dotsWrap = document.getElementById('carousel-dots');
553
+ var idx = 0;
554
+ var captionOf = function (s) {
555
+ var f = s.querySelector('figcaption');
556
+ return f ? f.textContent.trim() : '';
557
+ };
558
+ var dots = slides.map(function (s, n) {
559
+ var b = document.createElement('button');
560
+ b.type = 'button'; b.className = 'carousel__dot';
561
+ b.setAttribute('role', 'tab');
562
+ b.setAttribute('aria-label', captionOf(s) || (n + 1) + '번째 화면');
563
+ b.addEventListener('click', function () { go(n); });
564
+ dotsWrap.appendChild(b);
565
+ return b;
566
+ });
567
+ function go(n) {
568
+ idx = Math.max(0, Math.min(slides.length - 1, n));
569
+ track.style.transform = 'translateX(' + (-idx * 100) + '%)';
570
+ dots.forEach(function (d, n2) {
571
+ var on = n2 === idx;
572
+ d.classList.toggle('is-active', on);
573
+ d.setAttribute('aria-selected', on ? 'true' : 'false');
574
+ });
575
+ slides.forEach(function (s, n2) { s.setAttribute('aria-hidden', n2 === idx ? 'false' : 'true'); });
576
+ if (prevBtn) prevBtn.disabled = idx === 0;
577
+ if (nextBtn) nextBtn.disabled = idx === slides.length - 1;
578
+ }
579
+ if (prevBtn) prevBtn.addEventListener('click', function () { go(idx - 1); });
580
+ if (nextBtn) nextBtn.addEventListener('click', function () { go(idx + 1); });
581
+ carousel.addEventListener('keydown', function (e) {
582
+ if (e.key === 'ArrowLeft') { e.preventDefault(); go(idx - 1); }
583
+ else if (e.key === 'ArrowRight') { e.preventDefault(); go(idx + 1); }
584
+ });
585
+ var x0 = null;
586
+ track.addEventListener('touchstart', function (e) { x0 = e.touches[0].clientX; }, { passive: true });
587
+ track.addEventListener('touchend', function (e) {
588
+ if (x0 === null) return;
589
+ var dx = e.changedTouches[0].clientX - x0;
590
+ if (Math.abs(dx) > 40) go(idx + (dx < 0 ? 1 : -1));
591
+ x0 = null;
592
+ });
593
+ go(0);
594
+ }
595
+ })();
596
+ </script>
597
+ </body>
598
+ </html>