claude-cli-analytics 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.
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/bin/cli.js +44 -0
- package/dist/client/assets/index-BkOIudNK.css +1 -0
- package/dist/client/assets/index-CXwfzzf8.js +48 -0
- package/dist/client/index.html +14 -0
- package/dist/client/vite.svg +1 -0
- package/dist/server/analyzer.js +711 -0
- package/dist/server/config.js +120 -0
- package/dist/server/index.js +151 -0
- package/dist/server/parser.js +48 -0
- package/dist/server/types.js +2 -0
- package/package.json +77 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
# Claude CLI Analytics Dashboard
|
|
2
|
+
|
|
3
|
+
Claude CLI 대화 로그를 분석하여 토큰 효율성, 캐시 활용률, 컨텍스트 사용 패턴을 시각화하는 대시보드
|
|
4
|
+
|
|
5
|
+
## ✨ Features
|
|
6
|
+
|
|
7
|
+
- 📊 **Dashboard**: 전체 효율성 지표, 토큰 분포, 트렌드 차트
|
|
8
|
+
- 📝 **Session Detail**: 세션별 대화 타임라인, 질문별 읽은 컨텍스트 로그
|
|
9
|
+
- 🔄 **Real-time Refresh**: 새로고침 버튼으로 최신 데이터 로드
|
|
10
|
+
- 🏆 **Engineering Grade**: S/A/B/C 등급 + SEI (Spec Efficiency Index) 분석
|
|
11
|
+
- 🔍 **Auto-detection**: `.claude/projects` 경로 자동 탐색 — init 불필요
|
|
12
|
+
- 📦 **NPM 패키지**: `npm install -g`로 어디서든 설치 가능
|
|
13
|
+
|
|
14
|
+
## 🚀 설치 및 실행
|
|
15
|
+
|
|
16
|
+
### ✅ NPM (권장)
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
# 글로벌 설치
|
|
20
|
+
npm install -g claude-cli-analytics
|
|
21
|
+
|
|
22
|
+
# 실행 (자동으로 ~/.claude/projects 탐색)
|
|
23
|
+
claude-cli-analytics
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### 📦 npx (설치 없이 실행)
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npx claude-cli-analytics
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### 🛠️ 소스에서 빌드 (기여용)
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
git clone https://github.com/igeunpyo/claude-analytics.git
|
|
36
|
+
cd claude-analytics
|
|
37
|
+
npm install
|
|
38
|
+
npm run build
|
|
39
|
+
npm start
|
|
40
|
+
|
|
41
|
+
# 또는 글로벌로 링크하여 사용
|
|
42
|
+
npm link
|
|
43
|
+
claude-cli-analytics
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### ⚡ 개발 모드
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npm run dev
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- Frontend: http://localhost:3000
|
|
53
|
+
- Backend API: http://localhost:3001
|
|
54
|
+
|
|
55
|
+
## 🛠️ Requirements
|
|
56
|
+
|
|
57
|
+
- Node.js 18+ (권장: 20+)
|
|
58
|
+
- npm 9+
|
|
59
|
+
|
|
60
|
+
## 🔍 .claude 경로 자동 탐색
|
|
61
|
+
|
|
62
|
+
**별도의 `init` 과정이 필요 없습니다.** 서버 시작 시 자동으로 Claude Code의 데이터 디렉토리를 탐색합니다.
|
|
63
|
+
|
|
64
|
+
Claude Code는 설치 방법에 관계없이 항상 `~/.claude/projects`에 세션 데이터를 저장합니다:
|
|
65
|
+
|
|
66
|
+
| 설치 방법 | 데이터 경로 |
|
|
67
|
+
|----------|-----------|
|
|
68
|
+
| `brew install --cask claude-code` | `~/.claude/projects` |
|
|
69
|
+
| `npm install -g @anthropic-ai/claude-code` | `~/.claude/projects` |
|
|
70
|
+
| 직접 다운로드 | `~/.claude/projects` |
|
|
71
|
+
|
|
72
|
+
### 경로 탐색 우선순위
|
|
73
|
+
|
|
74
|
+
1. `CLAUDE_PROJECTS_DIR` 환경변수 (최우선)
|
|
75
|
+
2. 저장된 설정 파일 (`~/.claude-analytics/config.json`)
|
|
76
|
+
3. 자동 탐색 (`~/.claude/projects`, `$XDG_CONFIG_HOME/claude/projects`)
|
|
77
|
+
4. 기본 경로 (`~/.claude/projects`)
|
|
78
|
+
|
|
79
|
+
### 커스텀 경로 사용
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# 환경변수로 지정
|
|
83
|
+
CLAUDE_PROJECTS_DIR=/path/to/claude/projects claude-cli-analytics
|
|
84
|
+
|
|
85
|
+
# CLI 옵션으로 지정
|
|
86
|
+
claude-cli-analytics --path /path/to/claude/projects
|
|
87
|
+
|
|
88
|
+
# 포트 변경
|
|
89
|
+
claude-cli-analytics --port 8080
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## 📈 분석 지표
|
|
93
|
+
|
|
94
|
+
### 1. 컨텍스트 지표
|
|
95
|
+
|
|
96
|
+
| 지표 | 계산 방식 | 의미 |
|
|
97
|
+
|------|----------|------|
|
|
98
|
+
| **평균 컨텍스트** | `(input_tokens + cache_read) / 요청수` | 요청당 평균 컨텍스트 크기 |
|
|
99
|
+
| **위험 레벨** | `<20K: 안전, 20-50K: 주의, >50K: 위험` | 컨텍스트 과부하 경고 |
|
|
100
|
+
| **리미트 영향도** | `(총 컨텍스트 / 44,000) × 100%` | Claude Pro 5시간 한도 대비 사용량 |
|
|
101
|
+
|
|
102
|
+
### 2. 품질 지표
|
|
103
|
+
|
|
104
|
+
| 지표 | 계산 방식 | 목표 | 의미 |
|
|
105
|
+
|------|----------|------|------|
|
|
106
|
+
| **중복 읽기율** | `(전체읽기 - 고유파일) / 전체읽기 × 100` | <20% | 같은 파일을 여러 번 읽은 비율 |
|
|
107
|
+
| **Read/Edit 비율** | `Read도구 횟수 / Edit도구 횟수` | ≥5:1 | 수정 전 충분한 탐색 여부 |
|
|
108
|
+
| **반복 수정율** | `(전체수정 - 고유파일) / 전체수정 × 100` | <20% | 같은 파일을 여러 번 수정한 비율 |
|
|
109
|
+
| **수정당 토큰** | `총 컨텍스트 / Edit 횟수` | <50K | 수정 1회당 소비된 토큰 |
|
|
110
|
+
|
|
111
|
+
### 3. Engineering Grade (S/A/B/C)
|
|
112
|
+
|
|
113
|
+
100점 만점 종합 점수로 산출:
|
|
114
|
+
- **Efficiency (40%)**: 캐시 히트율 기반
|
|
115
|
+
- **Stability (30%)**: 도구 오류율 기반
|
|
116
|
+
- **Precision (30%)**: Read/Edit 비율 기반
|
|
117
|
+
- **Penalty**: 팬텀 파일 접근 횟수 × 5
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
🏆 S급 (90+): Elite — 최적화된 워크플로우
|
|
121
|
+
⭐ A급 (80+): Good — 우수한 효율성
|
|
122
|
+
✅ B급 (60+): Average — 개선 여지 있음
|
|
123
|
+
⚠️ C급 (40+): Below Average — 최적화 필요
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### 4. Spec Efficiency Index (SEI)
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
SEI = (Accuracy × 100) / log₁₀(Spec Volume + 1)
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
`.claude/` 컨텍스트 파일 읽기 후 오류율을 측정하여 스펙 문서의 실효성을 평가합니다.
|
|
133
|
+
|
|
134
|
+
## 🔧 API Endpoints
|
|
135
|
+
|
|
136
|
+
| Endpoint | Description |
|
|
137
|
+
|----------|-------------|
|
|
138
|
+
| `GET /api/analytics` | 전체 요약 통계 |
|
|
139
|
+
| `GET /api/sessions` | 세션 목록 (SEI + Grade 포함) |
|
|
140
|
+
| `GET /api/sessions/:id` | 세션 상세 (메시지, 토큰, 파일) |
|
|
141
|
+
| `GET /api/projects` | 프로젝트 목록 |
|
|
142
|
+
| `GET /api/config` | 현재 설정 + 자동 탐색 결과 |
|
|
143
|
+
| `GET /api/health` | 서버 상태 확인 |
|
|
144
|
+
| `POST /api/refresh` | 데이터 새로고침 |
|
|
145
|
+
|
|
146
|
+
## 📁 Project Structure
|
|
147
|
+
|
|
148
|
+
```
|
|
149
|
+
claude-cli-analytics/
|
|
150
|
+
├── src/ # React Frontend
|
|
151
|
+
│ ├── pages/
|
|
152
|
+
│ │ ├── Dashboard.tsx # 메인 대시보드
|
|
153
|
+
│ │ └── SessionDetail.tsx # 세션 상세 페이지
|
|
154
|
+
│ ├── App.tsx # 라우팅
|
|
155
|
+
│ └── index.css # Tailwind CSS
|
|
156
|
+
├── server/
|
|
157
|
+
│ ├── index.ts # Express API 서버
|
|
158
|
+
│ ├── config.ts # 설정 + 자동 탐색
|
|
159
|
+
│ ├── analyzer.ts # 세션 분석 로직
|
|
160
|
+
│ ├── parser.ts # JSONL 파서
|
|
161
|
+
│ └── types.ts # 타입 정의
|
|
162
|
+
├── bin/
|
|
163
|
+
│ └── cli.js # CLI 진입점 (--port, --path, --help)
|
|
164
|
+
├── dist/
|
|
165
|
+
│ ├── client/ # 빌드된 프론트엔드
|
|
166
|
+
│ └── server/ # 빌드된 백엔드
|
|
167
|
+
├── package.json
|
|
168
|
+
├── tsconfig.server.json # 서버 빌드 설정
|
|
169
|
+
└── vite.config.ts
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## 📄 License
|
|
173
|
+
|
|
174
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ── Claude Analytics CLI ──
|
|
4
|
+
// Supports: --port <number>, --path <dir>, --help
|
|
5
|
+
|
|
6
|
+
const args = process.argv.slice(2);
|
|
7
|
+
|
|
8
|
+
// Handle --help
|
|
9
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
10
|
+
console.log(`
|
|
11
|
+
Claude Analytics Dashboard
|
|
12
|
+
|
|
13
|
+
Usage: claude-cli-analytics [options]
|
|
14
|
+
|
|
15
|
+
Options:
|
|
16
|
+
--port <number> Server port (default: 3001)
|
|
17
|
+
--path <dir> Claude projects directory (overrides auto-detection)
|
|
18
|
+
--help, -h Show this help message
|
|
19
|
+
|
|
20
|
+
Auto-detection:
|
|
21
|
+
Automatically finds Claude Code data at ~/.claude/projects
|
|
22
|
+
Works with all installation methods (homebrew, npm, direct install)
|
|
23
|
+
|
|
24
|
+
Environment variables:
|
|
25
|
+
CLAUDE_PROJECTS_DIR Override projects directory path
|
|
26
|
+
PORT Override server port
|
|
27
|
+
`);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Parse --port
|
|
32
|
+
const portIdx = args.indexOf('--port');
|
|
33
|
+
if (portIdx !== -1 && args[portIdx + 1]) {
|
|
34
|
+
process.env.PORT = args[portIdx + 1];
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Parse --path
|
|
38
|
+
const pathIdx = args.indexOf('--path');
|
|
39
|
+
if (pathIdx !== -1 && args[pathIdx + 1]) {
|
|
40
|
+
process.env.CLAUDE_PROJECTS_DIR = args[pathIdx + 1];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Start the server
|
|
44
|
+
import('../dist/server/index.js');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@import"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap";:root{--bg-primary: #ffffff;--bg-secondary: #f8f9fa;--bg-tertiary: #f1f3f4;--bg-surface: #ffffff;--text-primary: #202124;--text-secondary: #5f6368;--text-tertiary: #9aa0a6;--blue-primary: #1a73e8;--blue-light: #e8f0fe;--blue-dark: #1557b0;--green-primary: #1e8e3e;--green-light: #e6f4ea;--orange-primary: #e37400;--orange-light: #fef7e0;--purple-primary: #7b1fa2;--purple-light: #f3e8fd;--red-primary: #d93025;--red-light: #fce8e6;--border-color: #e8eaed;--border-light: #f1f3f4;--shadow-sm: 0 1px 3px rgba(32, 33, 36, .04);--shadow-md: 0 4px 12px rgba(32, 33, 36, .06);--shadow-lg: 0 8px 24px rgba(32, 33, 36, .08);--shadow-xl: 0 12px 40px rgba(32, 33, 36, .1);--radius-sm: 12px;--radius-md: 20px;--radius-lg: 24px;--radius-xl: 32px;--radius-pill: 999px;--transition-fast: .15s cubic-bezier(.4, 0, .2, 1);--transition-normal: .25s cubic-bezier(.4, 0, .2, 1);--transition-slow: .35s cubic-bezier(.4, 0, .2, 1)}*{box-sizing:border-box;margin:0;padding:0}body{font-family:Inter,-apple-system,BlinkMacSystemFont,system-ui,Segoe UI,sans-serif;background-color:var(--bg-secondary);color:var(--text-primary);line-height:1.6;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;letter-spacing:-.011em}.app-container{min-height:100vh}.header{background:#ffffffb8;backdrop-filter:blur(20px) saturate(180%);-webkit-backdrop-filter:blur(20px) saturate(180%);border-bottom:1px solid rgba(232,234,237,.6);padding:0 32px;height:64px;position:sticky;top:0;z-index:100;transition:background var(--transition-normal)}.header-content{max-width:1200px;margin:0 auto;display:flex;align-items:center;justify-content:space-between;height:100%}.logo{font-size:17px;font-weight:600;color:var(--text-primary);display:flex;align-items:center;gap:10px;text-decoration:none;letter-spacing:-.02em}.logo-icon{display:inline-flex;align-items:center;justify-content:center;width:32px;height:32px;border-radius:10px;background:var(--text-primary);color:#fff;font-size:12px;font-weight:700;letter-spacing:.5px}.nav{display:flex;align-items:center;gap:4px}.nav-link{font-size:14px;font-weight:500;color:var(--text-secondary);text-decoration:none;padding:8px 16px;border-radius:var(--radius-pill);transition:all var(--transition-fast)}.nav-link:hover{background:var(--bg-tertiary);color:var(--text-primary)}.main{min-height:calc(100vh - 130px)}.container{max-width:1200px;margin:0 auto;padding:40px 32px}.footer{text-align:center;padding:32px;color:var(--text-tertiary);font-size:13px;font-weight:400}.card{background:var(--bg-primary);border-radius:var(--radius-lg);border:none;box-shadow:var(--shadow-sm);padding:28px;transition:all var(--transition-normal)}.card:hover{box-shadow:var(--shadow-md)}.card-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:24px}.card-title{font-size:16px;font-weight:600;color:var(--text-primary);letter-spacing:-.02em}.stat-card{background:var(--bg-primary);border-radius:var(--radius-lg);border:none;box-shadow:var(--shadow-sm);padding:28px;display:flex;flex-direction:column;gap:8px;transition:all var(--transition-normal)}.stat-card:hover{box-shadow:var(--shadow-md);transform:translateY(-2px)}.stat-label{font-size:13px;color:var(--text-tertiary);font-weight:500}.stat-value{font-size:32px;font-weight:700;color:var(--text-primary);line-height:1.2;letter-spacing:-.03em}.stat-value.blue{color:var(--blue-primary)}.stat-value.green{color:var(--green-primary)}.stat-value.orange{color:var(--orange-primary)}.stat-value.purple{color:var(--purple-primary)}.btn{display:inline-flex;align-items:center;justify-content:center;gap:8px;padding:10px 24px;border-radius:var(--radius-pill);font-size:14px;font-weight:500;cursor:pointer;transition:all var(--transition-fast);border:none;letter-spacing:-.01em}.btn-primary{background:var(--text-primary);color:#fff}.btn-primary:hover{background:#3c4043;box-shadow:var(--shadow-md)}.btn-secondary{background:var(--bg-primary);color:var(--text-primary);border:1px solid var(--border-color)}.btn-secondary:hover{background:var(--bg-tertiary);border-color:#dadce0}.select,.input{padding:10px 18px;border-radius:var(--radius-sm);border:1px solid var(--border-color);background:var(--bg-primary);color:var(--text-primary);font-size:14px;font-weight:400;font-family:inherit;cursor:pointer;transition:all var(--transition-fast)}.select{padding-right:40px;appearance:none;background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%235f6368' stroke-width='2'%3E%3Cpath d='M6 9l6 6 6-6'/%3E%3C/svg%3E");background-repeat:no-repeat;background-position:right 14px center}.select:focus,.input:focus{outline:none;border-color:var(--blue-primary);box-shadow:0 0 0 3px #1a73e81f}.tag{display:inline-flex;align-items:center;padding:4px 12px;border-radius:var(--radius-pill);font-size:12px;font-weight:500}.tag-blue{background:var(--blue-light);color:var(--blue-primary)}.tag-green{background:var(--green-light);color:var(--green-primary)}.tag-orange{background:var(--orange-light);color:var(--orange-primary)}.tag-purple{background:var(--purple-light);color:var(--purple-primary)}.table-container{overflow-x:auto;border-radius:var(--radius-sm)}table{width:100%;border-collapse:collapse}table th{text-align:left;font-size:12px;font-weight:600;color:var(--text-tertiary);padding:12px 16px;text-transform:uppercase;letter-spacing:.04em;border-bottom:1px solid var(--border-color)}table td{padding:14px 16px;border-bottom:1px solid var(--border-light);font-size:14px;color:var(--text-secondary);transition:background var(--transition-fast)}table tr:last-child td{border-bottom:none}table tbody tr{transition:background var(--transition-fast)}table tbody tr:hover td{background:var(--bg-secondary)}.chart-container{padding:20px 0}.message-card{background:var(--bg-primary);border-radius:var(--radius-md);border:none;box-shadow:var(--shadow-sm);padding:20px;margin-bottom:12px;transition:box-shadow var(--transition-fast)}.message-card:hover{box-shadow:var(--shadow-md)}.message-card.user{border-left:3px solid var(--blue-primary)}.message-card.assistant{border-left:3px solid var(--purple-primary)}.message-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.message-content{font-size:14px;color:var(--text-secondary);line-height:1.7;white-space:pre-wrap}.file-tags{display:flex;flex-wrap:wrap;gap:6px;margin:8px 0}.file-tag{display:inline-flex;align-items:center;padding:4px 10px;background:var(--bg-tertiary);border-radius:var(--radius-pill);font-size:11px;font-family:SF Mono,Fira Code,Monaco,monospace;color:var(--text-secondary);max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.file-tag.spec{background:var(--purple-light);color:var(--purple-primary)}.grid-4{display:grid;grid-template-columns:repeat(4,1fr);gap:20px}.grid-3{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}.grid-2{display:grid;grid-template-columns:repeat(2,1fr);gap:24px}@media(max-width:1200px){.grid-4,.grid-3{grid-template-columns:repeat(2,1fr)}}@media(max-width:768px){.grid-4,.grid-3,.grid-2{grid-template-columns:1fr}.container{padding:20px 16px}.header{padding:0 16px}}.spinner,.loading-spinner{width:36px;height:36px;border:3px solid var(--border-color);border-top-color:var(--text-primary);border-radius:50%;animation:spin .7s linear infinite}@keyframes spin{to{transform:rotate(360deg)}}@keyframes fadeIn{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.3}}::-webkit-scrollbar{width:6px;height:6px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:var(--border-color);border-radius:3px}::-webkit-scrollbar-thumb:hover{background:var(--text-tertiary)}.info-tooltip{position:relative;display:inline-flex;align-items:center;margin-left:4px;cursor:help}.info-tooltip-icon{width:15px;height:15px;border-radius:50%;background:var(--bg-tertiary);color:var(--text-tertiary);font-size:10px;font-weight:600;display:inline-flex;align-items:center;justify-content:center;transition:all var(--transition-fast)}.info-tooltip:hover .info-tooltip-icon{background:var(--text-primary);color:#fff}.info-tooltip-text{display:none;position:absolute;bottom:calc(100% + 8px);left:50%;transform:translate(-50%);background:var(--text-primary);color:#fff;padding:10px 14px;border-radius:var(--radius-sm);font-size:12px;line-height:1.5;white-space:pre-wrap;min-width:220px;max-width:320px;z-index:1000;box-shadow:var(--shadow-lg)}.info-tooltip:hover .info-tooltip-text{display:block}
|