ltcai 0.1.9 → 0.1.16
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 +174 -305
- package/docs/CHANGELOG.md +307 -0
- package/docs/architecture.md +121 -0
- package/docs/mcp-tools.md +116 -0
- package/docs/privacy.md +74 -0
- package/docs/public-deploy.md +137 -0
- package/docs/security-model.md +121 -0
- package/knowledge_graph.py +123 -15
- package/llm_router.py +100 -28
- package/ltcai_cli.py +138 -5
- package/package.json +14 -2
- package/server.py +1756 -329
- package/skills/SKILL_TEMPLATE.md +61 -29
- package/skills/code_review/SKILL.md +28 -0
- package/skills/code_review/examples.md +59 -0
- package/skills/code_review/risk.json +9 -0
- package/skills/code_review/schema.json +65 -0
- package/skills/data_analysis/SKILL.md +28 -0
- package/skills/data_analysis/examples.md +62 -0
- package/skills/data_analysis/risk.json +9 -0
- package/skills/data_analysis/schema.json +61 -0
- package/skills/file_edit/SKILL.md +33 -0
- package/skills/file_edit/examples.md +45 -0
- package/skills/file_edit/risk.json +9 -0
- package/skills/file_edit/schema.json +60 -0
- package/skills/summarize_document/SKILL.md +68 -0
- package/skills/summarize_document/examples.md +65 -0
- package/skills/summarize_document/risk.json +9 -0
- package/skills/summarize_document/schema.json +71 -0
- package/skills/web_search/SKILL.md +28 -0
- package/skills/web_search/examples.md +61 -0
- package/skills/web_search/risk.json +9 -0
- package/skills/web_search/schema.json +62 -0
- package/static/account.html +53 -51
- package/static/admin.html +50 -46
- package/static/chat.html +124 -96
- package/static/graph.html +1231 -337
- package/static/manifest.json +2 -2
- package/tests/integration/__pycache__/__init__.cpython-314.pyc +0 -0
- package/tests/integration/__pycache__/test_api.cpython-314-pytest-9.0.3.pyc +0 -0
- package/tests/unit/__pycache__/test_tools.cpython-314-pytest-9.0.3.pyc +0 -0
- package/tests/unit/test_tools.py +194 -1
- package/tools.py +264 -4
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Skill: summarize_document
|
|
2
|
+
|
|
3
|
+
## 메타데이터
|
|
4
|
+
- **버전**: 0.1.0
|
|
5
|
+
- **카테고리**: document
|
|
6
|
+
- **위험도**: low
|
|
7
|
+
- **필요 권한**: local_read
|
|
8
|
+
|
|
9
|
+
## 설명
|
|
10
|
+
텍스트 파일(.txt, .md, .pdf, .docx)을 읽어 핵심 내용을 요약하고, 섹션별 요점과 키워드를 추출한다.
|
|
11
|
+
|
|
12
|
+
## 거버넌스
|
|
13
|
+
|
|
14
|
+
`risk.json` 참고. 요약: `risk=read`, `destructive=false`, `shell=false`, `network=false`, `auto_approve=true`, `sandbox=home`, `rollback=none`
|
|
15
|
+
|
|
16
|
+
## 트리거 조건
|
|
17
|
+
|
|
18
|
+
호출해야 하는 상황:
|
|
19
|
+
- 사용자가 "이 문서 요약해줘", "핵심만 뽑아줘", "이 파일 내용이 뭐야?"라고 요청할 때
|
|
20
|
+
- 에이전트가 Discover 단계에서 긴 문서의 구조를 빠르게 파악해야 할 때
|
|
21
|
+
- 여러 문서를 비교하기 전 각 문서의 핵심 파악이 필요할 때
|
|
22
|
+
|
|
23
|
+
호출하면 **안** 되는 상황:
|
|
24
|
+
- 문서를 수정해야 할 때 → `edit_file` 사용
|
|
25
|
+
- CSV/Excel 데이터 분석 → `data_analysis` 사용
|
|
26
|
+
- 코드 파일 분석 → `code_review` 사용
|
|
27
|
+
|
|
28
|
+
## Side Effects
|
|
29
|
+
|
|
30
|
+
| 항목 | 내용 |
|
|
31
|
+
|------|------|
|
|
32
|
+
| 파일 변경 | 없음 |
|
|
33
|
+
| 생성 파일 | 없음 |
|
|
34
|
+
| 프로세스 | 없음 |
|
|
35
|
+
| 네트워크 | 없음 |
|
|
36
|
+
|
|
37
|
+
## Rollback
|
|
38
|
+
|
|
39
|
+
없음. 읽기 전용 작업.
|
|
40
|
+
|
|
41
|
+
## 입력 스키마
|
|
42
|
+
`schema.json` 참고.
|
|
43
|
+
|
|
44
|
+
## 출력 스키마
|
|
45
|
+
`schema.json` 참고.
|
|
46
|
+
|
|
47
|
+
## 실행 조건
|
|
48
|
+
- LLM 모델이 로드되어 있어야 함
|
|
49
|
+
- 파일이 존재해야 하며 .txt/.md/.pdf/.docx 형식이어야 함
|
|
50
|
+
- 파일 크기 20 MB 이하
|
|
51
|
+
|
|
52
|
+
## 예제
|
|
53
|
+
`examples.md` 참고.
|
|
54
|
+
|
|
55
|
+
## 실패 처리
|
|
56
|
+
| 에러 코드 | 원인 | 처리 방법 |
|
|
57
|
+
|-----------|------|-----------|
|
|
58
|
+
| `INVALID_INPUT` | path 누락 | 파일 경로 입력 |
|
|
59
|
+
| `FILE_NOT_FOUND` | 경로에 파일 없음 | 경로 확인 |
|
|
60
|
+
| `UNSUPPORTED_FORMAT` | 지원하지 않는 형식 | .txt/.md/.pdf/.docx로 변환 후 재시도 |
|
|
61
|
+
| `SIZE_LIMIT` | 20 MB 초과 | 파일 분할 후 재시도 |
|
|
62
|
+
| `MODEL_NOT_LOADED` | LLM 미로드 | `/model` 명령으로 모델 선택 |
|
|
63
|
+
|
|
64
|
+
## 테스트 케이스
|
|
65
|
+
```python
|
|
66
|
+
# tests/unit/test_tools.py::test_summarize_document_md
|
|
67
|
+
# tests/unit/test_tools.py::test_summarize_document_unsupported
|
|
68
|
+
```
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# summarize_document — Examples
|
|
2
|
+
|
|
3
|
+
## 1. Markdown file summary (success)
|
|
4
|
+
|
|
5
|
+
**Input**
|
|
6
|
+
```json
|
|
7
|
+
{ "path": "~/project/README.md", "style": "bullet" }
|
|
8
|
+
```
|
|
9
|
+
**Output**
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"success": true,
|
|
13
|
+
"result": {
|
|
14
|
+
"title": "README.md",
|
|
15
|
+
"summary": "• Lattice AI는 Apple Silicon 기반 로컬 AI 에이전트\n• FastAPI 서버 + VS Code 익스텐션 + Telegram bot 구조\n• MLX 및 클라우드 모델(OpenAI/Groq) 지원",
|
|
16
|
+
"keywords": ["Lattice AI", "MLX", "FastAPI", "VS Code", "Telegram", "local LLM"],
|
|
17
|
+
"sections": [
|
|
18
|
+
{ "heading": "Installation", "summary": "pip install ltcai 후 ltcai start로 실행" },
|
|
19
|
+
{ "heading": "Features", "summary": "채팅, 에이전트 모드, 파일 편집, 웹 검색 등 지원" }
|
|
20
|
+
],
|
|
21
|
+
"word_count": 1240
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## 2. PDF summary with focus section (success)
|
|
27
|
+
|
|
28
|
+
**Input**
|
|
29
|
+
```json
|
|
30
|
+
{ "path": "~/docs/report.pdf", "style": "paragraph", "focus_sections": ["결론", "권고사항"], "max_length": 300 }
|
|
31
|
+
```
|
|
32
|
+
**Output**
|
|
33
|
+
```json
|
|
34
|
+
{
|
|
35
|
+
"success": true,
|
|
36
|
+
"result": {
|
|
37
|
+
"title": "report.pdf",
|
|
38
|
+
"summary": "보고서의 결론: 시스템 성능이 전분기 대비 23% 향상되었으며, 추가 최적화를 위해 캐시 레이어 도입이 권고됩니다.",
|
|
39
|
+
"keywords": ["성능", "최적화", "캐시", "권고"],
|
|
40
|
+
"word_count": 8500
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## 3. Unsupported format (failure)
|
|
46
|
+
|
|
47
|
+
**Input**
|
|
48
|
+
```json
|
|
49
|
+
{ "path": "~/data/sales.csv" }
|
|
50
|
+
```
|
|
51
|
+
**Output**
|
|
52
|
+
```json
|
|
53
|
+
{ "success": false, "error": "UNSUPPORTED_FORMAT", "message": "Supported formats: txt, md, pdf, docx. Use data_analysis for CSV files." }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## 4. File not found (failure)
|
|
57
|
+
|
|
58
|
+
**Input**
|
|
59
|
+
```json
|
|
60
|
+
{ "path": "~/docs/nonexistent.md" }
|
|
61
|
+
```
|
|
62
|
+
**Output**
|
|
63
|
+
```json
|
|
64
|
+
{ "success": false, "error": "FILE_NOT_FOUND", "message": "No such file: /home/user/docs/nonexistent.md" }
|
|
65
|
+
```
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "summarize_document",
|
|
4
|
+
"input": {
|
|
5
|
+
"required": ["path"],
|
|
6
|
+
"optional": ["max_length", "style", "focus_sections"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"path": { "type": "string", "description": "절대 경로 또는 ~/로 시작하는 경로 (.txt, .md, .pdf, .docx)" },
|
|
9
|
+
"max_length": { "type": "integer", "default": 500, "description": "요약 최대 글자 수" },
|
|
10
|
+
"style": { "type": "string", "enum": ["bullet","paragraph","table"], "default": "bullet", "description": "출력 형식" },
|
|
11
|
+
"focus_sections": { "type": "array", "items": { "type": "string" }, "description": "특정 섹션만 요약할 때 섹션 제목 목록" }
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"output": {
|
|
15
|
+
"oneOf": [
|
|
16
|
+
{
|
|
17
|
+
"title": "success",
|
|
18
|
+
"properties": {
|
|
19
|
+
"success": { "const": true },
|
|
20
|
+
"result": {
|
|
21
|
+
"properties": {
|
|
22
|
+
"title": { "type": "string", "description": "문서 제목 또는 파일명" },
|
|
23
|
+
"summary": { "type": "string", "description": "핵심 요약" },
|
|
24
|
+
"keywords": { "type": "array", "items": { "type": "string" }, "description": "주요 키워드 (최대 10개)" },
|
|
25
|
+
"sections": {
|
|
26
|
+
"type": "array",
|
|
27
|
+
"items": {
|
|
28
|
+
"properties": {
|
|
29
|
+
"heading": { "type": "string" },
|
|
30
|
+
"summary": { "type": "string" }
|
|
31
|
+
},
|
|
32
|
+
"required": ["heading","summary"]
|
|
33
|
+
},
|
|
34
|
+
"description": "섹션별 요약 (있는 경우)"
|
|
35
|
+
},
|
|
36
|
+
"word_count": { "type": "integer" }
|
|
37
|
+
},
|
|
38
|
+
"required": ["title","summary","keywords"]
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"required": ["success","result"]
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
"title": "failure",
|
|
45
|
+
"properties": {
|
|
46
|
+
"success": { "const": false },
|
|
47
|
+
"error": { "type": "string", "enum": ["INVALID_INPUT","FILE_NOT_FOUND","UNSUPPORTED_FORMAT","SIZE_LIMIT","MODEL_NOT_LOADED"] },
|
|
48
|
+
"message": { "type": "string" }
|
|
49
|
+
},
|
|
50
|
+
"required": ["success","error","message"]
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"evals": [
|
|
55
|
+
{
|
|
56
|
+
"id": "markdown_summary",
|
|
57
|
+
"input": { "path": "__TEST__/README.md", "style": "bullet" },
|
|
58
|
+
"pass_criteria": "success == true and len(keywords) >= 1"
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"id": "unsupported_format",
|
|
62
|
+
"input": { "path": "__TEST__/data.csv" },
|
|
63
|
+
"pass_criteria": "error == UNSUPPORTED_FORMAT"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
"id": "missing_path",
|
|
67
|
+
"input": {},
|
|
68
|
+
"pass_criteria": "error == INVALID_INPUT"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
@@ -9,6 +9,34 @@
|
|
|
9
9
|
## 설명
|
|
10
10
|
외부 검색 엔진(DuckDuckGo/Brave)을 통해 웹 검색을 수행하고 상위 결과를 반환한다.
|
|
11
11
|
|
|
12
|
+
## 거버넌스
|
|
13
|
+
|
|
14
|
+
`policies/policy.md` 참고. 요약: `risk=read`, `destructive=false`, `shell=false`, `network=true`, `auto_approve=true`, `sandbox=system`, `rollback=none`
|
|
15
|
+
|
|
16
|
+
## 트리거 조건
|
|
17
|
+
|
|
18
|
+
호출해야 하는 상황:
|
|
19
|
+
- 사용자가 "검색해줘", "찾아봐줘", "최신 정보 알려줘"라고 요청할 때
|
|
20
|
+
- 에이전트가 Discover 단계에서 외부 문서/API/라이브러리 정보를 수집해야 할 때
|
|
21
|
+
- LLM의 학습 데이터 이후 최신 정보(라이브러리 버전, 뉴스 등)가 필요할 때
|
|
22
|
+
|
|
23
|
+
호출하면 **안** 되는 상황:
|
|
24
|
+
- 로컬 파일 내용 검색 시 → `grep` 사용
|
|
25
|
+
- LLM이 이미 알고 있는 일반 지식 질문 시
|
|
26
|
+
|
|
27
|
+
## Side Effects
|
|
28
|
+
|
|
29
|
+
| 항목 | 내용 |
|
|
30
|
+
|------|------|
|
|
31
|
+
| 파일 변경 | 없음 |
|
|
32
|
+
| 생성 파일 | 없음 |
|
|
33
|
+
| 프로세스 | 없음 |
|
|
34
|
+
| 네트워크 | 외부 검색 API로 검색어 전송 (DuckDuckGo/Brave) |
|
|
35
|
+
|
|
36
|
+
## Rollback
|
|
37
|
+
|
|
38
|
+
없음. 읽기 전용 네트워크 요청.
|
|
39
|
+
|
|
12
40
|
## 입력 스키마
|
|
13
41
|
```json
|
|
14
42
|
{
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# web_search — Examples
|
|
2
|
+
|
|
3
|
+
## 1. Basic search (success)
|
|
4
|
+
|
|
5
|
+
**Input**
|
|
6
|
+
```json
|
|
7
|
+
{ "query": "FastAPI 비동기 처리", "num_results": 3 }
|
|
8
|
+
```
|
|
9
|
+
**Output**
|
|
10
|
+
```json
|
|
11
|
+
{
|
|
12
|
+
"success": true,
|
|
13
|
+
"result": {
|
|
14
|
+
"query": "FastAPI 비동기 처리",
|
|
15
|
+
"results": [
|
|
16
|
+
{ "title": "FastAPI - Async", "url": "https://fastapi.tiangolo.com/async/", "snippet": "FastAPI는 Python의 asyncio를 완벽히 지원합니다..." }
|
|
17
|
+
]
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## 2. Search with language (success)
|
|
23
|
+
|
|
24
|
+
**Input**
|
|
25
|
+
```json
|
|
26
|
+
{ "query": "Python type hints best practices", "num_results": 5, "lang": "en-US" }
|
|
27
|
+
```
|
|
28
|
+
**Output**
|
|
29
|
+
```json
|
|
30
|
+
{
|
|
31
|
+
"success": true,
|
|
32
|
+
"result": {
|
|
33
|
+
"query": "Python type hints best practices",
|
|
34
|
+
"results": [
|
|
35
|
+
{ "title": "PEP 484 – Type Hints", "url": "https://peps.python.org/pep-0484/", "snippet": "This PEP introduces a standard syntax for type annotations..." }
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## 3. Empty query (failure)
|
|
42
|
+
|
|
43
|
+
**Input**
|
|
44
|
+
```json
|
|
45
|
+
{ "query": "" }
|
|
46
|
+
```
|
|
47
|
+
**Output**
|
|
48
|
+
```json
|
|
49
|
+
{ "success": false, "error": "INVALID_INPUT", "message": "query is required" }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## 4. Network error (failure)
|
|
53
|
+
|
|
54
|
+
**Input**
|
|
55
|
+
```json
|
|
56
|
+
{ "query": "offline test" }
|
|
57
|
+
```
|
|
58
|
+
**Output**
|
|
59
|
+
```json
|
|
60
|
+
{ "success": false, "error": "NETWORK_ERROR", "message": "외부 검색 API에 연결할 수 없습니다. 잠시 후 재시도하세요." }
|
|
61
|
+
```
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
|
3
|
+
"title": "web_search",
|
|
4
|
+
"input": {
|
|
5
|
+
"required": ["query"],
|
|
6
|
+
"optional": ["num_results", "lang"],
|
|
7
|
+
"properties": {
|
|
8
|
+
"query": { "type": "string", "description": "검색어" },
|
|
9
|
+
"num_results": { "type": "integer", "default": 5, "minimum": 1, "maximum": 20 },
|
|
10
|
+
"lang": { "type": "string", "default": "ko-KR" }
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"output": {
|
|
14
|
+
"oneOf": [
|
|
15
|
+
{
|
|
16
|
+
"title": "success",
|
|
17
|
+
"properties": {
|
|
18
|
+
"success": { "const": true },
|
|
19
|
+
"result": {
|
|
20
|
+
"properties": {
|
|
21
|
+
"query": { "type": "string" },
|
|
22
|
+
"results": {
|
|
23
|
+
"type": "array",
|
|
24
|
+
"items": {
|
|
25
|
+
"properties": {
|
|
26
|
+
"title": { "type": "string" },
|
|
27
|
+
"url": { "type": "string", "format": "uri" },
|
|
28
|
+
"snippet": { "type": "string" }
|
|
29
|
+
},
|
|
30
|
+
"required": ["title","url","snippet"]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
"required": ["query","results"]
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": ["success","result"]
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
"title": "failure",
|
|
41
|
+
"properties": {
|
|
42
|
+
"success": { "const": false },
|
|
43
|
+
"error": { "type": "string", "enum": ["INVALID_INPUT","NETWORK_ERROR","TIMEOUT"] },
|
|
44
|
+
"message": { "type": "string" }
|
|
45
|
+
},
|
|
46
|
+
"required": ["success","error","message"]
|
|
47
|
+
}
|
|
48
|
+
]
|
|
49
|
+
},
|
|
50
|
+
"evals": [
|
|
51
|
+
{
|
|
52
|
+
"id": "basic_search",
|
|
53
|
+
"input": { "query": "FastAPI async", "num_results": 3 },
|
|
54
|
+
"pass_criteria": "success == true and len(results) >= 1"
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"id": "empty_query",
|
|
58
|
+
"input": { "query": "" },
|
|
59
|
+
"pass_criteria": "error == INVALID_INPUT"
|
|
60
|
+
}
|
|
61
|
+
]
|
|
62
|
+
}
|
package/static/account.html
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
6
6
|
<title>Lattice AI</title>
|
|
7
7
|
<link rel="manifest" href="/manifest.json">
|
|
8
|
-
<meta name="theme-color" content="#
|
|
8
|
+
<meta name="theme-color" content="#282a36">
|
|
9
9
|
<meta name="apple-mobile-web-app-capable" content="yes">
|
|
10
10
|
<link rel="apple-touch-icon" href="/icons/apple-touch-icon.png">
|
|
11
11
|
<link rel="icon" type="image/png" sizes="32x32" href="/icons/favicon-32.png">
|
|
@@ -14,11 +14,13 @@
|
|
|
14
14
|
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&display=swap');
|
|
15
15
|
|
|
16
16
|
:root {
|
|
17
|
-
--bg: #
|
|
18
|
-
--text: #
|
|
19
|
-
--faint: #
|
|
20
|
-
--
|
|
21
|
-
--
|
|
17
|
+
--bg: #282a36;
|
|
18
|
+
--text: #f7f7f2;
|
|
19
|
+
--faint: #8d93ab;
|
|
20
|
+
--muted: #c4c8d8;
|
|
21
|
+
--accent: #a77cff;
|
|
22
|
+
--accent-2: #20b8aa;
|
|
23
|
+
--shadow: 0 24px 70px rgba(5,7,12,0.46);
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
@@ -27,8 +29,9 @@
|
|
|
27
29
|
font-family: Inter, -apple-system, BlinkMacSystemFont, sans-serif;
|
|
28
30
|
color: var(--text);
|
|
29
31
|
background:
|
|
30
|
-
|
|
31
|
-
|
|
32
|
+
radial-gradient(circle at 50% 35%, rgba(167,124,255,0.16), transparent 34%),
|
|
33
|
+
radial-gradient(circle at 80% 75%, rgba(32,184,170,0.10), transparent 28%),
|
|
34
|
+
linear-gradient(135deg, #292b38 0%, #242632 52%, #2f3040 100%);
|
|
32
35
|
display: flex;
|
|
33
36
|
align-items: center;
|
|
34
37
|
justify-content: center;
|
|
@@ -43,9 +46,11 @@
|
|
|
43
46
|
position: fixed;
|
|
44
47
|
inset: 0;
|
|
45
48
|
background:
|
|
46
|
-
|
|
47
|
-
linear-gradient(
|
|
48
|
-
|
|
49
|
+
radial-gradient(circle, rgba(247,247,242,0.66) 1px, transparent 1.8px),
|
|
50
|
+
linear-gradient(rgba(157,177,255,0.12) 1px, transparent 1px),
|
|
51
|
+
linear-gradient(90deg, rgba(157,177,255,0.08) 1px, transparent 1px);
|
|
52
|
+
background-size: 82px 82px, 46px 46px, 46px 46px;
|
|
53
|
+
background-position: 16px 18px, 0 0, 0 0;
|
|
49
54
|
mask-image: linear-gradient(180deg, rgba(0,0,0,0.35), rgba(0,0,0,0.06));
|
|
50
55
|
pointer-events: none;
|
|
51
56
|
}
|
|
@@ -55,27 +60,24 @@
|
|
|
55
60
|
position: fixed;
|
|
56
61
|
inset: 0;
|
|
57
62
|
background:
|
|
58
|
-
|
|
59
|
-
|
|
63
|
+
linear-gradient(116deg, transparent 0 44%, rgba(125,183,255,0.08) 44.1%, transparent 44.3% 100%),
|
|
64
|
+
linear-gradient(28deg, transparent 0 62%, rgba(167,124,255,0.08) 62.1%, transparent 62.3% 100%);
|
|
60
65
|
pointer-events: none;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
68
|
.orb {
|
|
64
|
-
|
|
65
|
-
border-radius: 50%;
|
|
66
|
-
filter: blur(70px);
|
|
67
|
-
pointer-events: none;
|
|
69
|
+
display: none;
|
|
68
70
|
}
|
|
69
71
|
.orb-1 { width: 440px; height: 440px; top: -170px; left: -120px; background: rgba(34,211,160,0.13); }
|
|
70
72
|
.orb-2 { width: 380px; height: 380px; bottom: -130px; right: -90px; background: rgba(129,140,248,0.11); }
|
|
71
73
|
|
|
72
74
|
.card {
|
|
73
75
|
width: min(400px, 100%);
|
|
74
|
-
background: rgba(
|
|
75
|
-
border: 1px solid rgba(
|
|
76
|
-
border-radius:
|
|
76
|
+
background: rgba(34,36,49,0.86);
|
|
77
|
+
border: 1px solid rgba(218,225,255,0.14);
|
|
78
|
+
border-radius: 10px;
|
|
77
79
|
padding: 38px 34px;
|
|
78
|
-
box-shadow: var(--shadow), 0 0 0 1px rgba(
|
|
80
|
+
box-shadow: var(--shadow), 0 0 0 1px rgba(167,124,255,0.06);
|
|
79
81
|
position: relative;
|
|
80
82
|
z-index: 1;
|
|
81
83
|
backdrop-filter: blur(28px);
|
|
@@ -87,17 +89,17 @@
|
|
|
87
89
|
top: 0; left: 50%;
|
|
88
90
|
transform: translateX(-50%);
|
|
89
91
|
width: 55%; height: 1px;
|
|
90
|
-
background: linear-gradient(90deg, transparent, rgba(
|
|
92
|
+
background: linear-gradient(90deg, transparent, rgba(167,124,255,0.55), rgba(32,184,170,0.5), transparent);
|
|
91
93
|
}
|
|
92
94
|
|
|
93
95
|
.logo {
|
|
94
96
|
width: 54px; height: 54px;
|
|
95
|
-
background:
|
|
96
|
-
border-radius:
|
|
97
|
+
background: radial-gradient(circle at 34% 30%, #ffffff 0 16%, #a77cff 17% 62%, #5b6cff 100%);
|
|
98
|
+
border-radius: 10px;
|
|
97
99
|
display: flex; align-items: center; justify-content: center;
|
|
98
100
|
font-size: 26px; color: #040706;
|
|
99
101
|
margin: 0 auto 18px;
|
|
100
|
-
box-shadow: 0 0 32px rgba(
|
|
102
|
+
box-shadow: 0 0 32px rgba(167,124,255,0.28), 0 8px 24px rgba(0,0,0,0.38);
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
.title {
|
|
@@ -105,7 +107,7 @@
|
|
|
105
107
|
font-size: 23px;
|
|
106
108
|
font-weight: 800;
|
|
107
109
|
margin-bottom: 6px;
|
|
108
|
-
background: linear-gradient(135deg, #fff 40%,
|
|
110
|
+
background: linear-gradient(135deg, #fff 40%, #cfc6ff 76%, #8be9df);
|
|
109
111
|
-webkit-background-clip: text;
|
|
110
112
|
-webkit-text-fill-color: transparent;
|
|
111
113
|
background-clip: text;
|
|
@@ -113,7 +115,7 @@
|
|
|
113
115
|
|
|
114
116
|
.subtitle {
|
|
115
117
|
text-align: center;
|
|
116
|
-
color: var(--
|
|
118
|
+
color: var(--muted);
|
|
117
119
|
font-size: 12.5px;
|
|
118
120
|
margin-bottom: 26px;
|
|
119
121
|
line-height: 1.5;
|
|
@@ -123,39 +125,39 @@
|
|
|
123
125
|
width: 100%;
|
|
124
126
|
padding: 12px 14px;
|
|
125
127
|
margin-bottom: 10px;
|
|
126
|
-
background: rgba(
|
|
127
|
-
border: 1px solid rgba(
|
|
128
|
+
background: rgba(20,22,31,0.74);
|
|
129
|
+
border: 1px solid rgba(218,225,255,0.14);
|
|
128
130
|
color: var(--text);
|
|
129
|
-
border-radius:
|
|
131
|
+
border-radius: 8px;
|
|
130
132
|
outline: none;
|
|
131
133
|
font-size: 14px;
|
|
132
134
|
font-family: inherit;
|
|
133
135
|
transition: border-color .15s, box-shadow .15s;
|
|
134
136
|
}
|
|
135
137
|
.input:focus {
|
|
136
|
-
border-color: rgba(
|
|
137
|
-
box-shadow: 0 0 0 3px rgba(
|
|
138
|
+
border-color: rgba(167,124,255,0.5);
|
|
139
|
+
box-shadow: 0 0 0 3px rgba(167,124,255,0.09);
|
|
138
140
|
}
|
|
139
141
|
.input::placeholder { color: var(--faint); }
|
|
140
142
|
|
|
141
143
|
.submit {
|
|
142
144
|
width: 100%;
|
|
143
145
|
padding: 13px;
|
|
144
|
-
background: linear-gradient(135deg, #
|
|
145
|
-
color: #
|
|
146
|
+
background: linear-gradient(135deg, #f7f7f2, #cfc6ff);
|
|
147
|
+
color: #242632;
|
|
146
148
|
border: none;
|
|
147
|
-
border-radius:
|
|
149
|
+
border-radius: 8px;
|
|
148
150
|
cursor: pointer;
|
|
149
151
|
font-weight: 800;
|
|
150
152
|
font-size: 14px;
|
|
151
153
|
font-family: inherit;
|
|
152
|
-
box-shadow: 0 0
|
|
154
|
+
box-shadow: 0 0 22px rgba(167,124,255,0.18), 0 4px 12px rgba(0,0,0,0.3);
|
|
153
155
|
transition: all .18s;
|
|
154
156
|
margin-top: 4px;
|
|
155
157
|
}
|
|
156
158
|
.submit:hover {
|
|
157
|
-
background: linear-gradient(135deg, #
|
|
158
|
-
box-shadow: 0 0 30px rgba(
|
|
159
|
+
background: linear-gradient(135deg, #ffffff, #d9d1ff);
|
|
160
|
+
box-shadow: 0 0 30px rgba(167,124,255,0.26), 0 4px 14px rgba(0,0,0,0.3);
|
|
159
161
|
transform: translateY(-1px);
|
|
160
162
|
}
|
|
161
163
|
.submit:disabled { opacity: 0.6; cursor: not-allowed; transform: none; }
|
|
@@ -166,7 +168,7 @@
|
|
|
166
168
|
font-size: 12.5px;
|
|
167
169
|
color: var(--faint);
|
|
168
170
|
}
|
|
169
|
-
.switch a { color:
|
|
171
|
+
.switch a { color: #cfc6ff; text-decoration: none; font-weight: 700; }
|
|
170
172
|
.switch a:hover { text-decoration: underline; }
|
|
171
173
|
|
|
172
174
|
.sso-divider {
|
|
@@ -181,10 +183,10 @@
|
|
|
181
183
|
.sso-btn {
|
|
182
184
|
width: 100%;
|
|
183
185
|
padding: 12px;
|
|
184
|
-
background: rgba(
|
|
185
|
-
border: 1px solid rgba(
|
|
186
|
+
background: rgba(247,247,242,0.045);
|
|
187
|
+
border: 1px solid rgba(218,225,255,0.13);
|
|
186
188
|
color: var(--text);
|
|
187
|
-
border-radius:
|
|
189
|
+
border-radius: 8px;
|
|
188
190
|
cursor: pointer;
|
|
189
191
|
font-weight: 600;
|
|
190
192
|
font-size: 13.5px;
|
|
@@ -193,8 +195,8 @@
|
|
|
193
195
|
transition: all .18s;
|
|
194
196
|
}
|
|
195
197
|
.sso-btn:hover {
|
|
196
|
-
background: rgba(
|
|
197
|
-
border-color: rgba(
|
|
198
|
+
background: rgba(167,124,255,0.10);
|
|
199
|
+
border-color: rgba(167,124,255,0.3);
|
|
198
200
|
}
|
|
199
201
|
|
|
200
202
|
.msg {
|
|
@@ -214,8 +216,8 @@
|
|
|
214
216
|
z-index: 10;
|
|
215
217
|
}
|
|
216
218
|
.lang-btn {
|
|
217
|
-
background: rgba(
|
|
218
|
-
border: 1px solid rgba(
|
|
219
|
+
background: rgba(34,36,49,0.72);
|
|
220
|
+
border: 1px solid rgba(218,225,255,0.14);
|
|
219
221
|
color: var(--text);
|
|
220
222
|
border-radius: 8px;
|
|
221
223
|
padding: 7px 12px;
|
|
@@ -224,15 +226,15 @@
|
|
|
224
226
|
cursor: pointer;
|
|
225
227
|
transition: background .15s;
|
|
226
228
|
}
|
|
227
|
-
.lang-btn:hover { background: rgba(
|
|
229
|
+
.lang-btn:hover { background: rgba(167,124,255,0.12); }
|
|
228
230
|
.lang-menu {
|
|
229
231
|
display: none;
|
|
230
232
|
position: absolute;
|
|
231
233
|
top: calc(100% + 6px);
|
|
232
234
|
right: 0;
|
|
233
|
-
background: #
|
|
234
|
-
border: 1px solid rgba(
|
|
235
|
-
border-radius:
|
|
235
|
+
background: #222431;
|
|
236
|
+
border: 1px solid rgba(218,225,255,0.14);
|
|
237
|
+
border-radius: 8px;
|
|
236
238
|
padding: 4px;
|
|
237
239
|
min-width: 130px;
|
|
238
240
|
box-shadow: 0 8px 24px rgba(0,0,0,0.4);
|
|
@@ -245,7 +247,7 @@
|
|
|
245
247
|
font-size: 13px;
|
|
246
248
|
color: var(--faint);
|
|
247
249
|
}
|
|
248
|
-
.lang-opt:hover { background: rgba(
|
|
250
|
+
.lang-opt:hover { background: rgba(167,124,255,0.10); color: var(--text); }
|
|
249
251
|
.lang-opt.active { color: var(--accent); font-weight: 700; }
|
|
250
252
|
</style>
|
|
251
253
|
</head>
|