From e674677689d7ab5580af19963a8ca875e338cad1 Mon Sep 17 00:00:00 2001 From: kunal_yelgate Date: Mon, 7 Sep 2026 20:49:28 +0530 Subject: [PATCH 1/3] Added the github workflow to project --- .github/workflows/ci.yml | 61 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..81ada07 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,61 @@ +name: CI + +on: + push: + branches: + - main + - master + pull_request: + +permissions: + contents: read + +jobs: + frontend: + name: Frontend checks + runs-on: ubuntu-latest + defaults: + run: + working-directory: frontend + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: npm ci + + - name: Lint + run: npm run lint + + - name: Build + run: npm run build + + backend: + name: Backend checks + runs-on: ubuntu-latest + defaults: + run: + working-directory: backend + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: pip + cache-dependency-path: backend/requirements.txt + + - name: Install dependencies + run: python -m pip install -r requirements.txt + + - name: Compile backend + run: python -m compileall -q app From 1f447365b42c743246982bf136ef8132282d7728 Mon Sep 17 00:00:00 2001 From: kunal_yelgate Date: Mon, 7 Sep 2026 21:52:17 +0530 Subject: [PATCH 2/3] Fixed provider context-length failures and added deliberate selection and improve analysis repository metadata --- README.md | 57 +++++++++++++---- backend/app/ai/context.py | 25 ++++++-- backend/app/ai/llm.py | 12 +++- backend/app/ai/retrieval.py | 11 +++- .../app/analyzers/architecture_analyzer.py | 61 ++++++++++++++++++- backend/app/analyzers/file_detector.py | 12 +++- backend/app/database/repositories.py | 16 +++-- backend/app/services/repo_analyzer.py | 19 +++--- frontend/src/pages/Repository.jsx | 24 ++++++++ 9 files changed, 198 insertions(+), 39 deletions(-) diff --git a/README.md b/README.md index 684e5bb..0c358e2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GitHub AI Repo Intelligence -A full-stack application that authenticates with GitHub, displays a user's repositories, and analyzes repository metadata, languages, structure, commits, and technologies. +A full-stack application that authenticates with GitHub, displays a user's repositories, and provides fresh, evidence-based repository and architecture analysis with Groq or Mistral. ## Stack @@ -8,6 +8,7 @@ A full-stack application that authenticates with GitHub, displays a user's repos - **Backend:** FastAPI, Uvicorn, HTTPX - **Authentication:** GitHub OAuth with a signed session cookie - **GitHub API:** User profile, repositories, repository languages, commits, tree, and file contents +- **AI:** Groq and Mistral chat completion providers with retrieval-augmented repository context ## Project Structure @@ -30,6 +31,7 @@ frontend/ package.json implementation/ Step-by-step implementation notes +.github/workflows/ Frontend and backend continuous integration ``` ## Prerequisites @@ -58,6 +60,17 @@ FRONTEND_URL=http://localhost:5173 Do not commit `backend/.env` or expose the client secret. +Optional AI configuration can be added to `backend/.env`: + +```env +AI_PROVIDER=groq +GROQ_API_KEY=your_groq_api_key +MISTRAL_API_KEY=your_mistral_api_key +AI_MODEL=your_provider_model +``` + +When `AI_MODEL` is not set, the application uses the configured provider's default model. Keep provider keys private and never commit them. + ## Backend Setup From the repository root: @@ -108,14 +121,18 @@ npm run preview ## API Endpoints -| Method | Endpoint | Description | -| ------ | ---------------------------------------------- | --------------------------------------------- | -| `GET` | `/` | API health response | -| `GET` | `/auth/github` | Starts GitHub OAuth | -| `GET` | `/auth/github/callback` | Handles the OAuth callback | -| `GET` | `/github/me` | Returns the authenticated GitHub user | -| `GET` | `/github/repositories` | Returns the authenticated user's repositories | -| `GET` | `/github/repositories/{owner}/{repo}/analysis` | Analyzes a repository | +| Method | Endpoint | Description | +| ------ | ---------------------------------------------- | ---------------------------------------------- | +| `GET` | `/` | API health response | +| `GET` | `/auth/github` | Starts GitHub OAuth | +| `GET` | `/auth/github/callback` | Handles the OAuth callback | +| `GET` | `/github/me` | Returns the authenticated GitHub user | +| `GET` | `/github/repositories` | Returns the authenticated user's repositories | +| `GET` | `/github/repositories/{owner}/{repo}/analysis` | Analyzes a repository | +| `GET` | `/github/activity-summary` | Returns authored pull request and issue counts | +| `POST` | `/github/analyze-all` | Analyzes all accessible user repositories | +| `POST` | `/ai/chat` | Answers a profile or repository question | +| `POST` | `/ai/index/{owner}/{repo}` | Rebuilds the repository code index | The GitHub data endpoints require the signed `session` cookie created during login. @@ -126,9 +143,27 @@ The repository analysis endpoint currently returns: - Repository metadata such as name, description, stars, forks, issues, and default branch - Language statistics - Repository structure, including files, directories, and extensions -- Number of fetched commits +- Recent commit and activity analysis +- Detected technologies, dependency files, README signals, and important files +- Source-level summaries for functions, classes, components, imports, exports, and routes +- Architecture type, inferred layers, entry points, and import-based module relationships + +The analyzer uses the repository's actual default branch when requesting the Git tree. It inspects a bounded set of prioritized files concurrently, then caches the result against GitHub's latest repository update timestamp. + +## AI Repository Questions + +Repository questions use the latest repository version as the retrieval source. The system combines structured analysis with relevant code chunks and import relationships before calling the selected AI provider. If the repository context is too large for the automatic Groq request, the service can retry with Mistral. + +Examples of useful questions: + +- `Explain the architecture and request flow.` +- `Where is authentication implemented?` +- `Which modules depend on the GitHub API?` +- `What are the main entry points and service boundaries?` + +## Continuous Integration -The analyzer uses the repository's actual default branch when requesting the Git tree. +The GitHub Actions workflow in `.github/workflows/ci.yml` runs on pushes to `main` or `master` and on pull requests. It installs dependencies, runs frontend ESLint and production build checks, and compiles the backend. ## Troubleshooting diff --git a/backend/app/ai/context.py b/backend/app/ai/context.py index 86041bc..78ea93e 100644 --- a/backend/app/ai/context.py +++ b/backend/app/ai/context.py @@ -11,12 +11,12 @@ Keep answers useful and concise, with short headings or bullets when appropriate. Format every answer for a human reader: 1. Start with a direct answer in one or two sentences. -2. Use short Markdown headings beginning with ### when sections are useful. +2. Use short Markdown headings beginning with -> when sections are useful. 3. Use simple bullet points for evidence and file paths in backticks. 4. End with a brief limitation statement only when the supplied context is incomplete. Do not return JSON, XML, provider metadata, or internal reasoning.""" -MAX_CONTEXT_CHARS = 18000 +MAX_CONTEXT_CHARS = 12000 def _compact_analysis(analysis: dict): @@ -35,8 +35,13 @@ def _compact_analysis(analysis: dict): def repository_context(analysis: dict, chunks: list[dict]): + compact = _compact_analysis(analysis) + architecture = compact.get("architecture", {}) + architecture["module_graph"] = architecture.get("module_graph", [])[:20] + compact["source_analysis"] = compact.get("source_analysis", [])[:25] + compact["architecture"] = architecture return { - "analysis": _compact_analysis(analysis), + "analysis": compact, "retrieved_code_chunks": [ { "file": chunk["file_path"], @@ -44,7 +49,7 @@ def repository_context(analysis: dict, chunks: list[dict]): "chunk_index": chunk["chunk_index"], "content": chunk["content"][:2400] } - for chunk in chunks[:4] + for chunk in chunks[:2] ] } @@ -77,7 +82,17 @@ def profile_context(repositories: list[dict]): def build_messages(question: str, context: dict): context_text = json.dumps(context, separators=(",", ":"), default=str) if len(context_text) > MAX_CONTEXT_CHARS: - context_text = context_text[:MAX_CONTEXT_CHARS] + "\n[context truncated]" + context_text = json.dumps( + { + **context, + "retrieved_code_chunks": context.get("retrieved_code_chunks", [])[:1], + "context_note": "Some low-priority context was omitted to preserve response speed." + }, + separators=(",", ":"), + default=str + ) + if len(context_text) > MAX_CONTEXT_CHARS: + context_text = context_text[:MAX_CONTEXT_CHARS] + "\n[context truncated]" return [ {"role": "system", "content": SYSTEM_PROMPT}, diff --git a/backend/app/ai/llm.py b/backend/app/ai/llm.py index de551d0..85a6b89 100644 --- a/backend/app/ai/llm.py +++ b/backend/app/ai/llm.py @@ -39,11 +39,12 @@ async def _request_provider(messages: list[dict], provider_name: str): if not settings["key"] or settings["key"].startswith("replace-after"): raise AIProviderError(f"{provider_name} API key is not configured") + configured_model = AI_MODEL if provider_name == AI_PROVIDER else None payload = { - "model": AI_MODEL or settings["default_model"], + "model": configured_model or settings["default_model"], "messages": messages, "temperature": 0.2, - "max_tokens": 1200 + "max_tokens": 900 } headers = { "Authorization": f"Bearer {settings['key']}", @@ -74,6 +75,11 @@ async def ask_llm(messages: list[dict], provider: str | None = None): return await _request_provider(messages, provider_name) except AIProviderError as error: error_text = str(error).lower() - if provider is None and provider_name == "groq" and "context_length" in error_text: + if ( + provider is None + and provider_name == "groq" + and ("context_length" in error_text or "rate_limit" in error_text) + and MISTRAL_API_KEY + ): return await _request_provider(messages, "mistral") raise \ No newline at end of file diff --git a/backend/app/ai/retrieval.py b/backend/app/ai/retrieval.py index 1b1e414..7ed1b36 100644 --- a/backend/app/ai/retrieval.py +++ b/backend/app/ai/retrieval.py @@ -38,10 +38,12 @@ async def index_repository(access_token: str, owner: str, repo: str): async def retrieve_chunks(access_token: str, owner: str, repo: str, question: str, limit: int = 3): full_name = f"{owner}/{repo}" - chunks = get_code_chunks(full_name) + repository = await get_repository(access_token, owner, repo) + source_version = repository.get("pushed_at") or repository.get("updated_at") + chunks = get_code_chunks(full_name, source_version) if not chunks: await index_repository(access_token, owner, repo) - chunks = get_code_chunks(full_name) + chunks = get_code_chunks(full_name, source_version) query_embedding = await embed_texts([question]) question_terms = _terms(question) @@ -49,7 +51,10 @@ async def retrieve_chunks(access_token: str, owner: str, repo: str, question: st for chunk in chunks: score = 0.0 if query_embedding and chunk.get("embedding"): - score = cosine_similarity(query_embedding[0], json.loads(chunk["embedding"])) + try: + score = cosine_similarity(query_embedding[0], json.loads(chunk["embedding"])) + except (TypeError, ValueError, json.JSONDecodeError): + score = 0.0 else: content_terms = _terms(chunk["file_path"] + " " + chunk["content"]) score = len(question_terms & content_terms) / max(len(question_terms), 1) diff --git a/backend/app/analyzers/architecture_analyzer.py b/backend/app/analyzers/architecture_analyzer.py index e8335c2..a0cfd2e 100644 --- a/backend/app/analyzers/architecture_analyzer.py +++ b/backend/app/analyzers/architecture_analyzer.py @@ -1,4 +1,44 @@ -def analyze_architecture(files: list[str], technologies: list[str]): +from pathlib import PurePosixPath + + +def _module_candidates(import_name: str, source_file: str): + if not import_name.startswith("."): + return [] + + base = PurePosixPath(source_file).parent + relative = import_name.lstrip(".").replace(".", "/") + if import_name.startswith(".."): + base = base.parent + target = str(base / relative).replace("\\", "/").lstrip("./") + return [ + target, + *[f"{target}.{extension}" for extension in ("py", "js", "jsx", "ts", "tsx")], + f"{target}/index.js", + f"{target}/index.ts", + f"{target}/__init__.py" + ] + + +def _build_module_graph(source_analysis: list[dict]): + known_files = {item["file"] for item in source_analysis} + graph = [] + for item in source_analysis: + for import_name in item.get("imports", []): + target = next( + (candidate for candidate in _module_candidates(import_name, item["file"]) + if candidate in known_files), + None + ) + if target: + graph.append({"from": item["file"], "to": target, "import": import_name}) + return graph[:100] + + +def analyze_architecture( + files: list[str], + technologies: list[str], + source_analysis: list[dict] | None = None +): lower_files = [path.lower() for path in files] lower_technologies = {technology.lower() for technology in technologies} layers = [] @@ -28,10 +68,27 @@ def analyze_architecture(files: list[str], technologies: list[str]): else: architecture_type = "Unclassified" + source_analysis = source_analysis or [] + module_graph = _build_module_graph(source_analysis) + entry_points = [ + path for path in files + if PurePosixPath(path).name.lower() in { + "main.py", "app.py", "server.py", "index.js", "main.jsx", "main.tsx" + } + ] + return { "architecture_type": architecture_type, "frontend": next((name for name in ("React", "Vue", "Angular", "Next.js") if name.lower() in lower_technologies), None), "backend": next((name for name in ("FastAPI", "Flask", "Django", "Node.js") if name.lower() in lower_technologies), None), "external_services": ["GitHub API"] if any("github" in path for path in lower_files) else [], - "layers": layers + "layers": layers, + "entry_points": entry_points, + "module_graph": module_graph, + "module_relationships": len(module_graph), + "analyzed_source_files": len(source_analysis), + "analysis_note": ( + "Architecture is inferred from repository paths, detected technologies, " + "and imports in the analyzed source files." + ) } \ No newline at end of file diff --git a/backend/app/analyzers/file_detector.py b/backend/app/analyzers/file_detector.py index cfe4a43..90c6ae3 100644 --- a/backend/app/analyzers/file_detector.py +++ b/backend/app/analyzers/file_detector.py @@ -13,10 +13,14 @@ "docker-compose.yml", "docker-compose.yaml", ".env.example", "README.md" } +SOURCE_EXTENSIONS = {"py", "js", "jsx", "ts", "tsx"} +MAX_ANALYSIS_FILES = 40 + def detect_important_files(files: list[str]): entry_points = [] config_files = [] + source_files = [] for path in files: name = PurePosixPath(path).name @@ -24,10 +28,14 @@ def detect_important_files(files: list[str]): entry_points.append(path) if name in CONFIG_FILE_NAMES or name.startswith("config."): config_files.append(path) + if PurePosixPath(path).suffix.lower().lstrip(".") in SOURCE_EXTENSIONS: + source_files.append(path) - important_files = list(dict.fromkeys(entry_points + config_files)) + analysis_files = list(dict.fromkeys(entry_points + config_files + source_files)) return { "entry_points": entry_points, "config_files": config_files, - "important_files": important_files + "source_files": source_files, + "important_files": analysis_files[:MAX_ANALYSIS_FILES], + "total_candidates": len(analysis_files) } \ No newline at end of file diff --git a/backend/app/database/repositories.py b/backend/app/database/repositories.py index 3516c96..fc41d08 100644 --- a/backend/app/database/repositories.py +++ b/backend/app/database/repositories.py @@ -27,12 +27,18 @@ def replace_code_chunks(repository: str, source_version: str | None, chunks: lis connection.close() -def get_code_chunks(repository: str): +def get_code_chunks(repository: str, source_version: str | None = None): connection = get_connection() - rows = connection.execute( - "SELECT * FROM code_chunks WHERE repository = ?", - (repository,) - ).fetchall() + if source_version is None: + rows = connection.execute( + "SELECT * FROM code_chunks WHERE repository = ?", + (repository,) + ).fetchall() + else: + rows = connection.execute( + "SELECT * FROM code_chunks WHERE repository = ? AND source_version = ?", + (repository, source_version) + ).fetchall() connection.close() return [dict(row) for row in rows] diff --git a/backend/app/services/repo_analyzer.py b/backend/app/services/repo_analyzer.py index 6ae0dec..eecfbda 100644 --- a/backend/app/services/repo_analyzer.py +++ b/backend/app/services/repo_analyzer.py @@ -1,3 +1,5 @@ +import asyncio + from app.services.github_api import ( get_repository, get_repository_languages, @@ -85,15 +87,16 @@ async def analyze_repository( selected_files = important_file_analysis["important_files"] source_contents = {} - for path in selected_files: + async def fetch_source(path: str): try: - content = await get_repository_file( - access_token, - owner, - repo, - path - ) + return path, await get_repository_file(access_token, owner, repo, path) except Exception: + return path, None + + fetched_files = await asyncio.gather(*(fetch_source(path) for path in selected_files)) + + for path, content in fetched_files: + if content is None: continue if path.endswith("package.json"): @@ -142,7 +145,7 @@ async def analyze_repository( "readme": analyze_readme(readme_content), "important_files": important_file_analysis, "source_analysis": source_analysis, - "architecture": analyze_architecture(files, technologies) + "architecture": analyze_architecture(files, technologies, source_analysis) } diff --git a/frontend/src/pages/Repository.jsx b/frontend/src/pages/Repository.jsx index 60940b9..453c926 100644 --- a/frontend/src/pages/Repository.jsx +++ b/frontend/src/pages/Repository.jsx @@ -97,6 +97,30 @@ function Repository({ repository, onBack }) { → {analysis.architecture.backend || "Source"} +

