Spaces:
Runtime error
Runtime error
Merge pull request #73 from EnvisionMindCa/codex/code-modern-react.js-front-end-with-glassmorphism
Browse files- frontend/.gitignore +24 -0
- frontend/README.md +12 -0
- frontend/eslint.config.js +33 -0
- frontend/index.html +13 -0
- frontend/package-lock.json +0 -0
- frontend/package.json +28 -0
- frontend/public/vite.svg +1 -0
- frontend/src/App.css +42 -0
- frontend/src/App.jsx +8 -0
- frontend/src/assets/react.svg +1 -0
- frontend/src/components/ChatWindow.jsx +47 -0
- frontend/src/components/InputBar.jsx +31 -0
- frontend/src/components/MessageBubble.jsx +13 -0
- frontend/src/components/MessageList.jsx +23 -0
- frontend/src/index.css +20 -0
- frontend/src/main.jsx +10 -0
- frontend/src/styles/chat.css +87 -0
- frontend/vite.config.js +7 -0
frontend/.gitignore
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Logs
|
| 2 |
+
logs
|
| 3 |
+
*.log
|
| 4 |
+
npm-debug.log*
|
| 5 |
+
yarn-debug.log*
|
| 6 |
+
yarn-error.log*
|
| 7 |
+
pnpm-debug.log*
|
| 8 |
+
lerna-debug.log*
|
| 9 |
+
|
| 10 |
+
node_modules
|
| 11 |
+
dist
|
| 12 |
+
dist-ssr
|
| 13 |
+
*.local
|
| 14 |
+
|
| 15 |
+
# Editor directories and files
|
| 16 |
+
.vscode/*
|
| 17 |
+
!.vscode/extensions.json
|
| 18 |
+
.idea
|
| 19 |
+
.DS_Store
|
| 20 |
+
*.suo
|
| 21 |
+
*.ntvs*
|
| 22 |
+
*.njsproj
|
| 23 |
+
*.sln
|
| 24 |
+
*.sw?
|
frontend/README.md
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# React + Vite
|
| 2 |
+
|
| 3 |
+
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
| 4 |
+
|
| 5 |
+
Currently, two official plugins are available:
|
| 6 |
+
|
| 7 |
+
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
|
| 8 |
+
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
| 9 |
+
|
| 10 |
+
## Expanding the ESLint configuration
|
| 11 |
+
|
| 12 |
+
If you are developing a production application, we recommend using TypeScript with type-aware lint rules enabled. Check out the [TS template](https://github.com/vitejs/vite/tree/main/packages/create-vite/template-react-ts) for information on how to integrate TypeScript and [`typescript-eslint`](https://typescript-eslint.io) in your project.
|
frontend/eslint.config.js
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import js from '@eslint/js'
|
| 2 |
+
import globals from 'globals'
|
| 3 |
+
import reactHooks from 'eslint-plugin-react-hooks'
|
| 4 |
+
import reactRefresh from 'eslint-plugin-react-refresh'
|
| 5 |
+
|
| 6 |
+
export default [
|
| 7 |
+
{ ignores: ['dist'] },
|
| 8 |
+
{
|
| 9 |
+
files: ['**/*.{js,jsx}'],
|
| 10 |
+
languageOptions: {
|
| 11 |
+
ecmaVersion: 2020,
|
| 12 |
+
globals: globals.browser,
|
| 13 |
+
parserOptions: {
|
| 14 |
+
ecmaVersion: 'latest',
|
| 15 |
+
ecmaFeatures: { jsx: true },
|
| 16 |
+
sourceType: 'module',
|
| 17 |
+
},
|
| 18 |
+
},
|
| 19 |
+
plugins: {
|
| 20 |
+
'react-hooks': reactHooks,
|
| 21 |
+
'react-refresh': reactRefresh,
|
| 22 |
+
},
|
| 23 |
+
rules: {
|
| 24 |
+
...js.configs.recommended.rules,
|
| 25 |
+
...reactHooks.configs.recommended.rules,
|
| 26 |
+
'no-unused-vars': ['error', { varsIgnorePattern: '^[A-Z_]' }],
|
| 27 |
+
'react-refresh/only-export-components': [
|
| 28 |
+
'warn',
|
| 29 |
+
{ allowConstantExport: true },
|
| 30 |
+
],
|
| 31 |
+
},
|
| 32 |
+
},
|
| 33 |
+
]
|
frontend/index.html
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="UTF-8" />
|
| 5 |
+
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
| 6 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
| 7 |
+
<title>LLM Chat</title>
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div id="root"></div>
|
| 11 |
+
<script type="module" src="/src/main.jsx"></script>
|
| 12 |
+
</body>
|
| 13 |
+
</html>
|
frontend/package-lock.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
frontend/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"name": "frontend",
|
| 3 |
+
"private": true,
|
| 4 |
+
"version": "0.0.0",
|
| 5 |
+
"type": "module",
|
| 6 |
+
"scripts": {
|
| 7 |
+
"dev": "vite",
|
| 8 |
+
"build": "vite build",
|
| 9 |
+
"lint": "eslint .",
|
| 10 |
+
"preview": "vite preview"
|
| 11 |
+
},
|
| 12 |
+
"dependencies": {
|
| 13 |
+
"prop-types": "^15.8.1",
|
| 14 |
+
"react": "^19.1.0",
|
| 15 |
+
"react-dom": "^19.1.0"
|
| 16 |
+
},
|
| 17 |
+
"devDependencies": {
|
| 18 |
+
"@eslint/js": "^9.25.0",
|
| 19 |
+
"@types/react": "^19.1.2",
|
| 20 |
+
"@types/react-dom": "^19.1.2",
|
| 21 |
+
"@vitejs/plugin-react": "^4.4.1",
|
| 22 |
+
"eslint": "^9.25.0",
|
| 23 |
+
"eslint-plugin-react-hooks": "^5.2.0",
|
| 24 |
+
"eslint-plugin-react-refresh": "^0.4.19",
|
| 25 |
+
"globals": "^16.0.0",
|
| 26 |
+
"vite": "^6.3.5"
|
| 27 |
+
}
|
| 28 |
+
}
|
frontend/public/vite.svg
ADDED
|
|
frontend/src/App.css
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#root {
|
| 2 |
+
max-width: 1280px;
|
| 3 |
+
margin: 0 auto;
|
| 4 |
+
padding: 2rem;
|
| 5 |
+
text-align: center;
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
.logo {
|
| 9 |
+
height: 6em;
|
| 10 |
+
padding: 1.5em;
|
| 11 |
+
will-change: filter;
|
| 12 |
+
transition: filter 300ms;
|
| 13 |
+
}
|
| 14 |
+
.logo:hover {
|
| 15 |
+
filter: drop-shadow(0 0 2em #646cffaa);
|
| 16 |
+
}
|
| 17 |
+
.logo.react:hover {
|
| 18 |
+
filter: drop-shadow(0 0 2em #61dafbaa);
|
| 19 |
+
}
|
| 20 |
+
|
| 21 |
+
@keyframes logo-spin {
|
| 22 |
+
from {
|
| 23 |
+
transform: rotate(0deg);
|
| 24 |
+
}
|
| 25 |
+
to {
|
| 26 |
+
transform: rotate(360deg);
|
| 27 |
+
}
|
| 28 |
+
}
|
| 29 |
+
|
| 30 |
+
@media (prefers-reduced-motion: no-preference) {
|
| 31 |
+
a:nth-of-type(2) .logo {
|
| 32 |
+
animation: logo-spin infinite 20s linear;
|
| 33 |
+
}
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
.card {
|
| 37 |
+
padding: 2em;
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
.read-the-docs {
|
| 41 |
+
color: #888;
|
| 42 |
+
}
|
frontend/src/App.jsx
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import ChatWindow from './components/ChatWindow'
|
| 2 |
+
import './index.css'
|
| 3 |
+
|
| 4 |
+
function App() {
|
| 5 |
+
return <ChatWindow />
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
export default App
|
frontend/src/assets/react.svg
ADDED
|
|
frontend/src/components/ChatWindow.jsx
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from 'react'
|
| 2 |
+
import MessageList from './MessageList'
|
| 3 |
+
import InputBar from './InputBar'
|
| 4 |
+
import '../styles/chat.css'
|
| 5 |
+
|
| 6 |
+
const API_URL = import.meta.env.VITE_API_URL || 'http://localhost:8000/chat/stream'
|
| 7 |
+
|
| 8 |
+
async function fetchStream(prompt, onChunk) {
|
| 9 |
+
const res = await fetch(API_URL, {
|
| 10 |
+
method: 'POST',
|
| 11 |
+
headers: { 'Content-Type': 'application/json' },
|
| 12 |
+
body: JSON.stringify({ user: 'demo', session: 'default', prompt }),
|
| 13 |
+
})
|
| 14 |
+
const reader = res.body.getReader()
|
| 15 |
+
const decoder = new TextDecoder()
|
| 16 |
+
while (true) {
|
| 17 |
+
const { value, done } = await reader.read()
|
| 18 |
+
if (done) break
|
| 19 |
+
onChunk(decoder.decode(value))
|
| 20 |
+
}
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
function ChatWindow() {
|
| 24 |
+
const [messages, setMessages] = useState([])
|
| 25 |
+
|
| 26 |
+
const sendMessage = async (text) => {
|
| 27 |
+
const userMsg = { role: 'user', content: text }
|
| 28 |
+
setMessages((prev) => [...prev, userMsg, { role: 'assistant', content: '' }])
|
| 29 |
+
const index = messages.length + 1
|
| 30 |
+
await fetchStream(text, (chunk) => {
|
| 31 |
+
setMessages((prev) => {
|
| 32 |
+
const copy = [...prev]
|
| 33 |
+
copy[index] = { ...copy[index], content: copy[index].content + chunk }
|
| 34 |
+
return copy
|
| 35 |
+
})
|
| 36 |
+
})
|
| 37 |
+
}
|
| 38 |
+
|
| 39 |
+
return (
|
| 40 |
+
<div className="chat-container">
|
| 41 |
+
<MessageList messages={messages} />
|
| 42 |
+
<InputBar onSend={sendMessage} />
|
| 43 |
+
</div>
|
| 44 |
+
)
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
export default ChatWindow
|
frontend/src/components/InputBar.jsx
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { useState } from 'react'
|
| 2 |
+
import PropTypes from 'prop-types'
|
| 3 |
+
|
| 4 |
+
function InputBar({ onSend }) {
|
| 5 |
+
const [text, setText] = useState('')
|
| 6 |
+
|
| 7 |
+
const handleSubmit = (e) => {
|
| 8 |
+
e.preventDefault()
|
| 9 |
+
if (!text.trim()) return
|
| 10 |
+
onSend(text)
|
| 11 |
+
setText('')
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
return (
|
| 15 |
+
<form className="input-bar" onSubmit={handleSubmit}>
|
| 16 |
+
<input
|
| 17 |
+
type="text"
|
| 18 |
+
value={text}
|
| 19 |
+
onChange={(e) => setText(e.target.value)}
|
| 20 |
+
placeholder="Type a message..."
|
| 21 |
+
/>
|
| 22 |
+
<button type="submit">Send</button>
|
| 23 |
+
</form>
|
| 24 |
+
)
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
InputBar.propTypes = {
|
| 28 |
+
onSend: PropTypes.func.isRequired,
|
| 29 |
+
}
|
| 30 |
+
|
| 31 |
+
export default InputBar
|
frontend/src/components/MessageBubble.jsx
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import PropTypes from 'prop-types'
|
| 2 |
+
import '../styles/chat.css'
|
| 3 |
+
|
| 4 |
+
function MessageBubble({ role, content }) {
|
| 5 |
+
return <div className={`message ${role}`}>{content}</div>
|
| 6 |
+
}
|
| 7 |
+
|
| 8 |
+
MessageBubble.propTypes = {
|
| 9 |
+
role: PropTypes.string.isRequired,
|
| 10 |
+
content: PropTypes.string.isRequired,
|
| 11 |
+
}
|
| 12 |
+
|
| 13 |
+
export default MessageBubble
|
frontend/src/components/MessageList.jsx
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import PropTypes from 'prop-types'
|
| 2 |
+
import MessageBubble from './MessageBubble'
|
| 3 |
+
|
| 4 |
+
function MessageList({ messages }) {
|
| 5 |
+
return (
|
| 6 |
+
<div className="message-list">
|
| 7 |
+
{messages.map((m, i) => (
|
| 8 |
+
<MessageBubble key={i} role={m.role} content={m.content} />
|
| 9 |
+
))}
|
| 10 |
+
</div>
|
| 11 |
+
)
|
| 12 |
+
}
|
| 13 |
+
|
| 14 |
+
MessageList.propTypes = {
|
| 15 |
+
messages: PropTypes.arrayOf(
|
| 16 |
+
PropTypes.shape({
|
| 17 |
+
role: PropTypes.string.isRequired,
|
| 18 |
+
content: PropTypes.string.isRequired,
|
| 19 |
+
})
|
| 20 |
+
).isRequired,
|
| 21 |
+
}
|
| 22 |
+
|
| 23 |
+
export default MessageList
|
frontend/src/index.css
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root {
|
| 2 |
+
--glass-bg: rgba(255, 255, 255, 0.2);
|
| 3 |
+
--glass-border: rgba(255, 255, 255, 0.4);
|
| 4 |
+
--blur: 16px;
|
| 5 |
+
}
|
| 6 |
+
|
| 7 |
+
* {
|
| 8 |
+
box-sizing: border-box;
|
| 9 |
+
}
|
| 10 |
+
|
| 11 |
+
body {
|
| 12 |
+
margin: 0;
|
| 13 |
+
padding: 0;
|
| 14 |
+
font-family: system-ui, Avenir, Helvetica, Arial, sans-serif;
|
| 15 |
+
background: linear-gradient(135deg, #a1c4fd 0%, #c2e9fb 100%);
|
| 16 |
+
display: flex;
|
| 17 |
+
justify-content: center;
|
| 18 |
+
align-items: center;
|
| 19 |
+
min-height: 100vh;
|
| 20 |
+
}
|
frontend/src/main.jsx
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { StrictMode } from 'react'
|
| 2 |
+
import { createRoot } from 'react-dom/client'
|
| 3 |
+
import './index.css'
|
| 4 |
+
import App from './App.jsx'
|
| 5 |
+
|
| 6 |
+
createRoot(document.getElementById('root')).render(
|
| 7 |
+
<StrictMode>
|
| 8 |
+
<App />
|
| 9 |
+
</StrictMode>,
|
| 10 |
+
)
|
frontend/src/styles/chat.css
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.chat-container {
|
| 2 |
+
width: 420px;
|
| 3 |
+
max-width: 90vw;
|
| 4 |
+
height: 600px;
|
| 5 |
+
display: flex;
|
| 6 |
+
flex-direction: column;
|
| 7 |
+
background: var(--glass-bg);
|
| 8 |
+
border: 1px solid var(--glass-border);
|
| 9 |
+
backdrop-filter: blur(var(--blur)) saturate(180%);
|
| 10 |
+
-webkit-backdrop-filter: blur(var(--blur)) saturate(180%);
|
| 11 |
+
border-radius: 16px;
|
| 12 |
+
padding: 1rem;
|
| 13 |
+
box-shadow: 0 4px 30px rgba(0, 0, 0, 0.1);
|
| 14 |
+
}
|
| 15 |
+
|
| 16 |
+
.message-list {
|
| 17 |
+
flex: 1;
|
| 18 |
+
overflow-y: auto;
|
| 19 |
+
display: flex;
|
| 20 |
+
flex-direction: column;
|
| 21 |
+
gap: 0.5rem;
|
| 22 |
+
padding-right: 4px;
|
| 23 |
+
}
|
| 24 |
+
|
| 25 |
+
.message {
|
| 26 |
+
width: fit-content;
|
| 27 |
+
max-width: 80%;
|
| 28 |
+
padding: 0.5rem 1rem;
|
| 29 |
+
background: var(--glass-bg);
|
| 30 |
+
border: 1px solid var(--glass-border);
|
| 31 |
+
border-radius: 20px;
|
| 32 |
+
backdrop-filter: blur(var(--blur));
|
| 33 |
+
-webkit-backdrop-filter: blur(var(--blur));
|
| 34 |
+
animation: jelly 0.5s ease;
|
| 35 |
+
}
|
| 36 |
+
|
| 37 |
+
.message.user {
|
| 38 |
+
align-self: flex-end;
|
| 39 |
+
}
|
| 40 |
+
|
| 41 |
+
.input-bar {
|
| 42 |
+
display: flex;
|
| 43 |
+
gap: 0.5rem;
|
| 44 |
+
margin-top: 0.5rem;
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
.input-bar input {
|
| 48 |
+
flex: 1;
|
| 49 |
+
padding: 0.5rem 1rem;
|
| 50 |
+
border: 1px solid var(--glass-border);
|
| 51 |
+
background: var(--glass-bg);
|
| 52 |
+
border-radius: 20px;
|
| 53 |
+
backdrop-filter: blur(var(--blur));
|
| 54 |
+
-webkit-backdrop-filter: blur(var(--blur));
|
| 55 |
+
color: #000;
|
| 56 |
+
}
|
| 57 |
+
|
| 58 |
+
.input-bar button {
|
| 59 |
+
padding: 0.5rem 1rem;
|
| 60 |
+
border: none;
|
| 61 |
+
border-radius: 20px;
|
| 62 |
+
background: rgba(255, 255, 255, 0.6);
|
| 63 |
+
color: #000;
|
| 64 |
+
font-weight: bold;
|
| 65 |
+
cursor: pointer;
|
| 66 |
+
transition: transform 0.2s;
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
.input-bar button:hover {
|
| 70 |
+
transform: scale(1.05);
|
| 71 |
+
}
|
| 72 |
+
|
| 73 |
+
.input-bar button:active {
|
| 74 |
+
transform: scale(0.95);
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
@keyframes jelly {
|
| 78 |
+
0% {
|
| 79 |
+
transform: scale(0.8);
|
| 80 |
+
}
|
| 81 |
+
50% {
|
| 82 |
+
transform: scale(1.1);
|
| 83 |
+
}
|
| 84 |
+
100% {
|
| 85 |
+
transform: scale(1);
|
| 86 |
+
}
|
| 87 |
+
}
|
frontend/vite.config.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { defineConfig } from 'vite'
|
| 2 |
+
import react from '@vitejs/plugin-react'
|
| 3 |
+
|
| 4 |
+
// https://vite.dev/config/
|
| 5 |
+
export default defineConfig({
|
| 6 |
+
plugins: [react()],
|
| 7 |
+
})
|