ltcai 2.1.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.
Files changed (51) hide show
  1. package/README.md +153 -609
  2. package/auto_setup.py +17 -17
  3. package/docs/CHANGELOG.md +83 -0
  4. package/docs/MULTI_AGENT_RUNTIME.md +4 -4
  5. package/docs/PLUGIN_SDK.md +7 -7
  6. package/docs/REALTIME_COLLABORATION.md +6 -6
  7. package/docs/V2_ARCHITECTURE.md +45 -25
  8. package/docs/WORKFLOW_DESIGNER.md +4 -4
  9. package/docs/architecture.md +127 -135
  10. package/docs/kg-schema.md +3 -3
  11. package/docs/public-deploy.md +2 -3
  12. package/docs/spec-vs-impl.md +13 -10
  13. package/knowledge_graph.py +2 -2
  14. package/latticeai/__init__.py +1 -1
  15. package/latticeai/api/models.py +8 -0
  16. package/latticeai/core/config.py +1 -1
  17. package/latticeai/core/graph_curator.py +2 -2
  18. package/latticeai/core/marketplace.py +2 -2
  19. package/latticeai/core/model_compat.py +7 -63
  20. package/latticeai/core/model_resolution.py +1 -1
  21. package/latticeai/core/multi_agent.py +1 -1
  22. package/latticeai/core/plugins.py +1 -1
  23. package/latticeai/core/realtime.py +1 -1
  24. package/latticeai/core/workflow_engine.py +1 -1
  25. package/latticeai/core/workspace_os.py +1 -1
  26. package/latticeai/server_app.py +1 -1
  27. package/latticeai/services/model_catalog.py +105 -153
  28. package/latticeai/services/model_recommendation.py +28 -17
  29. package/latticeai/services/model_runtime.py +2 -2
  30. package/llm_router.py +80 -92
  31. package/ltcai_cli.py +2 -3
  32. package/package.json +8 -3
  33. package/static/account.html +3 -1
  34. package/static/activity.html +5 -2
  35. package/static/admin.html +5 -1
  36. package/static/agents.html +5 -2
  37. package/static/chat.html +12 -10
  38. package/static/css/responsive.css +597 -0
  39. package/static/css/tokens.css +224 -165
  40. package/static/graph.html +12 -2
  41. package/static/lattice-reference.css +366 -739
  42. package/static/platform.css +45 -16
  43. package/static/plugins.html +5 -2
  44. package/static/scripts/admin.js +33 -33
  45. package/static/scripts/chat.js +109 -42
  46. package/static/scripts/graph.js +169 -11
  47. package/static/scripts/ux.js +167 -0
  48. package/static/workflows.html +5 -2
  49. package/static/workspace.css +55 -19
  50. package/static/workspace.html +5 -2
  51. package/telegram_bot.py +1 -1
