careermate 0.1.0 → 0.1.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 (93) hide show
  1. package/README.md +10 -7
  2. package/apps/mcp/src/index.ts +3 -1
  3. package/apps/web/DESIGN_GUIDE.md +2 -2
  4. package/apps/web/public/icon-180.png +0 -0
  5. package/apps/web/public/index.html +4 -1
  6. package/apps/web/public/lib.js +1 -1
  7. package/apps/web/public/pages/applications.js +17 -1
  8. package/apps/web/public/pages/settings.js +6 -4
  9. package/apps/web/public/styles.css +49 -36
  10. package/apps/web/src/info.ts +9 -22
  11. package/apps/web/src/routes.ts +17 -13
  12. package/apps/web/src/server.ts +33 -25
  13. package/apps/web/src/settings.ts +35 -17
  14. package/bin/careermate.mjs +8 -5
  15. package/dist/{mcpb-stage/dist/web.mjs → doctor.mjs} +1122 -1450
  16. package/dist/install-page/favicon.svg +1 -0
  17. package/dist/install-page/icon-180.png +0 -0
  18. package/dist/install-page/icon-512.png +0 -0
  19. package/dist/install-page/index.html +309 -167
  20. package/dist/install-page/llms-install.txt +202 -0
  21. package/dist/install-page/og.png +0 -0
  22. package/dist/install-page/shots/applications-dark.png +0 -0
  23. package/dist/install-page/shots/applications-light.png +0 -0
  24. package/dist/install-page/shots/documents-dark.png +0 -0
  25. package/dist/install-page/shots/documents-light.png +0 -0
  26. package/dist/install-page/shots/home-dark.png +0 -0
  27. package/dist/install-page/shots/home-light.png +0 -0
  28. package/dist/install-page/shots/interview-dark.png +0 -0
  29. package/dist/install-page/shots/interview-light.png +0 -0
  30. package/dist/install-page/shots/jobs-dark.png +0 -0
  31. package/dist/install-page/shots/jobs-light.png +0 -0
  32. package/dist/install-page/shots/settings-dark.png +0 -0
  33. package/dist/install-page/shots/settings-light.png +0 -0
  34. package/dist/install-page/start.html +346 -0
  35. package/dist/install-page/style.css +94 -3
  36. package/dist/mcp.mjs +326 -68
  37. package/dist/migrate.mjs +4715 -0
  38. package/dist/public/icon-180.png +0 -0
  39. package/dist/public/index.html +4 -1
  40. package/dist/public/lib.js +1 -1
  41. package/dist/public/pages/applications.js +17 -1
  42. package/dist/public/pages/settings.js +6 -4
  43. package/dist/public/styles.css +49 -36
  44. package/dist/web.mjs +323 -129
  45. package/docs/DEMO.md +3 -3
  46. package/docs/MCP_TOOLS.md +21 -2
  47. package/docs/ROADMAP.md +3 -3
  48. package/docs/RUNTIME_DISTRIBUTION_RESEARCH.md +135 -0
  49. package/docs/START_WORKFLOW.md +6 -0
  50. package/docs/SUPPORTED_AI_APPS.md +85 -33
  51. package/docs/WORKFLOWS.md +1 -1
  52. package/package.json +3 -2
  53. package/packages/core/src/services.ts +42 -1
  54. package/packages/db/src/connection.ts +30 -0
  55. package/packages/db/src/index.ts +1 -0
  56. package/packages/db/src/repositories.ts +109 -63
  57. package/packages/db/src/schema.ts +118 -0
  58. package/packages/exporters/src/html.ts +9 -12
  59. package/packages/mcp-tools/src/tools.ts +81 -0
  60. package/packages/shared/src/enums.ts +39 -0
  61. package/scripts/build-dist.mjs +27 -8
  62. package/scripts/build-mcpb.mjs +60 -4
  63. package/scripts/gen-og.mjs +86 -0
  64. package/scripts/init.ts +32 -3
  65. package/scripts/seed.ts +5 -5
  66. package/scripts/shots-seed.ts +192 -0
  67. package/scripts/shots.ts +89 -0
  68. package/tsconfig.json +3 -1
  69. package/dist/careermate.mcpb +0 -0
  70. package/dist/mcp-smoke.err +0 -3
  71. package/dist/mcpb-stage/README.md +0 -219
  72. package/dist/mcpb-stage/dist/install-page/index.html +0 -434
  73. package/dist/mcpb-stage/dist/install-page/style.css +0 -407
  74. package/dist/mcpb-stage/dist/install-page/vercel.json +0 -20
  75. package/dist/mcpb-stage/dist/mcp.mjs +0 -23704
  76. package/dist/mcpb-stage/dist/public/app.js +0 -118
  77. package/dist/mcpb-stage/dist/public/index.html +0 -41
  78. package/dist/mcpb-stage/dist/public/lib.js +0 -282
  79. package/dist/mcpb-stage/dist/public/pages/applications.js +0 -98
  80. package/dist/mcpb-stage/dist/public/pages/documents.js +0 -446
  81. package/dist/mcpb-stage/dist/public/pages/home.js +0 -263
  82. package/dist/mcpb-stage/dist/public/pages/interview.js +0 -230
  83. package/dist/mcpb-stage/dist/public/pages/jobs.js +0 -494
  84. package/dist/mcpb-stage/dist/public/pages/profile.js +0 -576
  85. package/dist/mcpb-stage/dist/public/pages/settings.js +0 -233
  86. package/dist/mcpb-stage/dist/public/styles.css +0 -420
  87. package/dist/mcpb-stage/manifest.json +0 -40
  88. package/docs/INSTALL.md +0 -148
  89. package/docs/INSTALL_AND_USAGE.md +0 -99
  90. package/install-page/index.html +0 -474
  91. package/install-page/style.css +0 -391
  92. package/install-page/vercel.json +0 -20
  93. /package/dist/{mcpb-stage/dist/public → install-page}/fonts/PretendardVariable.woff2 +0 -0
