krishnadhulipalla commited on
Commit
1094dcb
·
1 Parent(s): 366eed6

Add design skill and UI theming updates

Browse files

Add a new Portfolio Design System skill and overhaul UI theming and styling; update backend content and streaming logic.

- Add .agents/skills/portfolio-design/SKILL.md describing typography, colors, background textures, and Spider‑Verse chromatic/glitch effects for reuse across components.
- UI: load Google fonts, introduce theme variables (light/dark), CSS tokens, and spider-title/hover classes; apply extensive styling refactor in App.tsx (theme toggle, new visuals, improved components, accessibility/spacing, updated avatar, suggestions styling, code block UI, and many spider-hover/title usages).
- CSS: add root color variables, dark-mode overrides, font stacks, chroma text shadow utilities, and scrollbar theming in ui/src/index.css.
- Backend/content: update personal_data/bio.md and backend/data/all_chunks.json to new formatted bio/header, adjust get_career_timeline in backend/agent.py with revised roles/education/projects, and improve event streaming in backend/api.py to yield token chunks and handle disconnects.
- Data stores: update faiss store LFS pointers for v30 index files and remove older v41/v42 index files.

These changes introduce a reusable design system, improve theming and UX across the app, correct and expand personal content, and make streaming behavior more granular and robust.

.agents/skills/portfolio-design/SKILL.md ADDED
@@ -0,0 +1,69 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ name: Portfolio Design System
3
+ description: Recreates the design tokens, typography, colors, and specific visual glitch/Spider-Verse animations of the portfolio website.
4
+ ---
5
+
6
+ # Portfolio Design System Skill
7
+
8
+ This skill provides instructions on how to replicate the portfolio website's aesthetic for other applications like a personal chatbot. It focuses exclusively on the design tokens, fonts, colors, layouts, and the unique CSS-based animations/effects.
9
+
10
+ ## 1. Typography
11
+ Use the following font pairings sourced from Google Fonts:
12
+ - **Headings & Display Text:** `Source Serif 4` (serif) - Use for all major titles (`display-heading`, `section-title`, etc.), usually with tight letter spacing (`letter-spacing: -0.03em`).
13
+ - **Body Text:** `IBM Plex Sans` (sans-serif) - Standard reading text with `line-height: 1.65` and `letter-spacing: -0.01em`.
14
+ - **Labels, Eyebrows & Meta:** `IBM Plex Mono` (monospace) - Used for small caps labels, numbering, and metadata. Usually `text-transform: uppercase` with generous tracking (`letter-spacing: 0.12em`).
15
+
16
+ ## 2. Color Palette
17
+ Implement a theme-aware color system with Light and Dark modes.
18
+
19
+ **Light Mode:**
20
+ - Backgrounds: Paper (`#f4efe6`), Strong Paper (`#fcf8f1`), Panel (`#f7f1e8`)
21
+ - Text/Ink: Primary Ink (`#171412`), Soft Ink (`#5d564f`)
22
+ - Accents:
23
+ - Blue: `#4e67b6`
24
+ - Rust: `#ba5a45`
25
+ - Teal: `#1a766c`
26
+
27
+ **Dark Mode:**
28
+ - Backgrounds: Paper (`#11100f`), Strong Paper (`#181614`), Panel (`#141210`)
29
+ - Text/Ink: Primary Ink (`#f2ede4`), Soft Ink (`#b7aea2`)
30
+ - Accents:
31
+ - Blue: `#8fa7ff`
32
+ - Rust: `#ef8d74`
33
+ - Teal: `#62c2b4`
34
+
35
+ *Note: Use drop shadows consistently combining the blue and rust accent colors with low opacity (e.g., `4px 4px 0 var(--shadow-blue), -2px -2px 0 rgba(186, 90, 69, 0.04)`).*
36
+
37
+ ## 3. Background Textures
38
+ The site depth is achieved through layered background textures:
39
+ 1. **Noise Dots:** Fixed `radial-gradient` creating a dot grid pattern with multiply blend mode and opacity.
40
+ 2. **Vertical Grid Lines:** Fixed `linear-gradient` creating thin vertical structural lines across the screen.
41
+
42
+ ## 4. "Spider-Verse" Glitch Animations
43
+ The most unique feature is the chromatic aberration and glitch animations applied to text.
44
+
45
+ **Methodology:**
46
+ - An element with `spider-title` or `spider-field` adds a `data-text` attribute matching the element's inner text.
47
+ - CSS `::before` and `::after` pseudo-elements are generated using `content: attr(data-text)`, layered directly on top of the original text text (`position: absolute; inset: 0`).
48
+ - The `::before` element is tinted Blue and the `::after` element is tinted Rust, utilizing `mix-blend-mode: multiply` (Light mode) or `mix-blend-mode: screen` (Dark mode).
49
+
50
+ **Animation Types:**
51
+ 1. **Calm Glitch (`spider-title`)**:
52
+ - Always active.
53
+ - Gently animates the X and Y translation of the pseudo-elements periodically using `steps(2, end)` or `steps(4, end)` to create a jerky, frame-by-frame offset effect (`calmSpiderBlue` and `calmSpiderRust` keyframes).
54
+ 2. **Hover Glitch (`spider-field`, `spider-hover`)**:
55
+ - Inactive by default (`opacity: 0`).
56
+ - On hover, opacity increases and triggers the jitter shift keyframes, adding a brief chromatic tear to the element.
57
+ 3. **Chroma Ink Text Shadow**:
58
+ - Text shadows are used on headings like `chroma-ink` to create a static chromatic aberration edge: `text-shadow: 0.015em 0 0 var(--spider-blue), -0.015em 0 0 var(--spider-rust)`.
59
+ 4. **Intense Datamoshing and Slicing**:
60
+ - Advanced glitch elements use `clip-path` slicing (`glitch-slice-a`, `glitch-slice-b`) and horizontal scanning bands (`glitch-scan` with `datamoshBand` keyframes).
61
+ - Random blur (`glitchPulse`) and physical jitter (`digitJitter`) keyframes simulate heavy digital corruption.
62
+
63
+ ## Implementation Guide
64
+ When applying this to a new component (like a chatbot):
65
+ 1. Establish the color variables first.
66
+ 2. Load the google fonts (`IBM Plex Sans`, `IBM Plex Mono`, `Source Serif 4`).
67
+ 3. Add the structural background gradients.
68
+ 4. Wrap any headers or important labels in elements with `spider-hover` or `spider-title` classes, ensuring you mirror the text in the `data-text` attribute.
69
+ 5. Apply the `::before`/`::after` CSS to create the RGB offset effect whenever hovered or active.
backend/agent.py CHANGED
@@ -533,23 +533,25 @@ def get_career_timeline() -> str:
533
  return (
534
  "## 🧭 Career Timeline (reverse-chronological)\n"
535
  "### Experience\n"
536
- "- **ML Research Engineer – Cloud Systems LLC** · Jul 2024 – Present · Remote\n"
537
- " - Data pipelines (batch & real-time), complex SQL, automated ETL.\n"
538
- "- **ML Research Engineer Virginia Tech** · Sep 2024 – Jul 2024 · Blacksburg, VA\n"
539
- " - LLM pipelines (DNABERT, HyenaDNA), LoRA/soft prompting, 94%+ accuracy; Airflow automation.\n"
540
- "- **Research Assistant Virginia Tech** · Jun 2023 May 2024 · Blacksburg, VA\n"
541
- " - Genomic ETL on Airflow/AWS; CI/CD for retraining; runtime optimizations.\n"
542
- "- **Data Engineer UJR Technologies** · Jul 2021 Dec 2022 · Hyderabad, India\n"
543
- " - Kafka/Spark streaming migration; Snowflake perf; AWS ECS microservices.\n"
 
 
 
544
  "\n"
545
  "### Education\n"
546
- "- **M.S., Computer Science – Virginia Tech** · Jan 2023 – Dec 2024 (GPA 3.95/4)\n"
547
- "- **B.E., Computer Science Anna University** · Jul 2018 May 2022 (CGPA 8.24/10)\n"
548
  "\n"
549
  "### Selected Projects\n"
550
- "- **LLM-Based Android Agent for UI Automation** – 80%+ step accuracy; +25% goal-success with memory/reflection.\n"
551
- "- **ProxyTuNER** – +8% F1 via proxy-tuning and expert logit ensembling; 70% compute reduction.\n"
552
- "- **IntelliMeet** – decentralized video; <200ms latency; on-device ML for attention; STT + summarization.\n"
553
  )