+ {analysis.architecture.analyzed_source_files || 0} source files ·{" "} + {analysis.architecture.module_relationships || 0} detected module relationships +

+ {analysis.architecture.entry_points?.length > 0 && ( +
+ {analysis.architecture.entry_points.map((file) => ( +
+ {file} + entry point +
+ ))} +
+ )} + {analysis.architecture.module_graph?.length > 0 && ( +
+ {analysis.architecture.module_graph.slice(0, 12).map((edge) => ( +
+ {edge.from} + → {edge.to} +
+ ))} +
+ )}

Technologies

From 2fa1bd6fbaacb86054c14a3fb23ebe66eda8b656 Mon Sep 17 00:00:00 2001 From: kunal_yelgate Date: Mon, 7 Sep 2026 22:18:15 +0530 Subject: [PATCH 3/3] Added the shared visual theme layer in frontend --- README.md | 57 +- frontend/DESIGN.md | 537 +++++++++++++++++++ frontend/src/App.jsx | 1 + frontend/src/cursor-theme.css | 873 +++++++++++++++++++++++++++++++ frontend/src/pages/Dashboard.jsx | 15 +- frontend/src/pages/Login.jsx | 54 +- 6 files changed, 1477 insertions(+), 60 deletions(-) create mode 100644 frontend/DESIGN.md create mode 100644 frontend/src/cursor-theme.css diff --git a/README.md b/README.md index 0c358e2..684e5bb 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GitHub AI Repo Intelligence -A full-stack application that authenticates with GitHub, displays a user's repositories, and provides fresh, evidence-based repository and architecture analysis with Groq or Mistral. +A full-stack application that authenticates with GitHub, displays a user's repositories, and analyzes repository metadata, languages, structure, commits, and technologies. ## Stack @@ -8,7 +8,6 @@ A full-stack application that authenticates with GitHub, displays a user's repos - **Backend:** FastAPI, Uvicorn, HTTPX - **Authentication:** GitHub OAuth with a signed session cookie - **GitHub API:** User profile, repositories, repository languages, commits, tree, and file contents -- **AI:** Groq and Mistral chat completion providers with retrieval-augmented repository context ## Project Structure @@ -31,7 +30,6 @@ frontend/ package.json implementation/ Step-by-step implementation notes -.github/workflows/ Frontend and backend continuous integration ``` ## Prerequisites @@ -60,17 +58,6 @@ FRONTEND_URL=http://localhost:5173 Do not commit `backend/.env` or expose the client secret. -Optional AI configuration can be added to `backend/.env`: - -```env -AI_PROVIDER=groq -GROQ_API_KEY=your_groq_api_key -MISTRAL_API_KEY=your_mistral_api_key -AI_MODEL=your_provider_model -``` - -When `AI_MODEL` is not set, the application uses the configured provider's default model. Keep provider keys private and never commit them. - ## Backend Setup From the repository root: @@ -121,18 +108,14 @@ npm run preview ## API Endpoints -| Method | Endpoint | Description | -| ------ | ---------------------------------------------- | ---------------------------------------------- | -| `GET` | `/` | API health response | -| `GET` | `/auth/github` | Starts GitHub OAuth | -| `GET` | `/auth/github/callback` | Handles the OAuth callback | -| `GET` | `/github/me` | Returns the authenticated GitHub user | -| `GET` | `/github/repositories` | Returns the authenticated user's repositories | -| `GET` | `/github/repositories/{owner}/{repo}/analysis` | Analyzes a repository | -| `GET` | `/github/activity-summary` | Returns authored pull request and issue counts | -| `POST` | `/github/analyze-all` | Analyzes all accessible user repositories | -| `POST` | `/ai/chat` | Answers a profile or repository question | -| `POST` | `/ai/index/{owner}/{repo}` | Rebuilds the repository code index | +| Method | Endpoint | Description | +| ------ | ---------------------------------------------- | --------------------------------------------- | +| `GET` | `/` | API health response | +| `GET` | `/auth/github` | Starts GitHub OAuth | +| `GET` | `/auth/github/callback` | Handles the OAuth callback | +| `GET` | `/github/me` | Returns the authenticated GitHub user | +| `GET` | `/github/repositories` | Returns the authenticated user's repositories | +| `GET` | `/github/repositories/{owner}/{repo}/analysis` | Analyzes a repository | The GitHub data endpoints require the signed `session` cookie created during login. @@ -143,27 +126,9 @@ The repository analysis endpoint currently returns: - Repository metadata such as name, description, stars, forks, issues, and default branch - Language statistics - Repository structure, including files, directories, and extensions -- Recent commit and activity analysis -- Detected technologies, dependency files, README signals, and important files -- Source-level summaries for functions, classes, components, imports, exports, and routes -- Architecture type, inferred layers, entry points, and import-based module relationships - -The analyzer uses the repository's actual default branch when requesting the Git tree. It inspects a bounded set of prioritized files concurrently, then caches the result against GitHub's latest repository update timestamp. - -## AI Repository Questions - -Repository questions use the latest repository version as the retrieval source. The system combines structured analysis with relevant code chunks and import relationships before calling the selected AI provider. If the repository context is too large for the automatic Groq request, the service can retry with Mistral. - -Examples of useful questions: - -- `Explain the architecture and request flow.` -- `Where is authentication implemented?` -- `Which modules depend on the GitHub API?` -- `What are the main entry points and service boundaries?` - -## Continuous Integration +- Number of fetched commits -The GitHub Actions workflow in `.github/workflows/ci.yml` runs on pushes to `main` or `master` and on pull requests. It installs dependencies, runs frontend ESLint and production build checks, and compiles the backend. +The analyzer uses the repository's actual default branch when requesting the Git tree. ## Troubleshooting diff --git a/frontend/DESIGN.md b/frontend/DESIGN.md new file mode 100644 index 0000000..f457973 --- /dev/null +++ b/frontend/DESIGN.md @@ -0,0 +1,537 @@ +--- +version: alpha +name: Cursor-design-analysis +description: An AI-first code editor whose marketing site reads like a quietly-confident developer-tools brand with a warm-cream editorial canvas (`#f7f7f4`) instead of the typical dark IDE atmosphere. Near-black warm ink (`#26251e`) carries body and display alike — display sits at weight 400 with negative letter-spacing for a magazine feel rather than a bold tech voice. The single brand voltage is **Cursor Orange** (`#f54e00`) reserved for primary CTAs and the wordmark. A signature pastel timeline palette (peach, mint, blue, lavender, gold) marks AI-action stages (Thinking / Reading / Editing / Grepping / Done) — only inside in-product timeline visualizations. Cards use minimal hairlines, no shadows, generous 80px section rhythm. CursorGothic for display/body, JetBrains Mono on every code surface (which is roughly half the page). + +colors: + primary: "#f54e00" + primary-active: "#d04200" + ink: "#26251e" + body: "#5a5852" + body-strong: "#26251e" + muted: "#807d72" + muted-soft: "#a09c92" + hairline: "#e6e5e0" + hairline-soft: "#efeee8" + hairline-strong: "#cfcdc4" + canvas: "#f7f7f4" + canvas-soft: "#fafaf7" + surface-card: "#ffffff" + surface-strong: "#e6e5e0" + on-primary: "#ffffff" + timeline-thinking: "#dfa88f" + timeline-grep: "#9fc9a2" + timeline-read: "#9fbbe0" + timeline-edit: "#c0a8dd" + timeline-done: "#c08532" + semantic-error: "#cf2d56" + semantic-success: "#1f8a65" + +typography: + display-mega: + fontFamily: "'CursorGothic', system-ui, 'Helvetica Neue', Helvetica, Arial, sans-serif" + fontSize: 72px + fontWeight: 400 + lineHeight: 1.1 + letterSpacing: -2.16px + display-lg: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 36px + fontWeight: 400 + lineHeight: 1.2 + letterSpacing: -0.72px + display-md: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 26px + fontWeight: 400 + lineHeight: 1.25 + letterSpacing: -0.325px + display-sm: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 22px + fontWeight: 400 + lineHeight: 1.3 + letterSpacing: -0.11px + title-md: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 18px + fontWeight: 600 + lineHeight: 1.4 + letterSpacing: 0 + title-sm: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 16px + fontWeight: 600 + lineHeight: 1.4 + letterSpacing: 0 + body-md: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 16px + fontWeight: 400 + lineHeight: 1.5 + letterSpacing: 0 + body-tracked: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 16px + fontWeight: 400 + lineHeight: 1.5 + letterSpacing: 0.08px + body-sm: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 14px + fontWeight: 400 + lineHeight: 1.5 + letterSpacing: 0 + caption: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 13px + fontWeight: 400 + lineHeight: 1.4 + letterSpacing: 0 + caption-uppercase: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 11px + fontWeight: 600 + lineHeight: 1.4 + letterSpacing: 0.88px + textTransform: uppercase + code: + fontFamily: "'JetBrains Mono', 'Fira Code', monospace" + fontSize: 13px + fontWeight: 400 + lineHeight: 1.5 + letterSpacing: 0 + button: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 14px + fontWeight: 500 + lineHeight: 1.0 + letterSpacing: 0 + nav-link: + fontFamily: "'CursorGothic', sans-serif" + fontSize: 14px + fontWeight: 500 + lineHeight: 1.4 + letterSpacing: 0 + +rounded: + none: 0px + xs: 4px + sm: 6px + md: 8px + lg: 12px + xl: 16px + pill: 9999px + full: 9999px + +spacing: + xxs: 4px + xs: 8px + sm: 12px + base: 16px + md: 20px + lg: 24px + xl: 32px + xxl: 48px + section: 80px + +components: + top-nav: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + typography: "{typography.nav-link}" + height: 64px + button-primary: + backgroundColor: "{colors.primary}" + textColor: "{colors.on-primary}" + typography: "{typography.button}" + rounded: "{rounded.md}" + padding: 10px 18px + height: 40px + button-primary-active: + backgroundColor: "{colors.primary-active}" + textColor: "{colors.on-primary}" + rounded: "{rounded.md}" + button-secondary: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.ink}" + typography: "{typography.button}" + rounded: "{rounded.md}" + padding: 9px 17px + height: 40px + button-tertiary-text: + backgroundColor: transparent + textColor: "{colors.ink}" + typography: "{typography.button}" + button-download: + backgroundColor: "{colors.ink}" + textColor: "{colors.canvas}" + typography: "{typography.button}" + rounded: "{rounded.md}" + padding: 12px 20px + height: 44px + hero-band: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + typography: "{typography.display-mega}" + padding: 80px + ide-mockup-card: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.ink}" + rounded: "{rounded.lg}" + padding: 0 + ide-pane: + backgroundColor: "{colors.canvas-soft}" + textColor: "{colors.body}" + typography: "{typography.code}" + rounded: "{rounded.md}" + padding: 16px + feature-card: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.ink}" + typography: "{typography.title-md}" + rounded: "{rounded.lg}" + padding: 24px + comparison-card: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.ink}" + typography: "{typography.body-md}" + rounded: "{rounded.lg}" + padding: 24px + timeline-pill-thinking: + backgroundColor: "{colors.timeline-thinking}" + textColor: "{colors.ink}" + typography: "{typography.caption-uppercase}" + rounded: "{rounded.pill}" + padding: 4px 10px + timeline-pill-grep: + backgroundColor: "{colors.timeline-grep}" + textColor: "{colors.ink}" + typography: "{typography.caption-uppercase}" + rounded: "{rounded.pill}" + padding: 4px 10px + timeline-pill-read: + backgroundColor: "{colors.timeline-read}" + textColor: "{colors.ink}" + typography: "{typography.caption-uppercase}" + rounded: "{rounded.pill}" + padding: 4px 10px + timeline-pill-edit: + backgroundColor: "{colors.timeline-edit}" + textColor: "{colors.ink}" + typography: "{typography.caption-uppercase}" + rounded: "{rounded.pill}" + padding: 4px 10px + timeline-pill-done: + backgroundColor: "{colors.timeline-done}" + textColor: "{colors.on-primary}" + typography: "{typography.caption-uppercase}" + rounded: "{rounded.pill}" + padding: 4px 10px + code-block: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.ink}" + typography: "{typography.code}" + rounded: "{rounded.lg}" + padding: 20px + pricing-tier-card: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.ink}" + typography: "{typography.body-md}" + rounded: "{rounded.lg}" + padding: 32px + pricing-tier-featured: + backgroundColor: "{colors.ink}" + textColor: "{colors.canvas}" + typography: "{typography.body-md}" + rounded: "{rounded.lg}" + padding: 32px + text-input: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.ink}" + typography: "{typography.body-md}" + rounded: "{rounded.md}" + padding: 12px 16px + height: 44px + badge-pill: + backgroundColor: "{colors.surface-strong}" + textColor: "{colors.ink}" + typography: "{typography.caption-uppercase}" + rounded: "{rounded.pill}" + padding: 4px 10px + cta-band: + backgroundColor: "{colors.canvas}" + textColor: "{colors.ink}" + typography: "{typography.display-lg}" + padding: 96px + testimonial-card: + backgroundColor: "{colors.surface-card}" + textColor: "{colors.body}" + typography: "{typography.body-md}" + rounded: "{rounded.lg}" + padding: 24px + footer: + backgroundColor: "{colors.canvas}" + textColor: "{colors.body}" + typography: "{typography.body-sm}" + padding: 64px 48px + footer-link: + backgroundColor: transparent + textColor: "{colors.body}" + typography: "{typography.body-sm}" +--- + +## Overview + +Cursor's marketing site reads as a quietly-confident developer brand that believes in editorial calm over IDE-darkness. The base canvas is **warm cream** (`{colors.canvas}` — #f7f7f4) holding warm near-black ink (`{colors.ink}` — #26251e) for body and display alike. The single brand voltage is **Cursor Orange** (`{colors.primary}` — #f54e00) reserved for primary CTAs and the wordmark — used scarcely. + +Type runs **CursorGothic** as the single sans family. Display sits at weight 400 with negative letter-spacing — a magazine-editorial voice rather than tech-bombastic. JetBrains Mono carries every code surface (and code surfaces are roughly half the page). + +The brand's strongest visual signature is the **AI-timeline pill palette**: five pastel pills (peach `{colors.timeline-thinking}`, mint `{colors.timeline-grep}`, blue `{colors.timeline-read}`, lavender `{colors.timeline-edit}`, gold `{colors.timeline-done}`) marking AI-action stages inside in-product timeline visualizations. Used only in product UI — never as system action colors. + +**Key Characteristics:** +- Warm cream canvas, not white. Ink is warm (#26251e), not pure black. +- Single CTA color: `{colors.primary}` (Cursor Orange #f54e00). Used scarcely. +- Display weight stays at 400 — never bold. Magazine voice. +- AI timeline pastels: 5 dedicated tokens for in-product agent action stages. +- Compact 8px CTA radius — developer dialect. +- Hairline-only depth; no drop shadows. +- 80px section rhythm. + +## Colors + +### Brand & Accent +- **Cursor Orange** (`{colors.primary}` — #f54e00): Primary CTA pills, wordmark, hero accent. Used scarcely. +- **Cursor Orange Active** (`{colors.primary-active}` — #d04200): Press state. + +### Surface +- **Canvas** (`{colors.canvas}` — #f7f7f4): Warm cream page floor. +- **Canvas Soft** (`{colors.canvas-soft}` — #fafaf7): IDE-pane background inside mockups. +- **Surface Card** (`{colors.surface-card}` — #ffffff): Pure white card surface — slight contrast against the cream canvas. +- **Surface Strong** (`{colors.surface-strong}` — #e6e5e0): Badges, tag pills. + +### Hairlines +- **Hairline** (`{colors.hairline}` — #e6e5e0): 1px divider. +- **Hairline Soft** (`{colors.hairline-soft}` — #efeee8): Lighter divider. +- **Hairline Strong** (`{colors.hairline-strong}` — #cfcdc4): Stronger panel outline. + +### Text +- **Ink** (`{colors.ink}` — #26251e): Display, body emphasis. Warm near-black. +- **Body** (`{colors.body}` — #5a5852): Default running-text. +- **Body Strong** (`{colors.body-strong}` — #26251e): Same as ink. +- **Muted** (`{colors.muted}` — #807d72): Sub-titles. +- **Muted Soft** (`{colors.muted-soft}` — #a09c92): Disabled text. +- **On Primary** (`{colors.on-primary}` — #ffffff): White text on Cursor Orange. + +### Timeline (AI-action signature) +- **Thinking** (`{colors.timeline-thinking}` — #dfa88f): Peach. Used inside in-product agent timeline only. +- **Grep** (`{colors.timeline-grep}` — #9fc9a2): Mint. +- **Read** (`{colors.timeline-read}` — #9fbbe0): Pastel blue. +- **Edit** (`{colors.timeline-edit}` — #c0a8dd): Lavender. +- **Done** (`{colors.timeline-done}` — #c08532): Warm gold. + +### Semantic +- **Success** (`{colors.semantic-success}` — #1f8a65): Confirmation indicators. +- **Error** (`{colors.semantic-error}` — #cf2d56): Validation errors. + +## Typography + +### Font Family +**CursorGothic** is the licensed display + body family. Fallback: `system-ui, "Helvetica Neue", Helvetica, Arial, sans-serif`. Code surfaces switch to **JetBrains Mono**. + +### Hierarchy + +| Token | Size | Weight | Line Height | Letter Spacing | Use | +|---|---|---|---|---|---| +| `{typography.display-mega}` | 72px | 400 | 1.1 | -2.16px | Homepage hero h1 | +| `{typography.display-lg}` | 36px | 400 | 1.2 | -0.72px | Section heads | +| `{typography.display-md}` | 26px | 400 | 1.25 | -0.325px | Sub-section heads | +| `{typography.display-sm}` | 22px | 400 | 1.3 | -0.11px | Card group titles | +| `{typography.title-md}` | 18px | 600 | 1.4 | 0 | Component titles | +| `{typography.title-sm}` | 16px | 600 | 1.4 | 0 | List labels | +| `{typography.body-md}` | 16px | 400 | 1.5 | 0 | Default body | +| `{typography.body-tracked}` | 16px | 400 | 1.5 | 0.08px | Tracked editorial body | +| `{typography.body-sm}` | 14px | 400 | 1.5 | 0 | Footer body | +| `{typography.caption}` | 13px | 400 | 1.4 | 0 | Photo captions | +| `{typography.caption-uppercase}` | 11px | 600 | 1.4 | 0.88px | Section labels, timeline pill labels | +| `{typography.code}` | 13px | 400 | 1.5 | 0 | Code blocks — JetBrains Mono | +| `{typography.button}` | 14px | 500 | 1.0 | 0 | CTA pill labels | +| `{typography.nav-link}` | 14px | 500 | 1.4 | 0 | Top-nav menu | + +### Principles +- **Display weight stays at 400.** Magazine voice, never bold. +- **Negative letter-spacing on display only.** -0.11px to -2.16px tracking. +- **JetBrains Mono on every code surface.** + +### Note on Font Substitutes +CursorGothic is licensed. Open-source substitute: **Inter** at weight 400 with letter-spacing -1.5%. Or **GT Sectra** for a more editorial feel. + +## Layout + +### Spacing System +- **Base unit:** 4px. +- **Tokens:** `{spacing.xxs}` 4px · `{spacing.xs}` 8px · `{spacing.sm}` 12px · `{spacing.base}` 16px · `{spacing.md}` 20px · `{spacing.lg}` 24px · `{spacing.xl}` 32px · `{spacing.xxl}` 48px · `{spacing.section}` 80px. +- **Section padding:** 80px. + +### Grid & Container +- Max content width: ~1200px. +- Editorial body: 12-column grid. +- Feature card grids: 2-up at desktop for splits, 3-up for benefits. +- Footer: 5-column at desktop. + +### Whitespace Philosophy +Generous editorial pacing — closer to a print magazine than a tech site. The cream canvas has plenty of breathing room; cards within bands sit close (16-24px gap). + +## Elevation & Depth + +The system uses **hairline-only depth**. No drop shadows, no elevation tiers. Cards float above the canvas via 1px hairlines and the slight white-on-cream contrast. + +| Level | Treatment | Use | +|---|---|---| +| Flat (canvas) | `{colors.canvas}` (#f7f7f4) | Body bands, footer | +| Card | `{colors.surface-card}` (#ffffff) | Content cards | +| Hairline border | 1px `{colors.hairline}` | Card outlines, dividers | +| IDE pane | `{colors.canvas-soft}` (#fafaf7) | Inside IDE mockup cards | + +### Decorative Depth +- **IDE-mockup cards** are the only "elevated" element. White card on cream canvas with internal pane structure mimicking the actual Cursor editor. +- **Timeline pastel pills** add chromatic depth without surface elevation. + +## Shapes + +### Border Radius Scale + +| Token | Value | Use | +|---|---|---| +| `{rounded.none}` | 0px | Reserved | +| `{rounded.xs}` | 4px | Inline tags | +| `{rounded.sm}` | 6px | Compact rows | +| `{rounded.md}` | 8px | CTA buttons, form inputs | +| `{rounded.lg}` | 12px | Cards, IDE panes | +| `{rounded.xl}` | 16px | Larger feature cards (rare) | +| `{rounded.pill}` | 9999px | Timeline pills, badges | +| `{rounded.full}` | 9999px | Avatars (rare) | + +## Components + +### Top Navigation + +**`top-nav`** — Background `{colors.canvas}`, text `{colors.ink}`, height 64px. Layout: Cursor wordmark left, primary horizontal menu (Pricing / Features / Enterprise / Blog / Forum / Careers), Sign In + Download primary CTA right. + +### Buttons + +**`button-primary`** — The signature Cursor Orange CTA. Background `{colors.primary}`, text `{colors.on-primary}`, type `{typography.button}` (14px / 500), padding 10px × 18px, height 40px, rounded `{rounded.md}` (8px). + +**`button-primary-active`** — Press state. Background `{colors.primary-active}`. + +**`button-secondary`** — White card pill on cream canvas. Background `{colors.surface-card}`, text `{colors.ink}`, 1px `{colors.hairline-strong}` border. + +**`button-tertiary-text`** — Inline ink text link. + +**`button-download`** — Larger ink-canvas CTA. Background `{colors.ink}`, text `{colors.canvas}`, padding 12px × 20px, height 44px. Used for "Download for macOS" type CTAs. + +### Hero & IDE Mockups + +**`hero-band`** — Background `{colors.canvas}`, full-width display headline in `{typography.display-mega}` (72px / 400 / -2.16px), subhead in `{typography.body-md}`, two CTAs (`button-download` + `button-tertiary-text`), and a centered IDE-mockup card below the hero copy. + +**`ide-mockup-card`** — A white card containing a multi-pane IDE mockup (sidebar + main editor + chat panel + terminal). Background `{colors.surface-card}`, rounded `{rounded.lg}` (12px), 1px `{colors.hairline}` border, no padding (panes fill the card edge-to-edge). + +**`ide-pane`** — Individual IDE pane inside the mockup. Background `{colors.canvas-soft}`, text `{colors.body}` in `{typography.code}` (JetBrains Mono 13px), rounded `{rounded.md}` (8px), padding 16px. + +### Cards + +**`feature-card`** — Background `{colors.surface-card}`, text `{colors.ink}`, type `{typography.title-md}`, rounded `{rounded.lg}`, padding 24px. 1px `{colors.hairline}` border. + +**`comparison-card`** — Side-by-side "Cursor vs other tools" card. Same surface and rounding; internally split into 2 columns. + +**`testimonial-card`** — Quote card. Background `{colors.surface-card}`, text `{colors.body}`, rounded `{rounded.lg}`, padding 24px. + +### AI Timeline (signature) + +**`timeline-pill-thinking`** — Peach pill. Background `{colors.timeline-thinking}`, text `{colors.ink}`, type `{typography.caption-uppercase}` (11px / 600 / 0.88px tracking, uppercase), rounded `{rounded.pill}`, padding 4px × 10px. Marks "Thinking" stage in product timeline. + +**`timeline-pill-grep`** — Mint pill. Same shape, background `{colors.timeline-grep}`. Marks "Grepping" stage. + +**`timeline-pill-read`** — Pastel-blue pill. Background `{colors.timeline-read}`. Marks "Reading" stage. + +**`timeline-pill-edit`** — Lavender pill. Background `{colors.timeline-edit}`. Marks "Editing" stage. + +**`timeline-pill-done`** — Gold pill. Background `{colors.timeline-done}`, text `{colors.on-primary}` white. Marks "Done" stage. + +### Code + +**`code-block`** — Inline code block. Background `{colors.surface-card}`, text `{colors.ink}` in `{typography.code}`, rounded `{rounded.lg}`, padding 20px, 1px `{colors.hairline}` border. + +### Pricing + +**`pricing-tier-card`** — Background `{colors.surface-card}`, rounded `{rounded.lg}`, padding 32px, 1px `{colors.hairline}` border. + +**`pricing-tier-featured`** — Featured tier inverts to ink. Background `{colors.ink}`, text `{colors.canvas}`. Same shape, dark inversion signals "highlighted" without colored ribbon. + +### Forms & Tags + +**`text-input`** — Background `{colors.surface-card}`, text `{colors.ink}`, rounded `{rounded.md}` (8px), padding 12px × 16px, height 44px. + +**`badge-pill`** — Small uppercase pill. Background `{colors.surface-strong}`, text `{colors.ink}`, type `{typography.caption-uppercase}`, rounded `{rounded.pill}`, padding 4px × 10px. + +### CTA / Footer + +**`cta-band`** — Pre-footer "Try Cursor now" band. Background `{colors.canvas}`, centered display headline in `{typography.display-lg}`, single Cursor Orange CTA. 96px vertical padding. + +**`footer`** — Closing footer. Background `{colors.canvas}`, text `{colors.body}`. 5-column link list. 64×48px padding. + +**`footer-link`** — Background transparent, text `{colors.body}`, type `{typography.body-sm}`. + +## Do's and Don'ts + +### Do +- Reserve `{colors.primary}` (Cursor Orange) for primary CTAs and brand wordmark. +- Keep display weight at 400. The editorial voice depends on this. +- Use the cream `{colors.canvas}` page floor — never pure white. +- Render every code surface (inline, blocks, IDE panes) in JetBrains Mono. +- Use timeline pastels only inside in-product agent visualizations — never as system action colors. + +### Don't +- Don't introduce a secondary brand action color. Cursor Orange is the only one. +- Don't drop display to bold weights (700+). Magazine voice depends on 400. +- Don't add drop shadows. Hairlines + ink-on-cream contrast carry the depth. +- Don't use timeline pastels on non-timeline UI. They're scoped to the agent timeline only. +- Don't extract a CTA color from a third-party widget (cookie consent, OneTrust). The brand's CTA is what appears on actual product CTAs. + +## Responsive Behavior + +### Breakpoints + +| Name | Width | Key Changes | +|---|---|---| +| Mobile | < 640px | Hero h1 72→32px; IDE mockup collapses to single pane preview; feature grid 1-up; nav hamburger. | +| Tablet | 640–1024px | Hero h1 56px; IDE mockup compresses; feature grid 2-up. | +| Desktop | 1024–1280px | Full hero h1 72px; full multi-pane IDE mockup; feature grid 3-up. | +| Wide | > 1280px | Content caps at 1200px. | + +### Touch Targets +- Primary CTA at 40px height — at WCAG AA, padded for AAA. +- Download CTA at 44px — at AAA. + +### Collapsing Strategy +- Top nav switches to hamburger below 768px. +- IDE mockup multi-pane collapses to a single primary pane preview on mobile. +- Feature grid: 3-up → 2-up → 1-up. + +## Iteration Guide + +1. Focus on a single component at a time. +2. CTAs default to `{rounded.md}` (8px). Cards use `{rounded.lg}` (12px). +3. Variants live as separate entries inside `components:`. +4. Use `{token.refs}` everywhere — never inline hex. +5. Hover state never documented. +6. CursorGothic 400 for display, 400/500/600 for body. JetBrains Mono on every code surface. +7. Cursor Orange stays scarce. +8. Timeline pastels stay scoped to in-product agent visualizations. + +## Known Gaps + +- CursorGothic is a licensed typeface; Inter is the substitute. +- Animation timings (timeline pill entrance, IDE pane reveal) out of scope. +- In-app surfaces (code editor, chat panel, agent timeline) only partially captured via marketing IDE mockups. +- Form validation states beyond focus not visible on captured surfaces. diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index dc317f7..7c975c8 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import "./App.css"; import "./chat.css"; import "./profile.css"; +import "./cursor-theme.css"; import Login from "./pages/Login"; import Dashboard from "./pages/Dashboard"; import Repository from "./pages/Repository"; diff --git a/frontend/src/cursor-theme.css b/frontend/src/cursor-theme.css new file mode 100644 index 0000000..e5899ef --- /dev/null +++ b/frontend/src/cursor-theme.css @@ -0,0 +1,873 @@ +:root { + --paper: #0d1117; + --surface: #161b22; + --surface-soft: #21262d; + --ink: #f0f6fc; + --ink-soft: #c9d1d9; + --muted: #8b949e; + --line: #30363d; + --line-strong: #484f58; + --coral: #58a6ff; + --coral-dark: #79c0ff; + --success: #3fb950; + --danger: #f85149; + --mono: "DM Mono", "JetBrains Mono", Consolas, monospace; + --sans: "Manrope", "Helvetica Neue", Arial, sans-serif; +} + +html { + background: var(--paper); +} + +body { + background: var(--paper); + color: var(--ink); + font-family: var(--sans); +} + +button, +input, +select { + font-family: inherit; +} + +.shell { + width: min(1160px, calc(100% - 64px)); + padding: 32px 0 88px; +} + +.dashboard-shell { + overflow: hidden; +} + +.dashboard-hero { + position: relative; +} + +.dashboard-hero::after { + position: absolute; + right: 6%; + bottom: 52px; + width: 96px; + height: 1px; + background: var(--coral); + content: ""; + opacity: .7; +} + +.dashboard-hero h2 span { + color: var(--coral); +} + +.dashboard-stats, +.activity-band, +.profile-strip, +.chat-panel, +.section-heading, +.repo-toolbar, +.repo-grid { + animation: dashboard-rise .55s ease both; +} + +.dashboard-stats { + animation-delay: .08s; +} + +.activity-band { + animation-delay: .14s; +} + +.chat-panel { + animation-delay: .2s; +} + +.section-heading, +.repo-toolbar { + animation-delay: .26s; +} + +.repo-grid { + animation-delay: .32s; +} + +@keyframes dashboard-rise { + from { + opacity: 0; + transform: translateY(12px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +.topbar { + min-height: 64px; + padding-bottom: 20px; + border-bottom: 1px solid var(--line); +} + +.brand-mark { + width: 38px; + height: 38px; + margin-right: -8px; + border-radius: 8px; + background: var(--coral); + color: #fff; + font: 600 12px var(--mono); + letter-spacing: 0; +} + +.topbar h1 { + font-size: 16px; + font-weight: 600; +} + +.eyebrow { + margin-bottom: 8px; + color: var(--muted); + font: 600 10px/1.4 var(--mono); + letter-spacing: .08em; +} + +.accent { + color: var(--coral); +} + +.intro-row { + align-items: end; + padding: 88px 0 56px; +} + +.intro-row h2, +.detail-header h1 { + max-width: 720px; + margin: 0 0 16px; + font-size: clamp(38px, 5.5vw, 72px); + font-weight: 400; + line-height: 1.08; + letter-spacing: 0; +} + +.lede { + max-width: 580px; + color: var(--ink-soft); + font-size: 15px; + line-height: 1.7; +} + +.primary-button, +.outline-button, +.back-button { + min-height: 42px; + border-radius: 6px; + padding: 11px 18px; + font: 600 12px/1 var(--sans); + letter-spacing: 0; + transition: background-color .18s ease, border-color .18s ease, color .18s ease, transform .18s ease; +} + +.primary-button { + background: var(--coral); + color: #fff; + box-shadow: none; +} + +.primary-button:hover:not(:disabled) { + background: var(--coral-dark); + transform: translateY(-1px); +} + +.primary-button:disabled { + background: var(--line-strong); +} + +.outline-button { + border: 1px solid var(--line-strong); + background: var(--surface); + color: var(--ink); +} + +.outline-button:hover { + border-color: var(--ink); + background: var(--surface-soft); +} + +.back-button { + margin-bottom: 40px; + padding: 0; + border-radius: 0; + background: transparent; + color: var(--muted); +} + +.back-button:hover { + color: var(--coral); +} + +.stats-grid { + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); +} + +.stat { + min-height: 126px; + padding: 22px 24px; + border-right: 1px solid var(--line); +} + +.stat-label, +.stat-detail { + color: var(--muted); + font: 11px/1.4 var(--mono); +} + +.stat strong { + margin: 2px 0; + color: var(--ink); + font-size: 30px; + font-weight: 400; + letter-spacing: 0; +} + +.activity-band { + margin: 28px 0 0; + border-radius: 8px; + background: var(--ink); + box-shadow: none; +} + +.activity-spark { + border-color: rgba(255, 255, 255, .2); + color: var(--coral); +} + +.activity-intro h2 { + font-weight: 500; + letter-spacing: 0; +} + +.activity-metric strong { + font-weight: 400; + letter-spacing: 0; +} + +.activity-link:hover { + color: #ff9b72; +} + +.profile-strip { + margin-top: 1px; + padding: 24px 0; + border-bottom: 1px solid var(--line); +} + +.profile-strip strong { + font-weight: 600; + overflow-wrap: anywhere; +} + +.section-heading { + margin: 76px 0 22px; +} + +.section-heading h2, +.panel h2, +.chat-heading h2 { + font-weight: 500; + letter-spacing: 0; +} + +.muted { + color: var(--muted); + line-height: 1.6; +} + +.repo-toolbar { + align-items: stretch; + margin-bottom: 20px; +} + +.search-field { + width: 280px; + border-radius: 6px; + border-color: var(--line-strong); + background: var(--surface); +} + +.search-field:focus-within { + border-color: var(--coral); + box-shadow: 0 0 0 3px rgba(245, 78, 0, .1); +} + +.filter-pills { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.filter-pills button { + border: 1px solid var(--line); + border-radius: 999px; + padding: 8px 12px; + background: transparent; + color: var(--muted); + font: 11px var(--mono); + cursor: pointer; +} + +.filter-pills button:hover, +.filter-pills button.active { + border-color: var(--coral); + background: #fff4ef; + color: var(--coral); +} + +.repo-grid { + gap: 12px; +} + +.repo-card { + position: relative; + min-height: 216px; + overflow: hidden; + border: 1px solid var(--line); + border-radius: 8px; + padding: 22px; + background: var(--surface); + color: var(--ink); + text-align: left; + cursor: pointer; + transition: border-color .18s ease, transform .18s ease, box-shadow .18s ease; +} + +.repo-card:nth-child(2) { animation-delay: .04s; } +.repo-card:nth-child(3) { animation-delay: .08s; } +.repo-card:nth-child(4) { animation-delay: .12s; } + +.repo-card::before { + position: absolute; + inset: 0 auto 0 0; + width: 3px; + background: var(--coral); + content: ""; + opacity: 0; + transition: opacity .18s ease; +} + +.repo-card:hover { + border-color: var(--line-strong); + box-shadow: 0 10px 24px rgba(38, 37, 30, .07); + transform: translateY(-2px); +} + +.repo-card:hover::before { + opacity: 1; +} + +.repo-card-head { + margin-bottom: 24px; +} + +.repo-icon { + width: 34px; + height: 34px; + border-radius: 6px; + background: var(--ink); + color: var(--paper); + font: 11px var(--mono); +} + +.repo-card .arrow { + color: var(--coral); +} + +.repo-card h3 { + margin: 0 0 9px; + font-size: 18px; + font-weight: 600; + overflow-wrap: anywhere; +} + +.repo-card p { + min-height: 45px; + margin: 0 0 20px; + color: var(--ink-soft); + font-size: 13px; + line-height: 1.55; +} + +.repo-meta { + border-top: 1px solid var(--line); + padding-top: 13px; + color: var(--muted); + font: 10px var(--mono); +} + +.detail-header { + align-items: end; + padding-bottom: 52px; +} + +.detail-shell .detail-header h1 { + font-size: clamp(42px, 6vw, 80px); +} + +.detail-stats { + margin-bottom: 28px; +} + +.detail-grid { + gap: 12px; +} + +.panel { + min-height: 178px; + border: 1px solid var(--line); + border-radius: 8px; + padding: 24px; + background: var(--surface); +} + +.panel-title { + align-items: start; +} + +.status-dot { + color: var(--success); + font: 10px var(--mono); + white-space: nowrap; +} + +.pill-row { + gap: 6px; + margin: 22px 0; +} + +.pill { + border: 1px solid var(--line); + border-radius: 999px; + padding: 5px 9px; + background: var(--surface-soft); + color: var(--ink-soft); + font: 10px var(--mono); +} + +.architecture-line { + display: flex; + align-items: center; + gap: 12px; + margin: 26px 0 14px; + color: var(--coral); + font: 12px var(--mono); +} + +.architecture-line strong { + color: var(--ink); + font-weight: 500; +} + +.file-list, +.source-list { + margin-top: 18px; +} + +.file-list li, +.source-list > div { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + border-top: 1px solid var(--line); + padding: 11px 0; +} + +.file-list li span, +.source-list > div span { + color: var(--muted); + font: 10px var(--mono); + text-align: right; +} + +code { + color: var(--ink); + font-family: var(--mono); + font-size: 11px; + overflow-wrap: anywhere; +} + +.notice { + margin: 20px 0; + border-radius: 6px; + padding: 13px 15px; + font: 12px var(--mono); +} + +.notice.error { + border: 1px solid #f0b7c7; + background: #fff4f6; + color: var(--danger); +} + +.loading-state { + display: grid; + min-height: 100vh; + place-items: center; + background: var(--paper); + color: var(--muted); + font: 12px var(--mono); +} + +.login-shell { + display: grid; + grid-template-columns: minmax(0, 1.2fr) minmax(300px, .8fr); + align-items: center; + gap: clamp(56px, 10vw, 148px); + min-height: 100vh; + max-width: 1160px; +} + +.login-copy { + max-width: 680px; +} + +.login-shell h1 { + max-width: 620px; + margin: 0 0 20px; + font-size: clamp(48px, 6.2vw, 82px); + font-weight: 400; + line-height: 1.02; + letter-spacing: 0; +} + +.login-shell .lede { + margin-bottom: 30px; + font-size: 17px; +} + +.login-flow { + position: relative; + align-self: center; + border-top: 1px solid var(--line-strong); + border-bottom: 1px solid var(--line-strong); + padding: 22px 0 18px; + animation: reveal-up .7s .16s both; +} + +.flow-heading, +.flow-footer { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; +} + +.flow-heading .eyebrow { + margin: 0; +} + +.flow-live { + display: flex; + align-items: center; + gap: 7px; + color: var(--success); + font: 10px var(--mono); +} + +.flow-live i { + width: 6px; + height: 6px; + border-radius: 50%; + background: var(--success); + box-shadow: 0 0 0 4px rgba(31, 138, 101, .12); +} + +.flow-line { + position: absolute; + top: 67px; + bottom: 55px; + left: 17px; + width: 1px; + background: var(--line); +} + +.flow-step { + position: relative; + display: grid; + grid-template-columns: 35px 1fr; + gap: 14px; + align-items: start; + padding: 24px 0 0; +} + +.flow-step > span { + z-index: 1; + display: grid; + width: 35px; + height: 35px; + place-items: center; + border: 1px solid var(--line-strong); + border-radius: 50%; + background: var(--paper); + color: var(--muted); + font: 10px var(--mono); +} + +.flow-step-active > span { + border-color: var(--coral); + background: var(--coral); + color: #fff; +} + +.login-flow { + background: rgba(22, 27, 34, .58); + box-shadow: 0 24px 80px rgba(1, 4, 9, .24); + padding-right: 22px; + padding-left: 22px; +} + +.flow-step > span { + background: var(--paper); +} + +.profile-menu { + background: var(--surface); + box-shadow: 0 18px 42px rgba(1, 4, 9, .42); +} + +.profile-menu a:hover { + background: var(--surface-soft); +} + +.profile-menu > button:hover { + background: rgba(248, 81, 73, .12); +} + +.chat-panel { + background: var(--surface); + box-shadow: 0 16px 44px rgba(1, 4, 9, .18); +} + +.suggestions button, +.provider-control select, +.chat-form input { + background: var(--surface-soft); + color: var(--ink); +} + +.chat-answer { + background: var(--surface-soft); +} + +.answer-content code { + background: var(--surface); +} + +.answer-content pre { + background: #010409; +} + +.flow-step strong { + display: block; + padding-top: 3px; + font-size: 14px; + font-weight: 600; +} + +.flow-step p { + margin: 6px 0 0; + color: var(--muted); + font-size: 12px; + line-height: 1.5; +} + +.flow-footer { + margin: 28px 0 0 49px; + color: var(--muted); + font: 9px var(--mono); + letter-spacing: .08em; +} + +@keyframes reveal-up { + from { opacity: 0; transform: translateY(12px); } + to { opacity: 1; transform: translateY(0); } +} + +.login-shell > .login-copy { + animation: reveal-up .7s both; +} + +:focus-visible { + outline: 3px solid rgba(245, 78, 0, .35); + outline-offset: 3px; +} + +@media (max-width: 760px) { + .shell { + width: min(100% - 32px, 600px); + padding-top: 20px; + } + + .topbar, + .intro-row, + .detail-header { + align-items: flex-start; + flex-direction: column; + } + + .topbar { + flex-direction: row; + align-items: center; + } + + .topbar > div:first-of-type { + margin-right: 0; + } + + .topbar .profile-area { + margin-left: auto; + } + + .intro-row { + padding: 64px 0 40px; + } + + .dashboard-hero::after { + right: 0; + bottom: 34px; + width: 54px; + } + + .intro-row .primary-button, + .detail-header .outline-button { + width: 100%; + } + + .login-shell { + display: block; + min-height: 0; + padding-top: 18vh; + } + + .login-flow { + padding-right: 0; + padding-left: 0; + background: transparent; + box-shadow: none; + } + + .login-flow { + margin-top: 72px; + } + + .stats-grid { + grid-template-columns: repeat(2, 1fr); + } + + .stat:nth-child(2) { + border-right: 0; + } + + .stat:nth-child(-n + 2) { + border-bottom: 1px solid var(--line); + } + + .activity-band { + grid-template-columns: 1fr 1fr; + } + + .activity-intro, + .activity-link { + grid-column: 1 / -1; + } + + .activity-metric { + border-left: 0; + padding-left: 0; + } + + .profile-strip { + grid-template-columns: 1fr; + gap: 16px; + } + + .repo-toolbar { + align-items: stretch; + flex-direction: column; + } + + .search-field { + width: 100%; + } + + .detail-header { + padding-bottom: 36px; + } + + .detail-grid { + grid-template-columns: 1fr; + } + + .wide-panel { + grid-column: auto; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + animation-duration: .01ms !important; + animation-iteration-count: 1 !important; + scroll-behavior: auto !important; + transition-duration: .01ms !important; + } +} + +@media (max-width: 480px) { + .topbar .eyebrow, + .topbar h1 { + display: none; + } + + .user-chip span { + min-width: 0; + } + + .stats-grid { + grid-template-columns: 1fr; + } + + .stat, + .stat:nth-child(2) { + border-right: 0; + border-bottom: 1px solid var(--line); + } + + .stat:last-child { + border-bottom: 0; + } + + .activity-band { + grid-template-columns: 1fr; + } + + .activity-metric { + border-top: 1px solid rgba(255, 255, 255, .16); + padding-top: 13px; + } + + .file-list li, + .source-list > div { + align-items: flex-start; + flex-direction: column; + gap: 5px; + } + + .file-list li span, + .source-list > div span { + text-align: left; + } +} diff --git a/frontend/src/pages/Dashboard.jsx b/frontend/src/pages/Dashboard.jsx index ed6a9ee..12db324 100644 --- a/frontend/src/pages/Dashboard.jsx +++ b/frontend/src/pages/Dashboard.jsx @@ -91,7 +91,7 @@ function Dashboard({ onOpenRepository, onSignedOut }) { }); return ( -
+
GA
@@ -138,10 +138,12 @@ function Dashboard({ onOpenRepository, onSignedOut }) {
)}
-
+

Your workspace

-

A clearer map of how you build.

+

+ A clearer map of how you build. +

Analyze repository structure, technologies, activity, and architecture from one quiet workspace. @@ -156,7 +158,10 @@ function Dashboard({ onOpenRepository, onSignedOut }) {

{error &&
{error}
} -
+
-
+
{filteredRepositories.map((repo) => ( +
+

Repository intelligence

+

See the shape of your code.

+

+ Connect GitHub to map your repositories, understand the architecture, + and find the projects where your work is moving fastest. +

+ +
+
); }