@@ -1,10 +1,46 @@
1
1
  <!DOCTYPE html>
2
- <html lang="ko" data-theme="">
2
+ <html lang="ko" data-theme="dark">
3
3
  <head>
4
4
  <meta charset="utf-8" />
5
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('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>
6
15
  <title>CareerMate — 내 AI를 커리어 비서로</title>
7
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="theme-color" content="#faf8f4" media="(prefers-color-scheme: light)" />
19
+ <meta name="theme-color" content="#14130f" media="(prefers-color-scheme: dark)" />
20
+
21
+ <!-- icons -->
22
+ <link rel="icon" href="favicon.svg" type="image/svg+xml" />
23
+ <link rel="apple-touch-icon" href="icon-180.png" />
24
+
25
+ <!-- Open Graph -->
26
+ <meta property="og:type" content="website" />
27
+ <meta property="og:site_name" content="CareerMate" />
28
+ <meta property="og:locale" content="ko_KR" />
29
+ <meta property="og:url" content="https://careermate.life/" />
30
+ <meta property="og:title" content="CareerMate — 내 AI를 커리어 비서로" />
31
+ <meta property="og:description" content="이력서·자기소개서·지원 현황을 이 컴퓨터 안에서 관리하세요. 분석·글쓰기는 평소 쓰던 AI가, 데이터 보관은 CareerMate가 맡습니다." />
32
+ <meta property="og:image" content="https://careermate.life/og.png" />
33
+ <meta property="og:image:width" content="1200" />
34
+ <meta property="og:image:height" content="630" />
35
+ <meta property="og:image:alt" content="CareerMate — 로컬 전용 MCP 커리어 워크스페이스" />
36
+
37
+ <!-- Twitter / X -->
38
+ <meta name="twitter:card" content="summary_large_image" />
39
+ <meta name="twitter:title" content="CareerMate — 내 AI를 커리어 비서로" />
40
+ <meta name="twitter:description" content="이력서·자기소개서·지원 현황을 이 컴퓨터 안에서. 데이터는 외부로 나가지 않습니다." />
41
+ <meta name="twitter:image" content="https://careermate.life/og.png" />
42
+
43
+ <link rel="preload" href="fonts/PretendardVariable.woff2" as="font" type="font/woff2" crossorigin />
8
44
  <link rel="stylesheet" href="style.css" />
9
45
  </head>
10
46
  <body>
@@ -17,7 +53,9 @@
17
53
  <div class="nav__links">
18
54
  <a href="#how">동작 방식</a>
19
55
  <a href="#features">기능</a>
56
+ <a href="#preview">화면</a>
20
57
  <a href="#install">설치</a>
58
+ <a href="start.html">처음이세요?</a>
21
59
  <a href="#privacy">개인정보</a>
22
60
  </div>
23
61
  <div class="nav__actions">
@@ -35,7 +73,7 @@
35
73
  <header class="hero">
36
74
  <span class="pill-badge">
37
75
  <span class="pill-badge__dot" aria-hidden="true"></span>
38
- 로컬 전용 · 오픈소스 · Claude Desktop 원클릭
76
+ 로컬 전용 · 무료
39
77
  </span>
40
78
  <h1 class="hero__title">
41
79
  내가 쓰던 <span class="accent">AI</span>를<br />
@@ -51,11 +89,11 @@
51
89
  <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>
52
90
  설치 방법 보기
53
91
  </a>
54
- <a class="btn btn--ghost" href="#install-other">기타 MCP 클라이언트</a>
92
+ <a class="btn btn--ghost" href="start.html">처음이세요? — 내게 맞는 길 찾기</a>
55
93
  </div>
56
94
 
57
95
  <p class="hero__note">
58
- CareerMate는 <strong>내 컴퓨터에서 도는 로컬 도구</strong>라, 내 컴퓨터의 프로그램을 실행할 수 있는 AI에서 동작합니다 — <strong>Claude Desktop · Claude Code · Codex</strong>.
96
+ CareerMate는 <strong>내 컴퓨터에서 도는 로컬 도구</strong>예요. 그래서 <span class="nowrap">Claude&nbsp;Desktop · Claude&nbsp;Code · Codex</span>처럼 내 컴퓨터의 프로그램을 띄울 수 있는 AI에서 동작합니다.<br />
59
97
  설치를 마쳤다면 대시보드 <code>http://127.0.0.1:4319</code>에서 내 데이터를 확인하세요.
60
98
  </p>
61
99
  </header>
@@ -109,232 +147,285 @@
109
147
  </div>
110
148
  </section>
111
149
 
150
+ <!-- ======================================================= preview -->
151
+ <section id="preview" class="section">
152
+ <div class="section__head">
153
+ <span class="eyebrow">대시보드</span>
154
+ <h2>내 데이터를 <span class="accent">눈으로</span> 확인</h2>
155
+ <p>모든 기록은 이 컴퓨터의 대시보드(<code>127.0.0.1:4319</code>)에 모입니다. 아래는 실제 화면입니다.</p>
156
+ </div>
157
+
158
+ <div class="carousel" id="dash-carousel" role="group" aria-roledescription="캐러셀" aria-label="대시보드 화면 미리보기" tabindex="0">
159
+ <div class="carousel__viewport">
160
+ <div class="carousel__track" id="carousel-track">
161
+ <figure class="slide">
162
+ <div class="browser">
163
+ <div class="browser__bar">
164
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
165
+ <span class="browser__url">127.0.0.1:4319</span>
166
+ </div>
167
+ <div class="browser__body">
168
+ <img class="shot__img shot__img--light" src="shots/home-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 홈 대시보드 — 지원 파이프라인과 할 일, 최근 활동" />
169
+ <img class="shot__img shot__img--dark" src="shots/home-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 홈 대시보드 (다크) — 지원 파이프라인과 할 일, 최근 활동" />
170
+ </div>
171
+ </div>
172
+ <figcaption><strong>홈</strong> — 파이프라인·할 일·활동을 한 화면에</figcaption>
173
+ </figure>
174
+
175
+ <figure class="slide">
176
+ <div class="browser">
177
+ <div class="browser__bar">
178
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
179
+ <span class="browser__url">127.0.0.1:4319/#/applications</span>
180
+ </div>
181
+ <div class="browser__body">
182
+ <img class="shot__img shot__img--light" src="shots/applications-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 지원 현황 — 상태별로 정리된 지원 목록" />
183
+ <img class="shot__img shot__img--dark" src="shots/applications-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 지원 현황 (다크) — 상태별로 정리된 지원 목록" />
184
+ </div>
185
+ </div>
186
+ <figcaption><strong>지원 현황</strong> — 8단계 상태별로 한눈에</figcaption>
187
+ </figure>
188
+
189
+ <figure class="slide">
190
+ <div class="browser">
191
+ <div class="browser__bar">
192
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
193
+ <span class="browser__url">127.0.0.1:4319/#/jobs</span>
194
+ </div>
195
+ <div class="browser__body">
196
+ <img class="shot__img shot__img--light" src="shots/jobs-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 채용공고 — 적합도 점수와 진행 상태" />
197
+ <img class="shot__img shot__img--dark" src="shots/jobs-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 채용공고 (다크) — 적합도 점수와 진행 상태" />
198
+ </div>
199
+ </div>
200
+ <figcaption><strong>채용공고</strong> — 적합도 점수와 진행 상태</figcaption>
201
+ </figure>
202
+
203
+ <figure class="slide">
204
+ <div class="browser">
205
+ <div class="browser__bar">
206
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
207
+ <span class="browser__url">127.0.0.1:4319/#/documents</span>
208
+ </div>
209
+ <div class="browser__body">
210
+ <img class="shot__img shot__img--light" src="shots/documents-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 문서 — 자기소개서와 이력서를 버전과 함께 보관" />
211
+ <img class="shot__img shot__img--dark" src="shots/documents-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 문서 (다크) — 자기소개서와 이력서를 버전과 함께 보관" />
212
+ </div>
213
+ </div>
214
+ <figcaption><strong>문서</strong> — 자기소개서·이력서를 버전과 함께</figcaption>
215
+ </figure>
216
+
217
+ <figure class="slide">
218
+ <div class="browser">
219
+ <div class="browser__bar">
220
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
221
+ <span class="browser__url">127.0.0.1:4319/#/interview</span>
222
+ </div>
223
+ <div class="browser__body">
224
+ <img class="shot__img shot__img--light" src="shots/interview-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 면접 준비 — 예상 질문과 STAR, 1분 자기소개" />
225
+ <img class="shot__img shot__img--dark" src="shots/interview-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 면접 준비 (다크) — 예상 질문과 STAR, 1분 자기소개" />
226
+ </div>
227
+ </div>
228
+ <figcaption><strong>면접 준비</strong> — 예상 질문·STAR·1분 자기소개</figcaption>
229
+ </figure>
230
+
231
+ <figure class="slide">
232
+ <div class="browser">
233
+ <div class="browser__bar">
234
+ <span class="browser__dots" aria-hidden="true"><i></i><i></i><i></i></span>
235
+ <span class="browser__url">127.0.0.1:4319/#/settings</span>
236
+ </div>
237
+ <div class="browser__body">
238
+ <img class="shot__img shot__img--light" src="shots/settings-light.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 설정 — 데이터 저장 위치와 연결 상태, 테마" />
239
+ <img class="shot__img shot__img--dark" src="shots/settings-dark.png" width="1360" height="940" loading="lazy" decoding="async" alt="CareerMate 설정 (다크) — 데이터 저장 위치와 연결 상태, 테마" />
240
+ </div>
241
+ </div>
242
+ <figcaption><strong>설정</strong> — 데이터 위치·내보내기·테마</figcaption>
243
+ </figure>
244
+ </div>
245
+ </div>
246
+
247
+ <button class="carousel__arrow carousel__arrow--prev" id="carousel-prev" type="button" aria-label="이전 화면">
248
+ <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>
249
+ </button>
250
+ <button class="carousel__arrow carousel__arrow--next" id="carousel-next" type="button" aria-label="다음 화면">
251
+ <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>
252
+ </button>
253
+ </div>
254
+ <div class="carousel__dots" id="carousel-dots" role="tablist" aria-label="화면 선택"></div>
255
+ </section>
256
+
112
257
  <!-- ======================================================= install -->
113
258
  <section id="install" class="section">
114
259
  <div class="section__head">
115
260
  <span class="eyebrow">설치</span>
116
- <h2>세 가지 AI 앱에 연결</h2>
117
- <p>지원하는 로컬 AI 앱은 <strong>Claude Desktop · Claude Code · Codex</strong> 셋입니다. 어느 쪽이든 같은 코어(로컬 DB + 도구 24개)를 씁니다.</p>
261
+ <h2>내 <span class="accent">AI 앱</span>에 연결하기</h2>
262
+ <p>빈 폴더 잡고 <strong>한 문장이면 끝.</strong><br />
263
+ 터미널이 익숙하면 한 줄 설치, 싫으면 파일 하나로 — <strong>무료도 있습니다.</strong></p>
118
264
  </div>
119
265
 
266
+ <a class="note" href="start.html" style="display:block;text-decoration:none;border-left-color:var(--accent);background:var(--accent-soft)">
267
+ <strong style="color:var(--text)">완전 처음이세요?</strong> 터미널·구독이 뭔지 몰라도 괜찮아요 — 쓰는 AI와 컴퓨터만 고르면 <strong>맞는 길 하나</strong>를 알려드려요. <strong style="color:var(--accent)">길 찾기 →</strong>
268
+ </a>
269
+
120
270
  <div class="note">
121
- <strong>어떤 AI에서 되나요?</strong> CareerMate는 데이터를 내 컴퓨터에만 두는 <strong>로컬 MCP 도구</strong>입니다.
122
- 그래서 <strong>내 컴퓨터에서 실행되며 로컬 프로그램을 띄울 수 있는 AI</strong>에서 동작합니다 — Claude Desktop · Claude Code · Codex(그 외 Cursor·Cline·Windsurf 등도 가능).
123
- ChatGPT·Gemini·Claude의 <strong>웹/모바일 앱</strong>은 클라우드에서 돌아 내 컴퓨터의 로컬 서버에 직접 연결할 수 없습니다(원격 URL 기반 MCP만 지원).
271
+ <strong>어떤 AI에서 되나요?</strong> 내 컴퓨터에서 프로그램을 띄울 수 있는 AI에서만 됩니다.<br />
272
+ <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>(클라우드라 내 컴퓨터 서버에 못 닿음).
124
273
  </div>
125
274
 
126
- <!-- track 1 · Claude Desktop -->
127
- <div class="track">
128
- <div class="track__head">
129
- <span class="track__tag">1 · Claude Desktop</span>
130
- <h3>원클릭 <code>.mcpb</code> 설치</h3>
131
- <p>터미널도, Node 설치도 필요 없습니다. 파일 하나를 더블클릭하면 끝납니다.</p>
132
- </div>
133
- <ol class="steps">
275
+ <!-- track 1 · 앱에서 직접 설치 (비개발자 메인) -->
276
+ <details class="acc" open>
277
+ <summary><span class="track__tag">추천 · 앱에서 한 문장</span> 내 AI 앱에서 빈 폴더 잡고 한 문장</summary>
278
+ <div class="acc__body">
279
+ <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>
280
+ <ol class="steps">
134
281
  <li class="step">
135
282
  <div class="step__no">1</div>
136
283
  <div class="step__body">
137
- <h3>설치 파일 받기</h3>
138
- <p><code>careermate.mcpb</code> 파일이 필요합니다. <strong>(Release <code>v0.1.0</code>에 있으나 저장소가 비공개라 공개 다운로드 링크는 아직 없습니다.)</strong></p>
139
- <!-- TODO: 저장소를 public으로 전환하면 https://github.com/osntak/CareerMate/releases/latest 의 .mcpb로 이 버튼을 활성화. 현재는 private repo라 공개 다운로드 불가. -->
140
- <button class="btn btn--primary" type="button" disabled aria-disabled="true">
141
- <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>
142
- careermate.mcpb 다운로드 (비공개 저장소)
143
- </button>
144
- <p class="step__tip">접근 권한이 있으면 Release <code>v0.1.0</code>에서 받고, 없으면 터미널에서 <code>npx -y careermate init</code>로 설치하거나 소스에서 <code>npm run build:mcpb</code>로 빌드하세요.</p>
284
+ <h3>앱 열고 빈 폴더 선택</h3>
285
+ <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>(openai/codex#24718)가 있어 권하지 않습니다.</p>
145
286
  </div>
146
287
  </li>
147
288
  <li class="step">
148
289
  <div class="step__no">2</div>
149
290
  <div class="step__body">
150
- <h3>더블클릭으로 설치</h3>
151
- <p>내려받은 <code>careermate.mcpb</code>를 더블클릭하면 Claude Desktop이 설치 창을 엽니다. <strong>설치</strong>를 누르세요.
152
- (또는 Claude Desktop → <strong>Settings → Extensions</strong>에 파일을 끌어다 놓아도 됩니다.)</p>
291
+ <h3>이 문장 붙여넣기</h3>
292
+ <div class="code">
293
+ <button class="copy-btn" type="button" data-copy-target="code-prompt-agent">복사</button>
294
+ <pre id="code-prompt-agent">https://careermate.life/llms-install.txt 를 읽고, 그 안내대로 CareerMate를 이 폴더에 설치하고 연결해줘. 데이터도 이 폴더의 .cm/data 안에 저장하고, Node 설치처럼 시스템에 바꿀 게 있으면 실행 전에 먼저 알려줘. 설치가 끝나면 앱 재시작 방법과 이력서를 주는 법, 대시보드 주소(http://127.0.0.1:4319)도 알려줘.</pre>
295
+ </div>
296
+ <p class="step__tip">준비물은 <strong>Node.js 22.5 이상</strong> 하나뿐. 없으면 에이전트가 <strong>먼저 확인하고</strong> 설치를 도와줍니다. 데이터는 작업 폴더의 <code>.cm/data</code>에만 저장됩니다. (Codex CLI는 설치 단계에서 네트워크 허용을 한 번 물을 수 있어요 — 허용하면 됩니다.)</p>
153
297
  </div>
154
298
  </li>
155
299
  <li class="step">
156
300
  <div class="step__no">3</div>
157
301
  <div class="step__body">
158
- <h3>재시작하고 확인</h3>
159
- <p>Claude Desktop을 완전히 종료했다 다시 켜세요. 그다음 Claude에게
160
- <strong>“<code>get_onboarding_status</code> 호출해서 연결됐는지 확인해줘”</strong>라고 말해 결과가 돌아오면 정상입니다.</p>
302
+ <h3>재시작 후 확인</h3>
303
+ <p>AI 앱을 완전히 종료했다 다시 켜고(처음 한 번 승인을 물으면 허용), <strong>“<code>get_onboarding_status</code> 호출해서 확인해줘”</strong>로 점검하세요. Codex는 <code>/mcp</code>로도 확인됩니다.</p>
161
304
  </div>
162
305
  </li>
163
- </ol>
164
- <p class="step__tip">Claude Desktop이 Node를 내장하므로 따로 설치할 게 없습니다. 드물게 내장 Node가 22.5 미만이라 설치가 막히면 아래 <a href="#install-other">터미널 방식</a>을 쓰세요.</p>
165
- </div>
166
-
167
- <!-- track 2 · Claude Code -->
168
- <div class="track">
169
- <div class="track__head">
170
- <span class="track__tag">2 · Claude Code</span>
171
- <h3>CareerMate 폴더에서 한 문장으로</h3>
172
- <p>루트 <code>CLAUDE.md</code>가 설치 안내 역할을 합니다. 폴더를 열고 아래 문장만 말하세요.</p>
306
+ </ol>
173
307
  </div>
174
- <ol class="steps">
308
+ </details>
309
+
310
+ <!-- track 2 · npx 한 줄 -->
311
+ <details class="acc">
312
+ <summary><span class="track__tag">터미널 · 한 줄 설치</span> npx 한 줄로 자동 연결</summary>
313
+ <div class="acc__body">
314
+ <p class="track-desc">준비물은 <strong>Node.js 22.5 이상</strong> 하나. 감지된 AI 앱(Claude Desktop·Codex·Cursor)의 MCP 설정에 CareerMate를 자동 등록합니다.</p>
315
+ <ol class="steps">
175
316
  <li class="step">
176
317
  <div class="step__no">1</div>
177
318
  <div class="step__body">
178
- <h3>CareerMate 폴더에서 Claude Code 열기</h3>
179
- <p>CareerMate 폴더에서 <code>claude</code>를 실행한 뒤, 아래 문장을 그대로 입력합니다. Claude가 <code>INSTALL.md</code>를 따라 등록까지 진행합니다.</p>
319
+ <h3>한 줄 실행</h3>
320
+ <p>터미널(Windows는 PowerShell)에 붙여넣고 실행하세요. 클론도 빌드도 필요 없습니다. <code>node --version</code>이 22.5 미만이면 <a href="https://nodejs.org/" rel="noopener" target="_blank">nodejs.org</a>에서 LTS를 먼저 설치하세요.</p>
180
321
  <div class="code">
181
- <button class="copy-btn" type="button" data-copy-target="code-prompt-cc">복사</button>
182
- <pre id="code-prompt-cc">CareerMate를 설치하고 설정해줘. INSTALL.md를 따라 진행해줘.</pre>
322
+ <button class="copy-btn" type="button" data-copy-target="code-npx">복사</button>
323
+ <pre id="code-npx">npx -y careermate init</pre>
183
324
  </div>
325
+ <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>
184
326
  </div>
185
327
  </li>
186
328
  <li class="step">
187
329
  <div class="step__no">2</div>
188
330
  <div class="step__body">
189
- <h3>한 번만 승인하고 확인</h3>
190
- <p>Claude Code가 프로젝트 MCP 서버 등록을 <strong>한 번 승인</strong> 요청하면 허용하세요. 그다음 <strong>“<code>get_onboarding_status</code> 호출해서 연결됐는지 확인해줘”</strong>로 점검합니다.</p>
191
- </div>
192
- </li>
193
- </ol>
194
- </div>
195
-
196
- <!-- track 3 · Codex -->
197
- <div class="track">
198
- <div class="track__head">
199
- <span class="track__tag">3 · Codex</span>
200
- <h3>CareerMate 폴더에서 한 문장으로</h3>
201
- <p>루트 <code>AGENTS.md</code>가 설치 안내 역할을 합니다. 폴더를 열고 같은 문장을 말하세요.</p>
202
- </div>
203
- <ol class="steps">
204
- <li class="step">
205
- <div class="step__no">1</div>
206
- <div class="step__body">
207
- <h3>CareerMate 폴더에서 Codex 열기</h3>
208
- <p>CareerMate 폴더에서 <code>codex</code>를 실행한 뒤, 아래 문장을 그대로 입력합니다. Codex가 <code>INSTALL.md</code>를 따라 등록까지 진행합니다.</p>
209
- <div class="code">
210
- <button class="copy-btn" type="button" data-copy-target="code-prompt-codex">복사</button>
211
- <pre id="code-prompt-codex">CareerMate를 설치하고 설정해줘. INSTALL.md를 따라 진행해줘.</pre>
212
- </div>
331
+ <h3>AI 앱 재시작 후 확인</h3>
332
+ <p>연결한 AI 앱을 <strong>완전히 종료했다 다시 켜고</strong>, <strong>“<code>get_onboarding_status</code> 호출해서 연결됐는지 확인해줘”</strong>라고 말해 결과가 돌아오면 정상입니다.</p>
213
333
  </div>
214
334
  </li>
215
335
  <li class="step">
216
- <div class="step__no">2</div>
336
+ <div class="step__no">3</div>
217
337
  <div class="step__body">
218
- <h3><code>/mcp</code>로 확인</h3>
219
- <p>Codex 안에서 <code>/mcp</code> 명령으로 <code>careermate</code>가 등록됐는지 확인하고, <strong>“<code>get_onboarding_status</code> 호출해서 연결됐는지 확인해줘”</strong>로 점검합니다.</p>
338
+ <h3>대시보드 열기 (선택)</h3>
339
+ <p>내 데이터를 눈으로 보려면 <code>npx -y careermate start</code> 실행 → <code>http://127.0.0.1:4319</code>. 종료는 <kbd>Ctrl</kbd>+<kbd>C</kbd>.</p>
220
340
  </div>
221
341
  </li>
222
- </ol>
223
- </div>
224
-
225
- <!-- track · 기타 MCP 클라이언트 (터미널) -->
226
- <div class="track" id="install-other">
227
- <div class="track__head">
228
- <span class="track__tag">기타 MCP 클라이언트 · 터미널</span>
229
- <h3>Cursor 등 — 직접 등록</h3>
230
- <p>Cursor · Cline · Windsurf 등 다른 로컬 stdio MCP 클라이언트는 터미널에서 직접 설치·등록합니다.</p>
231
- </div>
232
-
233
- <div class="note">
234
- <strong>준비물은 Node.js 22.5 이상</strong> 하나뿐입니다. 내장 SQLite를 쓰므로 별도 컴파일러·빌드 도구가 필요 없습니다.
235
- 터미널(Windows는 PowerShell)에서 <code>node --version</code>으로 확인하고, 낮으면
236
- <a href="https://nodejs.org/" rel="noopener" target="_blank">nodejs.org</a>에서 LTS를 설치하세요.
342
+ </ol>
237
343
  </div>
344
+ </details>
238
345
 
239
- <ol class="steps">
346
+ <!-- track 3 · .mcpb (데모트) -->
347
+ <details class="acc">
348
+ <summary><span class="track__tag">Claude Desktop · <code>.mcpb</code></span> 설치 파일로 추가 (터미널 없이)</summary>
349
+ <div class="acc__body">
350
+ <p class="track-desc">터미널·Node 없이 파일 하나로 끝나지만, <strong>Claude Desktop 내장 Node 버전에 따라 불안정할 수 있어요.</strong> 잘 안 되면 위 <strong>에이전트가 직접 설치</strong> 또는 <strong>터미널 한 줄</strong>을 쓰세요.</p>
351
+ <ol class="steps">
240
352
  <li class="step">
241
353
  <div class="step__no">1</div>
242
354
  <div class="step__body">
243
- <h3>코드 받기</h3>
244
- <p>CareerMate 저장소를 내려받습니다. (Git이 없으면 <em>Code → Download ZIP</em>으로 받아 압축을 풀어도 됩니다.)</p>
245
- <!-- 저장소(osntak/CareerMate)는 현재 비공개라 clone에 접근 권한이 필요합니다. 권한이 없으면 위 npx 방식을 쓰세요. -->
246
- <div class="code">
247
- <button class="copy-btn" type="button" data-copy-target="code-clone">복사</button>
248
- <pre id="code-clone">git clone https://github.com/osntak/CareerMate.git
249
- cd CareerMate</pre>
250
- </div>
355
+ <h3>설치 파일 받기</h3>
356
+ <p>아래 버튼으로 <code>careermate.mcpb</code>를 내려받으세요. (약 2&nbsp;MB)</p>
357
+ <a class="btn btn--ghost" href="careermate.mcpb" download="careermate.mcpb">
358
+ <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>
359
+ careermate.mcpb 다운로드
360
+ </a>
251
361
  </div>
252
362
  </li>
253
363
  <li class="step">
254
364
  <div class="step__no">2</div>
255
365
  <div class="step__body">
256
- <h3>설치</h3>
257
- <p>의존성을 한 번만 설치합니다. (빌드 단계 없음) — 또는 클론 없이 <strong><code>npx -y careermate init</code></strong> 한 줄로도 등록됩니다(<code>careermate</code> npm 게시 완료).</p>
258
- <div class="code">
259
- <span class="code__label">프로젝트 폴더에서</span>
260
- <button class="copy-btn" type="button" data-copy-target="code-install">복사</button>
261
- <pre id="code-install">npm install</pre>
262
- </div>
366
+ <h3>Claude Desktop 설정에서 추가</h3>
367
+ <p>Claude Desktop → <strong>Settings(설정) → Extensions(확장)</strong>에서 <strong>Advanced settings(고급 설정)</strong>를 누르면 나오는 <strong>Install extension…</strong>으로 내려받은 <code>careermate.mcpb</code>를 선택하세요.</p>
368
+ <p class="step__tip">더블클릭·드래그는 Windows에서 파일 연결이 안 잡혀 <strong>반응이 없을 수 있으니</strong>, 위 설정 메뉴 경로를 쓰세요. (확장을 처음 설치하면 <strong>Advanced settings</strong>를 먼저 눌러야 버튼이 보입니다.)</p>
263
369
  </div>
264
370
  </li>
265
371
  <li class="step">
266
372
  <div class="step__no">3</div>
267
373
  <div class="step__body">
268
- <h3>AI 클라이언트에 자동 연결</h3>
269
- <p>감지된 클라이언트의 MCP 설정에 CareerMate를 자동 등록하고, Claude Code 프로젝트 설정(<code>.mcp.json</code>)은 항상 함께 작성합니다. 끝나면 해당 AI 클라이언트를 <strong>완전히 종료했다 다시 켜세요.</strong></p>
270
- <div class="code">
271
- <span class="code__label">자동 등록</span>
272
- <button class="copy-btn" type="button" data-copy-target="code-init">복사</button>
273
- <pre id="code-init">npm run init</pre>
274
- </div>
275
- <p class="step__tip">직접 붙여넣고 싶다면 <code>npm run init -- --print</code>로 설정 JSON·명령만 출력해 클라이언트에 추가하세요. 특정 클라이언트만 등록하려면 <code>npm run init -- --client cursor</code>처럼 지정합니다. 클라이언트별 설정 파일 위치는 아래 탭을 참고하세요.</p>
276
-
277
- <div class="tabs" role="tablist" aria-label="설정 파일 위치">
278
- <button class="tab is-active" id="tab-claude" role="tab" aria-controls="panel-claude" aria-selected="true">Claude Desktop</button>
279
- <button class="tab" id="tab-cursor" role="tab" aria-controls="panel-cursor" aria-selected="false" tabindex="-1">Cursor / 기타</button>
280
- </div>
281
- <div class="panel" id="panel-claude" role="tabpanel" aria-labelledby="tab-claude" data-active="true">
282
- <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>npm run init</code>이 이 파일을 자동으로 수정합니다.)</p>
283
- </div>
284
- <div class="panel" id="panel-cursor" role="tabpanel" aria-labelledby="tab-cursor" data-active="false" hidden>
285
- <p class="path-note">프로젝트별: <code>.cursor/mcp.json</code> · 전역: <code>~/.cursor/mcp.json</code> — Cursor·Cline·Windsurf 등 대부분 같은 형식입니다. 추가 후 클라이언트를 다시 시작하세요.</p>
286
- </div>
374
+ <h3>재시작하고 확인</h3>
375
+ <p>Claude Desktop을 완전히 종료했다 다시 켜고, <strong>“<code>get_onboarding_status</code> 호출해서 확인해줘”</strong>로 점검하세요.</p>
287
376
  </div>
288
377
  </li>
289
- <li class="step">
290
- <div class="step__no">4</div>
291
- <div class="step__body">
292
- <h3>대시보드 실행 (선택)</h3>
293
- <p>내 데이터를 눈으로 확인하려면 대시보드를 켭니다. 기본 주소는 <code>http://127.0.0.1:4319</code>, 포트가 사용 중이면 다음 빈 포트로 넘어갑니다. 종료는 <kbd>Ctrl</kbd>+<kbd>C</kbd>.</p>
294
- <div class="code">
295
- <span class="code__label">대시보드 시작</span>
296
- <button class="copy-btn" type="button" data-copy-target="code-start">복사</button>
297
- <pre id="code-start">npm start</pre>
298
- </div>
378
+ </ol>
379
+ </div>
380
+ </details>
381
+
382
+ <!-- 수동 등록 · 설정 파일 위치 · 소스에서 실행 -->
383
+ <details class="acc" id="install-other">
384
+ <summary>수동 등록 · 설정 파일 위치 <span class="acc__hint">Cursor·Cline·Windsurf</span></summary>
385
+ <div class="acc__body">
386
+ <p class="step__tip"><code>npx -y careermate init --print</code>로 설정 JSON·명령만 출력해 클라이언트에 직접 추가할 수 있습니다. 클라이언트별 설정 파일 위치는 아래 탭을 참고하세요.</p>
387
+
388
+ <div class="tabs" role="tablist" aria-label="설정 파일 위치">
389
+ <button class="tab is-active" id="tab-claude" role="tab" aria-controls="panel-claude" aria-selected="true">Claude Desktop</button>
390
+ <button class="tab" id="tab-cursor" role="tab" aria-controls="panel-cursor" aria-selected="false" tabindex="-1">Cursor / 기타</button>
391
+ </div>
392
+ <div class="panel" id="panel-claude" role="tabpanel" aria-labelledby="tab-claude" data-active="true">
393
+ <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>
394
+ </div>
395
+ <div class="panel" id="panel-cursor" role="tabpanel" aria-labelledby="tab-cursor" data-active="false" hidden>
396
+ <p class="path-note">프로젝트별: <code>.cursor/mcp.json</code> · 전역: <code>~/.cursor/mcp.json</code> — Cursor·Cline·Windsurf 등 대부분 같은 형식입니다. 추가 후 클라이언트를 다시 시작하세요.</p>
397
+ </div>
398
+ </div>
399
+ </details>
400
+
401
+ <!-- 트러블슈팅 -->
402
+ <details class="acc">
403
+ <summary>문제가 생겼을 때 <span class="acc__hint">포트 충돌 · MCP 연결 실패 · 데이터 위치 · 초기화</span></summary>
404
+ <div class="acc__body">
405
+ <div class="trouble">
406
+ <div class="trouble__item">
407
+ <h4>포트가 이미 사용 중</h4>
408
+ <p>4319가 막히면 자동으로 다음 빈 포트로 옮겨갑니다. 터미널에 출력된 실제 주소를 사용하거나, <code>CAREERMATE_PORT</code> 환경변수로 고정하세요.</p>
299
409
  </div>
300
- </li>
301
- <li class="step">
302
- <div class="step__no">5</div>
303
- <div class="step__body">
304
- <h3>연결 확인</h3>
305
- <p>AI에게 <strong>“<code>get_onboarding_status</code>를 호출해서 연결됐는지 확인해줘”</strong>라고 말해 보세요. 결과가 돌아오면 정상입니다.</p>
410
+ <div class="trouble__item">
411
+ <h4>AI에 MCP가 연결되지 않음</h4>
412
+ <p>① <code>npx -y careermate init</code>이 정상 종료됐는지 ② <code>node --version</code>이 22.5 이상인지 ③ 클라이언트를 <strong>완전히 종료 후 재시작</strong>했는지 확인하세요. 폴더를 옮기거나 이름을 바꿨다면 등록된 경로가 깨질 수 있으니 init을 다시 실행하세요.</p>
306
413
  </div>
307
- </li>
308
- </ol>
309
-
310
- <details class="acc">
311
- <summary>문제가 생겼을 때 <span class="acc__hint">포트 충돌 · MCP 연결 실패 · 데이터 위치 · 초기화</span></summary>
312
- <div class="acc__body">
313
- <div class="trouble">
314
- <div class="trouble__item">
315
- <h4>포트가 이미 사용 중</h4>
316
- <p>4319가 막히면 자동으로 다음 빈 포트로 옮겨갑니다. 터미널에 출력된 실제 주소를 사용하거나, <code>CAREERMATE_PORT</code> 환경변수로 고정하세요.</p>
317
- </div>
318
- <div class="trouble__item">
319
- <h4>AI에 MCP가 연결되지 않음</h4>
320
- <p>① <code>npm run init</code>이 정상 종료됐는지 ② <code>node --version</code>이 22.5 이상인지 ③ 클라이언트를 <strong>완전히 종료 후 재시작</strong>했는지 ④ <code>npm install</code>이 끝났는지 확인하세요. 폴더를 옮기거나 이름을 바꿨다면 등록된 경로가 깨지므로 <code>npm run init</code>을 다시 실행하세요.</p>
321
- </div>
322
- <div class="trouble__item">
323
- <h4>상태 점검</h4>
324
- <p>프로젝트 폴더에서 진단을 실행하면 Node 버전·데이터 폴더·DB 상태를 점검합니다.</p>
325
- <div class="code">
326
- <button class="copy-btn" type="button" data-copy-target="code-doctor">복사</button>
327
- <pre id="code-doctor">npm run doctor</pre>
328
- </div>
329
- </div>
330
- <div class="trouble__item">
331
- <h4>데이터 위치 / 초기화</h4>
332
- <p>기본 위치는 <code>~/.careermate/careermate.sqlite</code>(Windows: <code>%USERPROFILE%\.careermate</code>). <code>CAREERMATE_DATA_DIR</code>로 변경 가능. <code>npm run migrate</code>는 DB를 준비/업그레이드(반복 실행 안전)하고, 완전 초기화는 <code>careermate.sqlite</code> 삭제 후 재실행(백업 권장)입니다.</p>
414
+ <div class="trouble__item">
415
+ <h4>상태 점검</h4>
416
+ <p>진단을 실행하면 Node 버전·데이터 폴더·DB 상태를 점검합니다.</p>
417
+ <div class="code">
418
+ <button class="copy-btn" type="button" data-copy-target="code-doctor">복사</button>
419
+ <pre id="code-doctor">npx -y careermate doctor</pre>
333
420
  </div>
334
421
  </div>
422
+ <div class="trouble__item">
423
+ <h4>데이터 위치 / 초기화</h4>
424
+ <p>기본 위치는 <code>~/.careermate/careermate.sqlite</code>(Windows: <code>%USERPROFILE%\.careermate</code>). <code>CAREERMATE_DATA_DIR</code>로 변경 가능. 완전 초기화는 <code>careermate.sqlite</code> 삭제 후 재실행(백업 권장)입니다.</p>
425
+ </div>
335
426
  </div>
336
- </details>
337
- </div>
427
+ </div>
428
+ </details>
338
429
  </section>
339
430
 
340
431
  <!-- ======================================================= privacy -->
@@ -468,6 +559,57 @@ cd CareerMate</pre>
468
559
  try { window.localStorage.setItem('cf-theme', next); } catch (e) {}
469
560
  });