@@ -0,0 +1,597 @@
1
+ /* ============================================================================
2
+ * Lattice AI — Responsive / Accessibility / Theme Foundation (v2.2.1)
3
+ *
4
+ * 이 파일은 모든 페이지에서 가장 마지막에 로드된다 (lattice-reference.css /
5
+ * workspace.css / platform.css 다음). 따라서 기존 규칙을 "덮어쓰는" 레이어로
6
+ * 동작하며, 거대한 lattice-reference.css 를 직접 수술하지 않고도
7
+ * - 하단/입력창/모달/팝업 잘림
8
+ * - 수평 스크롤
9
+ * - 모바일 키보드 레이아웃 깨짐
10
+ * - 태블릿 레이아웃 깨짐
11
+ * - 작은 화면 메뉴 접근 불가
12
+ * - 44px 미만 터치 영역 / 포커스 링 부재
13
+ * - 다크/라이트 테마 패리티
14
+ * 를 일괄 해결한다. 라이트 테마는 건드리지 않으므로 회귀 위험이 없다.
15
+ *
16
+ * 표준 브레이크포인트 (이 파일이 프로젝트 규약):
17
+ * phone <= 600px
18
+ * tablet <= 1024px
19
+ * laptop (default)
20
+ * ultrawide >= 1600px
21
+ * ========================================================================== */
22
+
23
+ /* ============================================================================
24
+ * 0. 전역 가드 — 수평 스크롤 / 박스 모델 / 긴 문자열
25
+ * ========================================================================== */
26
+ *,
27
+ *::before,
28
+ *::after { box-sizing: border-box; }
29
+
30
+ html,
31
+ body {
32
+ max-width: 100%;
33
+ overflow-x: hidden; /* 어떤 와이드 자식도 페이지 수평 스크롤을 못 만들게 */
34
+ }
35
+
36
+ body { min-width: 0; }
37
+
38
+ /* 미디어/코드/표는 컨테이너를 넘지 않는다 */
39
+ img,
40
+ svg,
41
+ video,
42
+ canvas,
43
+ table,
44
+ pre {
45
+ max-width: 100%;
46
+ }
47
+
48
+ /* 긴 토큰/URL 줄바꿈 (수평 오버플로우 방지) */
49
+ pre,
50
+ code,
51
+ .bubble,
52
+ .detail-summary,
53
+ .perm-path,
54
+ .mcp-item-desc {
55
+ overflow-wrap: anywhere;
56
+ word-break: break-word;
57
+ }
58
+
59
+ /* flex 자식이 내용 때문에 줄어들지 못해 오버플로우 나는 고전 버그 */
60
+ .main-chat,
61
+ .messages-viewport,
62
+ .workspace-main,
63
+ .admin-main,
64
+ .stage { min-width: 0; }
65
+
66
+ /* ============================================================================
67
+ * 1. 앱 셸 — dvh 기반, 100vw 스크롤바 버그 제거
68
+ * ========================================================================== */
69
+ .app-layout {
70
+ width: 100%; /* 100vw 는 스크롤바 폭 만큼 수평 오버플로우 발생 */
71
+ height: 100vh; /* 폴백 */
72
+ height: 100dvh;
73
+ }
74
+
75
+ /* 인증/로그인 페이지: 키보드가 떠도 카드가 스크롤되어 버튼이 안 잘리게 */
76
+ body.lattice-ref-auth {
77
+ min-height: 100vh; /* 폴백 */
78
+ min-height: 100dvh;
79
+ overflow-y: auto; /* 기존 overflow:hidden 블로커 해제 */
80
+ overflow-x: hidden;
81
+ }
82
+
83
+ /* ============================================================================
84
+ * 2. 키보드-세이프 입력창 (composer)
85
+ * ux.js 의 visualViewport 리스너가 --kb-inset 를 채운다.
86
+ * ========================================================================== */
87
+ :root { --kb-inset: 0px; }
88
+
89
+ .input-area {
90
+ padding-bottom: calc(max(14px, env(safe-area-inset-bottom)) + var(--kb-inset));
91
+ transition: padding-bottom 0.12s ease-out;
92
+ }
93
+
94
+ /* ============================================================================
95
+ * 3. 터치 영역 최소 44x44 — 핵심 인터랙티브 컨트롤
96
+ * (시각적 아이콘 크기는 유지, 히트 박스만 확장)
97
+ * ========================================================================== */
98
+ .sidebar-toggle,
99
+ .sidebar-close,
100
+ .admin-close,
101
+ .mode-close,
102
+ .mcp-modal-close,
103
+ .field-eye {
104
+ min-width: 44px;
105
+ min-height: 44px;
106
+ display: inline-flex;
107
+ align-items: center;
108
+ justify-content: center;
109
+ }
110
+
111
+ .send-btn {
112
+ min-width: 44px;
113
+ min-height: 44px;
114
+ width: 44px;
115
+ height: 44px;
116
+ }
117
+
118
+ .action-btn,
119
+ .tb-btn,
120
+ .icon-btn,
121
+ .table-btn,
122
+ .lang-btn,
123
+ .logout-btn,
124
+ .model-badge {
125
+ min-height: 44px;
126
+ }
127
+
128
+ .action-btn { min-width: 44px; justify-content: center; }
129
+
130
+ .lang-option,
131
+ .filter-item,
132
+ .legend-item,
133
+ .rec-item,
134
+ .history-item-del {
135
+ min-height: 44px;
136
+ display: flex;
137
+ align-items: center;
138
+ }
139
+
140
+ .history-item-del { min-width: 44px; justify-content: center; }
141
+
142
+ /* 그래프 필터 체크박스 확대 */
143
+ .filter-item input[type="checkbox"],
144
+ .legend-item input[type="checkbox"],
145
+ .rec-checkbox {
146
+ width: 20px;
147
+ height: 20px;
148
+ min-width: 20px;
149
+ }
150
+
151
+ /* 터치 기기에서는 호버로만 보이던 삭제 버튼을 항상 노출 */
152
+ @media (hover: none) {
153
+ .history-item-del { opacity: 1; }
154
+ }
155
+
156
+ /* iOS 입력 자동 줌(폰트<16px) 방지 — 모든 폼 컨트롤 */
157
+ input,
158
+ textarea,
159
+ select {
160
+ font-size: max(16px, 1em);
161
+ }
162
+ @media (min-width: 1025px) {
163
+ /* 데스크톱에서는 디자인 크기 복원 (줌 이슈 없음) */
164
+ .sidebar-search input,
165
+ .mcp-add-form input,
166
+ .mcp-add-form textarea,
167
+ .mcp-add-form select { font-size: 13px; }
168
+ }
169
+
170
+ /* ============================================================================
171
+ * 4. 포커스 링 — tokens.css 가 링크 안 된 페이지를 위해 항상 보장
172
+ * (WCAG 2.4.7)
173
+ * ========================================================================== */
174
+ :focus-visible {
175
+ outline: 2px solid var(--accent, #6E4AE6);
176
+ outline-offset: 2px;
177
+ border-radius: inherit;
178
+ }
179
+ /* 마우스 클릭 시 링 숨김은 :focus-visible 가 알아서 처리 */
180
+
181
+ /* ============================================================================
182
+ * 5. 모달 / 팝업 / 오버레이 — 뷰포트 가둠 + 내부 스크롤
183
+ * (footer 버튼이 키보드/짧은 화면에 잘리지 않게)
184
+ * ========================================================================== */
185
+ .acct-modal,
186
+ .mcp-modal,
187
+ .mode-modal,
188
+ .workspace-modal,
189
+ .advanced-settings-panel,
190
+ .perm-dialog,
191
+ .model-panel,
192
+ .onboarding-card,
193
+ .wizard-card {
194
+ max-height: min(760px, calc(100dvh - 32px));
195
+ display: flex;
196
+ flex-direction: column;
197
+ overflow: hidden;
198
+ }
199
+
200
+ /* 모달 내부 스크롤 영역 */
201
+ .acct-body,
202
+ .mcp-modal-body,
203
+ .advanced-settings-body,
204
+ .mode-modal .mode-options,
205
+ .workspace-modal .workspace-options,
206
+ .model-list,
207
+ .onboarding-model-list,
208
+ .pipeline-modal-body {
209
+ flex: 1 1 auto;
210
+ min-height: 0;
211
+ overflow-y: auto;
212
+ }
213
+
214
+ /* 드롭다운/팝오버: 뷰포트 안에 가둠 */
215
+ .lang-picker-menu,
216
+ .mcp-dropdown,
217
+ .search-results {
218
+ max-width: calc(100vw - 24px);
219
+ max-height: min(60dvh, 360px);
220
+ overflow-y: auto;
221
+ }
222
+
223
+ #tooltip {
224
+ max-width: min(300px, calc(100vw - 24px));
225
+ max-height: 60dvh;
226
+ overflow: hidden;
227
+ }
228
+
229
+ /* 토스트가 좁은 화면을 넘지 않게 */
230
+ .toast {
231
+ left: auto;
232
+ right: max(12px, env(safe-area-inset-right));
233
+ bottom: max(16px, env(safe-area-inset-bottom));
234
+ max-width: min(360px, calc(100vw - 24px));
235
+ }
236
+
237
+ /* 짧은 뷰포트(가로 폰/키보드)에서도 인증 컨트롤은 44px 유지 */
238
+ @media (max-height: 760px) {
239
+ .auth-field,
240
+ .submit,
241
+ .register-cta,
242
+ .sso-btn { min-height: 44px; height: auto; }
243
+ }
244
+
245
+ /* 폰: 핵심 모달을 바텀시트로 — footer 버튼 항상 노출 */
246
+ @media (max-width: 600px) {
247
+ .acct-modal,
248
+ .mcp-modal,
249
+ .mode-modal,
250
+ .workspace-modal,
251
+ .advanced-settings-panel,
252
+ .model-panel {
253
+ width: 100%;
254
+ max-width: none;
255
+ max-height: 92dvh;
256
+ border-bottom-left-radius: 0;
257
+ border-bottom-right-radius: 0;
258
+ padding-bottom: env(safe-area-inset-bottom);
259
+ }
260
+ .acct-modal-overlay,
261
+ .mcp-modal-overlay,
262
+ .mode-modal-overlay,
263
+ .workspace-modal-overlay,
264
+ .advanced-settings-overlay,
265
+ .model-overlay {
266
+ align-items: flex-end;
267
+ }
268
+ }
269
+
270
+ /* ============================================================================
271
+ * 6. 채팅 — 태블릿 티어(<=1024) 에서도 드로어 + 햄버거
272
+ * 기존 파일은 768px 에서만 드로어로 전환 → 769~1024 구간이 깨져있음
273
+ * ========================================================================== */
274
+ @media (max-width: 1024px) {
275
+ body.lattice-ref-chat { overflow: hidden; }
276
+
277
+ .sidebar-toggle,
278
+ .sidebar-close { display: inline-flex; }
279
+
280
+ .app-layout .sidebar,
281
+ .sidebar {
282
+ position: fixed;
283
+ top: 0;
284
+ left: 0;
285
+ height: 100dvh;
286
+ width: min(86vw, 320px);
287
+ min-width: 0;
288
+ z-index: 100;
289
+ transform: translateX(-100%);
290
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
291
+ box-shadow: 4px 0 32px rgba(20, 16, 40, 0.22);
292
+ }
293
+ body.sidebar-open .sidebar { transform: translateX(0); }
294
+
295
+ .sidebar-overlay {
296
+ position: fixed;
297
+ inset: 0;
298
+ z-index: 99;
299
+ display: none;
300
+ background: rgba(15, 12, 30, 0.42);
301
+ -webkit-backdrop-filter: blur(2px);
302
+ backdrop-filter: blur(2px);
303
+ }
304
+ body.sidebar-open .sidebar-overlay { display: block; }
305
+
306
+ .main-chat { width: 100%; flex: 1; }
307
+
308
+ /* 모드 세그먼트/헤더가 두 줄로 안 깨지게: 가로 스크롤 */
309
+ .mode-segmented {
310
+ overflow-x: auto;
311
+ flex-wrap: nowrap;
312
+ -webkit-overflow-scrolling: touch;
313
+ scrollbar-width: none;
314
+ }
315
+ .mode-segmented::-webkit-scrollbar { display: none; }
316
+ }
317
+
318
+ /* ops-strip: 좁은 화면에서 카드 세로 적층 */
319
+ @media (max-width: 760px) {
320
+ .ops-strip {
321
+ grid-template-columns: 1fr;
322
+ width: calc(100% - 28px);
323
+ }
324
+ }
325
+
326
+ /* 홈 대시보드 카드 / 온보딩 스텝 단일 열 */
327
+ @media (max-width: 900px) {
328
+ .home-dash-cards { grid-template-columns: 1fr; }
329
+ }
330
+ @media (max-width: 560px) {
331
+ .onboarding-steps {
332
+ grid-template-columns: repeat(auto-fit, minmax(0, 1fr));
333
+ gap: 4px;
334
+ }
335
+ .onboarding-step { white-space: normal; font-size: 10px; }
336
+ }
337
+
338
+ /* 상태 pill 을 숨기지 말고 줄바꿈 허용 (기능 숨김 금지 원칙) */
339
+ @media (max-width: 480px) {
340
+ .header-pills .status-pill { display: inline-flex; }
341
+ .header-pills { flex-wrap: wrap; justify-content: flex-end; }
342
+ }
343
+
344
+ /* ============================================================================
345
+ * 7. 관리자 — 레일 접힘 + 표를 카드로
346
+ * ========================================================================== */
347
+ body.lattice-ref-admin {
348
+ overflow-x: hidden;
349
+ min-width: 0;
350
+ }
351
+
352
+ @media (max-width: 1024px) {
353
+ .lattice-ref-admin { grid-template-columns: 1fr; }
354
+ .lattice-ref-admin .content { width: 100%; max-width: 100%; }
355
+
356
+ /* 관리자 레일: 햄버거 드로어로 */
357
+ .admin-rail,
358
+ .lattice-ref-admin .admin-rail {
359
+ position: fixed;
360
+ top: 0;
361
+ left: 0;
362
+ height: 100dvh;
363
+ width: min(84vw, 300px);
364
+ z-index: 100;
365
+ transform: translateX(-100%);
366
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
367
+ }
368
+ body.admin-rail-open .admin-rail { transform: translateX(0); }
369
+ body.admin-rail-open .sidebar-overlay { display: block; }
370
+ }
371
+
372
+ /* 표 → 카드 (<=760) : .data-table / .admin-table / .table-wrap table
373
+ * JS 렌더러가 각 <td> 에 data-label 을 넣어준다 (admin.js). */
374
+ @media (max-width: 760px) {
375
+ .table-wrap { overflow: visible; }
376
+ .table-wrap table,
377
+ table.admin-table,
378
+ table.data-table {
379
+ min-width: 0;
380
+ width: 100%;
381
+ border: 0;
382
+ }
383
+ .table-wrap thead,
384
+ table.admin-table thead,
385
+ table.data-table thead { display: none; }
386
+ .table-wrap tr,
387
+ table.admin-table tr,
388
+ table.data-table tr {
389
+ display: block;
390
+ margin: 0 0 10px;
391
+ border: 1px solid var(--border, rgba(120,120,140,0.18));
392
+ border-radius: 12px;
393
+ padding: 6px 12px;
394
+ background: var(--surface, rgba(255,255,255,0.04));
395
+ }
396
+ .table-wrap td,
397
+ table.admin-table td,
398
+ table.data-table td {
399
+ display: flex;
400
+ justify-content: space-between;
401
+ align-items: center;
402
+ gap: 12px;
403
+ padding: 8px 0;
404
+ border: 0;
405
+ white-space: normal;
406
+ text-align: right;
407
+ }
408
+ .table-wrap td::before,
409
+ table.admin-table td::before,
410
+ table.data-table td::before {
411
+ content: attr(data-label);
412
+ font-weight: 700;
413
+ text-align: left;
414
+ color: var(--muted, #888);
415
+ flex: 0 0 42%;
416
+ }
417
+ /* 카드 안 액션 버튼은 전체폭으로 */
418
+ .admin-actions,
419
+ td .table-actions {
420
+ flex-wrap: wrap;
421
+ justify-content: flex-end;
422
+ }
423
+ .admin-action,
424
+ .table-btn { min-height: 44px; }
425
+ }
426
+
427
+ /* ============================================================================
428
+ * 8. 지식 그래프 — 레일 드로어(숨기지 않음) + 툴바 스크롤 + 검색창 위치
429
+ * JS(줌/전체화면/미니맵/관계필터/카드뷰/재맞춤)는 graph.js 에서 처리.
430
+ * ========================================================================== */
431
+ @media (max-width: 1024px) {
432
+ /* 기존: max-width:900 에서 .reference-rail{display:none} (네비 접근불가)
433
+ → 드로어로 전환해서 접근 가능하게 */
434
+ body.lattice-ref-graph .reference-rail {
435
+ display: flex;
436
+ position: fixed;
437
+ top: 0;
438
+ left: 0;
439
+ height: 100dvh;
440
+ width: min(84vw, 300px);
441
+ z-index: 100;
442
+ transform: translateX(-100%);
443
+ transition: transform 0.25s cubic-bezier(0.4, 0, 0.2, 1);
444
+ overflow-y: auto;
445
+ }
446
+ body.graph-nav-open .reference-rail { transform: translateX(0); }
447
+ body.graph-nav-open .sidebar-overlay { display: block; }
448
+
449
+ /* 그래프 햄버거 버튼 노출 (graph.html 에 추가) */
450
+ .graph-nav-toggle { display: inline-flex; }
451
+ }
452
+
453
+ /* 그래프 하단 툴바: 좁은 화면에서 줄바꿈 대신 가로 스크롤 */
454
+ @media (max-width: 900px) {
455
+ .lattice-ref-graph .toolbar,
456
+ .graph-toolbar {
457
+ flex-wrap: nowrap;
458
+ overflow-x: auto;
459
+ -webkit-overflow-scrolling: touch;
460
+ justify-content: flex-start;
461
+ padding-bottom: max(8px, env(safe-area-inset-bottom));
462
+ scrollbar-width: none;
463
+ }
464
+ .lattice-ref-graph .toolbar::-webkit-scrollbar,
465
+ .graph-toolbar::-webkit-scrollbar { display: none; }
466
+
467
+ /* 검색창을 스테이지 안쪽으로 (기존 top:-60px 로 화면 밖) */
468
+ .lattice-ref-graph .search-shell {
469
+ top: 12px;
470
+ right: 12px;
471
+ left: auto;
472
+ }
473
+ }
474
+
475
+ /* 새 햄버거 토글은 데스크톱에서 숨김 (모바일/태블릿 미디어쿼리에서 노출) */
476
+ .graph-nav-toggle,
477
+ .admin-rail-toggle { display: none; }
478
+
479
+ /* 그래프/카드 뷰 토글 (graph.js 가 .graph-card-view 클래스 토글) */
480
+ .graph-view-toggle { display: none; }
481
+ @media (max-width: 900px) {
482
+ .graph-view-toggle { display: inline-flex; }
483
+ }
484
+ /* 그래프 햄버거(모바일) 위치 */
485
+ .graph-nav-toggle {
486
+ position: fixed;
487
+ top: max(10px, env(safe-area-inset-top));
488
+ left: 10px;
489
+ z-index: 60;
490
+ width: 44px;
491
+ height: 44px;
492
+ border-radius: 12px;
493
+ background: var(--surface, #fff);
494
+ border: 1px solid var(--border, rgba(120,120,140,0.2));
495
+ box-shadow: 0 6px 18px rgba(20, 16, 40, 0.16);
496
+ }
497
+
498
+ /* 미니맵 — 스테이지 우상단 (작은 화면에선 카드뷰가 대체하므로 숨김) */
499
+ .stage { position: relative; }
500
+ .graph-minimap {
501
+ position: absolute;
502
+ right: 12px;
503
+ top: 12px;
504
+ width: 180px;
505
+ height: 120px;
506
+ border: 1px solid var(--border, rgba(120,120,140,0.25));
507
+ border-radius: 10px;
508
+ background: var(--surface, rgba(255,255,255,0.6));
509
+ box-shadow: 0 6px 18px rgba(20, 16, 40, 0.14);
510
+ z-index: 8;
511
+ cursor: pointer;
512
+ }
513
+ @media (max-width: 900px) {
514
+ .graph-minimap { display: none; }
515
+ }
516
+
517
+ /* 모바일 카드 뷰 — 스테이지를 덮는 노드 카드 리스트 */
518
+ .graph-card-list {
519
+ display: none;
520
+ position: absolute;
521
+ inset: 0;
522
+ z-index: 9;
523
+ padding: 16px;
524
+ padding-top: 64px;
525
+ overflow-y: auto;
526
+ background: var(--bg, #fff);
527
+ -webkit-overflow-scrolling: touch;
528
+ }
529
+ body.graph-card-view .graph-card-list { display: block; }
530
+ body.graph-card-view .stage > canvas#graph { visibility: hidden; }
531
+
532
+ /* ============================================================================
533
+ * 9. 워크스페이스 / 플랫폼 페이지 가드
534
+ * (세부는 workspace.css / platform.css 에서, 여기선 안전망)
535
+ * ========================================================================== */
536
+ .workspace-shell { min-height: 100vh; min-height: 100dvh; }
537
+ .workspace-rail { height: 100vh; height: 100dvh; }
538
+
539
+ @media (max-width: 600px) {
540
+ .platform-grid,
541
+ .grid {
542
+ grid-template-columns: 1fr;
543
+ }
544
+ }
545
+
546
+ /* ============================================================================
547
+ * 10. 울트라와이드 / 4K — 콘텐츠 폭 제한, 헤더 정렬
548
+ * ========================================================================== */
549
+ @media (min-width: 1600px) {
550
+ body.lattice-ref-chat { --content-width: 1040px; }
551
+ }
552
+ @media (min-width: 2200px) {
553
+ body.lattice-ref-chat { --content-width: 1180px; }
554
+ }
555
+
556
+ /* ============================================================================
557
+ * 11. 모션 줄이기 (a11y)
558
+ * ========================================================================== */
559
+ @media (prefers-reduced-motion: reduce) {
560
+ *,
561
+ *::before,
562
+ *::after {
563
+ animation-duration: 0.001ms;
564
+ animation-iteration-count: 1;
565
+ transition-duration: 0.001ms;
566
+ scroll-behavior: auto;
567
+ }
568
+ }
569
+
570
+ /* 다크에서 테마 토글 아이콘 상태 */
571
+ :root[data-lt-theme="dark"] .theme-toggle .ti-sun { display: inline-flex; }
572
+ :root[data-lt-theme="dark"] .theme-toggle .ti-moon { display: none; }
573
+ .theme-toggle .ti-sun { display: none; }
574
+ .theme-toggle .ti-moon { display: inline-flex; }
575
+
576
+ /* 드래그앤드롭 파일 첨부 시각 피드백 */
577
+ .main-chat.drag-over {
578
+ outline: 2px dashed var(--accent, #6E4AE6);
579
+ outline-offset: -8px;
580
+ background: var(--accent-soft, rgba(110, 74, 230, 0.06));
581
+ }
582
+
583
+ /* 테마 토글 버튼 기본 스타일 + 44px */
584
+ .theme-toggle {
585
+ min-width: 44px;
586
+ min-height: 44px;
587
+ display: inline-flex;
588
+ align-items: center;
589
+ justify-content: center;
590
+ gap: 6px;
591
+ background: transparent;
592
+ border: 1px solid var(--border, rgba(120,120,140,0.2));
593
+ border-radius: 10px;
594
+ color: var(--text, inherit);
595
+ cursor: pointer;
596
+ }
597
+ .theme-toggle:hover { background: var(--accent-soft, rgba(110,74,230,0.1)); }