quantumkv commited on
Commit
20fda5d
·
verified ·
1 Parent(s): 102ba02

Hi Design Team,

Browse files

I need to provide extremely comprehensive structural specifications for our upcoming Claude AI chat interface. This document covers every functional element, user flow, and technical requirement—please treat this as your primary blueprint for what to design (not how it should look).
1.0 PROJECT ARCHITECTURE OVERVIEW
We're building a single-page chat application that operates entirely client-side. The app will be delivered as one HTML file containing embedded CSS and JavaScript, with zero external dependencies except the Puter.js script tag. Users access multiple Claude AI models without API keys, authentication, or backend servers.
Core User Journey:
Land on page → See empty chat with onboarding message
Select AI model → Choose from 5 Claude variants
Input prompt → Type or click example button
Choose interaction mode → Standard (instant) or Stream (real-time)
Receive response → View formatted message in conversation thread
Continue conversation → Build context-aware chat history
2.0 HEADER SECTION (Fixed Position)
2.1 Left Zone: Application Identity
App Name: "Claude AI Chat" (text, not logo)
Tagline/Subtitle: "Powered by Puter.js" (optional)
Function: Clicking app name resets conversation (clear history)
2.2 Right Zone: Model Selection
Component: Dropdown selector (default: "claude-sonnet-4-5")
Options:
claude-sonnet-4-5 (label: "Claude Sonnet 4.5")
claude-sonnet-4 (label: "Claude Sonnet 4")
claude-opus-4-1 (label: "Claude Opus 4.1")
claude-opus-4 (label: "Claude Opus 4")
claude-haiku-4-5 (label: "Claude Haiku 4.5")
Behavior:
Changing model mid-conversation appends system message: "Model changed to [Model Name]"
Dropdown disabled during active requests
On mobile: Convert to bottom sheet or full-screen selector
2.3 Status Indicator Area
Location: Top-right corner, overlaying content
States:
Hidden (default)
Success: Green bar, auto-dismiss after 3 seconds
Error: Red bar, auto-dismiss after 5 seconds, contains "Retry" button
Loading: Persistent spinner with text "Connecting..."
3.0 MAIN CHAT CONTAINER (Scrollable Region)
3.1 Message Thread Structure
Container: Flex column, reverse chronological order (newest at bottom)
Scroll Behavior: Auto-scroll to bottom on new messages, unless user manually scrolled up
Message Components (per message):
Header: Role label ("You" or "Assistant") + timestamp (optional)
Body: Text content with markdown support
Metadata: Character count (optional, expandable)
Message Types:
User Message: Right-aligned, contains only plain text
Assistant Message: Left-aligned, contains:
Plain text
Code blocks (monospaced, syntax highlighting)
Inline code (backticks)
Lists (bulleted/numbered)
Links (clickable, open new tab)
3.2 Typing Indicator
Trigger: Appears immediately after user sends message
Structure: Three animated dots + "Claude is typing..." text
Position: Above assistant message, disappears when response starts
Stream Mode: Hidden, response appears directly
3.3 Empty State
Condition: Zero messages in thread
Content: Welcome message from assistant explaining capabilities
Actions: Prompt user to select example or type message
3.4 Message History Management
Persistence: Store last 50 messages in localStorage
Page Refresh: Reload history from localStorage
Reset: "New Chat" button clears history and container
Overflow: Auto-prune messages older than 50, show "Load older messages" button
4.0 EXAMPLE PROMPTS SECTION (Dynamic)
4.1 Container
Location: Between header and chat container
Visibility: Always visible until first user message, then collapsible
Structure: Horizontal flex wrap of button elements
4.2 Button Specifications
Default Prompts:
"Explain quantum computing in simple terms"
"Write a detailed essay on AI impact"
"Write a short poem about coding"
"Generate a JavaScript function to sort an array"
Behavior: Clicking injects text into textarea and focuses input
Customization: Allow users to edit prompt before sending
Dynamic Addition: Future feature: users can add custom example prompts
4.3 Responsive Behavior
Desktop: 4 buttons in row
Tablet: 2x2 grid
Mobile: Vertical stack, full-width buttons
5.0 INPUT AREA (Fixed Bottom)
5.1 Text Area Component
Type: Multi-line textarea (not single-line input)
Auto-resize: Grows vertically up to 150px max-height
Placeholder: "Type your message here... (Shift+Enter for new line)"
Keyboard Shortcuts:
Enter → Send message
Shift + Enter → New line
Cmd/Ctrl + K → Clear input
Cmd/Ctrl + N → New chat
Character Limit: 10,000 characters (show counter when >80%)
Disabled States: During API requests, show "Waiting for response..."
Paste Behavior: Accept text, code, and markdown; strip images/files
5.2 Button Group
Primary Button (Left): "Send"
Sends standard request, waits for complete response
Disabled when textarea empty or during active request
Icon: Paper plane or arrow right
Secondary Button (Right): "Stream"
Sends streaming request, shows real-time text generation
Best for long responses (essays, code generation)
Disabled when textarea empty or during active request
Icon: Lightning bolt or pulse waves
Tertiary Button (Optional): "New Chat"
Resets conversation, clears history
Place in header or as floating action button
5.3 Input Validation
Empty Input: Disable buttons, show tooltip on click attempt
Rate Limiting: Queue messages if user spams, show "Please wait..." message
Network Error: If Puter.js fails to load, show persistent error bar: "Failed to connect to AI service. Refresh page."
6.0 INTERACTION FLOWS (Detailed)
6.1 Standard Message Flow
User types message → Clicks "Send"
Input locked, buttons disabled, typing indicator appears
API request sent via puter.ai.chat() (non-streaming)
On success:
Typing indicator hidden
Assistant message rendered in full
Message saved to history
Input unlocked
On error:
Show error status: "Request failed. [Error message]"
Input unlocked, message preserved for retry
6.2 Streaming Message Flow
User types message → Clicks "Stream"
Input locked, buttons disabled, empty assistant message appears
API request sent with {stream: true} parameter
Response chunks arrive → Append to message in real-time
On completion: Input unlocked, message saved
On error: Show error bar, preserve partial response with "Streaming interrupted"
6.3 Model Switching Flow
User opens dropdown → Selects new model
If conversation empty: Silent switch
If conversation active:
Append system message to chat: "Model changed to [New Model] — previous context retained"
Dropdown shows checkmark briefly
Selected model becomes default for new chats
6.4 Error Recovery Flow
Network Error: Show "Retry" button in error status, clicking resends last message
Rate Limit: Show "Too many requests. Wait 30 seconds." with countdown
Model Unavailable: Auto-suggest next best model, show prompt: "[Model] unavailable. Use [Alternative] instead?"
7.0 TECHNICAL INTEGRATION POINTS
7.1 Puter.js API Calls
Host Script: <script src="https://js.puter.com/v2/"></script> in <head>
Standard Call: puter.ai.chat(message, {model: selectedModel})
Streaming Call: puter.ai.chat(message, {model: selectedModel, stream: true})
Response Handling:
Standard: response.message.content[0].text
Streaming: Async iterator for await (const part of response)
7.2 State Management
Global Variables:
conversationHistory (array of message objects)
selectedModel (string)
isProcessing (boolean)
isStreaming (boolean)
localStorage Keys:
claudeChat_history (JSON string)
claudeChat_selectedModel (string)
claudeChat_timestamp (for cache invalidation)
7.3 Performance Requirements
First Paint: < 1 second
Time to Interactive: < 2 seconds (including Puter.js load)
Message Render: < 50ms for new message insertion
Stream Latency: < 100ms between chunks
Memory: < 50MB total footprint
8.0 RESPONSIVE BREAKPOINTS & ADAPTIVE BEHAVIOR
8.1 Desktop (≥1024px)
Three-column layout: Header, Examples, Chat, Input
Chat container max-width: 1200px, centered
Buttons side-by-side
Model selector as dropdown
8.2 Tablet (768px – 1023px)
Two-column layout: Header full-width, Examples 2x2 grid, Chat, Input
Chat container: 90% width
Buttons slightly larger touch targets (44px min)
8.3 Mobile (≤767px)
Single-column stacked layout
Header: App name and model selector on same line, smaller font
Examples: Vertical stack, full-width buttons
Chat: Full-width, 1rem padding
Input: Textarea + buttons in flex column, buttons full-width stacked
Mobile-Specific: Add floating "Scroll to bottom" button when user scrolls up
9.0 ACCESSIBILITY (WCAG 2.1 AA Compliance)
Keyboard Navigation: All interactive elements reachable via Tab
Screen Readers: ARIA labels for dynamic content (role="status" for messages)
Focus Management: Return focus to textarea after sending
Color Contrast: Minimum 4.5:1 ratio for all text
Motion: Respect prefers-reduced-motion (disable animations)
Alt Text: Icons must have aria-hidden or descriptive labels
10.0 FUTURE-PROOFING STRUCTURE
Design components to support upcoming features:
**File Upload:**预留 drag-drop zone above textarea
Multi-turn Conversations: Message threading indentation
User Accounts: Login button space in header
Settings Panel: Gear icon in header → modal/sheet
Export Chat: Download button per message or entire thread
Voice Input: Mic button next to textarea
Please design the interface structure based on these specifications. Focus on layout, component hierarchy, interaction patterns, and responsive behavior. Visual styling (colors, fonts, effects) is your creative domain—just ensure the underlying UX skeleton matches this document exactly.

