careermate 0.1.0
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.
- package/README.md +256 -0
- package/THIRD_PARTY_NOTICES.md +40 -0
- package/apps/mcp/src/index.ts +66 -0
- package/apps/web/DESIGN_GUIDE.md +105 -0
- package/apps/web/UI_CONTRACT.md +44 -0
- package/apps/web/public/app.js +118 -0
- package/apps/web/public/fonts/PretendardVariable.woff2 +0 -0
- package/apps/web/public/index.html +41 -0
- package/apps/web/public/lib.js +282 -0
- package/apps/web/public/pages/applications.js +98 -0
- package/apps/web/public/pages/documents.js +446 -0
- package/apps/web/public/pages/home.js +263 -0
- package/apps/web/public/pages/interview.js +230 -0
- package/apps/web/public/pages/jobs.js +494 -0
- package/apps/web/public/pages/profile.js +576 -0
- package/apps/web/public/pages/settings.js +233 -0
- package/apps/web/public/styles.css +426 -0
- package/apps/web/src/exports.ts +68 -0
- package/apps/web/src/http.ts +180 -0
- package/apps/web/src/index.ts +49 -0
- package/apps/web/src/info.ts +50 -0
- package/apps/web/src/routes.ts +350 -0
- package/apps/web/src/security.ts +102 -0
- package/apps/web/src/server.ts +141 -0
- package/apps/web/src/settings.ts +88 -0
- package/bin/careermate.mjs +74 -0
- package/dist/careermate.mcpb +0 -0
- package/dist/install-page/index.html +474 -0
- package/dist/install-page/style.css +391 -0
- package/dist/install-page/vercel.json +20 -0
- package/dist/mcp-smoke.err +3 -0
- package/dist/mcp.mjs +23704 -0
- package/dist/mcpb-stage/README.md +219 -0
- package/dist/mcpb-stage/dist/install-page/index.html +434 -0
- package/dist/mcpb-stage/dist/install-page/style.css +407 -0
- package/dist/mcpb-stage/dist/install-page/vercel.json +20 -0
- package/dist/mcpb-stage/dist/mcp.mjs +23704 -0
- package/dist/mcpb-stage/dist/public/app.js +118 -0
- package/dist/mcpb-stage/dist/public/fonts/PretendardVariable.woff2 +0 -0
- package/dist/mcpb-stage/dist/public/index.html +41 -0
- package/dist/mcpb-stage/dist/public/lib.js +282 -0
- package/dist/mcpb-stage/dist/public/pages/applications.js +98 -0
- package/dist/mcpb-stage/dist/public/pages/documents.js +446 -0
- package/dist/mcpb-stage/dist/public/pages/home.js +263 -0
- package/dist/mcpb-stage/dist/public/pages/interview.js +230 -0
- package/dist/mcpb-stage/dist/public/pages/jobs.js +494 -0
- package/dist/mcpb-stage/dist/public/pages/profile.js +576 -0
- package/dist/mcpb-stage/dist/public/pages/settings.js +233 -0
- package/dist/mcpb-stage/dist/public/styles.css +420 -0
- package/dist/mcpb-stage/dist/web.mjs +7240 -0
- package/dist/mcpb-stage/manifest.json +40 -0
- package/dist/public/app.js +118 -0
- package/dist/public/fonts/PretendardVariable.woff2 +0 -0
- package/dist/public/index.html +41 -0
- package/dist/public/lib.js +282 -0
- package/dist/public/pages/applications.js +98 -0
- package/dist/public/pages/documents.js +446 -0
- package/dist/public/pages/home.js +263 -0
- package/dist/public/pages/interview.js +230 -0
- package/dist/public/pages/jobs.js +494 -0
- package/dist/public/pages/profile.js +576 -0
- package/dist/public/pages/settings.js +233 -0
- package/dist/public/styles.css +426 -0
- package/dist/web.mjs +7240 -0
- package/docs/ARCHITECTURE.md +208 -0
- package/docs/CHANGES_V1.md +103 -0
- package/docs/DATA_MODEL.md +460 -0
- package/docs/DECISIONS.md +277 -0
- package/docs/DEMO.md +242 -0
- package/docs/INSTALL.md +148 -0
- package/docs/INSTALL_AND_USAGE.md +99 -0
- package/docs/MCP_TOOLS.md +233 -0
- package/docs/ROADMAP.md +134 -0
- package/docs/START_WORKFLOW.md +125 -0
- package/docs/SUPPORTED_AI_APPS.md +60 -0
- package/docs/TODO.md +57 -0
- package/docs/UX_NOTES.md +247 -0
- package/docs/WORKFLOWS.md +200 -0
- package/install-page/index.html +474 -0
- package/install-page/style.css +391 -0
- package/install-page/vercel.json +20 -0
- package/package.json +68 -0
- package/packages/core/src/context.ts +74 -0
- package/packages/core/src/index.ts +8 -0
- package/packages/core/src/onboarding.ts +81 -0
- package/packages/core/src/services.ts +146 -0
- package/packages/core/src/summary.ts +104 -0
- package/packages/db/src/connection.ts +46 -0
- package/packages/db/src/index.ts +22 -0
- package/packages/db/src/paths.ts +41 -0
- package/packages/db/src/repositories.ts +828 -0
- package/packages/db/src/runtime.ts +58 -0
- package/packages/db/src/schema.ts +189 -0
- package/packages/exporters/src/html.ts +113 -0
- package/packages/exporters/src/index.ts +364 -0
- package/packages/exporters/src/markdown.ts +178 -0
- package/packages/mcp-tools/src/bridge.ts +83 -0
- package/packages/mcp-tools/src/index.ts +8 -0
- package/packages/mcp-tools/src/result.ts +49 -0
- package/packages/mcp-tools/src/tools.ts +455 -0
- package/packages/parsers/src/html.ts +86 -0
- package/packages/parsers/src/index.ts +228 -0
- package/packages/parsers/src/keywords.ts +151 -0
- package/packages/prompts/src/humanize.ts +59 -0
- package/packages/prompts/src/index.ts +82 -0
- package/packages/prompts/src/install.ts +43 -0
- package/packages/prompts/src/onboarding.ts +35 -0
- package/packages/prompts/src/system.ts +53 -0
- package/packages/shared/src/enums.ts +103 -0
- package/packages/shared/src/index.ts +18 -0
- package/packages/shared/src/schemas.ts +398 -0
- package/packages/workflows/src/definitions.ts +107 -0
- package/packages/workflows/src/index.ts +39 -0
- package/scripts/build-dist.mjs +62 -0
- package/scripts/build-mcpb.mjs +70 -0
- package/scripts/doctor.ts +81 -0
- package/scripts/init.ts +342 -0
- package/scripts/mcp-probe.ts +55 -0
- package/scripts/migrate.ts +6 -0
- package/scripts/run.mjs +33 -0
- package/scripts/seed.ts +129 -0
- package/scripts/test.ts +117 -0
- package/scripts/ui-smoke.ts +73 -0
- package/tsconfig.json +29 -0
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
// Settings — data location, connection, theme, backup/export, and danger zone.
|
|
2
|
+
// Everything stays local; this page surfaces where data lives and how to manage it.
|
|
3
|
+
import {
|
|
4
|
+
el, get, post, icon, navigate, Card, Stat, Btn, IconBtn, Input,
|
|
5
|
+
openModal, copyText, downloadUrl, fmtRelative, toastOk, toastError, mount,
|
|
6
|
+
} from '/lib.js';
|
|
7
|
+
|
|
8
|
+
const COUNT_LABELS = {
|
|
9
|
+
profile: '프로필', experiences: '경력', projects: '프로젝트', skills: '스킬',
|
|
10
|
+
documents: '문서', cover_letters: '자기소개서', jobs: '채용공고',
|
|
11
|
+
applications: '지원', interview_preps: '면접 준비',
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export async function render(ctx) {
|
|
15
|
+
ctx.setActions([]);
|
|
16
|
+
const { info, backups } = await get('/api/settings');
|
|
17
|
+
|
|
18
|
+
const wrap = el('div', { class: 'stack-4' },
|
|
19
|
+
DataLocation(info),
|
|
20
|
+
Connection(info),
|
|
21
|
+
Theme(),
|
|
22
|
+
MyData(info.counts),
|
|
23
|
+
Backup(backups, () => render(ctx)),
|
|
24
|
+
DangerZone(ctx),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
mount(ctx.view, wrap);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* ----------------------------------------------------- 데이터 저장 위치 */
|
|
31
|
+
function kvRow(label, value, copyable) {
|
|
32
|
+
return [
|
|
33
|
+
el('dt', {}, label),
|
|
34
|
+
el('dd', {},
|
|
35
|
+
el('div', { class: 'flex between gap-3' },
|
|
36
|
+
el('span', { class: 'truncate tnum', style: { fontFamily: 'var(--mono, ui-monospace, monospace)' }, title: value }, value || '—'),
|
|
37
|
+
copyable && value
|
|
38
|
+
? IconBtn('copy', { title: '복사', onClick: () => copyText(value) })
|
|
39
|
+
: null)),
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function DataLocation(info) {
|
|
44
|
+
const kv = el('dl', { class: 'kv' },
|
|
45
|
+
...kvRow('데이터 폴더', info.data_dir, true),
|
|
46
|
+
...kvRow('데이터베이스', info.db_path, true),
|
|
47
|
+
...kvRow('앱 버전', info.version, false),
|
|
48
|
+
...kvRow('Node 버전', info.node_version, false),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
const privacy = el('div', { class: 'callout callout--privacy' },
|
|
52
|
+
icon('lock'),
|
|
53
|
+
el('div', {},
|
|
54
|
+
el('div', { class: 'callout__title' }, '내 컴퓨터에만 저장됩니다'),
|
|
55
|
+
el('div', { class: 'callout__body' },
|
|
56
|
+
'모든 데이터는 이 컴퓨터에만 저장됩니다. 외부로 전송되지 않습니다. AI에는 사용자가 직접 입력하거나 AI가 MCP로 조회한 정보만 전달됩니다.')));
|
|
57
|
+
|
|
58
|
+
return Card({
|
|
59
|
+
title: '데이터 저장 위치',
|
|
60
|
+
body: el('div', { class: 'stack-3' }, kv, privacy),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* ---------------------------------------------------------- 연결 상태 */
|
|
65
|
+
function Connection(info) {
|
|
66
|
+
const dashRow = el('div', { class: 'flex between gap-3', style: { padding: '4px 0' } },
|
|
67
|
+
el('div', { class: 'flex gap-3', style: { alignItems: 'center' } },
|
|
68
|
+
el('span', { class: 'badge badge--final_passed' }, el('span', { class: 'dot' }), '실행 중'),
|
|
69
|
+
el('div', {},
|
|
70
|
+
el('div', { class: 'strong' }, '대시보드'),
|
|
71
|
+
el('div', { class: 'muted text-sm tnum' }, info.url || '—'))),
|
|
72
|
+
info.url
|
|
73
|
+
? Btn('새 탭에서 열기', { icon: 'external', sm: true, variant: 'ghost', onClick: () => window.open(info.url, '_blank') })
|
|
74
|
+
: null);
|
|
75
|
+
|
|
76
|
+
const mcpRow = el('div', { class: 'flex between gap-3', style: { padding: '4px 0', alignItems: 'flex-start' } },
|
|
77
|
+
el('div', { class: 'flex gap-3', style: { alignItems: 'flex-start' } },
|
|
78
|
+
el('div', { class: 'empty__icon', style: { width: '30px', height: '30px', margin: 0, borderRadius: '8px' } }, icon('link')),
|
|
79
|
+
el('div', {},
|
|
80
|
+
el('div', { class: 'strong' }, 'MCP 서버'),
|
|
81
|
+
el('div', { class: 'muted text-sm', style: { maxWidth: '440px', lineHeight: '1.6' } },
|
|
82
|
+
'MCP 서버는 사용하시는 AI 클라이언트(Claude·ChatGPT·Cursor)가 직접 실행하며, 이 대시보드와 같은 데이터베이스를 공유합니다.'))),
|
|
83
|
+
Btn('설치·연결 안내 열기', { icon: 'external', sm: true, variant: 'ghost', onClick: () => window.open('/install', '_blank') }));
|
|
84
|
+
|
|
85
|
+
return Card({
|
|
86
|
+
title: '연결 상태',
|
|
87
|
+
body: el('div', { class: 'stack-2' }, dashRow, el('div', { class: 'divider' }), mcpRow),
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/* ------------------------------------------------------------- 테마 */
|
|
92
|
+
function currentTheme() {
|
|
93
|
+
const saved = localStorage.getItem('cf-theme');
|
|
94
|
+
return saved === 'light' || saved === 'dark' ? saved : 'system';
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function Theme() {
|
|
98
|
+
const options = [
|
|
99
|
+
{ value: 'light', label: '라이트' },
|
|
100
|
+
{ value: 'dark', label: '다크' },
|
|
101
|
+
{ value: 'system', label: '시스템' },
|
|
102
|
+
];
|
|
103
|
+
|
|
104
|
+
const row = el('div', { class: 'flex gap-2 wrap' });
|
|
105
|
+
|
|
106
|
+
function applyTheme(value) {
|
|
107
|
+
if (value === 'system') {
|
|
108
|
+
delete document.documentElement.dataset.theme;
|
|
109
|
+
localStorage.removeItem('cf-theme');
|
|
110
|
+
} else {
|
|
111
|
+
document.documentElement.dataset.theme = value;
|
|
112
|
+
localStorage.setItem('cf-theme', value);
|
|
113
|
+
}
|
|
114
|
+
paint();
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function paint() {
|
|
118
|
+
const active = currentTheme();
|
|
119
|
+
mount(row, ...options.map((o) => {
|
|
120
|
+
const b = Btn(o.label, { variant: 'ghost', onClick: () => applyTheme(o.value) });
|
|
121
|
+
b.setAttribute('aria-pressed', String(o.value === active));
|
|
122
|
+
if (o.value === active) b.classList.add('is-selected');
|
|
123
|
+
return b;
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
paint();
|
|
127
|
+
|
|
128
|
+
return Card({
|
|
129
|
+
title: '테마',
|
|
130
|
+
sub: '시스템 설정을 따르거나 직접 고를 수 있어요',
|
|
131
|
+
body: row,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/* ---------------------------------------------------------- 내 데이터 */
|
|
136
|
+
function MyData(counts = {}) {
|
|
137
|
+
const entries = Object.entries(COUNT_LABELS)
|
|
138
|
+
.map(([key, label]) => Stat({ label, value: counts[key] ?? 0 }));
|
|
139
|
+
return Card({
|
|
140
|
+
title: '내 데이터',
|
|
141
|
+
sub: '이 컴퓨터에 저장된 항목 수',
|
|
142
|
+
body: el('div', { class: 'grid grid--3' }, ...entries),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/* ------------------------------------------------- 백업 / 내보내기 */
|
|
147
|
+
function Backup(backups = [], rerender) {
|
|
148
|
+
async function createBackup() {
|
|
149
|
+
try {
|
|
150
|
+
const res = await post('/api/settings/backup');
|
|
151
|
+
toastOk('백업을 생성했습니다.');
|
|
152
|
+
await rerender();
|
|
153
|
+
} catch (e) {
|
|
154
|
+
toastError(e);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const list = backups.length
|
|
159
|
+
? el('table', { class: 'table' },
|
|
160
|
+
el('thead', {}, el('tr', {},
|
|
161
|
+
el('th', {}, '파일'), el('th', {}, '생성'), el('th', { style: { textAlign: 'right' } }, '크기'))),
|
|
162
|
+
el('tbody', {}, ...backups.map((b) => el('tr', {},
|
|
163
|
+
el('td', {}, el('span', { class: 'truncate', title: b.path || b.filename }, b.filename)),
|
|
164
|
+
el('td', { class: 'muted text-sm' }, fmtRelative(b.created_at)),
|
|
165
|
+
el('td', { class: 'muted text-sm tnum', style: { textAlign: 'right' } }, `${Math.round((b.size || 0) / 1024)} KB`)))))
|
|
166
|
+
: el('p', { class: 'muted', style: { margin: 0 } }, '아직 백업이 없습니다. "백업 생성"으로 현재 데이터를 안전하게 보관하세요.');
|
|
167
|
+
|
|
168
|
+
return Card({
|
|
169
|
+
title: '백업 / 내보내기',
|
|
170
|
+
actions: [
|
|
171
|
+
Btn('백업 생성', { icon: 'copy', sm: true, variant: 'primary', onClick: createBackup }),
|
|
172
|
+
Btn('전체 데이터 내보내기(JSON)', { icon: 'download', sm: true, variant: 'ghost', onClick: () => downloadUrl('/api/settings/export-all') }),
|
|
173
|
+
],
|
|
174
|
+
body: list,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/* -------------------------------------------------- 위험 구역 (Danger zone) */
|
|
179
|
+
function DangerZone(ctx) {
|
|
180
|
+
function openResetModal() {
|
|
181
|
+
let confirmBtn;
|
|
182
|
+
const input = Input({
|
|
183
|
+
placeholder: 'DELETE',
|
|
184
|
+
onInput: (e) => { if (confirmBtn) confirmBtn.disabled = e.target.value.trim() !== 'DELETE'; },
|
|
185
|
+
onKeydown: (e) => { if (e.key === 'Enter' && e.target.value.trim() === 'DELETE' && confirmBtn && !confirmBtn.disabled) confirmBtn.click(); },
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
openModal({
|
|
189
|
+
title: '모든 데이터 초기화',
|
|
190
|
+
body: el('div', { class: 'stack-3' },
|
|
191
|
+
el('div', { class: 'callout' },
|
|
192
|
+
icon('info'),
|
|
193
|
+
el('div', {},
|
|
194
|
+
el('div', { class: 'callout__title' }, '되돌릴 수 없습니다'),
|
|
195
|
+
el('div', { class: 'callout__body' },
|
|
196
|
+
'프로필·공고·지원·문서·면접 준비를 포함한 모든 데이터가 영구적으로 삭제됩니다. 안전을 위해 초기화 직전에 자동으로 백업이 한 번 생성됩니다.'))),
|
|
197
|
+
el('p', { class: 'text-secondary', style: { margin: 0, lineHeight: '1.6' } },
|
|
198
|
+
'계속하려면 아래 입력란에 ', el('span', { class: 'strong' }, 'DELETE'), ' 를 입력하세요.'),
|
|
199
|
+
input),
|
|
200
|
+
footer: (close) => {
|
|
201
|
+
confirmBtn = Btn('모든 데이터 초기화', {
|
|
202
|
+
variant: 'danger',
|
|
203
|
+
disabled: true,
|
|
204
|
+
onClick: async () => {
|
|
205
|
+
confirmBtn.disabled = true;
|
|
206
|
+
try {
|
|
207
|
+
await post('/api/settings/reset', { confirm: 'DELETE' });
|
|
208
|
+
close();
|
|
209
|
+
toastOk('모든 데이터가 초기화되었습니다.');
|
|
210
|
+
navigate('/');
|
|
211
|
+
await ctx.refreshNav();
|
|
212
|
+
} catch (e) {
|
|
213
|
+
toastError(e);
|
|
214
|
+
confirmBtn.disabled = input.value.trim() !== 'DELETE';
|
|
215
|
+
}
|
|
216
|
+
},
|
|
217
|
+
});
|
|
218
|
+
return [Btn('취소', { onClick: close }), confirmBtn];
|
|
219
|
+
},
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
return Card({
|
|
224
|
+
title: '위험 구역',
|
|
225
|
+
sub: '이 작업은 되돌릴 수 없습니다',
|
|
226
|
+
body: el('div', { class: 'flex between gap-3 wrap' },
|
|
227
|
+
el('div', { style: { maxWidth: '440px' } },
|
|
228
|
+
el('div', { class: 'strong' }, '모든 데이터 초기화'),
|
|
229
|
+
el('div', { class: 'muted text-sm', style: { lineHeight: '1.6' } },
|
|
230
|
+
'이 컴퓨터에 저장된 모든 CareerMate 데이터를 삭제합니다. 초기화 전에 자동으로 백업이 생성됩니다.')),
|
|
231
|
+
Btn('모든 데이터 초기화', { icon: 'trash', variant: 'danger', onClick: openResetModal })),
|
|
232
|
+
});
|
|
233
|
+
}
|
|
@@ -0,0 +1,420 @@
|
|
|
1
|
+
/* =============================================================================
|
|
2
|
+
CareerMate design system — a calm, dense, real-productivity-tool aesthetic.
|
|
3
|
+
No framework, no CDN (works fully offline; nothing leaves the machine).
|
|
4
|
+
Inspired by the restraint of Linear / Notion / Raycast — not AI-flashy.
|
|
5
|
+
========================================================================== */
|
|
6
|
+
|
|
7
|
+
/* Self-hosted brand font (offline; served from /fonts by the local server).
|
|
8
|
+
A real loaded face — not a system fallback — so the type is intentional and
|
|
9
|
+
identical on every machine. One variable file covers all weights. */
|
|
10
|
+
@font-face {
|
|
11
|
+
font-family: 'Pretendard Variable';
|
|
12
|
+
font-weight: 45 920;
|
|
13
|
+
font-style: normal;
|
|
14
|
+
/* block (not swap): show no fallback-then-swap flash; the font is local so the
|
|
15
|
+
brief invisibility is imperceptible, and it's preloaded in index.html. */
|
|
16
|
+
font-display: block;
|
|
17
|
+
src: local('Pretendard Variable'),
|
|
18
|
+
url('/fonts/PretendardVariable.woff2') format('woff2');
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
:root {
|
|
22
|
+
/* Neutral palette (light) */
|
|
23
|
+
--bg: #ffffff;
|
|
24
|
+
--bg-subtle: #f7f7f8;
|
|
25
|
+
--bg-sunken: #f1f1f3;
|
|
26
|
+
--surface: #ffffff;
|
|
27
|
+
--surface-hover: #f7f7f8;
|
|
28
|
+
--border: #e6e6e9;
|
|
29
|
+
--border-strong: #d4d4d8;
|
|
30
|
+
--text: #1c1c1f;
|
|
31
|
+
--text-secondary: #5c5c66;
|
|
32
|
+
--text-tertiary: #8a8a94;
|
|
33
|
+
--text-on-accent: #ffffff;
|
|
34
|
+
|
|
35
|
+
/* Accent — calm indigo */
|
|
36
|
+
--accent: #4f46e5;
|
|
37
|
+
--accent-hover: #4338ca;
|
|
38
|
+
--accent-soft: #eef2ff;
|
|
39
|
+
--accent-border: #c7d2fe;
|
|
40
|
+
|
|
41
|
+
/* Semantic */
|
|
42
|
+
--green: #15803d; --green-soft: #ecfdf3; --green-border: #bbf7d0;
|
|
43
|
+
--teal: #0f766e; --teal-soft: #effcf9; --teal-border: #99f6e4;
|
|
44
|
+
--blue: #1d4ed8; --blue-soft: #eff4ff; --blue-border: #bfdbfe;
|
|
45
|
+
--violet: #6d28d9; --violet-soft: #f5f0ff; --violet-border: #ddd6fe;
|
|
46
|
+
--amber: #b45309; --amber-soft: #fffbeb; --amber-border: #fde68a;
|
|
47
|
+
--red: #b91c1c; --red-soft: #fef2f2; --red-border: #fecaca;
|
|
48
|
+
--slate: #475569; --slate-soft: #f5f6f8; --slate-border: #dbe0e6;
|
|
49
|
+
|
|
50
|
+
/* Elevation */
|
|
51
|
+
--shadow-sm: 0 1px 2px rgba(16,16,20,.05);
|
|
52
|
+
--shadow-md: 0 4px 16px rgba(16,16,20,.08);
|
|
53
|
+
--shadow-lg: 0 12px 40px rgba(16,16,20,.16);
|
|
54
|
+
|
|
55
|
+
/* Geometry */
|
|
56
|
+
--radius-sm: 6px;
|
|
57
|
+
--radius: 9px;
|
|
58
|
+
--radius-lg: 14px;
|
|
59
|
+
--sidebar-w: 248px;
|
|
60
|
+
--topbar-h: 56px;
|
|
61
|
+
--maxw: 1080px;
|
|
62
|
+
|
|
63
|
+
--font: 'Pretendard Variable', Pretendard, -apple-system, BlinkMacSystemFont,
|
|
64
|
+
'Apple SD Gothic Neo', 'Malgun Gothic', sans-serif;
|
|
65
|
+
--mono: 'SFMono-Regular', 'JetBrains Mono', Consolas, 'D2Coding', monospace;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
@media (prefers-color-scheme: dark) {
|
|
69
|
+
:root:not([data-theme='light']) {
|
|
70
|
+
--bg: #161618;
|
|
71
|
+
--bg-subtle: #1d1d20;
|
|
72
|
+
--bg-sunken: #0f0f11;
|
|
73
|
+
--surface: #1d1d20;
|
|
74
|
+
--surface-hover: #26262a;
|
|
75
|
+
--border: #2c2c31;
|
|
76
|
+
--border-strong: #3a3a41;
|
|
77
|
+
--text: #ededf0;
|
|
78
|
+
--text-secondary: #a5a5b0;
|
|
79
|
+
--text-tertiary: #74747e;
|
|
80
|
+
--accent: #818cf8;
|
|
81
|
+
--accent-hover: #a5b4fc;
|
|
82
|
+
--accent-soft: #1e1b3a;
|
|
83
|
+
--accent-border: #3730a3;
|
|
84
|
+
--green-soft:#0e2a1a; --teal-soft:#0c2826; --blue-soft:#11203f; --violet-soft:#1e1638;
|
|
85
|
+
--amber-soft:#2a2008; --red-soft:#2c1414; --slate-soft:#1f242b;
|
|
86
|
+
--shadow-lg: 0 12px 40px rgba(0,0,0,.5);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
:root[data-theme='dark'] {
|
|
90
|
+
--bg:#161618; --bg-subtle:#1d1d20; --bg-sunken:#0f0f11; --surface:#1d1d20; --surface-hover:#26262a;
|
|
91
|
+
--border:#2c2c31; --border-strong:#3a3a41; --text:#ededf0; --text-secondary:#a5a5b0; --text-tertiary:#74747e;
|
|
92
|
+
--accent:#818cf8; --accent-hover:#a5b4fc; --accent-soft:#1e1b3a; --accent-border:#3730a3;
|
|
93
|
+
--green-soft:#0e2a1a; --teal-soft:#0c2826; --blue-soft:#11203f; --violet-soft:#1e1638;
|
|
94
|
+
--amber-soft:#2a2008; --red-soft:#2c1414; --slate-soft:#1f242b; --shadow-lg:0 12px 40px rgba(0,0,0,.5);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
* { box-sizing: border-box; }
|
|
98
|
+
/* Icons must never stretch inside flex containers. */
|
|
99
|
+
svg { flex: none; }
|
|
100
|
+
html, body { height: 100%; }
|
|
101
|
+
body {
|
|
102
|
+
margin: 0; font-family: var(--font); color: var(--text); background: var(--bg-subtle);
|
|
103
|
+
font-size: 15px; line-height: 1.55; -webkit-font-smoothing: antialiased;
|
|
104
|
+
text-rendering: optimizeLegibility;
|
|
105
|
+
}
|
|
106
|
+
a { color: var(--accent); text-decoration: none; }
|
|
107
|
+
a:hover { text-decoration: underline; }
|
|
108
|
+
h1,h2,h3,h4 { margin: 0; font-weight: 650; letter-spacing: -0.01em; }
|
|
109
|
+
::selection { background: var(--accent-soft); }
|
|
110
|
+
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
|
|
111
|
+
|
|
112
|
+
/* ----------------------------------------------------------------- layout */
|
|
113
|
+
.app { display: grid; grid-template-columns: var(--sidebar-w) 1fr; min-height: 100vh; }
|
|
114
|
+
|
|
115
|
+
.sidebar {
|
|
116
|
+
background: var(--bg); border-right: 1px solid var(--border);
|
|
117
|
+
display: flex; flex-direction: column; position: sticky; top: 0; height: 100vh;
|
|
118
|
+
}
|
|
119
|
+
.sidebar__brand { display: flex; align-items: center; gap: 10px; padding: 18px 18px 14px; }
|
|
120
|
+
.sidebar__logo {
|
|
121
|
+
width: 30px; height: 30px; border-radius: 8px; flex: none;
|
|
122
|
+
background: var(--accent);
|
|
123
|
+
display: grid; place-items: center; color: #fff; font-weight: 800; font-size: 15px;
|
|
124
|
+
}
|
|
125
|
+
.sidebar__title { font-weight: 700; font-size: 15px; letter-spacing: -.01em; }
|
|
126
|
+
.sidebar__subtitle { font-size: 12px; color: var(--text-tertiary); margin-top: 1px; }
|
|
127
|
+
.nav { padding: 6px 10px; display: flex; flex-direction: column; gap: 1px; flex: 1; overflow-y: auto; }
|
|
128
|
+
.nav__label { font-size: 11.5px; font-weight: 600; color: var(--text-tertiary); padding: 12px 10px 4px; text-transform: uppercase; letter-spacing: .04em; }
|
|
129
|
+
.nav__item {
|
|
130
|
+
display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-radius: var(--radius-sm);
|
|
131
|
+
color: var(--text-secondary); font-weight: 500; cursor: pointer; user-select: none; position: relative;
|
|
132
|
+
}
|
|
133
|
+
.nav__item:hover { background: var(--surface-hover); color: var(--text); text-decoration: none; }
|
|
134
|
+
.nav__item.is-active { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
|
|
135
|
+
.nav__item svg { width: 17px; height: 17px; flex: none; }
|
|
136
|
+
.nav__badge { margin-left: auto; font-size: 11.5px; background: var(--bg-sunken); color: var(--text-secondary);
|
|
137
|
+
padding: 1px 7px; border-radius: 999px; font-weight: 600; }
|
|
138
|
+
.nav__item.is-active .nav__badge { background: var(--accent); color: #fff; }
|
|
139
|
+
.sidebar__foot { padding: 12px 16px; border-top: 1px solid var(--border); font-size: 12px; color: var(--text-tertiary); }
|
|
140
|
+
.sidebar__foot .dot { display:inline-block; width:7px; height:7px; border-radius:999px; background: var(--green); margin-right:6px; }
|
|
141
|
+
|
|
142
|
+
.main { display: flex; flex-direction: column; min-width: 0; }
|
|
143
|
+
.topbar {
|
|
144
|
+
height: var(--topbar-h); border-bottom: 1px solid var(--border); background: color-mix(in srgb, var(--bg) 88%, transparent);
|
|
145
|
+
backdrop-filter: blur(8px); position: sticky; top: 0; z-index: 20;
|
|
146
|
+
display: flex; align-items: center; gap: 12px; padding: 0 24px;
|
|
147
|
+
}
|
|
148
|
+
.topbar__title { font-size: 15px; font-weight: 650; }
|
|
149
|
+
.topbar__crumb { color: var(--text-tertiary); }
|
|
150
|
+
.topbar__spacer { flex: 1; }
|
|
151
|
+
.view { padding: 24px; max-width: var(--maxw); width: 100%; margin: 0 auto; }
|
|
152
|
+
.view--wide { max-width: 1280px; }
|
|
153
|
+
|
|
154
|
+
.page-head { display: flex; align-items: flex-start; gap: 16px; margin-bottom: 20px; }
|
|
155
|
+
.page-head__text h1 { font-size: 22px; }
|
|
156
|
+
.page-head__text p { margin: 4px 0 0; color: var(--text-secondary); }
|
|
157
|
+
.page-head__actions { margin-left: auto; display: flex; gap: 8px; flex: none; }
|
|
158
|
+
|
|
159
|
+
/* ----------------------------------------------------------------- grid */
|
|
160
|
+
.grid { display: grid; gap: 16px; }
|
|
161
|
+
.grid--2 { grid-template-columns: repeat(2, 1fr); }
|
|
162
|
+
.grid--3 { grid-template-columns: repeat(3, 1fr); }
|
|
163
|
+
.grid--4 { grid-template-columns: repeat(4, 1fr); }
|
|
164
|
+
@media (max-width: 900px){ .grid--2,.grid--3,.grid--4{ grid-template-columns: 1fr; } .app{ grid-template-columns: 1fr; } .sidebar{ display:none; } }
|
|
165
|
+
|
|
166
|
+
/* ----------------------------------------------------------------- card */
|
|
167
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); box-shadow: var(--shadow-sm); }
|
|
168
|
+
.card__head { padding: 14px 18px; border-bottom: 1px solid var(--border); display: flex; align-items: center; gap: 10px; }
|
|
169
|
+
.card__head h3 { font-size: 15px; }
|
|
170
|
+
.card__head .sub { color: var(--text-tertiary); font-size: 13px; }
|
|
171
|
+
.card__head .right { margin-left: auto; display: flex; gap: 6px; }
|
|
172
|
+
.card__body { padding: 18px; }
|
|
173
|
+
.card__body--tight { padding: 0; }
|
|
174
|
+
/* a light bordered block for grouping inside a card/modal — never nest a full .card */
|
|
175
|
+
.subcard { border: 1px solid var(--border); border-radius: var(--radius); padding: 14px; }
|
|
176
|
+
.card.is-clickable { cursor: pointer; transition: border-color .12s, box-shadow .12s, transform .12s; }
|
|
177
|
+
.card.is-clickable:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
|
|
178
|
+
|
|
179
|
+
/* stat tiles */
|
|
180
|
+
.stat { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 16px 18px; }
|
|
181
|
+
.stat.is-clickable { cursor: pointer; transition: border-color .12s, box-shadow .12s; }
|
|
182
|
+
.stat.is-clickable:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
|
|
183
|
+
.stat__label { font-size: 13px; color: var(--text-secondary); display:flex; align-items:center; gap:7px; }
|
|
184
|
+
.stat__label svg { width: 15px; height: 15px; color: var(--text-tertiary); }
|
|
185
|
+
.stat__value { font-size: 26px; font-weight: 700; letter-spacing: -.02em; margin-top: 6px; }
|
|
186
|
+
.stat__hint { font-size: 12.5px; color: var(--text-tertiary); margin-top: 2px; }
|
|
187
|
+
|
|
188
|
+
/* ----------------------------------------------------------------- buttons */
|
|
189
|
+
.btn {
|
|
190
|
+
display: inline-flex; align-items: center; gap: 7px; height: 34px; padding: 0 13px;
|
|
191
|
+
border-radius: var(--radius-sm); border: 1px solid var(--border-strong); background: var(--surface);
|
|
192
|
+
color: var(--text); font-weight: 550; font-size: 14px; cursor: pointer; font-family: inherit;
|
|
193
|
+
transition: background .12s, border-color .12s, opacity .12s; white-space: nowrap; user-select: none;
|
|
194
|
+
}
|
|
195
|
+
.btn:hover { background: var(--surface-hover); }
|
|
196
|
+
.btn:disabled { opacity: .5; cursor: not-allowed; }
|
|
197
|
+
.btn svg { width: 15px; height: 15px; }
|
|
198
|
+
.btn--primary { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
199
|
+
.btn--primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
|
|
200
|
+
.btn--ghost { border-color: transparent; background: transparent; }
|
|
201
|
+
.btn--ghost:hover { background: var(--surface-hover); }
|
|
202
|
+
.btn--danger { color: var(--red); border-color: var(--red-border); background: var(--red-soft); }
|
|
203
|
+
.btn--danger:hover { background: var(--red); color:#fff; border-color: var(--red); }
|
|
204
|
+
.btn--sm { height: 28px; padding: 0 10px; font-size: 13px; }
|
|
205
|
+
.btn--block { width: 100%; justify-content: center; }
|
|
206
|
+
.icon-btn { width: 30px; height: 30px; padding: 0; justify-content: center; }
|
|
207
|
+
/* a ghost button marked as the selected option (segmented toggle), e.g. theme picker */
|
|
208
|
+
.btn.is-selected { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }
|
|
209
|
+
|
|
210
|
+
/* ----------------------------------------------------------------- forms */
|
|
211
|
+
.field { margin-bottom: 14px; }
|
|
212
|
+
.field > label { display: block; font-size: 13px; font-weight: 600; color: var(--text-secondary); margin-bottom: 6px; }
|
|
213
|
+
.field .hint { font-size: 12px; color: var(--text-tertiary); margin-top: 5px; }
|
|
214
|
+
.input, .textarea, .select {
|
|
215
|
+
width: 100%; font-family: inherit; font-size: 14px; color: var(--text);
|
|
216
|
+
background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm);
|
|
217
|
+
padding: 8px 11px; transition: border-color .12s, box-shadow .12s;
|
|
218
|
+
}
|
|
219
|
+
.input:focus, .textarea:focus, .select:focus { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
|
|
220
|
+
.textarea { resize: vertical; min-height: 90px; line-height: 1.6; }
|
|
221
|
+
.textarea--lg { min-height: 280px; font-size: 15px; }
|
|
222
|
+
.input::placeholder, .textarea::placeholder { color: var(--text-tertiary); }
|
|
223
|
+
.row { display: flex; gap: 10px; }
|
|
224
|
+
.row > * { flex: 1; }
|
|
225
|
+
/* compact inline input for the topbar/toolbar (search boxes) */
|
|
226
|
+
.input--inline { width: 220px; max-width: 46vw; }
|
|
227
|
+
|
|
228
|
+
/* ----------------------------------------------------------------- badges */
|
|
229
|
+
.badge { display: inline-flex; align-items: center; gap: 5px; font-size: 12.5px; font-weight: 600;
|
|
230
|
+
padding: 2px 9px; border-radius: 999px; border: 1px solid var(--border); background: var(--bg-sunken); color: var(--text-secondary); white-space: nowrap; }
|
|
231
|
+
.badge .dot { width: 6px; height: 6px; border-radius: 999px; background: currentColor; }
|
|
232
|
+
.badge--draft { color: var(--slate); background: var(--slate-soft); border-color: var(--slate-border); }
|
|
233
|
+
.badge--planned { color: var(--blue); background: var(--blue-soft); border-color: var(--blue-border); }
|
|
234
|
+
.badge--applied { color: var(--violet); background: var(--violet-soft); border-color: var(--violet-border); }
|
|
235
|
+
.badge--document_passed { color: var(--teal); background: var(--teal-soft); border-color: var(--teal-border); }
|
|
236
|
+
.badge--interview { color: var(--amber); background: var(--amber-soft); border-color: var(--amber-border); }
|
|
237
|
+
.badge--final_passed { color: var(--green); background: var(--green-soft); border-color: var(--green-border); }
|
|
238
|
+
.badge--rejected { color: var(--red); background: var(--red-soft); border-color: var(--red-border); }
|
|
239
|
+
.badge--on_hold { color: var(--slate); background: var(--slate-soft); border-color: var(--slate-border); }
|
|
240
|
+
.badge--accent { color: var(--accent); background: var(--accent-soft); border-color: var(--accent-border); }
|
|
241
|
+
.badge--score { font-variant-numeric: tabular-nums; }
|
|
242
|
+
|
|
243
|
+
.chip { display:inline-flex; align-items:center; gap:6px; font-size:12px; padding:3px 9px; border-radius:7px;
|
|
244
|
+
background: var(--bg-sunken); color: var(--text-secondary); border:1px solid var(--border); }
|
|
245
|
+
.chip--accent { background: var(--accent-soft); color: var(--accent); border-color: var(--accent-border); }
|
|
246
|
+
.chips { display:flex; flex-wrap:wrap; gap:6px; }
|
|
247
|
+
|
|
248
|
+
/* ----------------------------------------------------------------- table */
|
|
249
|
+
.table { width: 100%; border-collapse: collapse; }
|
|
250
|
+
.table th { text-align: left; font-size: 12px; font-weight: 600; color: var(--text-tertiary); text-transform: uppercase;
|
|
251
|
+
letter-spacing: .03em; padding: 10px 16px; border-bottom: 1px solid var(--border); }
|
|
252
|
+
.table td { padding: 12px 16px; border-bottom: 1px solid var(--border); vertical-align: middle; }
|
|
253
|
+
.table tr:last-child td { border-bottom: none; }
|
|
254
|
+
.table tbody tr.is-clickable { cursor: pointer; }
|
|
255
|
+
.table tbody tr.is-clickable:hover { background: var(--surface-hover); }
|
|
256
|
+
.table .muted { color: var(--text-tertiary); }
|
|
257
|
+
.table .strong { font-weight: 600; }
|
|
258
|
+
|
|
259
|
+
/* ----------------------------------------------------------------- progress */
|
|
260
|
+
.progress { height: 7px; background: var(--bg-sunken); border-radius: 999px; overflow: hidden; }
|
|
261
|
+
.progress__bar { height: 100%; background: var(--accent); border-radius: 999px; transition: width .4s ease; }
|
|
262
|
+
|
|
263
|
+
/* ----------------------------------------------------------------- empty state */
|
|
264
|
+
.empty { text-align: center; padding: 44px 24px; color: var(--text-secondary); }
|
|
265
|
+
.empty__icon { width: 46px; height: 46px; border-radius: 12px; background: var(--bg-sunken); display: grid; place-items: center; margin: 0 auto 14px; color: var(--text-tertiary); }
|
|
266
|
+
.empty__icon svg { width: 22px; height: 22px; }
|
|
267
|
+
.empty h3 { font-size: 15px; color: var(--text); margin-bottom: 6px; }
|
|
268
|
+
.empty p { max-width: 420px; margin: 0 auto 16px; font-size: 13.5px; }
|
|
269
|
+
|
|
270
|
+
/* ----------------------------------------------------------------- timeline / versions */
|
|
271
|
+
.timeline { display: flex; flex-direction: column; }
|
|
272
|
+
.tl-item { display: grid; grid-template-columns: 22px 1fr; gap: 12px; padding-bottom: 4px; }
|
|
273
|
+
.tl-item__rail { display: flex; flex-direction: column; align-items: center; }
|
|
274
|
+
.tl-item__dot { width: 11px; height: 11px; border-radius: 999px; background: var(--surface); border: 2px solid var(--border-strong); margin-top: 4px; flex: none; }
|
|
275
|
+
.tl-item.is-current .tl-item__dot { background: var(--accent); border-color: var(--accent); }
|
|
276
|
+
.tl-item__line { width: 2px; flex: 1; background: var(--border); margin: 4px 0; }
|
|
277
|
+
.tl-item:last-child .tl-item__line { display: none; }
|
|
278
|
+
.tl-item__body { padding-bottom: 16px; }
|
|
279
|
+
|
|
280
|
+
/* ----------------------------------------------------------------- kanban */
|
|
281
|
+
.board { display: grid; grid-auto-flow: column; grid-auto-columns: 272px; gap: 14px; overflow-x: auto; padding-bottom: 8px; }
|
|
282
|
+
.board__col { background: var(--bg-subtle); border: 1px solid var(--border); border-radius: var(--radius-lg); display: flex; flex-direction: column; max-height: calc(100vh - 200px); }
|
|
283
|
+
.board__col-head { padding: 11px 14px; display: flex; align-items: center; gap: 8px; position: sticky; top: 0; }
|
|
284
|
+
.board__col-head .count { margin-left: auto; font-size: 12.5px; color: var(--text-tertiary); font-weight: 600; }
|
|
285
|
+
.board__cards { padding: 0 10px 12px; display: flex; flex-direction: column; gap: 8px; overflow-y: auto; }
|
|
286
|
+
.board-card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 11px 12px; cursor: pointer; box-shadow: var(--shadow-sm); }
|
|
287
|
+
.board-card:hover { border-color: var(--border-strong); box-shadow: var(--shadow-md); }
|
|
288
|
+
.board-card__company { font-weight: 600; font-size: 14px; }
|
|
289
|
+
.board-card__role { font-size: 13px; color: var(--text-secondary); margin-top: 1px; }
|
|
290
|
+
.board-card__meta { display: flex; align-items: center; gap: 8px; margin-top: 9px; }
|
|
291
|
+
|
|
292
|
+
/* ----------------------------------------------------------------- list row (shared)
|
|
293
|
+
One primitive behind the home action lane, recent-job list, and any dense list.
|
|
294
|
+
Leading slot → title + muted sub → right-aligned trailing (badges/score). */
|
|
295
|
+
.list-row { display: flex; align-items: center; gap: 12px; padding: 10px 0; border-bottom: 1px solid var(--border); }
|
|
296
|
+
.list-row:last-child { border-bottom: none; }
|
|
297
|
+
.list-row.is-clickable { cursor: pointer; transition: background .12s; }
|
|
298
|
+
.list-row.is-clickable:hover { background: var(--surface-hover); }
|
|
299
|
+
.list-row__lead { flex: none; display: grid; place-items: center; }
|
|
300
|
+
.list-row__lead--chip { width: 30px; height: 30px; border-radius: 8px; background: var(--bg-sunken); color: var(--text-tertiary); }
|
|
301
|
+
.list-row__lead--chip svg { width: 16px; height: 16px; }
|
|
302
|
+
.list-row__dot { width: 9px; height: 9px; border-radius: 999px; flex: none; }
|
|
303
|
+
.list-row__main { flex: 1; min-width: 0; }
|
|
304
|
+
.list-row__title { font-weight: 600; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
305
|
+
.list-row__sub { font-size: 13px; color: var(--text-tertiary); margin-top: 1px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
306
|
+
.list-row__trail { flex: none; display: flex; align-items: center; gap: 8px; margin-left: auto; white-space: nowrap; }
|
|
307
|
+
|
|
308
|
+
/* applications: status-group section header (reads clearly as a section title) */
|
|
309
|
+
.app-group { }
|
|
310
|
+
.app-group__head { display: flex; align-items: center; gap: 9px; padding-bottom: 8px; margin-bottom: 2px; border-bottom: 1px solid var(--border); }
|
|
311
|
+
.app-group__dot { width: 9px; height: 9px; border-radius: 999px; flex: none; }
|
|
312
|
+
.app-group__title { font-weight: 650; font-size: 15px; letter-spacing: -.01em; }
|
|
313
|
+
.app-group__count { margin-left: auto; color: var(--text-tertiary); font-size: 13px; }
|
|
314
|
+
|
|
315
|
+
/* ----------------------------------------------------------------- pipeline bar + funnel
|
|
316
|
+
Visualizes the application status_breakdown. Segment color == badge--{status} hue,
|
|
317
|
+
so a colour means the same stage everywhere (board, badges, here). */
|
|
318
|
+
.pipebar { display: flex; height: 14px; border-radius: 999px; overflow: hidden; background: var(--bg-sunken); gap: 2px; }
|
|
319
|
+
.pipebar__seg { min-width: 8px; cursor: pointer; transition: flex-grow .4s ease, opacity .12s; }
|
|
320
|
+
.pipebar__seg:hover { opacity: .8; }
|
|
321
|
+
.pipefunnel { display: grid; grid-template-columns: repeat(4, 1fr); gap: 14px; margin-top: 18px; }
|
|
322
|
+
.pipefunnel__tile { cursor: pointer; }
|
|
323
|
+
.pipefunnel__label { font-size: 13px; color: var(--text-secondary); display: flex; align-items: center; gap: 6px; }
|
|
324
|
+
.pipefunnel__dot { width: 8px; height: 8px; border-radius: 999px; flex: none; }
|
|
325
|
+
.pipefunnel__value { font-size: 24px; font-weight: 700; letter-spacing: -.02em; margin-top: 5px; line-height: 1.1; }
|
|
326
|
+
.pipefunnel__conv { font-size: 12px; color: var(--text-tertiary); margin-top: 2px; }
|
|
327
|
+
@media (max-width: 560px){ .pipefunnel { grid-template-columns: repeat(2, 1fr); } }
|
|
328
|
+
|
|
329
|
+
/* ----------------------------------------------------------------- check row (onboarding)
|
|
330
|
+
The ONE place setup guidance lives. Outcome rows auto-detect completion. */
|
|
331
|
+
.check-row { display: flex; align-items: center; gap: 10px; padding: 7px 0; }
|
|
332
|
+
.check-row__dot { width: 16px; height: 16px; border: 2px solid var(--border-strong); border-radius: 999px; flex: none; box-sizing: border-box; }
|
|
333
|
+
.check-row__icon { width: 16px; height: 16px; flex: none; color: var(--green); }
|
|
334
|
+
.check-row__label { font-size: 14px; }
|
|
335
|
+
.check-row.is-done .check-row__label { color: var(--text-tertiary); text-decoration: line-through; }
|
|
336
|
+
.check-row__label.is-link { color: var(--accent); cursor: pointer; }
|
|
337
|
+
.check-row__label.is-link:hover { text-decoration: underline; }
|
|
338
|
+
|
|
339
|
+
/* ----------------------------------------------------------------- activity feed (day-bucketed) */
|
|
340
|
+
.feed-group { font-size: 11.5px; font-weight: 600; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: .04em; padding: 14px 0 6px; }
|
|
341
|
+
.feed-group:first-child { padding-top: 0; }
|
|
342
|
+
.feed-item { display: flex; gap: 12px; align-items: flex-start; padding: 6px 0; }
|
|
343
|
+
.feed-item__icon { width: 30px; height: 30px; border-radius: 8px; display: grid; place-items: center; flex: none; background: var(--bg-sunken); color: var(--text-tertiary); }
|
|
344
|
+
.feed-item__icon svg { width: 16px; height: 16px; }
|
|
345
|
+
.feed-item__body { flex: 1; min-width: 0; }
|
|
346
|
+
.feed-item__text { font-size: 13.5px; line-height: 1.5; }
|
|
347
|
+
.feed-item__time { display: block; font-size: 12.5px; color: var(--text-tertiary); margin-top: 1px; }
|
|
348
|
+
|
|
349
|
+
/* ----------------------------------------------------------------- first-run hero
|
|
350
|
+
Day one: a single getting-started card centered on a quiet dot canvas, instead
|
|
351
|
+
of a wall of empty cards. */
|
|
352
|
+
.firstrun-hero { display: grid; place-items: start center; padding: 28px 0;
|
|
353
|
+
background-image: radial-gradient(var(--border) 1px, transparent 1px); background-size: 24px 24px; border-radius: var(--radius-lg); }
|
|
354
|
+
.firstrun-hero > .card { width: 100%; max-width: 540px; }
|
|
355
|
+
|
|
356
|
+
/* ----------------------------------------------------------------- doc preview */
|
|
357
|
+
.doc-preview { white-space: pre-wrap; word-break: break-word; font-size: 15px; line-height: 1.75; color: var(--text);
|
|
358
|
+
background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius); padding: 22px 26px; max-height: 60vh; overflow-y: auto; }
|
|
359
|
+
.kv { display: grid; grid-template-columns: 132px 1fr; gap: 8px 16px; }
|
|
360
|
+
.kv dt { color: var(--text-tertiary); font-size: 13px; }
|
|
361
|
+
.kv dd { margin: 0; }
|
|
362
|
+
|
|
363
|
+
/* ----------------------------------------------------------------- modal */
|
|
364
|
+
.modal-root { position: fixed; inset: 0; z-index: 60; display: none; }
|
|
365
|
+
.modal-root.is-open { display: block; }
|
|
366
|
+
.modal__scrim { position: absolute; inset: 0; background: rgba(16,16,20,.42); backdrop-filter: blur(2px); animation: fade .15s; }
|
|
367
|
+
.modal { position: absolute; top: 50%; left: 50%; transform: translate(-50%,-50%); width: min(640px, calc(100vw - 32px));
|
|
368
|
+
max-height: calc(100vh - 48px); background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-lg);
|
|
369
|
+
box-shadow: var(--shadow-lg); display: flex; flex-direction: column; animation: pop .16s ease; }
|
|
370
|
+
.modal--lg { width: min(880px, calc(100vw - 32px)); }
|
|
371
|
+
.modal__head { padding: 16px 20px; border-bottom: 1px solid var(--border); display: flex; align-items: center; }
|
|
372
|
+
.modal__head h3 { font-size: 15px; }
|
|
373
|
+
.modal__body { padding: 20px; overflow-y: auto; }
|
|
374
|
+
.modal__foot { padding: 14px 20px; border-top: 1px solid var(--border); display: flex; justify-content: flex-end; gap: 8px; }
|
|
375
|
+
@keyframes pop { from { transform: translate(-50%,-46%); opacity: 0; } to { transform: translate(-50%,-50%); opacity: 1; } }
|
|
376
|
+
@keyframes fade { from { opacity: 0; } to { opacity: 1; } }
|
|
377
|
+
|
|
378
|
+
/* ----------------------------------------------------------------- toast */
|
|
379
|
+
.toasts { position: fixed; bottom: 20px; right: 20px; z-index: 80; display: flex; flex-direction: column; gap: 10px; }
|
|
380
|
+
.toast { background: var(--surface); border: 1px solid var(--border); border-left: 3px solid var(--accent); border-radius: var(--radius);
|
|
381
|
+
box-shadow: var(--shadow-md); padding: 12px 16px; min-width: 260px; max-width: 380px; animation: slidein .2s ease; font-size: 14px; }
|
|
382
|
+
.toast--success { border-left-color: var(--green); }
|
|
383
|
+
.toast--error { border-left-color: var(--red); }
|
|
384
|
+
.toast__title { font-weight: 600; margin-bottom: 2px; }
|
|
385
|
+
.toast__body { color: var(--text-secondary); font-size: 13px; }
|
|
386
|
+
@keyframes slidein { from { transform: translateX(20px); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
|
|
387
|
+
|
|
388
|
+
/* ----------------------------------------------------------------- tabs */
|
|
389
|
+
.tabs { display: flex; gap: 2px; border-bottom: 1px solid var(--border); margin-bottom: 18px; }
|
|
390
|
+
.tab { padding: 9px 14px; font-size: 14px; font-weight: 550; color: var(--text-secondary); cursor: pointer; border-bottom: 2px solid transparent; margin-bottom: -1px; }
|
|
391
|
+
.tab:hover { color: var(--text); }
|
|
392
|
+
.tab.is-active { color: var(--accent); border-bottom-color: var(--accent); }
|
|
393
|
+
|
|
394
|
+
/* ----------------------------------------------------------------- utils */
|
|
395
|
+
.muted { color: var(--text-tertiary); }
|
|
396
|
+
.text-sm { font-size: 13px; }
|
|
397
|
+
.text-secondary { color: var(--text-secondary); }
|
|
398
|
+
.stack { display: flex; flex-direction: column; }
|
|
399
|
+
.stack-2 > * + * { margin-top: 8px; } .stack-3 > * + * { margin-top: 12px; } .stack-4 > * + * { margin-top: 16px; }
|
|
400
|
+
.flex { display: flex; align-items: center; } .flex-col{ display:flex; flex-direction:column; }
|
|
401
|
+
.gap-2 { gap: 8px; } .gap-3 { gap: 12px; } .gap-4 { gap: 16px; }
|
|
402
|
+
.between { justify-content: space-between; } .center { align-items: center; } .wrap { flex-wrap: wrap; }
|
|
403
|
+
.ml-auto { margin-left: auto; } .mt-2{margin-top:8px;} .mt-3{margin-top:12px;} .mt-4{margin-top:16px;} .mb-2{margin-bottom:8px;} .mb-3{margin-bottom:12px;} .mb-4{margin-bottom:16px;}
|
|
404
|
+
.tnum { font-variant-numeric: tabular-nums; }
|
|
405
|
+
.truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
|
|
406
|
+
.divider { height: 1px; background: var(--border); margin: 16px 0; }
|
|
407
|
+
.skeleton { background: linear-gradient(90deg, var(--bg-sunken) 25%, var(--surface-hover) 37%, var(--bg-sunken) 63%); background-size: 400% 100%; animation: shimmer 1.4s infinite; border-radius: 6px; }
|
|
408
|
+
@keyframes shimmer { 0% { background-position: 100% 0; } 100% { background-position: 0 0; } }
|
|
409
|
+
.hide { display: none !important; }
|
|
410
|
+
|
|
411
|
+
/* score color helper */
|
|
412
|
+
.score-strong { color: var(--green); } .score-mid { color: var(--amber); } .score-weak { color: var(--red); }
|
|
413
|
+
|
|
414
|
+
/* banner / callout */
|
|
415
|
+
.callout { display: flex; gap: 12px; padding: 14px 16px; border-radius: var(--radius); border: 1px solid var(--accent-border); background: var(--accent-soft); }
|
|
416
|
+
.callout svg { flex: none; width: 18px; height: 18px; color: var(--accent); margin-top: 1px; }
|
|
417
|
+
.callout--privacy { border-color: var(--border); background: var(--bg-subtle); }
|
|
418
|
+
.callout--privacy svg { color: var(--text-tertiary); }
|
|
419
|
+
.callout__title { font-weight: 600; margin-bottom: 2px; }
|
|
420
|
+
.callout__body { color: var(--text-secondary); font-size: 13px; }
|