554
 
555
  @tool("analyze_job_description")
 
533
  return (
534
  "## 🧭 Career Timeline (reverse-chronological)\n"
535
  "### Experience\n"
536
+ "- **AI Software Engineer – Tabner Inc** · Jul 2025 – Present · Remote (US)\n"
537
+ " - Built agentic RAG pipelines (LangGraph, Claude), reducing report time from 4h to 2m.\n"
538
+ " - Improved reliability to 96% with multi-agent orchestration, tool calling, guardrails.\n"
539
+ "- **Machine Learning Engineer Virginia Tech** · Aug 2024 – Jul 2025 · Blacksburg, VA\n"
540
+ " - Built PyTorch training pipelines for large sequence models; applied LoRA fine-tuning reducing training time.\n"
541
+ "- **Graduate Research Assistant Virginia Tech** · Jun 2023 – May 2024 · Blacksburg, VA\n"
542
+ " - Trained/fine-tuned LLaMA & SciBERT for cross-domain NER; automated HPC workflows.\n"
543
+ "- **Software Engineer UJR Technologies** · Jul 2021 – Dec 2022 · Hyderabad, India\n"
544
+ " - Built typed Python SDK, optimized BigQuery, and hardened CI/CD pipelines in GitHub Actions.\n"
545
+ "- **Software Engineer Intern – Cognizant** · Dec 2019 – May 2020 · Chennai, India\n"
546
+ " - Developed Python-based sports event management system.\n"
547
  "\n"
548
  "### Education\n"
549
+ "- **M.Eng., Computer Science – Virginia Tech**\n"
550
+ "- **B.Tech., Computer Science & EngineeringVel Tech University**\n"
551
  "\n"
552
  "### Selected Projects\n"
553
+ "- **CoreLink AI, A2A Finance Agent Runtime** – Stabilized policy-constrained flows; 87% task success rate on OfficeQA.\n"
554
+ "- **K8s Agent Sandbox** – Kubernetes Operator for isolated LLM agent execution (98% reliability).\n"
 
555
  )
556
 
557
  @tool("analyze_job_description")
backend/api.py CHANGED
@@ -18,7 +18,7 @@ from . import config
18
 
19
  # logging + helpers
20
  import logging, os, time
21
- from datetime import datetime, timezone
22
  from fastapi.responses import JSONResponse, FileResponse, RedirectResponse
23
 