README.md CHANGED
@@ -1,10 +1,13 @@
1
  ---
2
- title: Claudeverse Ai Chat Interface
3
- emoji: 🌖
4
- colorFrom: indigo
5
- colorTo: red
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
1
  ---
2
+ title: ClaudeVerse AI Chat Interface 🤖
3
+ colorFrom: purple
4
+ colorTo: green
5
+ emoji: 🐳
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite-v3
10
  ---
11
 
12
+ # Welcome to your new DeepSite project!
13
+ This project was created with [DeepSite](https://huggingface.co/deepsite).
components/chat-container.js ADDED
@@ -0,0 +1,320 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ClaudeChatContainer extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ this.autoScroll = true;
5
+ }
6
+
7
+ connectedCallback() {
8
+ this.attachShadow({ mode: 'open' });
9
+ this.render();
10
+ this.setupEventListeners();
11
+ this.loadConversationHistory();
12
+ }
13
+
14
+ render() {
15
+ this.shadowRoot.innerHTML = `
16
+ <style>
17
+ .chat-container {
18
+ max-width: 1200px;
19
+ margin: 0 auto;
20
+ padding: 1rem 1.5rem 2rem;
21
+ flex: 1;
22
+ overflow-y: auto;
23
+ max-height: calc(100vh - 240px);
24
+ display: flex;
25
+ flex-direction: column;
26
+ }
27
+
28
+ .messages-wrapper {
29
+ display: flex;
30
+ flex-direction: column;
31
+ gap: 1.5rem;
32
+ }
33
+
34
+ .message {
35
+ max-width: 85%;
36
+ animation: message-enter 0.3s ease-out;
37
+ }
38
+
39
+ .message.user {
40
+ align-self: flex-end;
41
+ }
42
+
43
+ .message.assistant {
44
+ align-self: flex-start;
45
+ }
46
+
47
+ .message-header {
48
+ display: flex;
49
+ align-items: center;
50
+ gap: 0.5rem;
51
+ margin-bottom: 0.5rem;
52
+ }
53
+
54
+ .message-role {
55
+ font-size: 0.875rem;
56
+ font-weight: 600;
57
+ }
58
+
59
+ .user .message-role {
60
+ color: var(--primary-blue);
61
+ }
62
+
63
+ .assistant .message-role {
64
+ color: var(--success-green);
65
+ }
66
+
67
+ .message-timestamp {
68
+ font-size: 0.75rem;
69
+ color: #94a3b8;
70
+ }
71
+
72
+ .message-body {
73
+ padding: 1rem;
74
+ border-radius: 1rem;
75
+ box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
76
+ }
77
+
78
+ .user .message-body {
79
+ background: var(--primary-blue);
80
+ color: white;
81
+ }
82
+
83
+ .assistant .message-body {
84
+ background: white;
85
+ border: 1px solid #e2e8f0;
86
+ color: #1e293b;
87
+ }
88
+
89
+ .typing-indicator {
90
+ display: none;
91
+ align-items: center;
92
+ gap: 0.5rem;
93
+ padding: 1rem;
94
+ background: white;
95
+ border: 1px solid #e2e8f0;
96
+ border-radius: 1rem;
97
+ margin-bottom: 1rem;
98
+ align-self: flex-start;
99
+ max-width: 85%;
100
+ }
101
+
102
+ .typing-indicator.show {
103
+ display: flex;
104
+ }
105
+
106
+ .typing-dots {
107
+ display: flex;
108
+ gap: 0.25rem;
109
+ }
110
+
111
+ .typing-dot {
112
+ width: 6px;
113
+ height: 6px;
114
+ border-radius: 50%;
115
+ background: #64748b;
116
+ }
117
+
118
+ .empty-state {
119
+ text-align: center;
120
+ padding: 3rem 2rem;
121
+ color: #64748b;
122
+ }
123
+
124
+ .empty-title {
125
+ font-size: 1.5rem;
126
+ font-weight: 600;
127
+ margin-bottom: 1rem;
128
+ }
129
+
130
+ .empty-subtitle {
131
+ font-size: 1rem;
132
+ line-height: 1.6;
133
+ }
134
+
135
+ .scroll-to-bottom {
136
+ position: fixed;
137
+ bottom: 120px;
138
+ right: 2rem;
139
+ background: var(--primary-blue);
140
+ color: white;
141
+ border: none;
142
+ border-radius: 50%;
143
+ width: 48px;
144
+ height: 48px;
145
+ display: none;
146
+ align-items: center;
147
+ justify-content: center;
148
+ cursor: pointer;
149
+ box-shadow: 0 4px 12px rgba(37, 99, 235, 0.3);
150
+ transition: all 0.2s;
151
+ }
152
+
153
+ .scroll-to-bottom.show {
154
+ display: flex;
155
+ }
156
+
157
+ .scroll-to-bottom:hover {
158
+ background: var(--primary-blue-dark);
159
+ transform: scale(1.05);
160
+ }
161
+
162
+ @keyframes message-enter {
163
+ from {
164
+ opacity: 0;
165
+ transform: translateY(10px);
166
+ }
167
+ to {
168
+ opacity: 1;
169
+ transform: translateY(0);
170
+ }
171
+ }
172
+
173
+ @media (max-width: 767px) {
174
+ .chat-container {
175
+ padding: 0.5rem 1rem 1rem;
176
+ max-height: calc(100vh - 200px);
177
+ }
178
+
179
+ .message {
180
+ max-width: 95%;
181
+ }
182
+
183
+ .scroll-to-bottom {
184
+ right: 1rem;
185
+ bottom: 100px;
186
+ }
187
+ }
188
+ </style>
189
+
190
+ <div class="chat-container" id="chatContainer">
191
+ <div class="messages-wrapper" id="messagesWrapper">
192
+ <div class="empty-state" id="emptyState">
193
+ <div class="empty-title">Welcome to ClaudeVerse AI</div>
194
+ <div class="empty-subtitle">
195
+ I'm here to help you with writing, analysis, coding, and much more.
196
+ Choose a Claude model above and start a conversation!
197
+ </div>
198
+ </div>
199
+ </div>
200
+
201
+ <button class="scroll-to-bottom" id="scrollToBottom">
202
+ <i data-feather="arrow-down" width="20" height="20"></i>
203
+ </button>
204
+
205
+ <div class="typing-indicator" id="typingIndicator">
206
+ <div class="typing-dots">
207
+ <div class="typing-dot"></div>
208
+ <div class="typing-dot"></div>
209
+ <div class="typing-dot"></div>
210
+ </div>
211
+ <span>Claude is typing...</span>
212
+ </div>
213
+ </div>
214
+ `;
215
+
216
+ setTimeout(() => {
217
+ feather.replace();
218
+ }, 100);
219
+ }
220
+
221
+ setupEventListeners() {
222
+ const chatContainer = this.shadowRoot.getElementById('chatContainer');
223
+ const scrollToBottom = this.shadowRoot.getElementById('scrollToBottom');
224
+
225
+ // Scroll behavior
226
+ chatContainer.addEventListener('scroll', () => {
227
+ const { scrollTop, scrollHeight, clientHeight } = chatContainer;
228
+ const isAtBottom = scrollHeight - scrollTop - clientHeight < 50;
229
+
230
+ if (isAtBottom) {
231
+ this.autoScroll = true;
232
+ scrollToBottom.classList.remove('show');
233
+ } else {
234
+ this.autoScroll = false;
235
+ scrollToBottom.classList.add('show');
236
+ }
237
+ });
238
+
239
+ // Scroll to bottom button
240
+ scrollToBottom.addEventListener('click', () => {
241
+ this.scrollToBottom();
242
+ });
243
+
244
+ // Global events
245
+ document.addEventListener('claude-show-typing', () => {
246
+ this.showTypingIndicator();
247
+ });
248
+
249
+ document.addEventListener('claude-hide-typing', () => {
250
+ this.hideTypingIndicator();
251
+ });
252
+
253
+ document.addEventListener('claude-clear-chat', () => {
254
+ this.clearMessages();
255
+ this.showEmptyState();
256
+ });
257
+
258
+ document.addEventListener('claude-new-chat', () => {
259
+ this.clearMessages();
260
+ this.showEmptyState();
261
+ });
262
+
263
+ // Listen for new messages
264
+ document.addEventListener('claude-message-added', (event) => {
265
+ const { message } = event.detail;
266
+ this.addMessage(message);
267
+ });
268
+ }
269
+
270
+ loadConversationHistory() {
271
+ const history = window.claudeApp.conversationHistory;
272
+ if (history.length === 0) {
273
+ this.showEmptyState();
274
+ } else {
275
+ this.hideEmptyState();
276
+ history.forEach(message => {
277
+ this.addMessage(message, false);
278
+ });
279
+ this.scrollToBottom();
280
+ }
281
+ }
282
+
283
+ addMessage(message, animate = true) {
284
+ this.hideEmptyState();
285
+
286
+ const messagesWrapper = this.shadowRoot.getElementById('messagesWrapper');
287
+ const messageElement = document.createElement('div');
288
+ messageElement.className = `message ${message.role}`;
289
+
290
+ if (animate) {
291
+ messageElement.classList.add('message-enter');
292
+ }
293
+
294
+ const timestamp = new Date(message.timestamp).toLocaleTimeString();
295
+
296
+ messageElement.innerHTML = `
297
+ <div class="message-header">
298
+ <span class="message-role">${message.role === 'user' ? 'You' : 'Assistant'}</span>
299
+ </div>
300
+ <div class="message-body">
301
+ ${this.formatMessageContent(message.content)}
302
+ </div>
303
+ `;
304
+
305
+ messagesWrapper.appendChild(messageElement);
306
+
307
+ if (this.autoScroll) {
308
+ this.scrollToBottom();
309
+ }
310
+
311
+ // Update feather icons
312
+ setTimeout(() => {
313
+ feather.replace();
314
+ }, 100);
315
+ }
316
+
317
+ formatMessageContent(content) {
318
+ // Simple markdown parsing for code blocks and inline code
319
+ return content
320
+ .replace(/
components/examples.js ADDED
@@ -0,0 +1,129 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ClaudeExamples extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ }
5
+
6
+ connectedCallback() {
7
+ this.attachShadow({ mode: 'open' });
8
+ this.render();
9
+ this.setupEventListeners();
10
+ }
11
+
12
+ render() {
13
+ this.shadowRoot.innerHTML = `
14
+ <style>
15
+ .examples-container {
16
+ max-width: 1200px;
17
+ margin: 0 auto;
18
+ padding: 2rem 1.5rem 1rem;
19
+ transition: all 0.3s ease;
20
+ }
21
+
22
+ .examples-container.hidden {
23
+ opacity: 0;
24
+ height: 0;
25
+ padding: 0;
26
+ overflow: hidden;
27
+ }
28
+
29
+ .examples-title {
30
+ font-size: 1.125rem;
31
+ font-weight: 600;
32
+ color: #1e293b;
33
+ margin-bottom: 1rem;
34
+ }
35
+
36
+ .examples-grid {
37
+ display: grid;
38
+ grid-template-columns: repeat(4, 1fr);
39
+ gap: 1rem;
40
+ }
41
+
42
+ .example-button {
43
+ padding: 1rem;
44
+ background: white;
45
+ border: 1px solid #e2e8f0;
46
+ border-radius: 0.75rem;
47
+ text-align: left;
48
+ cursor: pointer;
49
+ transition: all 0.2s;
50
+ font-size: 0.875rem;
51
+ color: #475569;
52
+ }
53
+
54
+ .example-button:hover {
55
+ border-color: var(--primary-blue);
56
+ background: #f8fafc;
57
+ transform: translateY(-2px);
58
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
59
+ }
60
+
61
+ @media (max-width: 1023px) and (min-width: 768px) {
62
+ .examples-grid {
63
+ grid-template-columns: repeat(2, 1fr);
64
+ }
65
+ }
66
+
67
+ @media (max-width: 767px) {
68
+ .examples-container {
69
+ padding: 1.5rem 1rem 0.5rem;
70
+ }
71
+
72
+ .examples-grid {
73
+ grid-template-columns: 1fr;
74
+ gap: 0.75rem;
75
+ }
76
+
77
+ .example-button {
78
+ width: 100%;
79
+ }
80
+ }
81
+ </style>
82
+
83
+ <div class="examples-container" id="examplesContainer">
84
+ <div class="examples-title">Try these examples:</div>
85
+ <div class="examples-grid" id="examplesGrid">
86
+ <button class="example-button" data-prompt="Explain quantum computing in simple terms">
87
+ Explain quantum computing in simple terms
88
+ </button>
89
+ <button class="example-button" data-prompt="Write a detailed essay on AI impact">
90
+ Write a detailed essay on AI impact
91
+ </button>
92
+ <button class="example-button" data-prompt="Write a short poem about coding">
93
+ Write a short poem about coding
94
+ </button>
95
+ <button class="example-button" data-prompt="Generate a JavaScript function to sort an array">
96
+ Generate a JavaScript function to sort an array
97
+ </button>
98
+ </div>
99
+ </div>
100
+ `;
101
+ }
102
+
103
+ setupEventListeners() {
104
+ const exampleButtons = this.shadowRoot.querySelectorAll('.example-button');
105
+
106
+ exampleButtons.forEach(button => {
107
+ button.addEventListener('click', () => {
108
+ const prompt = button.dataset.prompt;
109
+ document.dispatchEvent(new CustomEvent('claude-fill-input', {
110
+ detail: { prompt }
111
+ }));
112
+ });
113
+ });
114
+
115
+ // Hide examples after first user message
116
+ document.addEventListener('claude-user-message-sent', () => {
117
+ const container = this.shadowRoot.getElementById('examplesContainer');
118
+ container.classList.add('hidden');
119
+ });
120
+
121
+ // Show examples when starting new chat
122
+ document.addEventListener('claude-new-chat', () => {
123
+ const container = this.shadowRoot.getElementById('examplesContainer');
124
+ container.classList.remove('hidden');
125
+ });
126
+ }
127
+ }
128
+
129
+ customElements.define('claude-examples', ClaudeExamples);
components/header.js ADDED
@@ -0,0 +1,246 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ClaudeHeader extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ }
5
+
6
+ connectedCallback() {
7
+ this.attachShadow({ mode: 'open' });
8
+ this.render();
9
+ this.setupEventListeners();
10
+ }
11
+
12
+ render() {
13
+ this.shadowRoot.innerHTML = `
14
+ <style>
15
+ .header {
16
+ background: rgba(255, 255, 255, 0.8);
17
+ backdrop-filter: blur(10px);
18
+ border-bottom: 1px solid #e2e8f0;
19
+ padding: 1rem 1.5rem;
20
+ position: sticky;
21
+ top: 0;
22
+ z-index: 40;
23
+ }
24
+
25
+ .header-content {
26
+ max-width: 1200px;
27
+ margin: 0 auto;
28
+ display: flex;
29
+ justify-content: space-between;
30
+ align-items: center;
31
+ }
32
+
33
+ .app-identity {
34
+ display: flex;
35
+ flex-direction: column;
36
+ gap: 0.25rem;
37
+ }
38
+
39
+ .app-name {
40
+ font-size: 1.5rem;
41
+ font-weight: 700;
42
+ color: #1e293b;
43
+ cursor: pointer;
44
+ transition: color 0.2s;
45
+ }
46
+
47
+ .app-name:hover {
48
+ color: var(--primary-blue);
49
+ }
50
+
51
+ .tagline {
52
+ font-size: 0.875rem;
53
+ color: #64748b;
54
+ }
55
+
56
+ .model-selector {
57
+ position: relative;
58
+ }
59
+
60
+ .model-button {
61
+ display: flex;
62
+ align-items: center;
63
+ gap: 0.5rem;
64
+ padding: 0.5rem 1rem;
65
+ background: white;
66
+ border: 1px solid #d1d5db;
67
+ border-radius: 0.75rem;
68
+ font-size: 0.875rem;
69
+ font-weight: 500;
70
+ color: #374151;
71
+ cursor: pointer;
72
+ transition: all 0.2s;
73
+ }
74
+
75
+ .model-button:hover {
76
+ border-color: var(--primary-blue);
77
+ background: #f8fafc;
78
+ }
79
+
80
+ .model-button:disabled {
81
+ opacity: 0.6;
82
+ cursor: not-allowed;
83
+ }
84
+
85
+ .dropdown {
86
+ position: absolute;
87
+ top: 100%;
88
+ right: 0;
89
+ margin-top: 0.5rem;
90
+ background: white;
91
+ border: 1px solid #e2e8f0;
92
+ border-radius: 0.75rem;
93
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
94
+ z-index: 50;
95
+ min-width: 200px;
96
+ display: none;
97
+ }
98
+
99
+ .dropdown.show {
100
+ display: block;
101
+ }
102
+
103
+ .dropdown-item {
104
+ padding: 0.75rem 1rem;
105
+ cursor: pointer;
106
+ transition: background 0.2s;
107
+ border-bottom: 1px solid #f1f5f9;
108
+ }
109
+
110
+ .dropdown-item:last-child {
111
+ border-bottom: none;
112
+ }
113
+
114
+ .dropdown-item:hover {
115
+ background: #f8fafc;
116
+ }
117
+
118
+ .dropdown-item.selected {
119
+ background: #eff6ff;
120
+ color: var(--primary-blue);
121
+ }
122
+
123
+ @media (max-width: 767px) {
124
+ .header {
125
+ padding: 0.75rem 1rem;
126
+ }
127
+
128
+ .app-name {
129
+ font-size: 1.25rem;
130
+ }
131
+
132
+ .tagline {
133
+ display: none;
134
+ }
135
+ }
136
+ </style>
137
+
138
+ <header class="header">
139
+ <div class="header-content">
140
+ <div class="app-identity">
141
+ <div class="app-name" id="appName">
142
+ ClaudeVerse AI
143
+ </div>
144
+ <div class="tagline">
145
+ Powered by Puter.js
146
+ </div>
147
+ </div>
148
+
149
+ <div class="model-selector">
150
+ <button class="model-button" id="modelButton">
151
+ <span id="currentModel">Claude Sonnet 4.5</span>
152
+ <i data-feather="chevron-down" width="16" height="16"></i>
153
+ </button>
154
+
155
+ <div class="dropdown" id="modelDropdown">
156
+ <div class="dropdown-item" data-model="claude-sonnet-4-5">Claude Sonnet 4.5</div>
157
+ <div class="dropdown-item" data-model="claude-sonnet-4">Claude Sonnet 4</div>
158
+ <div class="dropdown-item" data-model="claude-opus-4-1">Claude Opus 4.1</div>
159
+ <div class="dropdown-item" data-model="claude-opus-4">Claude Opus 4</div>
160
+ <div class="dropdown-item" data-model="claude-haiku-4-5">Claude Haiku 4.5</div>
161
+ </div>
162
+ </div>
163
+ </div>
164
+ </header>
165
+ `;
166
+
167
+ // Update feather icons
168
+ setTimeout(() => {
169
+ if (this.shadowRoot.querySelector('[data-feather]')) {
170
+ feather.replace();
171
+ }
172
+ }, 100);
173
+ }
174
+
175
+ setupEventListeners() {
176
+ const appName = this.shadowRoot.getElementById('appName');
177
+ const modelButton = this.shadowRoot.getElementById('modelButton');
178
+ const modelDropdown = this.shadowRoot.getElementById('modelDropdown');
179
+ const currentModel = this.shadowRoot.getElementById('currentModel');
180
+
181
+ // Reset conversation on app name click
182
+ appName.addEventListener('click', () => {
183
+ document.dispatchEvent(new CustomEvent('claude-new-chat'));
184
+ });
185
+
186
+ // Toggle dropdown
187
+ modelButton.addEventListener('click', () => {
188
+ if (window.claudeApp.isProcessing) return;
189
+
190
+ const isShowing = modelDropdown.classList.contains('show');
191
+ if (isShowing) {
192
+ modelDropdown.classList.remove('show');
193
+ } else {
194
+ modelDropdown.classList.add('show');
195
+ }
196
+ });
197
+
198
+ // Model selection
199
+ modelDropdown.addEventListener('click', (event) => {
200
+ const item = event.target.closest('.dropdown-item');
201
+ if (item) {
202
+ const model = item.dataset.model;
203
+ document.dispatchEvent(new CustomEvent('claude-change-model', {
204
+ detail: { model }
205
+ }));
206
+
207
+ // Update current model display
208
+ currentModel.textContent = this.getModelLabel(model);
209
+
210
+ // Close dropdown
211
+ modelDropdown.classList.remove('show');
212
+
213
+ // Show selection feedback
214
+ item.classList.add('selected');
215
+ setTimeout(() => item.classList.remove('selected'), 500);
216
+ }
217
+ });
218
+
219
+ // Close dropdown when clicking outside
220
+ document.addEventListener('click', (event) => {
221
+ if (!this.contains(event.target)) {
222
+ modelDropdown.classList.remove('show');
223
+ }
224
+ });
225
+
226
+ // Update model display when changed externally
227
+ document.addEventListener('claude-model-changed', (event) => {
228
+ const { model } = event.detail;
229
+ currentModel.textContent = this.getModelLabel(model);
230
+ });
231
+ }
232
+
233
+ getModelLabel(model) {
234
+ const modelLabels = {
235
+ 'claude-sonnet-4-5': 'Claude Sonnet 4.5',
236
+ 'claude-sonnet-4': 'Claude Sonnet 4',
237
+ 'claude-opus-4-1': 'Claude Opus 4.1',
238
+ 'claude-opus-4': 'Claude Opus 4',
239
+ 'claude-haiku-4-5': 'Claude Haiku 4.5'
240
+ };
241
+
242
+ return modelLabels[model] || model;
243
+ }
244
+ }
245
+
246
+ customElements.define('claude-header', ClaudeHeader);
components/input-area.js ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ class ClaudeInputArea extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ }
5
+
6
+ connectedCallback() {
7
+ this.attachShadow({ mode: 'open' });
8
+ this.render();
9
+ this.setupEventListeners();
10
+ }
11
+
12
+ render() {
13
+ this.shadowRoot.innerHTML = `
14
+ <style>
15
+ .input-container {
16
+ background: white;
17
+ border-top: 1px solid #e2e8f0;
18
+ padding: 1.5rem;
19
+ position: fixed;
20
+ bottom: 0;
21
+ left: 0;
22
+ right: 0;
23
+ }
24
+
25
+ .input-wrapper {
26
+ max-width: 1200px;
27
+ margin: 0 auto;
28
+ display: flex;
29
+ gap: 1rem;
30
+ align-items: flex-end;
31
+ }
32
+
33
+ .textarea-container {
34
+ flex: 1;
35
+ position: relative;
36
+ }
37
+
38
+ .message-textarea {
39
+ width: 100%;
40
+ min-height: 56px;
41
+ max-height: 150px;
42
+ padding: 1rem;
43
+ border:
index.html CHANGED
@@ -1,19 +1,40 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>ClaudeVerse AI Chat Interface</title>
7
+ <link rel="icon" type="image/x-icon" href="/static/favicon.ico">
8
+ <link rel="stylesheet" href="style.css">
9
+ <script src="https://cdn.tailwindcss.com"></script>
10
+ <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
11
+ <script src="https://unpkg.com/feather-icons"></script>
12
+ <script src="https://js.puter.com/v2/"></script>
13
+ </head>
14
+ <body class="bg-gradient-to-br from-slate-50 to-blue-50 min-h-screen">
15
+ <claude-header></claude-header>
16
+ <claude-examples></claude-examples>
17
+ <claude-chat-container></claude-chat-container>
18
+ <claude-input-area></claude-input-area>
19
+ <claude-status-indicator></claude-status-indicator>
20
+
21
+ <script src="components/header.js"></script>
22
+ <script src="components/examples.js"></script>
23
+ <script src="components/chat-container.js"></script>
24
+ <script src="components/input-area.js"></script>
25
+ <script src="components/status-indicator.js"></script>
26
+ <script src="script.js"></script>
27
+ <script>
28
+ feather.replace();
29
+ // Initialize app state
30
+ window.claudeApp = {
31
+ conversationHistory: [],
32
+ selectedModel: 'claude-sonnet-4-5',
33
+ isProcessing: false,
34
+ isStreaming: false,
35
+ currentStreamMessage: null
36
+ };
37
+ </script>
38
+ <script src="https://huggingface.co/deepsite/deepsite-badge.js"></script>
39
+ </body>
40
+ </html>
script.js ADDED
@@ -0,0 +1,230 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Global state management and utility functions
2
+ class ClaudeAppState {
3
+ constructor() {
4
+ this.loadFromStorage();
5
+ }
6
+
7
+ loadFromStorage() {
8
+ try {
9
+ const history = localStorage.getItem('claudeChat_history');
10
+ const model = localStorage.getItem('claudeChat_selectedModel');
11
+ const timestamp = localStorage.getItem('claudeChat_timestamp');
12
+
13
+ // Validate timestamp (clear if older than 24 hours)
14
+ if (timestamp && Date.now() - parseInt(timestamp) > 24 * 60 * 60 * 1000) {
15
+ this.clearStorage();
16
+ return;
17
+ }
18
+
19
+ if (history) {
20
+ window.claudeApp.conversationHistory = JSON.parse(history);
21
+ }
22
+ if (model) {
23
+ window.claudeApp.selectedModel = model;
24
+ }
25
+ } catch (error) {
26
+ console.error('Error loading from storage:', error);
27
+ this.clearStorage();
28
+ }
29
+ }
30
+
31
+ saveToStorage() {
32
+ try {
33
+ localStorage.setItem('claudeChat_history', JSON.stringify(window.claudeApp.conversationHistory));
34
+ localStorage.setItem('claudeChat_selectedModel', window.claudeApp.selectedModel);
35
+ localStorage.setItem('claudeChat_timestamp', Date.now().toString());
36
+ } catch (error) {
37
+ console.error('Error saving to storage:', error);
38
+ }
39
+ }
40
+
41
+ clearStorage() {
42
+ localStorage.removeItem('claudeChat_history');
43
+ localStorage.removeItem('claudeChat_selectedModel');
44
+ localStorage.removeItem('claudeChat_timestamp');
45
+ window.claudeApp.conversationHistory = [];
46
+ window.claudeApp.selectedModel = 'claude-sonnet-4-5';
47
+ }
48
+
49
+ addMessage(role, content, timestamp = Date.now()) {
50
+ const message = {
51
+ role,
52
+ content,
53
+ timestamp,
54
+ id: this.generateId()
55
+ };
56
+
57
+ window.claudeApp.conversationHistory.push(message);
58
+
59
+ // Maintain 50 message limit
60
+ if (window.claudeApp.conversationHistory.length > 50) {
61
+ window.claudeApp.conversationHistory.shift();
62
+ }
63
+
64
+ this.saveToStorage();
65
+ return message;
66
+ }
67
+
68
+ generateId() {
69
+ return Date.now().toString(36) + Math.random().toString(36).substr(2);
70
+ }
71
+
72
+ setModel(model) {
73
+ const oldModel = window.claudeApp.selectedModel;
74
+ window.claudeApp.selectedModel = model;
75
+
76
+ // Add system message if switching models mid-conversation
77
+ if (window.claudeApp.conversationHistory.length > 0 && oldModel !== model) {
78
+ this.addMessage('system', `Model changed to ${this.getModelLabel(model)} — previous context retained`);
79
+ }
80
+
81
+ this.saveToStorage();
82
+ }
83
+
84
+ getModelLabel(model) {
85
+ const modelLabels = {
86
+ 'claude-sonnet-4-5': 'Claude Sonnet 4.5',
87
+ 'claude-sonnet-4': 'Claude Sonnet 4',
88
+ 'claude-opus-4-1': 'Claude Opus 4.1',
89
+ 'claude-opus-4': 'Claude Opus 4',
90
+ 'claude-haiku-4-5': 'Claude Haiku 4.5'
91
+ };
92
+
93
+ return modelLabels[model] || model;
94
+ }
95
+ }
96
+
97
+ // Initialize app state
98
+ const appState = new ClaudeAppState();
99
+
100
+ // API Communication
101
+ class ClaudeAPI {
102
+ async sendMessage(message, stream = false) {
103
+ if (window.claudeApp.isProcessing) {
104
+ throw new Error('Another request is already in progress');
105
+ }
106
+
107
+ window.claudeApp.isProcessing = true;
108
+ window.claudeApp.isStreaming = stream;
109
+
110
+ try {
111
+ const options = {
112
+ model: window.claudeApp.selectedModel,
113
+ stream: stream
114
+ };
115
+
116
+ if (stream) {
117
+ return this.sendStreamingMessage(message, options);
118
+ } else {
119
+ return this.sendStandardMessage(message, options);
120
+ }
121
+ } catch (error) {
122
+ window.claudeApp.isProcessing = false;
123
+ window.claudeApp.isStreaming = false;
124
+ throw error;
125
+ }
126
+ }
127
+
128
+ async sendStandardMessage(message, options) {
129
+ try {
130
+ const response = await puter.ai.chat(message, options);
131
+ window.claudeApp.isProcessing = false;
132
+
133
+ return response.message.content[0].text;
134
+ } catch (error) {
135
+ window.claudeApp.isProcessing = false;
136
+ throw new Error(`API Error: ${error.message}`);
137
+ }
138
+ }
139
+
140
+ async sendStreamingMessage(message, options) {
141
+ try {
142
+ const response = await puter.ai.chat(message, options);
143
+ let fullResponse = '';
144
+
145
+ for await (const part of response) {
146
+ if (part.type === 'content_block_delta' && part.delta?.text) {
147
+ fullResponse += part.delta.text;
148
+ // Update the UI with the current response
149
+ this.updateStreamingResponse(fullResponse);
150
+ }
151
+ }
152
+
153
+ window.claudeApp.isProcessing = false;
154
+ window.claudeApp.isStreaming = false;
155
+ return fullResponse;
156
+ } catch (error) {
157
+ window.claudeApp.isProcessing = false;
158
+ window.claudeApp.isStreaming = false;
159
+ throw new Error(`Streaming Error: ${error.message}`);
160
+ }
161
+ }
162
+
163
+ updateStreamingResponse(content) {
164
+ // This will be implemented in the chat-container component
165
+ const chatContainer = document.querySelector('claude-chat-container');
166
+ if (chatContainer && chatContainer.updateStreamingMessage) {
167
+ chatContainer.updateStreamingMessage(content);
168
+ }
169
+ }
170
+ }
171
+
172
+ // Event handling and coordination
173
+ document.addEventListener('DOMContentLoaded', function() {
174
+ const api = new ClaudeAPI();
175
+
176
+ // Global event listeners for cross-component communication
177
+ document.addEventListener('claude-send-message', async (event) => {
178
+ const { message, stream } = event.detail;
179
+
180
+ try {
181
+ // Add user message to history
182
+ appState.addMessage('user', message);
183
+
184
+ // Show typing indicator
185
+ document.dispatchEvent(new CustomEvent('claude-show-typing'));
186
+
187
+ const response = await api.sendMessage(message, stream);
188
+
189
+ // Hide typing indicator and add assistant response
190
+ document.dispatchEvent(new CustomEvent('claude-hide-typing'));
191
+ appState.addMessage('assistant', response);
192
+
193
+ // Show success status
194
+ document.dispatchEvent(new CustomEvent('claude-show-status', {
195
+ detail: { type: 'success', message: 'Response received' }
196
+ }));
197
+ } catch (error) {
198
+ document.dispatchEvent(new CustomEvent('claude-hide-typing'));
199
+ document.dispatchEvent(new CustomEvent('claude-show-status', {
200
+ detail: { type: 'error', message: error.message }
201
+ }));
202
+ }
203
+ });
204
+
205
+ document.addEventListener('claude-change-model', (event) => {
206
+ const { model } = event.detail;
207
+ appState.setModel(model);
208
+ });
209
+
210
+ document.addEventListener('claude-new-chat', () => {
211
+ appState.clearStorage();
212
+ document.dispatchEvent(new CustomEvent('claude-clear-chat'));
213
+ });
214
+
215
+ // Keyboard shortcuts
216
+ document.addEventListener('keydown', (event) => {
217
+ const isCmdK = (event.metaKey || event.ctrlKey) && event.key === 'k';
218
+ const isCmdN = (event.metaKey || event.ctrlKey) && event.key === 'n';
219
+
220
+ if (isCmdK) {
221
+ event.preventDefault();
222
+ document.dispatchEvent(new CustomEvent('claude-clear-input'));
223
+ }
224
+
225
+ if (isCmdN) {
226
+ event.preventDefault();
227
+ document.dispatchEvent(new CustomEvent('claude-new-chat'));
228
+ }
229
+ });
230
+ });
style.css CHANGED
@@ -1,28 +1,126 @@
1
- body {
2
- padding: 2rem;
3
- font-family: -apple-system, BlinkMacSystemFont, "Arial", sans-serif;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  }
5
 
6
- h1 {
7
- font-size: 16px;
8
- margin-top: 0;
 
9
  }
10
 
11
- p {
12
- color: rgb(107, 114, 128);
13
- font-size: 15px;
14
- margin-bottom: 10px;
15
- margin-top: 5px;
 
 
16
  }
17
 
18
- .card {
19
- max-width: 620px;
20
- margin: 0 auto;
21
- padding: 16px;
22
- border: 1px solid lightgray;
23
- border-radius: 16px;
 
 
24
  }
25
 
26
- .card p:last-child {
27
- margin-bottom: 0;
 
28
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ /* Custom CSS for Claude AI Chat Interface */
2
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap');
3
+
4
+ :root {
5
+ --primary-blue: #2563eb;
6
+ --primary-blue-dark: #1d4ed8;
7
+ --success-green: #10b981;
8
+ --error-red: #ef4444;
9
+ --warning-orange: #f59e0b;
10
+ }
11
+
12
+ * {
13
+ font-family: 'Inter', sans-serif;
14
+ }
15
+
16
+ /* Scrollbar Styling */
17
+ ::-webkit-scrollbar {
18
+ width: 6px;
19
+ }
20
+
21
+ ::-webkit-scrollbar-track {
22
+ background: #f1f5f9;
23
+ border-radius: 3px;
24
+ }
25
+
26
+ ::-webkit-scrollbar-thumb {
27
+ background: #cbd5e1;
28
+ border-radius: 3px;
29
+ }
30
+
31
+ ::-webkit-scrollbar-thumb:hover {
32
+ background: #94a3b8;
33
+ }
34
+
35
+ /* Typing Animation */
36
+ @keyframes typing-dots {
37
+ 0%, 20% {
38
+ opacity: 0;
39
+ }
40
+ 50% {
41
+ opacity: 1;
42
+ }
43
+ 100% {
44
+ opacity: 0;
45
+ }
46
+ }
47
+
48
+ .typing-dot {
49
+ animation: typing-dots 1.4s infinite ease-in-out;
50
+ }
51
+
52
+ .typing-dot:nth-child(2) {
53
+ animation-delay: 0.2s;
54
+ }
55
+
56
+ .typing-dot:nth-child(3) {
57
+ animation-delay: 0.4s;
58
+ }
59
+
60
+ /* Message Transitions */
61
+ .message-enter {
62
+ opacity: 0;
63
+ transform: translateY(10px);
64
  }
65
 
66
+ .message-enter-active {
67
+ opacity: 1;
68
+ transform: translateY(0);
69
+ transition: opacity 300ms, transform 300ms;
70
  }
71
 
72
+ /* Code Block Styling */
73
+ .code-block {
74
+ background: #1e293b;
75
+ color: #e2e8f0;
76
+ border-radius: 8px;
77
+ overflow: hidden;
78
+ margin: 1rem 0;
79
  }
80
 
81
+ .code-header {
82
+ background: #334155;
83
+ padding: 0.75rem 1rem;
84
+ font-size: 0.875rem;
85
+ color: #94a3b8;
86
+ display: flex;
87
+ justify-content: space-between;
88
+ align-items: center;
89
  }
90
 
91
+ .code-content {
92
+ padding: 1rem;
93
+ overflow-x: auto;
94
  }
95
+
96
+ /* Responsive adjustments */
97
+ @media (max-width: 767px) {
98
+ .mobile-hidden {
99
+ display: none;
100
+ }
101
+
102
+ .mobile-full {
103
+ width: 100%;
104
+ }
105
+ }
106
+
107
+ /* Focus states for accessibility */
108
+ .focus-visible {
109
+ outline: 2px solid var(--primary-blue);
110
+ outline-offset: 2px;
111
+ }
112
+
113
+ /* Loading spinner */
114
+ .spinner {
115
+ border: 2px solid #f3f4f6;
116
+ border-top: 2px solid var(--primary-blue);
117
+ border-radius: 50%;
118
+ width: 20px;
119
+ height: 20px;
120
+ animation: spin 1s linear infinite;
121
+ }
122
+
123
+ @keyframes spin {
124
+ 0% { transform: rotate(0deg); }
125
+ 100% { transform: rotate(360deg); }
126
+ }