ltcai 2.2.0 → 2.2.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.
@@ -1,195 +1,254 @@
1
- /* ============================================================
2
- * Lattice AI — Design Tokens (Single Source of Truth)
1
+ /* ============================================================================
2
+ * Lattice AI — Design Tokens (Single Source of Truth) v2.2.1
3
3
  *
4
- * 명세: lattice_ai_full_spec.pptx 슬라이드 19 (디자인 토큰)
4
+ * 파일이 색·면·테두리·그림자·포커스의 단일 출처다.
5
+ * :root → 라이트 테마 값
6
+ * :root[data-lt-theme="dark"] → 동일 토큰명의 다크 값
7
+ * @media (prefers-color-scheme: dark) → 사용자가 명시 선택 안 했을 때 OS 추종
5
8
  *
6
- * 파일이 모든 화면의 색·간격·타이포·반경·모션의 기준이다.
7
- * chat.html / graph.html / admin.html / account.html :root {}
8
- * 블록은 점차 이 파일의 변수만 참조하도록 정리할 것.
9
+ * 컴포넌트 CSS 항상 var(--token) 만 사용한다. 따라서 이 파일의 값만 바꾸면
10
+ * 전체 UI 자연스럽게 전환된다. (컴포넌트별 다크 오버라이드 / !important 금지)
9
11
  *
10
- * 토큰 명명 규칙:
11
- * --lt-{category}-{scale} (예: --lt-color-primary-600)
12
- * --lt-bg / --lt-ink / --lt-accent-* 의미 토큰 (semantic)
13
- * 기존 --bg / --text 등은 이 파일의 의미 토큰으로 alias
14
- * ============================================================ */
12
+ * 토큰 카테고리(요청 범위): background · surface · card · sidebar · modal · table ·
13
+ * graph · text · muted-text · border · accent · success · warning · danger ·
14
+ * input · overlay · shadow · focus-ring
15
+ * ========================================================================== */
15
16
 