24
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s :: %(message)s")
@@ -158,7 +158,16 @@ async def _event_stream_with_config(thread_id: str, message: str, request: Reque
158
  yield {"event": "thread", "data": thread_id}
159
  try:
160
  async for ev in lg_app.astream_events({"messages": [("user", message)]}, config=config, version="v2"):
161
- ...
 
 
 
 
 
 
 
 
 
162
  finally:
163
  yield {"event": "done", "data": "1"}
164
 
 
18
 
19
  # logging + helpers
20
  import logging, os, time
21
+ from datetime import datetime, timezone, timedelta
22
  from fastapi.responses import JSONResponse, FileResponse, RedirectResponse
23
 
24
  logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s :: %(message)s")
 
158
  yield {"event": "thread", "data": thread_id}
159
  try:
160
  async for ev in lg_app.astream_events({"messages": [("user", message)]}, config=config, version="v2"):
161
+ if ev["event"] == "on_chat_model_stream":
162
+ chunk = ev["data"]["chunk"].content
163
+ if isinstance(chunk, list):
164
+ text = "".join(getattr(p, "text", "") or str(p) for p in chunk)
165
+ else:
166
+ text = chunk or ""
167
+ if text:
168
+ yield {"event": "token", "data": text}
169
+ if await request.is_disconnected():
170
+ break
171
  finally:
172
  yield {"event": "done", "data": "1"}
173
 
backend/data/all_chunks.json CHANGED
@@ -1,12 +1,12 @@
1
  [
2
  {
3
- "text": "[HEADER] Krishna Vamsi Dhulipalla is a Software Engineer specializing in generic workflows and AI platforms. He currently works at **Cloud Systems LLC**, where he architects LangGraph-based agents to automate data auditing. Previously, he served as a Machine Learning Engineer at **Virginia Tech**, optimizing genomic models with LoRA/soft prompting, and as a Software Engineer at **UJR Technologies**, building ML SDKs and CI/CD pipelines. He holds an M.S. in Computer Science from Virginia Tech (Dec 2024) and has significant expertise in **LangGraph**, **Kubernetes**, **PyTorch**, and **MLOps**.\n\nKrishna Vamsi Dhulipalla is a Software Engineer specializing in generic workflows and AI platforms. He currently works at **Cloud Systems LLC**, where he architects LangGraph-based agents to automate data auditing. Previously, he served as a Machine Learning Engineer at **Virginia Tech**, optimizing genomic models with LoRA/soft prompting, and as a Software Engineer at **UJR Technologies**, building ML SDKs and CI/CD pipelines. He holds an M.S. in Computer Science from Virginia Tech (Dec 2024) and has significant expertise in **LangGraph**, **Kubernetes**, **PyTorch**, and **MLOps**.",
4
  "metadata": {
5
  "source": "bio.md",
6
- "header": "Krishna Vamsi Dhulipalla is a Software Engineer specializing in generic workflows and AI platforms. He currently works at **Cloud Systems LLC**, where he architects LangGraph-based agents to automate data auditing. Previously, he served as a Machine Learning Engineer at **Virginia Tech**, optimizing genomic models with LoRA/soft prompting, and as a Software Engineer at **UJR Technologies**, building ML SDKs and CI/CD pipelines. He holds an M.S. in Computer Science from Virginia Tech (Dec 2024) and has significant expertise in **LangGraph**, **Kubernetes**, **PyTorch**, and **MLOps**.",
7
- "chunk_id": "bio.md_#0_9ac3944c",
8
- "has_header": false,
9
- "word_count": 83
10
  }
11
  },
12
  {
 
1
  [
2
  {
3
+ "text": "[HEADER] # Krishna Vamsi Dhulipalla\n\n# Krishna Vamsi Dhulipalla\n**AI Engineer**\n+1 540-558-3528 | [krishnadhulipalla13@gmail.com](mailto:krishnadhulipalla13@gmail.com)\n\nI am an AI Engineer specializing in building production LLM systems, agentic workflows, and RAG pipelines for enterprise use cases. I have extensive experience designing multi-agent systems with LangGraph, MCP servers, and API-driven orchestration. \n\nMy work ranges from rapid prototyping of AI agents and integrating enterprise data systems to deploying scalable AI services on AWS and Kubernetes. Currently, I work as an AI Software Engineer at Tabner Inc., previously holding roles as a Machine Learning Engineer and Graduate Research Assistant at Virginia Tech, where I also completed my M.Eng. in Computer Science.\n\n**Core Proficiencies:** Python, TypeScript, LangGraph, RAG architectures, Multi-Agent Orchestration, AWS Bedrock, OpenAI API, PyTorch, LoRA fine-tuning, AWS, Kubernetes, FastAPI, and PostgreSQL.",
4
  "metadata": {
5
  "source": "bio.md",
6
+ "header": "# Krishna Vamsi Dhulipalla",
7
+ "chunk_id": "bio.md_#0_c5fdc65d",
8
+ "has_header": true,
9
+ "word_count": 123
10
  }
11
  },
12
  {
backend/data/faiss_store/v30_1000-250/index.faiss CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:bd1d8e7e716c51242a7b4af9c2f8a7900de4a0a5b7d90374286abd2c75bd3e7c
3
  size 184365
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8d1db4c2cefa1dd60438240fdc9c583bd8b2e847f79f39d0448712bc44074409
3
  size 184365
backend/data/faiss_store/v30_1000-250/index.pkl CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:685b70a0fbd47f8ccb3e9f940de304370dd699ef9d72e0dd17256a007d3b6528
3
- size 29588
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c4c5e78343da7bf6f3aa2e85e88d245516041431fa1424395de8e16bc86136d5
3
+ size 28814
backend/data/faiss_store/v41_1000-250/index.faiss DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:8ee9d834545d3be697618907fc9bef3e70b8f75da655efbbedcbfc6b1d5b439e
3
- size 63021
 
 
 
 
backend/data/faiss_store/v41_1000-250/index.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:bad387134da4e80a88b9ffb304a0c427a8024c7b1bff07bb3e9a96856978b7d9
3
- size 39546
 
 
 
 
backend/data/faiss_store/v42_1000-250/index.faiss DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:401590397d9754522466693adaca908407d119593e187dfb212e938bb5313eb4
3
- size 64557
 
 
 
 
backend/data/faiss_store/v42_1000-250/index.pkl DELETED
@@ -1,3 +0,0 @@
1
- version https://git-lfs.github.com/spec/v1
2
- oid sha256:1adfd6e3ac55f44a3d8acde031bf6c5a734ff0e6ab665c61e9cddbbbe12c5e58
3
- size 40986
 
 
 
 
personal_data/bio.md CHANGED
@@ -1 +1,9 @@
1
- Krishna Vamsi Dhulipalla is a Software Engineer specializing in generic workflows and AI platforms. He currently works at **Cloud Systems LLC**, where he architects LangGraph-based agents to automate data auditing. Previously, he served as a Machine Learning Engineer at **Virginia Tech**, optimizing genomic models with LoRA/soft prompting, and as a Software Engineer at **UJR Technologies**, building ML SDKs and CI/CD pipelines. He holds an M.S. in Computer Science from Virginia Tech (Dec 2024) and has significant expertise in **LangGraph**, **Kubernetes**, **PyTorch**, and **MLOps**.
 
 
 
 
 
 
 
 
 
1
+ # Krishna Vamsi Dhulipalla
2
+ **AI Engineer**
3
+ +1 540-558-3528 | [krishnadhulipalla13@gmail.com](mailto:krishnadhulipalla13@gmail.com)
4
+
5
+ I am an AI Engineer specializing in building production LLM systems, agentic workflows, and RAG pipelines for enterprise use cases. I have extensive experience designing multi-agent systems with LangGraph, MCP servers, and API-driven orchestration.
6
+
7
+ My work ranges from rapid prototyping of AI agents and integrating enterprise data systems to deploying scalable AI services on AWS and Kubernetes. Currently, I work as an AI Software Engineer at Tabner Inc., previously holding roles as a Machine Learning Engineer and Graduate Research Assistant at Virginia Tech, where I also completed my M.Eng. in Computer Science.
8
+
9
+ **Core Proficiencies:** Python, TypeScript, LangGraph, RAG architectures, Multi-Agent Orchestration, AWS Bedrock, OpenAI API, PyTorch, LoRA fine-tuning, AWS, Kubernetes, FastAPI, and PostgreSQL.
ui/index.html CHANGED
@@ -4,6 +4,9 @@
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/png" href="/favicon.png" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
 
 
 
7
  <title>Krishn'a Chatbot</title>
8
  </head>
9
  <body>
 
4
  <meta charset="UTF-8" />
5
  <link rel="icon" type="image/png" href="/favicon.png" />
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <link rel="preconnect" href="https://fonts.googleapis.com">
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
9
+ <link href="https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:ital,wght@0,100..700;1,100..700&family=IBM+Plex+Sans:ital,wght@0,100..700;1,100..700&family=Source+Serif+4:ital,opsz,wght@0,8..60,200..900;1,8..60,200..900&display=swap" rel="stylesheet">
10
  <title>Krishn'a Chatbot</title>
11
  </head>
12
  <body>
ui/src/App.tsx CHANGED
@@ -15,6 +15,8 @@ import {
15
  DocumentDuplicateIcon,
16
  ChatBubbleLeftRightIcon,
17
  CommandLineIcon,
 
 
18
  } from "@heroicons/react/24/outline";
19
  import ReactMarkdown from "react-markdown";
20
  import remarkGfm from "remark-gfm";
@@ -26,16 +28,7 @@ import type { ThreadMeta } from "./threads";
26
  // --- Constants & Config ---
27
  const APP_TITLE = "Krishna's Digital Twin";
28
  const BOT_AVATAR =
29
- "https://api.dicebear.com/9.x/bottts-neutral/svg?seed=Krishna1&backgroundColor=6366f1"; // Cleaner 3D-ish Robot
30
-
31
- /*
32
- const DID_YOU_KNOW = [
33
- "Krishna achieved a 3.95 GPA during his M.S. at Virginia Tech.",
34
- "Krishna built an agent that automates Android UI tasks with 80%+ accuracy.",
35
- "Krishna optimized genomic ETL pipelines reducing runtime by 70%.",
36
- "Krishna specializes in building autonomous agents and RAG systems.",
37
- ];
38
- */
39
 
40
  const SUGGESTIONS = [
41
  {
@@ -60,32 +53,6 @@ const SUGGESTIONS = [
60
  },
61
  ];
62
 
63
- /*
64
- function DidYouKnowRotator() {
65
- const [index, setIndex] = useState(0);
66
- useEffect(() => {
67
- const interval = setInterval(() => {
68
- setIndex((prev) => (prev + 1) % DID_YOU_KNOW.length);
69
- }, 5000); // Rotate every 5 seconds
70
- return () => clearInterval(interval);
71
- }, []);
72
-
73
- return (
74
- <div className="mt-8 p-4 rounded-xl bg-white/5 border border-white/5 max-w-lg mx-auto text-center animate-in fade-in slide-in-from-bottom-2 duration-700">
75
- <p className="text-[10px] uppercase tracking-widest text-zinc-500 font-semibold mb-2">
76
- Did you know?
77
- </p>
78
- <p
79
- key={index}
80
- className="text-sm text-zinc-300 italic min-h-[20px] transition-all duration-500"
81
- >
82
- "{DID_YOU_KNOW[index]}"
83
- </p>
84
- </div>
85
- );
86
- }
87
- */
88
-
89
  // --- Helper: Date Grouping for Sidebar ---
90
  function groupThreadsByDate(threads: ThreadMeta[]) {
91
  const today = new Date();
@@ -135,6 +102,22 @@ export default function App() {
135
  const inputRef = useRef<HTMLTextAreaElement | null>(null);
136
  const prevThreadId = useRef<string | null>(null);
137
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
138
  // --- Voice Input Logic ---
139
  const [isListening, setIsListening] = useState(false);
140
  const recognitionRef = useRef<any>(null);
@@ -205,41 +188,46 @@ export default function App() {
205
  sendMessage();
206
  }
207
  },
208
- [sendMessage]
209
  );
210
 
211
  const groupedThreads = useMemo(() => groupThreadsByDate(threads), [threads]);
212
 
213
  return (
214
- <div className="flex h-screen w-screen bg-[#05050A] text-zinc-100 font-sans selection:bg-indigo-500/30">
215
- {/* Background Ambience */}
216
- <div className="fixed inset-0 z-0 pointer-events-none">
217
- <div className="absolute top-[-10%] right-[-5%] w-[500px] h-[500px] bg-indigo-900/20 rounded-full blur-[128px]" />
218
- <div className="absolute bottom-[-10%] left-[-10%] w-[600px] h-[600px] bg-purple-900/10 rounded-full blur-[128px]" />
219
- </div>
220
 
221
  {/* --- Sidebar --- */}
222
- <aside className="hidden md:flex flex-col w-[280px] z-10 bg-zinc-950/40 backdrop-blur-xl border-r border-white/5 transition-all">
223
  {/* Header */}
224
- <div className="p-4 border-b border-white/5">
225
  <button
226
  onClick={newChat}
227
- className="group flex items-center gap-3 w-full px-3 py-2.5 rounded-xl bg-white/5 hover:bg-white/10 border border-white/5 transition-all duration-200"
228
  >
229
- <div className="p-1.5 rounded-lg bg-indigo-500/10 text-indigo-400 group-hover:text-indigo-300">
230
  <SparklesIcon className="w-5 h-5" />
231
  </div>
232
- <span className="text-sm font-medium text-zinc-200">New Chat</span>
 
 
 
 
 
233
  </button>
234
  </div>
235
 
236
  {/* History List */}
237
- <div className="flex-1 overflow-y-auto p-3 space-y-6 scrollbar-thin scrollbar-thumb-zinc-800">
238
  {Object.entries(groupedThreads).map(([label, group]) => {
239
  if (group.length === 0) return null;
240
  return (
241
  <div key={label}>
242
- <h3 className="px-3 mb-2 text-[11px] font-semibold uppercase tracking-wider text-zinc-500">
 
 
 
243
  {label}
244
  </h3>
245
  <div className="space-y-0.5">
@@ -249,12 +237,13 @@ export default function App() {
249
  onClick={() => setActiveThread(t)}
250
  className={`group relative flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer transition-colors ${
251
  active?.id === t.id
252
- ? "bg-white/10 text-zinc-100"
253
- : "text-zinc-400 hover:bg-white/5 hover:text-zinc-200"
254
  }`}
255
  >
256
  <span
257
- className="text-sm flex-1 line-clamp-1 break-all"
 
258
  title={t.title || "New Conversation"}
259
  >
260
  {t.title || "New Conversation"}
@@ -266,7 +255,7 @@ export default function App() {
266
  if (window.confirm("Delete thread?"))
267
  deleteThread(t.id);
268
  }}
269
- className="opacity-0 group-hover:opacity-100 p-1 rounded hover:bg-red-500/20 text-zinc-500 hover:text-red-400 transition-all"
270
  >
271
  <TrashIcon className="w-3.5 h-3.5" />
272
  </button>
@@ -279,12 +268,13 @@ export default function App() {
279
  </div>
280
 
281
  {/* Sidebar Footer */}
282
- <div className="p-4 border-t border-white/5">
283
  <a
284
  href="https://github.com/krishna-dhulipalla/LangGraph_ChatBot"
285
  target="_blank"
286
  rel="noreferrer"
287
- className="flex items-center gap-2 text-xs text-zinc-500 hover:text-zinc-300 transition-colors"
 
288
  >
289
  <CommandLineIcon className="w-4 h-4" />
290
  <span>View Source Code</span>
@@ -295,31 +285,45 @@ export default function App() {
295
  {/* --- Main Chat Area --- */}
296
  <main className="relative z-10 flex-1 flex flex-col h-full overflow-hidden">
297
  {/* Top Navigation */}
298
- <header className="h-16 flex items-center justify-between px-6 border-b border-white/5 bg-zinc-950/20 backdrop-blur-sm">
299
  <div className="flex items-center gap-3">
300
  <div className="relative">
301
  <img
302
  src={BOT_AVATAR}
303
  alt="Bot"
304
- className="w-9 h-9 rounded-full ring-2 ring-indigo-500/20 shadow-lg shadow-indigo-500/10"
305
  />
306
- <span className="absolute bottom-0 right-0 w-2.5 h-2.5 bg-emerald-500 border-2 border-[#05050A] rounded-full"></span>
307
  </div>
308
  <div>
309
- <h1 className="text-sm font-semibold text-zinc-100">
 
 
 
310
  {APP_TITLE}
311
  </h1>
312
- <p className="text-[10px] text-zinc-400 flex items-center gap-1">
313
- <span className="inline-block w-1 h-1 rounded-full bg-indigo-500 animate-pulse" />
314
  Online & Ready
315
  </p>
316
  </div>
317
  </div>
318
 
319
  <div className="flex items-center gap-2">
 
 
 
 
 
 
 
 
 
 
 
320
  <button
321
  onClick={clearChat}
322
- className="p-2 text-zinc-400 hover:text-zinc-200 hover:bg-white/5 rounded-full transition-all"
323
  title="Clear Chat"
324
  >
325
  <ArrowPathIcon className="w-5 h-5" />
@@ -331,39 +335,38 @@ export default function App() {
331
  <div className="flex-1 overflow-y-auto p-4 md:p-8 scroll-smooth">
332
  {messages.length === 0 ? (
333
  <div className="h-full flex flex-col items-center justify-center max-w-2xl mx-auto animate-in fade-in duration-500">
334
- <div className="mb-8 p-4 rounded-full bg-white/5 ring-1 ring-white/10 shadow-2xl shadow-indigo-500/10">
335
- <ChatBubbleLeftRightIcon className="w-10 h-10 text-indigo-400" />
336
  </div>
337
- <h2 className="text-2xl font-semibold text-transparent bg-clip-text bg-gradient-to-br from-zinc-100 to-zinc-500 mb-2">
 
 
 
338
  How can I help you today?
339
  </h2>
340
- <p className="text-zinc-400 text-sm mb-6 max-w-md text-center">
341
  I'm Krishna's digital twin. Ask me about his architecture
342
  skills, recent projects, or schedule a meeting.
343
  </p>
344
 
345
- {/* <DidYouKnowRotator /> */}
346
- {/* <div className="h-8"></div> */}
347
-
348
  {/* Suggestions Grid */}
349
- <div className="grid grid-cols-1 md:grid-cols-2 gap-3 w-full">
350
  {SUGGESTIONS.map((s, i) => (
351
  <button
352
  key={i}
353
  onClick={() => {
354
- // Just send immediately without prefilling input state visually
355
  if (!isStreaming) send(s.text);
356
  }}
357
- className="group flex items-start gap-4 p-4 rounded-2xl bg-white/5 hover:bg-white/10 border border-white/5 hover:border-white/10 text-left transition-all hover:-translate-y-0.5 hover:shadow-lg hover:shadow-indigo-500/10"
358
  >
359
- <span className="text-xl grayscale group-hover:grayscale-0 transition-all">
360
  {s.icon}
361
  </span>
362
  <div>
363
- <div className="text-sm font-medium text-zinc-200 group-hover:text-indigo-300 transition-colors">
364
  {s.title}
365
  </div>
366
- <div className="text-xs text-zinc-500 mt-1 line-clamp-1 group-hover:text-zinc-400 transition-colors">
367
  {s.text}
368
  </div>
369
  </div>
@@ -383,9 +386,9 @@ export default function App() {
383
  {/* Streaming Indicator */}
384
  {isStreaming && !hasFirstToken && (
385
  <div className="flex gap-1 ml-14">
386
- <span className="w-2 h-2 rounded-full bg-zinc-600 animate-bounce" />
387
- <span className="w-2 h-2 rounded-full bg-zinc-600 animate-bounce [animation-delay:0.1s]" />
388
- <span className="w-2 h-2 rounded-full bg-zinc-600 animate-bounce [animation-delay:0.2s]" />
389
  </div>
390
  )}
391
  <div ref={bottomRef} className="h-4" />
@@ -396,14 +399,14 @@ export default function App() {
396
  {/* --- Input Area --- */}
397
  <div className="p-4 md:p-6 pb-8">
398
  <div className="max-w-3xl mx-auto relative">
399
- <div className="relative flex flex-col gap-2 p-2 rounded-3xl bg-zinc-900/50 backdrop-blur-xl border border-white/10 shadow-2xl focus-within:ring-1 focus-within:ring-indigo-500/50 transition-all">
400
  <textarea
401
  ref={inputRef}
402
  value={input}
403
  onChange={(e) => setInput(e.target.value)}
404
  onKeyDown={handleKeyDown}
405
  placeholder="Type a message..."
406
- className="w-full bg-transparent text-zinc-100 placeholder-zinc-500 px-4 py-3 min-h-[50px] max-h-[200px] resize-none outline-none text-[15px] leading-relaxed scrollbar-hide"
407
  rows={1}
408
  />
409
 
@@ -411,17 +414,17 @@ export default function App() {
411
  <div className="flex items-center justify-between px-2 pb-1">
412
  <div className="flex items-center gap-1">
413
  <button
414
- className="cursor-not-allowed opacity-50 p-2 rounded-xl text-zinc-400 hover:text-zinc-200 hover:bg-white/5 transition-colors"
415
  title="Attach file (coming soon)"
416
  >
417
  <PaperClipIcon className="w-5 h-5" />
418
  </button>
419
  <button
420
  onClick={toggleListening}
421
- className={`p-2 rounded-xl transition-all duration-300 ${
422
  isListening
423
- ? "text-red-400 bg-red-500/10 animate-pulse ring-1 ring-red-500/20"
424
- : "text-zinc-400 hover:text-zinc-200 hover:bg-white/5"
425
  }`}
426
  title={isListening ? "Stop Listening" : "Voice Input"}
427
  >
@@ -432,25 +435,23 @@ export default function App() {
432
  <button
433
  onClick={sendMessage}
434
  disabled={!input.trim() || isStreaming}
435
- className={`p-2 rounded-xl flex items-center gap-2 transition-all ${
436
  input.trim() && !isStreaming
437
- ? "bg-indigo-600 text-white shadow-lg shadow-indigo-500/20 hover:bg-indigo-500"
438
- : "bg-zinc-800 text-zinc-500 cursor-not-allowed"
439
  }`}
440
  >
441
  {isStreaming ? (
442
- <span className="w-5 h-5 border-2 border-white/20 border-t-white rounded-full animate-spin" />
443
  ) : (
444
- <PaperAirplaneIcon className="w-5 h-5 -ml-0.5 transform -rotate-45 translate-x-0.5" />
 
 
 
445
  )}
446
  </button>
447
  </div>
448
  </div>
449
- <div className="text-center mt-3">
450
- <p className="text-[10px] text-zinc-600">
451
- AI can make mistakes. Please verify important information.
452
- </p>
453
- </div>
454
  </div>
455
  </div>
456
  </main>
@@ -473,20 +474,21 @@ function Bubble({
473
  className={`flex gap-4 ${isUser ? "justify-end" : "justify-start group"}`}
474
  >
475
  {!isUser && (
476
- <div className="shrink-0 flex flex-col gap-2">
477
  <img
478
  src={BOT_AVATAR}
479
  alt="AI"
480
- className="w-8 h-8 rounded-full ring-1 ring-white/10"
 
481
  />
482
  </div>
483
  )}
484
 
485
  <div
486
- className={`relative max-w-[85%] md:max-w-[75%] rounded-2xl px-5 py-3.5 shadow-sm text-[15px] leading-7 ${
487
  isUser
488
- ? "bg-gradient-to-br from-indigo-600 to-violet-600 text-white rounded-br-sm shadow-md shadow-indigo-500/10"
489
- : "bg-white/5 border border-white/5 text-zinc-100 rounded-bl-sm backdrop-blur-md"
490
  }`}
491
  >
492
  <ReactMarkdown
@@ -495,41 +497,76 @@ function Bubble({
495
  a: ({ ...props }) => (
496
  <a
497
  {...props}
498
- className="text-blue-400 hover:underline"
 
499
  target="_blank"
500
  />
501
  ),
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  code: ({ inline, className, children, ...props }: any) => {
503
  if (inline)
504
  return (
505
  <code
506
- className="bg-white/10 px-1 py-0.5 rounded font-mono text-sm"
507
  {...props}
508
  >
509
  {children}
510
  </code>
511
  );
512
  return (
513
- <pre className="bg-zinc-950/50 p-3 rounded-xl border border-white/5 overflow-x-auto my-2 text-sm">
514
- <code className={className} {...props}>
515
- {children}
516
- </code>
517
- </pre>
 
 
 
 
 
 
 
 
518
  );
519
  },
520
  ul: (props) => (
521
  <ul
522
- className="list-disc list-inside ml-2 space-y-1 my-2"
523
  {...props}
524
  />
525
  ),
526
  ol: (props) => (
527
  <ol
528
- className="list-decimal list-inside ml-2 space-y-1 my-2"
 
 
 
 
 
 
 
529
  {...props}
530
  />
531
  ),
532
- p: (props) => <p className="mb-2 last:mb-0" {...props} />,
533
  }}
534
  >
535
  {message.content}
@@ -537,7 +574,7 @@ function Bubble({
537
 
538
  {/* Actions for Assistant */}
539
  {!isUser && !isStreaming && (
540
- <div className="absolute -bottom-6 left-0 opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-2">
541
  <ActionButton
542
  icon={<DocumentDuplicateIcon className="w-3.5 h-3.5" />}
543
  label="Copy"
@@ -567,7 +604,8 @@ function ActionButton({
567
  return (
568
  <button
569
  onClick={onClick}
570
- className="flex items-center gap-1 text-[10px] text-zinc-500 hover:text-zinc-300 bg-white/5 px-2 py-1 rounded-md transition-colors"
 
571
  >
572
  {icon} {label}
573
  </button>
 
15
  DocumentDuplicateIcon,
16
  ChatBubbleLeftRightIcon,
17
  CommandLineIcon,
18
+ SunIcon,
19
+ MoonIcon,
20
  } from "@heroicons/react/24/outline";
21
  import ReactMarkdown from "react-markdown";
22
  import remarkGfm from "remark-gfm";
 
28
  // --- Constants & Config ---
29
  const APP_TITLE = "Krishna's Digital Twin";
30
  const BOT_AVATAR =
31
+ "https://api.dicebear.com/9.x/bottts-neutral/svg?seed=Krishna1&backgroundColor=4e67b6";
 
 
 
 
 
 
 
 
 
32
 
33
  const SUGGESTIONS = [
34
  {
 
53
  },
54
  ];
55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
56
  // --- Helper: Date Grouping for Sidebar ---
57
  function groupThreadsByDate(threads: ThreadMeta[]) {
58
  const today = new Date();
 
102
  const inputRef = useRef<HTMLTextAreaElement | null>(null);
103
  const prevThreadId = useRef<string | null>(null);
104
 
105
+ // Theme toggle logic
106
+ const [theme, setTheme] = useState<"light" | "dark">("light");
107
+
108
+ useEffect(() => {
109
+ // Sync theme with HTML class
110
+ if (theme === "dark") {
111
+ document.documentElement.classList.add("dark");
112
+ } else {
113
+ document.documentElement.classList.remove("dark");
114
+ }
115
+ }, [theme]);
116
+
117
+ const toggleTheme = () => {
118
+ setTheme((prev) => (prev === "light" ? "dark" : "light"));
119
+ };
120
+
121
  // --- Voice Input Logic ---
122
  const [isListening, setIsListening] = useState(false);
123
  const recognitionRef = useRef<any>(null);
 
188
  sendMessage();
189
  }
190
  },
191
+ [sendMessage],
192
  );
193
 
194
  const groupedThreads = useMemo(() => groupThreadsByDate(threads), [threads]);
195
 
196
  return (
197
+ <div className="flex h-screen w-screen selection:-text-accent-teal/30">
198
+ {/* Background Ambience / Structural Divs */}
199
+ <div className="fixed inset-0 z-0 pointer-events-none"></div>
 
 
 
200
 
201
  {/* --- Sidebar --- */}
202
+ <aside className="hidden md:flex flex-col w-[280px] z-10 bg-[var(--bg-strong-paper)]/80 backdrop-blur-xl border-r border-[var(--text-soft-ink)]/10 transition-all">
203
  {/* Header */}
204
+ <div className="p-4 border-b border-[var(--text-soft-ink)]/10">
205
  <button
206
  onClick={newChat}
207
+ className="group flex items-center gap-3 w-full px-3 py-2.5 rounded-xl border border-[var(--text-soft-ink)]/10 bg-[var(--bg-panel)] hover:bg-[var(--text-ink)]/5 transition-all duration-200"
208
  >
209
+ <div className="p-1.5 rounded-lg bg-[var(--accent-blue)]/10 text-[var(--accent-blue)]">
210
  <SparklesIcon className="w-5 h-5" />
211
  </div>
212
+ <span
213
+ className="text-sm font-medium font-mono uppercase tracking-wide text-[var(--text-ink)] spider-hover"
214
+ data-text="New Chat"
215
+ >
216
+ New Chat
217
+ </span>
218
  </button>
219
  </div>
220
 
221
  {/* History List */}
222
+ <div className="flex-1 overflow-y-auto p-3 space-y-6 scrollbar-thin scrollbar-thumb-[var(--text-soft-ink)]">
223
  {Object.entries(groupedThreads).map(([label, group]) => {
224
  if (group.length === 0) return null;
225
  return (
226
  <div key={label}>
227
+ <h3
228
+ className="px-3 mb-2 text-[10px] font-mono uppercase tracking-[0.12em] text-[var(--text-soft-ink)] spider-hover"
229
+ data-text={label}
230
+ >
231
  {label}
232
  </h3>
233
  <div className="space-y-0.5">
 
237
  onClick={() => setActiveThread(t)}
238
  className={`group relative flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer transition-colors ${
239
  active?.id === t.id
240
+ ? "bg-[var(--text-ink)]/10 text-[var(--text-ink)]"
241
+ : "text-[var(--text-soft-ink)] hover:bg-[var(--text-ink)]/5 hover:text-[var(--text-ink)]"
242
  }`}
243
  >
244
  <span
245
+ className="text-sm flex-1 line-clamp-1 break-all spider-hover"
246
+ data-text={t.title || "New Conversation"}
247
  title={t.title || "New Conversation"}
248
  >
249
  {t.title || "New Conversation"}
 
255
  if (window.confirm("Delete thread?"))
256
  deleteThread(t.id);
257
  }}
258
+ className="opacity-0 group-hover:opacity-100 p-1 rounded hover:bg-[var(--accent-rust)]/10 text-[var(--text-soft-ink)] hover:text-[var(--accent-rust)] transition-all z-10"
259
  >
260
  <TrashIcon className="w-3.5 h-3.5" />
261
  </button>
 
268
  </div>
269
 
270
  {/* Sidebar Footer */}
271
+ <div className="p-4 border-t border-[var(--text-soft-ink)]/10">
272
  <a
273
  href="https://github.com/krishna-dhulipalla/LangGraph_ChatBot"
274
  target="_blank"
275
  rel="noreferrer"
276
+ className="flex items-center gap-2 text-xs font-mono uppercase tracking-wide text-[var(--text-soft-ink)] hover:text-[var(--text-ink)] transition-colors spider-hover"
277
+ data-text="View Source Code"
278
  >
279
  <CommandLineIcon className="w-4 h-4" />
280
  <span>View Source Code</span>
 
285
  {/* --- Main Chat Area --- */}
286
  <main className="relative z-10 flex-1 flex flex-col h-full overflow-hidden">
287
  {/* Top Navigation */}
288
+ <header className="h-16 flex items-center justify-between px-6 border-b border-[var(--text-soft-ink)]/10 bg-[var(--bg-paper)]/60 backdrop-blur-md">
289
  <div className="flex items-center gap-3">
290
  <div className="relative">
291
  <img
292
  src={BOT_AVATAR}
293
  alt="Bot"
294
+ className="w-9 h-9 rounded-md border border-[var(--text-soft-ink)]/20 shadow-[2px_2px_0_var(--shadow-blue)]"
295
  />
296
+ <span className="absolute -bottom-1 -right-1 w-3 h-3 bg-[var(--accent-teal)] border-2 border-[var(--bg-paper)] rounded-full"></span>
297
  </div>
298
  <div>
299
+ <h1
300
+ className="text-lg display-heading text-[var(--text-ink)] spider-title font-semibold"
301
+ data-text={APP_TITLE}
302
+ >
303
  {APP_TITLE}
304
  </h1>
305
+ <p className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-soft-ink)] flex items-center gap-1">
306
+ <span className="inline-block w-1.5 h-1.5 rounded-full bg-[var(--accent-blue)] animate-pulse" />
307
  Online & Ready
308
  </p>
309
  </div>
310
  </div>
311
 
312
  <div className="flex items-center gap-2">
313
+ <button
314
+ onClick={toggleTheme}
315
+ className="p-2 text-[var(--text-soft-ink)] hover:text-[var(--text-ink)] hover:bg-[var(--text-ink)]/5 rounded-full transition-all"
316
+ title="Toggle Theme"
317
+ >
318
+ {theme === "light" ? (
319
+ <MoonIcon className="w-5 h-5" />
320
+ ) : (
321
+ <SunIcon className="w-5 h-5" />
322
+ )}
323
+ </button>
324
  <button
325
  onClick={clearChat}
326
+ className="p-2 text-[var(--text-soft-ink)] hover:text-[var(--text-ink)] hover:bg-[var(--text-ink)]/5 rounded-full transition-all"
327
  title="Clear Chat"
328
  >
329
  <ArrowPathIcon className="w-5 h-5" />
 
335
  <div className="flex-1 overflow-y-auto p-4 md:p-8 scroll-smooth">
336
  {messages.length === 0 ? (
337
  <div className="h-full flex flex-col items-center justify-center max-w-2xl mx-auto animate-in fade-in duration-500">
338
+ <div className="mb-2 p-4 bg-[var(--bg-panel)] border border-[var(--text-soft-ink)]/10 shadow-[4px_4px_0_var(--shadow-blue),-2px_-2px_0_rgba(186,90,69,0.04)] rotate-3">
339
+ <ChatBubbleLeftRightIcon className="w-10 h-10 text-[var(--accent-blue)] -rotate-3" />
340
  </div>
341
+ <h2
342
+ className="text-3xl section-title text-[var(--text-ink)] mb-4 spider-title"
343
+ data-text="How can I help you today?"
344
+ >
345
  How can I help you today?
346
  </h2>
347
+ <p className="text-[var(--text-soft-ink)] text-sm mb-8 max-w-md text-center leading-relaxed">
348
  I'm Krishna's digital twin. Ask me about his architecture
349
  skills, recent projects, or schedule a meeting.
350
  </p>
351
 
 
 
 
352
  {/* Suggestions Grid */}
353
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4 w-full">
354
  {SUGGESTIONS.map((s, i) => (
355
  <button
356
  key={i}
357
  onClick={() => {
 
358
  if (!isStreaming) send(s.text);
359
  }}
360
+ className="group flex items-start gap-4 p-5 bg-[var(--bg-panel)] hover:bg-[var(--bg-strong-paper)] border border-[var(--text-soft-ink)]/10 text-left transition-all hover:-translate-y-1 hover:shadow-[4px_4px_0_var(--shadow-blue)]"
361
  >
362
+ <span className="text-xl inline-block group-hover:scale-110 transition-transform">
363
  {s.icon}
364
  </span>
365
  <div>
366
+ <div className="text-sm font-semibold font-serif text-[var(--text-ink)] group-hover:text-[var(--accent-blue)] transition-colors">
367
  {s.title}
368
  </div>
369
+ <div className="text-xs text-[var(--text-soft-ink)] mt-1 line-clamp-2 leading-relaxed">
370
  {s.text}
371
  </div>
372
  </div>
 
386
  {/* Streaming Indicator */}
387
  {isStreaming && !hasFirstToken && (
388
  <div className="flex gap-1 ml-14">
389
+ <span className="w-2 h-2 rounded-full bg-[var(--text-soft-ink)] animate-bounce" />
390
+ <span className="w-2 h-2 rounded-full bg-[var(--text-soft-ink)] animate-bounce [animation-delay:0.1s]" />
391
+ <span className="w-2 h-2 rounded-full bg-[var(--text-soft-ink)] animate-bounce [animation-delay:0.2s]" />
392
  </div>
393
  )}
394
  <div ref={bottomRef} className="h-4" />
 
399
  {/* --- Input Area --- */}
400
  <div className="p-4 md:p-6 pb-8">
401
  <div className="max-w-3xl mx-auto relative">
402
+ <div className="relative flex flex-col gap-2 p-2 bg-[var(--bg-strong-paper)]/90 backdrop-blur-xl rounded-2xl border border-[var(--text-soft-ink)]/20 shadow-sm focus-within:ring-1 focus-within:ring-[var(--accent-blue)] transition-all">
403
  <textarea
404
  ref={inputRef}
405
  value={input}
406
  onChange={(e) => setInput(e.target.value)}
407
  onKeyDown={handleKeyDown}
408
  placeholder="Type a message..."
409
+ className="w-full bg-transparent text-[var(--text-ink)] placeholder-[var(--text-soft-ink)] px-4 py-3 min-h-[50px] max-h-[200px] resize-none outline-none text-[15px] leading-relaxed scrollbar-hide font-sans"
410
  rows={1}
411
  />
412
 
 
414
  <div className="flex items-center justify-between px-2 pb-1">
415
  <div className="flex items-center gap-1">
416
  <button
417
+ className="cursor-not-allowed opacity-50 p-2 text-[var(--text-soft-ink)] hover:text-[var(--text-ink)] hover:bg-[var(--text-ink)]/5 transition-colors"
418
  title="Attach file (coming soon)"
419
  >
420
  <PaperClipIcon className="w-5 h-5" />
421
  </button>
422
  <button
423
  onClick={toggleListening}
424
+ className={`p-2 transition-all duration-300 ${
425
  isListening
426
+ ? "text-[var(--accent-rust)] bg-[var(--accent-rust)]/10 animate-pulse ring-1 ring-[var(--accent-rust)]/20"
427
+ : "text-[var(--text-soft-ink)] hover:text-[var(--text-ink)] hover:bg-[var(--text-ink)]/5"
428
  }`}
429
  title={isListening ? "Stop Listening" : "Voice Input"}
430
  >
 
435
  <button
436
  onClick={sendMessage}
437
  disabled={!input.trim() || isStreaming}
438
+ className={`p-2 px-4 rounded-xl flex items-center gap-2 transition-all font-mono uppercase tracking-widest text-xs font-semibold ${
439
  input.trim() && !isStreaming
440
+ ? "bg-[var(--text-ink)] text-[var(--bg-paper)] hover:opacity-90 shadow-sm"
441
+ : "bg-[var(--text-soft-ink)]/10 text-[var(--text-soft-ink)] cursor-not-allowed"
442
  }`}
443
  >
444
  {isStreaming ? (
445
+ <span className="w-4 h-4 border-2 border-[var(--bg-paper)] border-t-transparent rounded-full animate-spin" />
446
  ) : (
447
+ <>
448
+ Send
449
+ <PaperAirplaneIcon className="w-4 h-4 -ml-0.5" />
450
+ </>
451
  )}
452
  </button>
453
  </div>
454
  </div>
 
 
 
 
 
455
  </div>
456
  </div>
457
  </main>
 
474
  className={`flex gap-4 ${isUser ? "justify-end" : "justify-start group"}`}
475
  >
476
  {!isUser && (
477
+ <div className="shrink-0 flex flex-col gap-2 pt-1">
478
  <img
479
  src={BOT_AVATAR}
480
  alt="AI"
481
+ className="w-8 h-8 rounded border border-[var(--text-soft-ink)]/20 spider-title"
482
+ data-text=""
483
  />
484
  </div>
485
  )}
486
 
487
  <div
488
+ className={`relative max-w-[85%] md:max-w-[80%] px-5 py-4 text-[15px] leading-relaxed transition-all ${
489
  isUser
490
+ ? "bg-[var(--bg-panel)] border-2 border-[var(--accent-blue)] text-[var(--text-ink)] shadow-[4px_4px_0_var(--shadow-blue)] font-medium"
491
+ : "bg-[var(--bg-strong-paper)] border border-[var(--text-soft-ink)]/20 text-[var(--text-ink)] shadow-[2px_2px_0_rgba(186,90,69,0.04)]"
492
  }`}
493
  >
494
  <ReactMarkdown
 
497
  a: ({ ...props }) => (
498
  <a
499
  {...props}
500
+ className="text-[var(--accent-blue)] underline underline-offset-4 decoration-[var(--accent-blue)]/30 hover:decoration-[var(--accent-blue)] hover:text-[var(--accent-blue)] inline-block spider-hover"
501
+ data-text={props.children}
502
  target="_blank"
503
  />
504
  ),
505
+ h1: ({ ...props }) => (
506
+ <h1
507
+ className="text-2xl display-heading mt-6 mb-4 spider-title"
508
+ data-text={props.children}
509
+ {...props}
510
+ />
511
+ ),
512
+ h2: ({ ...props }) => (
513
+ <h2
514
+ className="text-xl section-title mt-5 mb-3 spider-title"
515
+ data-text={props.children}
516
+ {...props}
517
+ />
518
+ ),
519
+ h3: ({ ...props }) => (
520
+ <h3
521
+ className="text-lg font-semibold font-serif mt-4 mb-2"
522
+ {...props}
523
+ />
524
+ ),
525
  code: ({ inline, className, children, ...props }: any) => {
526
  if (inline)
527
  return (
528
  <code
529
+ className="bg-[var(--text-soft-ink)]/10 text-[var(--accent-rust)] px-1.5 py-0.5 font-mono text-[13px]"
530
  {...props}
531
  >
532
  {children}
533
  </code>
534
  );
535
  return (
536
+ <div className="relative group/code">
537
+ <div
538
+ className="absolute top-2 right-2 text-[10px] font-mono text-[var(--text-soft-ink)] px-2 py-1 bg-[var(--bg-paper)]/50 uppercase tracking-widest pointer-events-none opacity-50 spider-title"
539
+ data-text="CODE"
540
+ >
541
+ CODE
542
+ </div>
543
+ <pre className="bg-[var(--bg-panel)] p-4 border border-[var(--text-soft-ink)]/20 overflow-x-auto my-4 text-sm shadow-inner font-mono text-[var(--text-ink)]">
544
+ <code className={className} {...props}>
545
+ {children}
546
+ </code>
547
+ </pre>
548
+ </div>
549
  );
550
  },
551
  ul: (props) => (
552
  <ul
553
+ className="list-disc list-inside ml-2 space-y-1.5 my-3"
554
  {...props}
555
  />
556
  ),
557
  ol: (props) => (
558
  <ol
559
+ className="list-decimal list-inside ml-2 space-y-1.5 my-3"
560
+ {...props}
561
+ />
562
+ ),
563
+ p: (props) => <p className="mb-3 last:mb-0" {...props} />,
564
+ strong: (props) => (
565
+ <strong
566
+ className="font-semibold text-[var(--accent-blue)]"
567
  {...props}
568
  />
569
  ),
 
570
  }}
571
  >
572
  {message.content}
 
574
 
575
  {/* Actions for Assistant */}
576
  {!isUser && !isStreaming && (
577
+ <div className="absolute -bottom-8 left-0 opacity-0 group-hover:opacity-100 transition-opacity flex items-center gap-2">
578
  <ActionButton
579
  icon={<DocumentDuplicateIcon className="w-3.5 h-3.5" />}
580
  label="Copy"
 
604
  return (
605
  <button
606
  onClick={onClick}
607
+ className="flex items-center gap-1.5 font-mono text-[10px] uppercase tracking-widest text-[var(--text-soft-ink)] hover:text-[var(--text-ink)] bg-[var(--bg-panel)] border border-[var(--text-soft-ink)]/10 px-2 py-1 transition-colors spider-hover"
608
+ data-text={label}
609
  >
610
  {icon} {label}
611
  </button>
ui/src/index.css CHANGED
@@ -1,7 +1,69 @@
1
  @import "tailwindcss";
2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3
  @layer utilities {
4
- /* Custom Scrollbar for Dark Mode */
5
  ::-webkit-scrollbar {
6
  width: 6px;
7
  height: 6px;
@@ -10,10 +72,10 @@
10
  background: transparent;
11
  }
12
  ::-webkit-scrollbar-thumb {
13
- background: #3f3f46; /* zinc-700 */
14
  border-radius: 10px;
15
  }
16
  ::-webkit-scrollbar-thumb:hover {
17
- background: #52525b; /* zinc-600 */
18
  }
19
  }
 
1
  @import "tailwindcss";
2
 
3
+ @theme {
4
+ --color-paper: var(--bg-paper);
5
+ --color-strong-paper: var(--bg-strong-paper);
6
+ --color-panel: var(--bg-panel);
7
+ --color-ink: var(--text-ink);
8
+ --color-soft-ink: var(--text-soft-ink);
9
+ --color-accent-blue: var(--accent-blue);
10
+ --color-accent-rust: var(--accent-rust);
11
+ --color-accent-teal: var(--accent-teal);
12
+
13
+ --font-serif: "Source Serif 4", serif;
14
+ --font-sans: "IBM Plex Sans", sans-serif;
15
+ --font-mono: "IBM Plex Mono", monospace;
16
+ }
17
+
18
+ :root {
19
+ /* Light Mode */
20
+ --bg-paper: #f4efe6;
21
+ --bg-strong-paper: #fcf8f1;
22
+ --bg-panel: #f7f1e8;
23
+ --text-ink: #171412;
24
+ --text-soft-ink: #5d564f;
25
+ --accent-blue: #4e67b6;
26
+ --accent-rust: #ba5a45;
27
+ --accent-teal: #1a766c;
28
+
29
+ --spider-blend: multiply;
30
+ }
31
+
32
+ html.dark {
33
+ /* Dark Mode */
34
+ --bg-paper: #11100f;
35
+ --bg-strong-paper: #181614;
36
+ --bg-panel: #141210;
37
+ --text-ink: #f2ede4;
38
+ --text-soft-ink: #b7aea2;
39
+ --accent-blue: #8fa7ff;
40
+ --accent-rust: #ef8d74;
41
+ --accent-teal: #62c2b4;
42
+
43
+ --spider-blend: screen;
44
+ }
45
+
46
+ body {
47
+ background-color: var(--bg-paper);
48
+ color: var(--text-ink);
49
+ font-family: var(--font-sans);
50
+ }
51
+
52
+ .display-heading,
53
+ .section-title {
54
+ font-family: var(--font-serif);
55
+ letter-spacing: -0.03em;
56
+ }
57
+
58
+ /* 3. Chroma Ink Text Shadow */
59
+ .chroma-ink {
60
+ text-shadow:
61
+ 0.02em 0 0 var(--accent-blue),
62
+ -0.02em 0 0 var(--accent-rust);
63
+ }
64
+
65
  @layer utilities {
66
+ /* Custom Scrollbar */
67
  ::-webkit-scrollbar {
68
  width: 6px;
69
  height: 6px;
 
72
  background: transparent;
73
  }
74
  ::-webkit-scrollbar-thumb {
75
+ background: var(--text-soft-ink);
76
  border-radius: 10px;
77
  }
78
  ::-webkit-scrollbar-thumb:hover {
79
+ background: var(--text-ink);
80
  }
81
  }