470
561
  }
562
+
563
+ // --- dashboard carousel (prev/next + dots + keyboard + swipe) -----
564
+ var carousel = document.getElementById('dash-carousel');
565
+ var track = document.getElementById('carousel-track');
566
+ if (carousel && track) {
567
+ var slides = Array.prototype.slice.call(track.children);
568
+ var prevBtn = document.getElementById('carousel-prev');
569
+ var nextBtn = document.getElementById('carousel-next');
570
+ var dotsWrap = document.getElementById('carousel-dots');
571
+ var idx = 0;
572
+ var captionOf = function (s) {
573
+ var f = s.querySelector('figcaption');
574
+ return f ? f.textContent.trim() : '';
575
+ };
576
+ var dots = slides.map(function (s, n) {
577
+ var b = document.createElement('button');
578
+ b.type = 'button'; b.className = 'carousel__dot';
579
+ b.setAttribute('role', 'tab');
580
+ b.setAttribute('aria-label', captionOf(s) || (n + 1) + '번째 화면');
581
+ b.addEventListener('click', function () { go(n); });
582
+ dotsWrap.appendChild(b);
583
+ return b;
584
+ });
585
+ function go(n) {
586
+ idx = Math.max(0, Math.min(slides.length - 1, n));
587
+ track.style.transform = 'translateX(' + (-idx * 100) + '%)';
588
+ dots.forEach(function (d, n2) {
589
+ var on = n2 === idx;
590
+ d.classList.toggle('is-active', on);
591
+ d.setAttribute('aria-selected', on ? 'true' : 'false');
592
+ });
593
+ slides.forEach(function (s, n2) { s.setAttribute('aria-hidden', n2 === idx ? 'false' : 'true'); });
594
+ if (prevBtn) prevBtn.disabled = idx === 0;
595
+ if (nextBtn) nextBtn.disabled = idx === slides.length - 1;
596
+ }
597
+ if (prevBtn) prevBtn.addEventListener('click', function () { go(idx - 1); });
598
+ if (nextBtn) nextBtn.addEventListener('click', function () { go(idx + 1); });
599
+ carousel.addEventListener('keydown', function (e) {
600
+ if (e.key === 'ArrowLeft') { e.preventDefault(); go(idx - 1); }
601
+ else if (e.key === 'ArrowRight') { e.preventDefault(); go(idx + 1); }
602
+ });
603
+ var x0 = null;
604
+ track.addEventListener('touchstart', function (e) { x0 = e.touches[0].clientX; }, { passive: true });
605
+ track.addEventListener('touchend', function (e) {
606
+ if (x0 === null) return;
607
+ var dx = e.changedTouches[0].clientX - x0;
608
+ if (Math.abs(dx) > 40) go(idx + (dx < 0 ? 1 : -1));
609
+ x0 = null;
610
+ });
611
+ go(0);
612
+ }
471
613
  })();
472
614
  </script>
473
615
  </body>