16
17
  :root {
17
- /* ── Brand · Color (raw scale) ────────────────────────────── */
18
+ color-scheme: light;
19
+
20
+ /* ── Brand raw scale (불변, 테마 무관 기준점) ───────────────────────────── */
18
21
  --lt-color-primary-100: #EAE2FD;
19
22
  --lt-color-primary-200: #D3C2FB;
20
23
  --lt-color-primary-400: #9A78F0;
21
- --lt-color-primary-600: #6E4AE6; /* PRIMARY — Lattice 보라 */
22
- --lt-color-primary-800: #3B1F8E; /* PRIMARY_DEEP */
23
-
24
- --lt-color-ink-900: #0E102A; /* 본문/제목 텍스트 (딥 네이비) */
25
- --lt-color-ink-700: #2A2D4A;
26
- --lt-color-ink-600: #4A4F6D; /* 보조 텍스트 */
27
- --lt-color-ink-400: #8A90A7; /* 캡션 */
28
- --lt-color-ink-200: #C8CCDC;
29
- --lt-color-line: #E3E3F0; /* 경계선 */
30
-
31
- --lt-color-surface-0: #FFFFFF; /* 페이지 배경 (라이트) */
32
- --lt-color-surface-50: #F7F5FD; /* 카드 면 */
33
- --lt-color-surface-100:#EEE7FE; /* 보조 면 */
34
-
35
- --lt-color-dark-0: #0B0B1E; /* 페이지 배경 (다크) */
36
- --lt-color-dark-50: #151539;
37
- --lt-color-dark-card: #181A35;
38
- --lt-color-dark-line: rgba(151,179,231,0.18);
39
-
40
- /* Accents */
24
+ --lt-color-primary-600: #6E4AE6;
25
+ --lt-color-primary-800: #3B1F8E;
26
+ --lt-color-ink-900: #0E102A;
41
27
  --lt-color-accent-cyan: #18C6D9;
42
28
  --lt-color-accent-green: #2FC28E;
43
29
  --lt-color-accent-amber: #F1A52A;
44
30
  --lt-color-accent-pink: #F060B8;
45
- --lt-color-danger: #F47171;
46
-
47
- /* ── Spacing · 4px grid ──────────────────────────────────── */
48
- --lt-space-1: 4px;
49
- --lt-space-2: 8px;
50
- --lt-space-3: 12px;
51
- --lt-space-4: 16px;
52
- --lt-space-5: 24px;
53
- --lt-space-6: 32px;
54
- --lt-space-7: 48px;
55
- --lt-space-8: 64px;
56
-
57
- /* ── Radius ──────────────────────────────────────────────── */
58
- --lt-radius-sm: 8px;
59
- --lt-radius-md: 12px;
60
- --lt-radius-lg: 16px;
61
- --lt-radius-xl: 24px;
62
- --lt-radius-pill: 9999px;
63
-
64
- /* ── Typography ──────────────────────────────────────────── */
65
- --lt-font-head: "Pretendard", "Inter", -apple-system, BlinkMacSystemFont,
66
- "Apple SD Gothic Neo", "Malgun Gothic", "Noto Sans KR",
67
- system-ui, sans-serif;
68
- --lt-font-body: var(--lt-font-head);
69
- --lt-font-mono: "JetBrains Mono", "SF Mono", Menlo, Consolas,
70
- "Liberation Mono", monospace;
71
-
72
- --lt-fs-display: 32px;
73
- --lt-fs-title: 22px;
74
- --lt-fs-section: 18px;
75
- --lt-fs-body: 14px;
76
- --lt-fs-caption: 11px;
77
- --lt-fs-mono: 12px;
78
-
79
- --lt-fw-regular: 400;
80
- --lt-fw-medium: 500;
81
- --lt-fw-semibold:600;
82
- --lt-fw-bold: 700;
83
-
84
- --lt-lh-tight: 1.15;
85
- --lt-lh-normal: 1.4;
86
- --lt-lh-relaxed: 1.55;
87
-
88
- /* ── Motion ──────────────────────────────────────────────── */
89
- --lt-motion-fast: 120ms;
90
- --lt-motion-normal: 200ms;
91
- --lt-motion-slow: 280ms;
92
- --lt-motion-ease: cubic-bezier(0.22, 1, 0.36, 1); /* ease-out-quint */
93
-
94
- /* ── Shadow ──────────────────────────────────────────────── */
95
- --lt-shadow-sm: 0 4px 12px rgba(14, 16, 42, 0.06);
96
- --lt-shadow-md: 0 12px 32px rgba(14, 16, 42, 0.10);
97
- --lt-shadow-lg: 0 24px 64px rgba(14, 16, 42, 0.14);
98
-
99
- /* ── Semantic aliases LIGHT mode (default) ──────────────── */
100
- --lt-bg: var(--lt-color-surface-0);
101
- --lt-surface: var(--lt-color-surface-50);
102
- --lt-surface-2: var(--lt-color-surface-100);
103
- --lt-ink: var(--lt-color-ink-900);
104
- --lt-ink-soft: var(--lt-color-ink-600);
105
- --lt-muted: var(--lt-color-ink-400);
106
- --lt-line: var(--lt-color-line);
107
- --lt-accent: var(--lt-color-primary-600);
108
- --lt-accent-deep: var(--lt-color-primary-800);
31
+
32
+ /* ╔══════════════════════════════════════════════════════════════════════╗
33
+ SEMANTIC TOKENS LIGHT (현재 라벤더 보존) ║
34
+ ╚══════════════════════════════════════════════════════════════════════╝ */
35
+
36
+ /* background / surfaces */
37
+ --bg: #f3ecff;
38
+ --bg-soft: #ede6ff;
39
+ --surface: #f6f0ff;
40
+ --surface-2: #efe8ff;
41
+ --surface-3: #e8dfff;
42
+ --card: rgba(255, 255, 255, 0.90);
43
+ --sidebar: rgba(244, 237, 255, 0.96);
44
+ --modal: rgba(247, 242, 255, 0.98);
45
+ --table: rgba(255, 255, 255, 0.72);
46
+ --input: rgba(255, 255, 255, 0.80);
47
+ --overlay: rgba(20, 16, 40, 0.42);
48
+ /* decorative page background (그라데이션 한 토큰) */
49
+ --app-bg:
50
+ radial-gradient(circle at 82% 12%, rgba(111, 66, 232, 0.14), transparent 30%),
51
+ radial-gradient(circle at 10% 80%, rgba(180, 160, 255, 0.16), transparent 35%),
52
+ linear-gradient(180deg, #f3ecff 0%, #efe8ff 52%, #f2ecff 100%);
53
+
54
+ /* text */
55
+ --text: #14162c;
56
+ --muted: #4a4668; /* muted-text */
57
+ --faint: #7a74a0;
58
+
59
+ /* border / line */
60
+ --border: rgba(111, 66, 232, 0.14);
61
+ --border-strong: rgba(111, 66, 232, 0.26);
62
+ --line: rgba(111, 66, 232, 0.12);
63
+ --line-strong: rgba(111, 66, 232, 0.22);
64
+
65
+ /* accent */
66
+ --accent: #6f42e8;
67
+ --accent-2: #8b6cff;
68
+ --accent-3: #a78fff;
69
+ --accent-pink: #c084fc;
70
+ --accent-soft: rgba(111, 66, 232, 0.12);
71
+ --accent-deep: #3B1F8E;
72
+
73
+ /* status */
74
+ --success: #1f9d6b;
75
+ --warning: #b4690e;
76
+ --danger: #e53935;
77
+
78
+ /* graph */
79
+ --graph-bg: #fbfaff;
80
+ --graph-grid: rgba(111, 66, 232, 0.06);
81
+ --graph-pill: rgba(255, 255, 255, 0.90);
82
+
83
+ /* shadow */
84
+ --shadow: 0 18px 54px rgba(88, 72, 150, 0.15);
85
+ --shadow-sm: 0 8px 24px rgba(88, 72, 150, 0.11);
86
+
87
+ /* focus ring */
88
+ --focus-ring: rgba(110, 74, 230, 0.55);
89
+
90
+ /* ── 구조 토큰(색 아님) ─────────────────────────────────────────────── */
91
+ --radius: 14px;
92
+ --radius-sm: 8px;
93
+ --content-width: 900px;
94
+
95
+ /* ── 레거시/별칭 — 코드가 참조하는 모든 변수를 의미 토큰으로 매핑 ──────── */
96
+ --ink: var(--text);
97
+ --blue: var(--accent);
98
+ --green: var(--success);
99
+ --amber: var(--warning);
100
+ --red: var(--danger);
101
+ --pink: var(--accent-pink);
102
+ --ok: var(--success);
103
+ --warn: var(--warning);
104
+ --err: var(--danger);
105
+ --panel: var(--surface);
106
+ --panel-2: var(--surface-2);
107
+ --panel-3: var(--surface-3);
108
+ --panel-strong: var(--surface);
109
+ --glow-green: 0 0 34px rgba(111, 66, 232, 0.14);
110
+ --glow-blue: 0 0 34px rgba(139, 108, 255, 0.14);
111
+ /* PPT reference 스킨 별칭 */
112
+ --ref-purple: var(--accent);
113
+ --ref-purple-2: var(--accent-2);
114
+ --ref-indigo: #2f73ff;
115
+ --ref-ink: var(--text);
116
+ --ref-muted: var(--muted);
117
+ --ref-faint: var(--faint);
118
+ --ref-line: var(--border);
119
+ --ref-soft: var(--surface);
120
+ --ref-card: var(--card);
121
+ --ref-shadow: var(--shadow);
122
+
123
+ /* ── --lt-* 별칭 (workspace.css / platform.css 가 참조) — LIGHT ──────────
124
+ 순환참조를 피하려 구체값으로 둔다(소비자가 --bg 등을 --lt-* 로 재정의해도 안전). */
125
+ --lt-bg: #f3ecff;
126
+ --lt-surface: #f6f0ff;
127
+ --lt-surface-2: #efe8ff;
128
+ --lt-ink: #14162c;
129
+ --lt-ink-soft: #4a4668;
130
+ --lt-muted: #7a74a0;
131
+ --lt-line: rgba(111, 66, 232, 0.14);
132
+ --lt-accent: #6f42e8;
133
+ --lt-shadow-md: 0 18px 54px rgba(88, 72, 150, 0.15);
134
+
135
+ /* ── 모션 / 타이포 ─────────────────────────────────────────────────── */
136
+ --lt-motion-ease: cubic-bezier(0.22, 1, 0.36, 1);
109
137
  }
110
138
 
111
- /* ── Semantic aliases — DARK mode ──────────────────────────── */
112
- :root[data-lt-theme="dark"],
113
- .lt-theme-dark {
114
- --lt-bg: var(--lt-color-dark-0);
115
- --lt-surface: var(--lt-color-dark-50);
116
- --lt-surface-2: var(--lt-color-dark-card);
117
- --lt-ink: #FFFFFF;
118
- --lt-ink-soft: var(--lt-color-ink-200);
139
+ /* ╔════════════════════════════════════════════════════════════════════════╗
140
+ ║ SEMANTIC TOKENS — DARK (값만 교체, 같은 토큰명) ║
141
+ ╚════════════════════════════════════════════════════════════════════════╝ */
142
+ :root[data-lt-theme="dark"] {
143
+ color-scheme: dark;
144
+
145
+ --bg: #0B0B1E;
146
+ --bg-soft: #0e0e26;
147
+ --surface: #16163a;
148
+ --surface-2: #1b1d42;
149
+ --surface-3: #23264f;
150
+ --card: rgba(255, 255, 255, 0.05);
151
+ --sidebar: rgba(18, 18, 46, 0.96);
152
+ --modal: rgba(22, 22, 58, 0.98);
153
+ --table: rgba(255, 255, 255, 0.04);
154
+ --input: rgba(255, 255, 255, 0.06);
155
+ --overlay: rgba(0, 0, 0, 0.58);
156
+ --app-bg:
157
+ radial-gradient(circle at 82% 12%, rgba(110, 74, 230, 0.22), transparent 32%),
158
+ radial-gradient(circle at 10% 80%, rgba(24, 198, 217, 0.10), transparent 38%),
159
+ linear-gradient(180deg, #0B0B1E 0%, #0e0e24 52%, #0B0B1E 100%);
160
+
161
+ --text: #F4F5FB;
162
+ --muted: #C2C7DC;
163
+ --faint: #8B92AC;
164
+
165
+ --border: rgba(160, 170, 230, 0.20);
166
+ --border-strong: rgba(160, 170, 230, 0.32);
167
+ --line: rgba(160, 170, 230, 0.16);
168
+ --line-strong: rgba(160, 170, 230, 0.28);
169
+
170
+ --accent: #A78BFA;
171
+ --accent-2: #c4b5fd;
172
+ --accent-3: #ddd6fe;
173
+ --accent-pink: #e0b0ff;
174
+ --accent-soft: rgba(167, 139, 250, 0.18);
175
+ --accent-deep: #ddd6fe;
176
+
177
+ --success: #34d399;
178
+ --warning: #fbbf24;
179
+ --danger: #F47171;
180
+
181
+ --graph-bg: #0B0B1E;
182
+ --graph-grid: rgba(160, 170, 230, 0.08);
183
+ --graph-pill: rgba(24, 26, 53, 0.92);
184
+
185
+ --shadow: 0 18px 54px rgba(0, 0, 0, 0.50);
186
+ --shadow-sm: 0 8px 24px rgba(0, 0, 0, 0.42);
187
+
188
+ --focus-ring: rgba(167, 139, 250, 0.70);
189
+
190
+ --lt-bg: #0B0B1E;
191
+ --lt-surface: #16163a;
192
+ --lt-surface-2: #1b1d42;
193
+ --lt-ink: #F4F5FB;
194
+ --lt-ink-soft: #C2C7DC;
119
195
  --lt-muted: #8B92AC;
120
- --lt-line: var(--lt-color-dark-line);
121
- --lt-accent: var(--lt-color-primary-400);
122
- --lt-accent-deep:var(--lt-color-primary-200);
196
+ --lt-line: rgba(160, 170, 230, 0.20);
197
+ --lt-accent: #A78BFA;
198
+ --lt-shadow-md: 0 18px 54px rgba(0, 0, 0, 0.50);
199
+
200
+ --ref-indigo: #6f93ff;
201
+ --glow-green: 0 0 34px rgba(110, 74, 230, 0.22);
202
+ --glow-blue: 0 0 34px rgba(24, 198, 217, 0.18);
123
203
  }
124
204
 
125
- /* Auto dark mode (respects OS preference unless overridden) */
205
+ /* OS 다크 추종 사용자가 명시적으로 라이트를 고르지 않은 경우에만 */
126
206
  @media (prefers-color-scheme: dark) {
127
- :root:not([data-lt-theme="light"]) {
128
- --lt-bg: var(--lt-color-dark-0);
129
- --lt-surface: var(--lt-color-dark-50);
130
- --lt-surface-2: var(--lt-color-dark-card);
131
- --lt-ink: #FFFFFF;
132
- --lt-ink-soft: var(--lt-color-ink-200);
133
- --lt-muted: #8B92AC;
134
- --lt-line: var(--lt-color-dark-line);
135
- --lt-accent: var(--lt-color-primary-400);
136
- --lt-accent-deep:var(--lt-color-primary-200);
207
+ :root:not([data-lt-theme="light"]):not([data-lt-theme="dark"]) {
208
+ color-scheme: dark;
209
+ --bg: #0B0B1E; --bg-soft: #0e0e26;
210
+ --surface: #16163a; --surface-2: #1b1d42; --surface-3: #23264f;
211
+ --card: rgba(255,255,255,0.05); --sidebar: rgba(18,18,46,0.96);
212
+ --modal: rgba(22,22,58,0.98); --table: rgba(255,255,255,0.04);
213
+ --input: rgba(255,255,255,0.06); --overlay: rgba(0,0,0,0.58);
214
+ --app-bg:
215
+ radial-gradient(circle at 82% 12%, rgba(110,74,230,0.22), transparent 32%),
216
+ radial-gradient(circle at 10% 80%, rgba(24,198,217,0.10), transparent 38%),
217
+ linear-gradient(180deg, #0B0B1E 0%, #0e0e24 52%, #0B0B1E 100%);
218
+ --text: #F4F5FB; --muted: #C2C7DC; --faint: #8B92AC;
219
+ --border: rgba(160,170,230,0.20); --border-strong: rgba(160,170,230,0.32);
220
+ --line: rgba(160,170,230,0.16); --line-strong: rgba(160,170,230,0.28);
221
+ --accent: #A78BFA; --accent-2: #c4b5fd; --accent-3: #ddd6fe;
222
+ --accent-pink: #e0b0ff; --accent-soft: rgba(167,139,250,0.18); --accent-deep: #ddd6fe;
223
+ --success: #34d399; --warning: #fbbf24; --danger: #F47171;
224
+ --graph-bg: #0B0B1E; --graph-grid: rgba(160,170,230,0.08); --graph-pill: rgba(24,26,53,0.92);
225
+ --shadow: 0 18px 54px rgba(0,0,0,0.50); --shadow-sm: 0 8px 24px rgba(0,0,0,0.42);
226
+ --focus-ring: rgba(167,139,250,0.70);
227
+ --lt-bg: #0B0B1E; --lt-surface: #16163a; --lt-surface-2: #1b1d42;
228
+ --lt-ink: #F4F5FB; --lt-ink-soft: #C2C7DC; --lt-muted: #8B92AC;
229
+ --lt-line: rgba(160,170,230,0.20); --lt-accent: #A78BFA;
230
+ --lt-shadow-md: 0 18px 54px rgba(0,0,0,0.50);
137
231
  }
138
232
  }
139
233
 
140
- /* ============================================================
141
- * Backward-compat aliases for legacy --bg / --text / --accent.
142
- * 기존 HTML 들이 자체 :root 를 가지고 있어도, 이 파일을 import 한
143
- * 뒤 자체 :root 의 값을 지우면 자연히 토큰을 따라간다.
144
- * ============================================================ */
145
- :root {
146
- --bg: var(--lt-bg);
147
- --surface: var(--lt-surface);
148
- --surface-2: var(--lt-surface-2);
149
- --text: var(--lt-ink);
150
- --muted: var(--lt-ink-soft);
151
- --faint: var(--lt-muted);
152
- --border: var(--lt-line);
153
- --accent: var(--lt-accent);
154
- --accent-2: var(--lt-color-accent-amber);
155
- --accent-3: var(--lt-color-accent-cyan);
156
- --accent-pink: var(--lt-color-accent-pink);
157
- --danger: var(--lt-color-danger);
158
- --radius: var(--lt-radius-lg);
159
- --radius-sm: var(--lt-radius-sm);
160
- }
161
-
162
- /* ── Global polish ─────────────────────────────────────────── */
163
- ::selection {
164
- background: rgba(110, 74, 230, 0.18);
165
- color: var(--lt-color-ink-900);
166
- }
234
+ /* ── 전역 폴리시 ───────────────────────────────────────────────────────── */
235
+ ::selection { background: var(--accent-soft); color: var(--text); }
167
236
 
168
237
  :focus-visible {
169
- outline: 2px solid rgba(110, 74, 230, 0.40);
238
+ outline: 2px solid var(--focus-ring);
170
239
  outline-offset: 2px;
171
240
  }
172
241
 
173
- ::-webkit-scrollbar {
174
- width: 6px;
175
- height: 6px;
176
- }
177
- ::-webkit-scrollbar-track {
178
- background: transparent;
179
- }
180
- ::-webkit-scrollbar-thumb {
181
- background: rgba(110, 74, 230, 0.16);
182
- border-radius: 99px;
183
- }
184
- ::-webkit-scrollbar-thumb:hover {
185
- background: rgba(110, 74, 230, 0.28);
186
- }
242
+ ::-webkit-scrollbar { width: 6px; height: 6px; }
243
+ ::-webkit-scrollbar-track { background: transparent; }
244
+ ::-webkit-scrollbar-thumb { background: var(--accent-soft); border-radius: 99px; }
245
+ ::-webkit-scrollbar-thumb:hover { background: var(--border-strong); }
187
246
 
188
- /* ── Reduced motion (a11y) ─────────────────────────────────── */
189
247
  @media (prefers-reduced-motion: reduce) {
190
- :root {
191
- --lt-motion-fast: 1ms;
192
- --lt-motion-normal: 1ms;
193
- --lt-motion-slow: 1ms;
248
+ *, *::before, *::after {
249
+ animation-duration: 0.001ms;
250
+ animation-iteration-count: 1;
251
+ transition-duration: 0.001ms;
252
+ scroll-behavior: auto;
194
253
  }
195
254
  }
package/static/graph.html CHANGED
@@ -2,15 +2,19 @@
2
2
  <html lang="ko">
3
3
  <head>
4
4
  <meta charset="utf-8">
5
- <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover, interactive-widget=resizes-content">
6
6
  <title>Lattice AI - 지식 그래프</title>
7
+ <script src="/static/scripts/ux.js?v=2.2.1"></script>
7
8
  <link rel="preconnect" href="https://fonts.googleapis.com">
8
9
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
10
  <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap">
10
11
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@tabler/icons-webfont@latest/tabler-icons.min.css">
11
12
  <link rel="stylesheet" href="/static/lattice-reference.css?v=0.3.3">
13
+ <link rel="stylesheet" href="/static/css/responsive.css?v=2.2.1">
12
14
  </head>
13
15
  <body class="lattice-ref-graph">
16
+ <div class="sidebar-overlay" onclick="closeGraphNav&&closeGraphNav()"></div>
17
+ <button class="graph-nav-toggle icon-btn" onclick="toggleGraphNav&&toggleGraphNav()" title="메뉴" aria-label="네비게이션 열기"><i class="ti ti-menu-2"></i></button>
14
18
  <aside class="reference-rail graph-rail">
15
19
  <div class="rail-brand"><i class="ti ti-chart-dots-3"></i><strong>Lattice AI</strong></div>
16
20
  <nav>
@@ -30,6 +34,8 @@
30
34
  <div class="app">
31
35
  <main class="stage">
32
36
  <canvas id="graph"></canvas>
37
+ <canvas id="minimap" class="graph-minimap" width="180" height="120" title="미니맵"></canvas>
38
+ <div id="graph-card-list" class="graph-card-list"></div>
33
39
 
34
40
  <section class="search-shell">
35
41
  <div class="search-head">
@@ -50,7 +56,11 @@
50
56
  </div>
51
57
  </section>
52
58
 
53
- <div class="toolbar">
59
+ <div class="toolbar graph-toolbar">
60
+ <button class="tb-btn" id="zoom-out-btn" title="축소" aria-label="축소"><i class="ti ti-minus"></i></button>
61
+ <button class="tb-btn" id="zoom-in-btn" title="확대" aria-label="확대"><i class="ti ti-plus"></i></button>
62
+ <button class="tb-btn" id="fullscreen-btn" title="전체화면" aria-label="전체화면"><i class="ti ti-maximize"></i></button>
63
+ <button class="tb-btn graph-view-toggle" id="view-toggle-btn" title="그래프/카드 보기 전환" aria-label="그래프/카드 보기 전환"><i class="ti ti-layout-cards"></i></button>
54
64
  <button class="tb-btn" id="refresh-btn"><i class="ti ti-refresh"></i> Refresh</button>
55
65
  <button class="tb-btn" id="fit-btn" title="Fit graph"><i class="ti ti-arrows-maximize"></i> Fit</button>
56
66
  <button class="tb-btn" id="expand-btn" title="Expand selected node"><i class="ti ti-circle-plus"></i> Expand</button>