diff --git a/.DS_Store b/.DS_Store
new file mode 100644
index 00000000..73e005c2
Binary files /dev/null and b/.DS_Store differ
diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml
new file mode 100644
index 00000000..d239dc23
--- /dev/null
+++ b/.github/workflows/deploy.yaml
@@ -0,0 +1,56 @@
+name: Docker Build & Deploy to Vercel
+
+on:
+ push:
+ branches:
+ - main
+
+jobs:
+ deploy:
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout Code
+ uses: actions/checkout@v4
+
+ - name: Install Vercel CLI
+ run: npm install --global vercel@latest
+
+ # Step A: Pull Vercel Config
+ # We do this OUTSIDE Docker first so we have the valid .vercel folder
+ # to copy INTO the Docker container.
+ - name: Pull Vercel Environment Information
+ run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }}
+ env:
+ VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
+ VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
+
+ # Step B: Build the Docker Image
+ # Automates: docker build --build-arg VERCEL_TOKEN=... -t overlap-chatgpt .
+ - name: Build Docker Image
+ run: |
+ docker build \
+ --build-arg VERCEL_TOKEN=${{ secrets.VERCEL_TOKEN }} \
+ -t overlap-chatgpt .
+
+ # Step C: Extract Artifacts
+ # Automates: docker create, docker cp, docker rm
+ - name: Extract Prebuilt Artifacts
+ run: |
+ # Create a temporary container (don't run it, just create it)
+ docker create --name temp_container overlap-chatgpt
+
+ # Copy the .vercel output folder from the container to the runner
+ # Note: We overwrite the local .vercel folder with the build output
+ docker cp temp_container:/.vercel .
+
+ # Cleanup
+ docker rm temp_container
+
+ # Step D: Deploy to Vercel
+ # Automates: vercel deploy --prebuilt --prod
+ - name: Deploy to Vercel
+ run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }}
+ env:
+ VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
+ VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 6769e21d..f66f6d63 100644
--- a/.gitignore
+++ b/.gitignore
@@ -157,4 +157,5 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
-#.idea/
\ No newline at end of file
+#.idea/
+.vercel
diff --git a/ClientServer.md b/ClientServer.md
new file mode 100644
index 00000000..a69da583
--- /dev/null
+++ b/ClientServer.md
@@ -0,0 +1,150 @@
+Overlap-chatgptCloneThis is the backend server for the Overlap ChatGPT Clone, a Flask application designed to serve a chat API. It is configured for deployment on Vercel using a custom Docker build environment.🚀 How to Run LocallyFollow these steps to run the server on your local machine for development.1. PrerequisitesPython 3.12A Python virtual environment (recommended)A config.json file (see below)2. Local InstallationClone the repository:git clone [https://github.com/KennyAngelikas/Overlap-chatgptClone]
+cd Overlap-chatgptClone
+Create and activate a virtual environment:python3 -m venv venv
+source venv/bin/activate
+Install the required Python packages:pip install -r requirements.txt
+Create your configuration file. Your app reads settings from config.json. Create this file in the root directory.{
+ "site_config": {
+ "host": "0.0.0.0",
+ "port": 1338,
+ "debug": true
+ },
+ "database": {
+ "url": "postgresql://user:password@localhost:5432/mydb"
+ },
+ "api_keys": {
+ "gemini": "YOUR_GEMINI_API_KEY_HERE"
+ }
+}
+Run the application:python run.py
+Your server should now be running on http://localhost:1338.📦 How to Deploy to VercelThis project is deployed using a prebuilt output from a custom Docker container. This complex process is required to build psycopg2 correctly for Vercel's Amazon Linux runtime.1. PrerequisitesDocker Desktop must be installed and running.Vercel CLI must be installed: npm install -g vercelA Vercel account.2. Required Project FilesYou must have these four files in your project's root directory.DockerfileThis file builds your project inside an environment identical to Vercel's (Amazon Linux 2023).# Stage 1: The "builder"
+# USE THE OFFICIAL AWS LAMBDA PYTHON 3.12 IMAGE (Amazon Linux 2023)
+FROM public.ecr.aws/lambda/python:3.12 AS builder
+
+WORKDIR /app
+
+# Install build tools, node, and npm using DNF
+RUN dnf update -y && dnf install -y "Development Tools" nodejs npm
+
+# 2. Install Python dependencies
+COPY requirements.txt requirements.txt
+RUN pip3 install --user --no-cache-dir -r requirements.txt
+# Add Python's user bin to the PATH
+ENV PATH=/root/.local/bin:$PATH
+
+# 3. Install Vercel CLI
+RUN npm install --global vercel@latest
+
+# 4. Copy all your project files
+COPY . .
+
+# 5. Copy your Vercel project link
+COPY .vercel .vercel
+
+# 6. Build the project using Vercel CLI
+ARG VERCEL_TOKEN
+RUN VERCEL_TOKEN=$VERCEL_TOKEN vercel build --prod
+
+# ---
+# Stage 2: The "final output"
+FROM alpine:latest
+
+# Copy the entire .vercel folder
+COPY --from=builder /app/.vercel /.vercel
+vercel.jsonThis file tells Vercel how to build and route your Python app.{
+ "builds": [
+ {
+ "src": "run.py",
+ "use": "@vercel/python",
+ "config": { "pythonVersion": "3.12" }
+ }
+ ],
+ "routes": [
+ {
+ "src": "/(.*)",
+ "dest": "run.py"
+ }
+ ]
+}
+requirements.txtMake sure this file uses psycopg2-binary.flask
+python-dotenv
+requests
+beautifulsoup4
+psycopg2-binary
+# ... any other libraries
+.dockerignoreThis speeds up your Docker build by ignoring unnecessary files.# Venv
+venv/
+
+# Docker build output
+.vercel
+
+# Python cache
+__pycache__/
+*.pyc
+3. ⚠️ Important: Fix config.json for VercelYour run.py script (which reads config.json) will fail on Vercel. Vercel uses Environment Variables for secrets, not JSON files.You must modify your run.py to read from os.environ.Original run.py (Local only):# ...
+from json import load
+
+if __name__ == '__main__':
+ config = load(open('config.json', 'r'))
+ site_config = config['site_config']
+ # ...
+Modified run.py (Works locally AND on Vercel):from server.app import app
+from server.website import Website
+from server.controller.conversation_controller import ConversationController
+from json import load
+import os # Import os
+
+# --- VERCEL FIX ---
+# Check if running on Vercel (or any system with ENV VARS)
+db_url = os.environ.get('DATABASE_URL')
+site_port = os.environ.get('PORT', 1338) # Vercel provides a PORT
+
+if db_url:
+ # We are on Vercel or similar
+ site_config = {
+ "host": "0.0.0.0",
+ "port": int(site_port),
+ "debug": False
+ }
+ # You would also load other configs (like GEMINI_API_KEY) here
+ # os.environ.get('GEMINI_API_KEY')
+else:
+ # We are local, load from config.json
+ config = load(open('config.json', 'r'))
+ site_config = config['site_config']
+ # You would also load DB URL from config here
+ # db_url = config['database']['url']
+# --- END FIX ---
+
+
+# This logic is now outside the __name__ block
+site = Website(app)
+for route in site.routes:
+ app.add_url_rule(
+ route,
+ view_func = site.routes[route]['function'],
+ methods = site.routes[route]['methods'],
+ )
+
+ConversationController(app)
+
+# This will run for a 404
+@app.route('/', methods=['GET'])
+def handle_root():
+ return "Flask server is running!"
+
+# This block is for local development only
+if __name__ == '__main__':
+ print(f"Running on port {site_config['port']}")
+ app.run(**site_config)
+ print(f"Closing port {site_config['port']}")
+4. Deployment StepsStep 1: One-Time Vercel SetupLog in to Vercel CLI:vercel login
+Link your project:vercel link
+Pull project settings:vercel pull --yes
+Add Vercel Environment Variables:Go to your project's dashboard on Vercel.Go to Settings > Environment Variables.Add all your secrets (e.g., DATABASE_URL, GEMINI_API_KEY). These must match the os.environ.get() keys in your run.py.Step 2: The 6-Step Deploy ProcessRun these commands from your project's root directory every time you want to deploy a change.Build the Docker image: (This will take a few minutes)docker build --build-arg VERCEL_TOKEN="YOUR_VERCEL_TOKEN_HERE" -t overlap-chatgpt .
+(Get your token from Vercel Dashboard > Settings > Tokens)Remove the old container (in case it exists):docker rm temp_container
+Create a new container from the image:docker create --name temp_container overlap-chatgpt
+Copy the build output from the container to your computer:docker cp temp_container:/.vercel .
+Clean up the container:docker rm temp_container
+Deploy the prebuilt output!vercel deploy --prebuilt --prod
+🔌 Architecture: Client-Server InteractionThis repository is a JSON API backend. It is only the "server" part of your application.Client (The "Browser")A user visits your Vercel URL (e.g., https://overlap-chatgpt-clone.vercel.app).Vercel serves your static frontend (e.g., React, HTML/JS) from the Website routes.The user types a message in the chat.Server (This Flask App)Your frontend's JavaScript makes an HTTP request (e.g., a POST request to /api/chat) with the user's message.Vercel routes this request to your run.py serverless function.The ConversationController receives the request.It calls services like gemini_service (to talk to an AI) and teams_service (to get data).The teams_service uses db_model to query your PostgreSQL database (using psycopg2).The services return data to the controller.ResponseThe ConversationController formats a JSON response.Flask sends this JSON back to the client.Your frontend's JavaScript receives the JSON and displays the chat message to the user.
\ No newline at end of file
diff --git a/Dockerfile b/Dockerfile
index ffd3f024..3637d339 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,22 +1,34 @@
-# Build stage
-FROM python:3.8-alpine AS build
+# Stage 1: The "builder"
+# USE THE OFFICIAL AWS LAMBDA PYTHON 3.12 IMAGE (Amazon Linux 2023)
+FROM public.ecr.aws/lambda/python:3.12 AS builder
WORKDIR /app
+# CHANGED: Removed "Development Tools". We only need nodejs and npm.
+RUN dnf update -y && dnf install -y nodejs npm
+
+# 2. Install Python dependencies
COPY requirements.txt requirements.txt
-RUN apk add --no-cache build-base && \
- pip3 install --user --no-cache-dir -r requirements.txt
+RUN pip3 install --user --no-cache-dir -r requirements.txt
+# Add Python's user bin to the PATH
+ENV PATH=/root/.local/bin:$PATH
+# 3. Install Vercel CLI
+RUN npm install --global vercel@latest
+
+# 4. Copy all your project files
COPY . .
-# Production stage
-FROM python:3.8-alpine AS production
+# 5. Copy your Vercel project link
+COPY .vercel .vercel
-WORKDIR /app
+# 6. Build the project using Vercel CLI
+ARG VERCEL_TOKEN
+RUN VERCEL_TOKEN=$VERCEL_TOKEN vercel build --prod
-COPY --from=build /root/.local /root/.local
-COPY . .
-
-ENV PATH=/root/.local/bin:$PATH
+# ---
+# Stage 2: The "final output"
+FROM alpine:latest
-CMD ["python3", "./run.py"]
+# Copy the entire .vercel folder
+COPY --from=builder /app/.vercel /.vercel
\ No newline at end of file
diff --git a/README.md b/README.md
index 739cc900..d0fef7cf 100644
--- a/README.md
+++ b/README.md
@@ -1,25 +1,33 @@
-Development of this repository is currently in a halt, due to lack of time. Updates are comming end of June.
-
-working again ; )
-I am very busy at the moment so I would be very thankful for contributions and PR's
-
-## To do
-- [x] Double confirm when deleting conversation
-- [x] remember user preferences
-- [x] theme changer
-- [ ] loading / exporting a conversation
-- [ ] speech output and input (elevenlabs; ex: https://github.com/cogentapps/chat-with-gpt)
-- [ ] load files, ex: https://github.com/mayooear/gpt4-pdf-chatbot-langchain
-- [ ] better documentation
-- [ ] use react / faster backend language ? (newbies may be more confused and discouraged to use it)
-
-# ChatGPT Clone
-feel free to improve the code / suggest improvements
-
+# Project Overlap
+UPDATE THIS IMAGE WITH AN EXAMPLE IMAGE
+## Overview
+### Project Abstract
+Overlap is a collaborative chatbot ecosystem designed to enhance peer-to-peer knowledge sharing within a team. Instead of acting as a generic Q&A assistant, the system connects teammates to one another based on overlapping interests, recent learning activity, or self-declared expertise.
+
+Each team member has a personal chatbot (e.g., in Slack). When a user asks their bot for help — for example, “Teach me React basics.” — the bot consults a shared, opt-in knowledge index to identify peers who either know or recently asked about React. If a match is found, the bot responds: “Bob has ‘React’ expertise, and Alice asked about React two days ago. Want to connect with them?”
+
+This replaces solitary AI help with socially intelligent nudges that build team relationships while maintaining individual privacy. Later phases will visualize shared learning across the team through a mind-map view, showing connections between topics and people (opt-in only).
+
+### Novelty
+Why is this novel? Recent work shows that AI is eroding the social fabric inside and outside the classroom. Students are going to TAs, peers, and instructors at the lowest rates ever. That’s a problem because peer support is tightly linked to students’ wellbeing—and without it, students feel more isolated than ever.
+
+_**Our AI directly confronts this. Instead of replacing relationships, it is designed to build them.**_
+
+A second piece of novelty is how we handle “expertise finding.” Academic teams struggle to know who to go to for help. Our AI reduces that cognitive load by matching the questions students ask with the knowledge and experience already present in their community, connecting them to the right expert and strengthening human relationships along the way.
+
+
+# Testing
+feel free to improve the code / suggest improvements
+
+## Client + Testing
+To test our application, please go here --> https://overlap-chatgpt-clone-oah03fquq-manvender-singhs-projects.vercel.app/chat/
+```
+WARNING: PLEASE DO NOT PROMPT TOO MUCH OR IT WILL START CHARGING YOU
+```
-## Getting Started
+## Getting Started Development
To get started with this project, you'll need to clone the repository and set up a virtual environment. This will allow you to install the required dependencies without affecting your system-wide Python installation.
### Prequisites
@@ -28,7 +36,7 @@ Before you can set up a virtual environment, you'll need to have Python installe
### Cloning the Repository
Run the following command to clone the repository:
```
-git clone https://github.com/xtekky/chatgpt-clone.git
+git clone this repo
```
### Setting up a Virtual Environment
diff --git a/client/css/style.css b/client/css/style.css
index a1f69087..a2d464b1 100644
--- a/client/css/style.css
+++ b/client/css/style.css
@@ -16,800 +16,1019 @@
} */
:root {
- --colour-1: #000000;
- --colour-2: #ccc;
- --colour-3: #e4d4ff;
- --colour-4: #f0f0f0;
- --colour-5: #181818;
- --colour-6: #242424;
+ --colour-1: #000000;
+ --colour-2: #ccc;
+ --colour-3: #e4d4ff;
+ --colour-4: #f0f0f0;
+ --colour-5: #181818;
+ --colour-6: #242424;
- --accent: #8b3dff;
- --blur-bg: #16101b66;
- --blur-border: #84719040;
- --user-input: #ac87bb;
- --conversations: #c7a2ff;
+ --accent: #8b3dff;
+ --blur-bg: #16101b66;
+ --blur-border: #84719040;
+ --user-input: #ac87bb;
+ --conversations: #c7a2ff;
}
:root {
- --font-1: "Inter", sans-serif;
- --section-gap: 25px;
- --border-radius-1: 8px;
+ --font-1: "Inter", sans-serif;
+ --section-gap: 25px;
+ --border-radius-1: 8px;
}
* {
- margin: 0;
- padding: 0;
- box-sizing: border-box;
- position: relative;
- font-family: var(--font-1);
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+ position: relative;
+ font-family: var(--font-1);
+}
+
+.bottom_buttons button {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ text-align: center;
+ vertical-align: middle;
+ line-height: normal;
}
html,
body {
- scroll-behavior: smooth;
- overflow: hidden;
+ scroll-behavior: smooth;
+ /* Keep the overall page from scrolling — the app will make the chat
+ messages area the only scrollable region. This keeps the input and
+ header always visible. */
+ overflow: hidden;
+ height: 100%;
}
body {
- padding: var(--section-gap);
- background: var(--colour-1);
- color: var(--colour-3);
- min-height: 100vh;
+ padding: var(--section-gap);
+ background: var(--colour-1);
+ color: var(--colour-3);
+ min-height: 100vh;
}
.row {
- display: flex;
- gap: var(--section-gap);
- height: 100%;
+ display: flex;
+ gap: var(--section-gap);
+ /* occupy the viewport (accounting for body padding) */
+ height: calc(100vh - 2 * var(--section-gap));
}
.box {
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- background-color: var(--blur-bg);
- height: 100%;
- width: 100%;
- border-radius: var(--border-radius-1);
- border: 1px solid var(--blur-border);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ background-color: var(--blur-bg);
+ height: 100%;
+ width: 100%;
+ border-radius: var(--border-radius-1);
+ border: 1px solid var(--blur-border);
}
.conversations {
- max-width: 260px;
- padding: var(--section-gap);
- overflow: auto;
- flex-shrink: 0;
- display: flex;
- flex-direction: column;
- justify-content: space-between;
+ max-width: 260px;
+ padding: var(--section-gap);
+ /* keep the left sidebar static and visible; do not let the page scroll */
+ overflow: visible;
+ /* make the sidebar a fixed column so it doesn't collapse to zero width */
+ flex: 0 0 260px;
+ width: 260px;
+ display: flex;
+ flex-direction: column;
+ justify-content: space-between;
}
.conversation {
- width: 100%;
- min-height: 50%;
- height: 100vh;
- overflow-y: scroll;
- overflow-x: hidden;
- display: flex;
- flex-direction: column;
- gap: 15px;
+ width: 100%;
+ min-height: 50%;
+ /* Fill remaining vertical space inside .row. The messages area will
+ scroll independently; the input stays fixed at the bottom of this box. */
+ /* allow the conversation column to grow and take remaining horizontal space */
+ flex: 1 1 auto;
+ height: 100%;
+ overflow: hidden;
+ display: flex;
+ flex-direction: column;
+ gap: 15px;
}
.conversation #messages {
- width: 100%;
- display: flex;
- flex-direction: column;
- overflow-wrap: break-word;
- overflow-y: inherit;
- overflow-x: hidden;
- padding-bottom: 50px;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ overflow-wrap: break-word;
+ /* messages area takes remaining space and scrolls */
+ flex: 1 1 auto;
+ overflow-y: auto;
+ overflow-x: hidden;
+ padding-bottom: 12px;
}
.conversation .user-input {
- max-height: 10vh;
+ /* keep input area fixed height so layout is predictable */
+ flex: 0 0 auto;
}
.conversation .user-input input {
- font-size: 15px;
- width: 100%;
- height: 100%;
- padding: 12px 15px;
- background: none;
- border: none;
- outline: none;
- color: var(--colour-3);
+ font-size: 15px;
+ width: 100%;
+ height: 100%;
+ padding: 12px 15px;
+ background: none;
+ border: none;
+ outline: none;
+ color: var(--colour-3);
}
.conversation .user-input input::placeholder {
- color: var(--user-input)
+ color: var(--user-input);
}
.gradient:nth-child(1) {
- --top: 0;
- --right: 0;
- --size: 70vw;
- --blur: calc(0.5 * var(--size));
- --opacity: 0.3;
- animation: zoom_gradient 6s infinite;
+ --top: 0;
+ --right: 0;
+ --size: 70vw;
+ --blur: calc(0.5 * var(--size));
+ --opacity: 0.3;
+ animation: zoom_gradient 6s infinite;
}
.gradient {
- position: absolute;
- z-index: -1;
- border-radius: calc(0.5 * var(--size));
- background-color: var(--accent);
- background: radial-gradient(circle at center, var(--accent), var(--accent));
- width: 70vw;
- height: 70vw;
- top: 50%;
- right: 0;
- transform: translateY(-50%);
- filter: blur(calc(0.5 * 70vw)) opacity(var(--opacity));
+ position: absolute;
+ z-index: -1;
+ border-radius: calc(0.5 * var(--size));
+ background-color: var(--accent);
+ background: radial-gradient(circle at center, var(--accent), var(--accent));
+ width: 70vw;
+ height: 70vw;
+ top: 50%;
+ right: 0;
+ transform: translateY(-50%);
+ filter: blur(calc(0.5 * 70vw)) opacity(var(--opacity));
}
.conversations {
- display: flex;
- flex-direction: column;
- gap: 16px;
- flex: auto;
- min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ flex: auto;
+ min-width: 0;
}
.conversations .title {
- font-size: 14px;
- font-weight: 500;
+ font-size: 14px;
+ font-weight: 500;
}
.conversations .convo {
- padding: 8px 12px;
- display: flex;
- gap: 18px;
- align-items: center;
- user-select: none;
- justify-content: space-between;
+ padding: 8px 12px;
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ user-select: none;
+ justify-content: space-between;
}
.conversations .convo .left {
- cursor: pointer;
- display: flex;
- align-items: center;
- gap: 10px;
- flex: auto;
- min-width: 0;
+ cursor: pointer;
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ flex: auto;
+ min-width: 0;
}
.conversations i {
- color: var(--conversations);
- cursor: pointer;
+ color: var(--conversations);
+ cursor: pointer;
}
.convo-title {
- color: var(--colour-3);
- font-size: 14px;
- overflow: hidden;
- text-overflow: ellipsis;
+ color: var(--colour-3);
+ font-size: 14px;
+ overflow: hidden;
+ text-overflow: ellipsis;
}
.message {
-
- width: 100%;
- overflow-wrap: break-word;
- display: flex;
- gap: var(--section-gap);
- padding: var(--section-gap);
- padding-bottom: 0;
+ width: 100%;
+ overflow-wrap: break-word;
+ display: flex;
+ gap: var(--section-gap);
+ padding: var(--section-gap);
+ padding-bottom: 0;
}
.message:last-child {
- animation: 0.6s show_message;
+ animation: 0.6s show_message;
}
@keyframes show_message {
- from {
- transform: translateY(10px);
- opacity: 0;
- }
+ from {
+ transform: translateY(10px);
+ opacity: 0;
+ }
}
.message .user {
- max-width: 48px;
- max-height: 48px;
- flex-shrink: 0;
+ max-width: 48px;
+ max-height: 48px;
+ flex-shrink: 0;
}
.message .user img {
- width: 100%;
- height: 100%;
- object-fit: cover;
- border-radius: 8px;
- outline: 1px solid var(--blur-border);
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ border-radius: 8px;
+ outline: 1px solid var(--blur-border);
}
.message .user:after {
- content: "63";
- position: absolute;
- bottom: 0;
- right: 0;
- height: 60%;
- width: 60%;
- background: var(--colour-3);
- filter: blur(10px) opacity(0.5);
- z-index: 10000;
+ content: "63";
+ position: absolute;
+ bottom: 0;
+ right: 0;
+ height: 60%;
+ width: 60%;
+ background: var(--colour-3);
+ filter: blur(10px) opacity(0.5);
+ z-index: 10000;
}
.message .content {
- display: flex;
- flex-direction: column;
- gap: 18px;
- min-width: 0;
+ display: flex;
+ flex-direction: column;
+ gap: 18px;
+ min-width: 0;
}
.message .content p,
.message .content li,
.message .content code {
- font-size: 15px;
- line-height: 1.3;
+ font-size: 15px;
+ line-height: 1.3;
}
.message .user i {
- position: absolute;
- bottom: -6px;
- right: -6px;
- z-index: 1000;
+ position: absolute;
+ bottom: -6px;
+ right: -6px;
+ z-index: 1000;
}
.new_convo {
- padding: 8px 12px;
- display: flex;
- gap: 18px;
- align-items: center;
- cursor: pointer;
- user-select: none;
- background: transparent;
- border: 1px dashed var(--conversations);
- border-radius: var(--border-radius-1);
+ padding: 8px 12px;
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ cursor: pointer;
+ user-select: none;
+ background: transparent;
+ border: 1px dashed var(--conversations);
+ border-radius: var(--border-radius-1);
}
.new_convo span {
- color: var(--colour-3);
- font-size: 14px;
+ color: var(--colour-3);
+ font-size: 14px;
}
.new_convo:hover {
- border-style: solid;
+ border-style: solid;
}
.stop_generating {
- position: absolute;
- bottom: 118px;
- /* left: 10px;
+ position: absolute;
+ bottom: 118px;
+ /* left: 10px;
bottom: 125px;
right: 8px; */
- left: 50%;
- transform: translateX(-50%);
- z-index: 1000000;
+ left: 50%;
+ transform: translateX(-50%);
+ z-index: 1000000;
}
.stop_generating button {
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- background-color: var(--blur-bg);
- border-radius: var(--border-radius-1);
- border: 1px solid var(--blur-border);
- padding: 10px 15px;
- color: var(--colour-3);
- display: flex;
- justify-content: center;
- align-items: center;
- gap: 12px;
- cursor: pointer;
- animation: show_popup 0.4s;
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ background-color: var(--blur-bg);
+ border-radius: var(--border-radius-1);
+ border: 1px solid var(--blur-border);
+ padding: 10px 15px;
+ color: var(--colour-3);
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ gap: 12px;
+ cursor: pointer;
+ animation: show_popup 0.4s;
}
@keyframes show_popup {
- from {
- opacity: 0;
- transform: translateY(10px);
- }
+ from {
+ opacity: 0;
+ transform: translateY(10px);
+ }
}
@keyframes hide_popup {
- to {
- opacity: 0;
- transform: translateY(10px);
- }
+ to {
+ opacity: 0;
+ transform: translateY(10px);
+ }
}
.stop_generating-hiding button {
- animation: hide_popup 0.4s;
+ animation: hide_popup 0.4s;
}
.stop_generating-hidden button {
- display: none;
+ display: none;
}
.typing {
- position: absolute;
- top: -25px;
- left: 0;
- font-size: 14px;
- animation: show_popup 0.4s;
+ position: absolute;
+ top: -25px;
+ left: 0;
+ font-size: 14px;
+ animation: show_popup 0.4s;
}
.typing-hiding {
- animation: hide_popup 0.4s;
+ animation: hide_popup 0.4s;
}
.typing-hidden {
- display: none;
+ display: none;
}
input[type="checkbox"] {
- height: 0;
- width: 0;
- display: none;
+ height: 0;
+ width: 0;
+ display: none;
}
label {
- cursor: pointer;
- text-indent: -9999px;
- width: 50px;
- height: 30px;
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- background-color: var(--blur-bg);
- border-radius: var(--border-radius-1);
- border: 1px solid var(--blur-border);
- display: block;
- border-radius: 100px;
- position: relative;
- overflow: hidden;
- transition: 0.33s;
+ cursor: pointer;
+ text-indent: -9999px;
+ width: 50px;
+ height: 30px;
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ background-color: var(--blur-bg);
+ border-radius: var(--border-radius-1);
+ border: 1px solid var(--blur-border);
+ display: block;
+ border-radius: 100px;
+ position: relative;
+ overflow: hidden;
+ transition: 0.33s;
}
label:after {
- content: "";
- position: absolute;
- top: 50%;
- transform: translateY(-50%);
- left: 5px;
- width: 20px;
- height: 20px;
- background: var(--colour-3);
- border-radius: 90px;
- transition: 0.33s;
+ content: "";
+ position: absolute;
+ top: 50%;
+ transform: translateY(-50%);
+ left: 5px;
+ width: 20px;
+ height: 20px;
+ background: var(--colour-3);
+ border-radius: 90px;
+ transition: 0.33s;
}
-input:checked+label {
- background: var(--blur-border);
+input:checked + label {
+ background: var(--blur-border);
}
-input:checked+label:after {
- left: calc(100% - 5px - 20px);
+input:checked + label:after {
+ left: calc(100% - 5px - 20px);
}
.buttons {
- min-height: 10vh;
- display: flex;
- align-items: start;
- justify-content: left;
- width: 100%;
+ min-height: 10vh;
+ display: flex;
+ align-items: start;
+ justify-content: left;
+ width: 100%;
}
.field {
- height: fit-content;
- display: flex;
- align-items: center;
- gap: 16px;
- padding-right: 15px
+ height: fit-content;
+ display: flex;
+ align-items: center;
+ gap: 16px;
+ padding-right: 15px;
}
.field .about {
- font-size: 14px;
- color: var(--colour-3);
+ font-size: 14px;
+ color: var(--colour-3);
}
.disable-scrollbars::-webkit-scrollbar {
background: transparent; /* Chrome/Safari/Webkit */
width: 0px;
}
-
+
.disable-scrollbars {
scrollbar-width: none; /* Firefox */
- -ms-overflow-style: none; /* IE 10+ */
+ -ms-overflow-style: none; /* IE 10+ */
}
select {
- -webkit-border-radius: 8px;
- -moz-border-radius: 8px;
- border-radius: 8px;
+ -webkit-border-radius: 8px;
+ -moz-border-radius: 8px;
+ border-radius: 8px;
- -webkit-backdrop-filter: blur(20px);
- backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ backdrop-filter: blur(20px);
- cursor: pointer;
- background-color: var(--blur-bg);
- border: 1px solid var(--blur-border);
- color: var(--colour-3);
- display: block;
- position: relative;
- overflow: hidden;
- outline: none;
- padding: 8px 16px;
+ cursor: pointer;
+ background-color: var(--blur-bg);
+ border: 1px solid var(--blur-border);
+ color: var(--colour-3);
+ display: block;
+ position: relative;
+ overflow: hidden;
+ outline: none;
+ padding: 8px 16px;
- appearance: none;
+ appearance: none;
}
.input-box {
- display: flex;
- align-items: center;
- padding-right: 15px;
- cursor: pointer;
+ display: flex;
+ align-items: center;
+ padding-right: 15px;
+ cursor: pointer;
}
.info {
- padding: 8px 12px;
- display: flex;
- gap: 18px;
- align-items: center;
- user-select: none;
- background: transparent;
- border-radius: var(--border-radius-1);
- width: 100%;
- cursor: default;
- border: 1px dashed var(--conversations)
+ padding: 8px 12px;
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ user-select: none;
+ background: transparent;
+ border-radius: var(--border-radius-1);
+ width: 100%;
+ cursor: default;
+ border: 1px dashed var(--conversations);
}
.bottom_buttons {
- width: 100%;
- display: flex;
- flex-direction: column;
- gap: 10px;
+ width: 100%;
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
}
.bottom_buttons button {
- padding: 8px 12px;
- display: flex;
- gap: 18px;
- align-items: center;
- cursor: pointer;
- user-select: none;
- background: transparent;
- border: 1px solid #c7a2ff;
- border-radius: var(--border-radius-1);
- width: 100%;
+ padding: 8px 12px;
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ cursor: pointer;
+ user-select: none;
+ background: transparent;
+ border: 1px solid #c7a2ff;
+ border-radius: var(--border-radius-1);
+ width: 100%;
}
.bottom_buttons button span {
- color: var(--colour-3);
- font-size: 14px;
+ color: var(--colour-3);
+ font-size: 14px;
+}
+
+.settings-section {
+ width: 100%;
+ margin: 10px 0;
+ border-top: 1px solid var(--blur-border);
+ padding-top: 10px;
+}
+
+.settings-toggle {
+ padding: 8px 12px;
+ display: flex;
+ gap: 18px;
+ align-items: center;
+ cursor: pointer;
+ user-select: none;
+ background: transparent;
+ border: 1px solid var(--conversations);
+ border-radius: var(--border-radius-1);
+ width: 100%;
+ justify-content: space-between;
+}
+
+.settings-toggle span {
+ color: var(--colour-3);
+ font-size: 14px;
+ flex: 1;
+ text-align: left;
+}
+
+.settings-toggle i {
+ color: var(--colour-3);
+ transition: transform 0.3s ease;
+}
+
+.settings-toggle:hover {
+ border-color: var(--accent);
+}
+
+.settings-content {
+ margin-top: 10px;
+ padding: 12px;
+ background: var(--blur-bg);
+ border: 1px solid var(--blur-border);
+ border-radius: var(--border-radius-1);
+}
+
+.settings-field {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.settings-field label {
+ color: var(--colour-3);
+ font-size: 13px;
+ font-weight: 500;
+}
+
+.api-key-input {
+ padding: 8px 12px;
+ background: var(--blur-bg);
+ border: 1px solid var(--blur-border);
+ border-radius: var(--border-radius-1);
+ color: var(--colour-3);
+ font-size: 13px;
+ width: 100%;
+ font-family: monospace;
+}
+
+.api-key-input:focus {
+ outline: none;
+ border-color: var(--accent);
+}
+
+.api-key-input::placeholder {
+ color: var(--user-input);
+ opacity: 0.6;
+}
+
+.settings-actions {
+ display: flex;
+ gap: 8px;
+}
+
+.save-api-key-btn,
+.clear-api-key-btn {
+ padding: 6px 12px;
+ display: flex;
+ gap: 8px;
+ align-items: center;
+ cursor: pointer;
+ user-select: none;
+ background: transparent;
+ border: 1px solid var(--conversations);
+ border-radius: var(--border-radius-1);
+ flex: 1;
+ justify-content: center;
+}
+
+.save-api-key-btn span,
+.clear-api-key-btn span {
+ color: var(--colour-3);
+ font-size: 12px;
+}
+
+.save-api-key-btn:hover {
+ border-color: var(--accent);
+ background: rgba(139, 61, 255, 0.1);
+}
+
+.clear-api-key-btn:hover {
+ border-color: #ff3d3d;
+ background: rgba(255, 61, 61, 0.1);
+}
+
+.api-key-status {
+ font-size: 12px;
+ padding: 6px;
+ border-radius: var(--border-radius-1);
+ min-height: 20px;
+}
+
+.api-key-status.success {
+ color: #4ade80;
+ background: rgba(74, 222, 128, 0.1);
+}
+
+.api-key-status.error {
+ color: #ff3d3d;
+ background: rgba(255, 61, 61, 0.1);
+}
+
+.api-key-status.info {
+ color: var(--accent);
+ background: rgba(139, 61, 255, 0.1);
}
.conversations .top {
- display: flex;
- flex-direction: column;
- gap: 16px;
- overflow: auto;
+ display: flex;
+ flex-direction: column;
+ gap: 16px;
+ /* allow the left-top area to scroll vertically if conversation list grows */
+ overflow-y: auto;
+}
+
+/* Styles for the conversation list container we render into */
+.conversation-list {
+ display: flex;
+ flex-direction: column;
+ gap: 8px;
+ max-height: calc(100vh - 260px);
+ overflow-y: auto;
+}
+
+.conversation-list-items .no-convos {
+ color: var(--colour-3);
+ opacity: 0.8;
+ padding: 8px 12px;
+ border-radius: 6px;
+ border: 1px dashed var(--conversations);
}
#cursor {
- line-height: 17px;
- margin-left: 3px;
- -webkit-animation: blink 0.8s infinite;
- animation: blink 0.8s infinite;
- width: 7px;
- height: 15px;
+ line-height: 17px;
+ margin-left: 3px;
+ -webkit-animation: blink 0.8s infinite;
+ animation: blink 0.8s infinite;
+ width: 7px;
+ height: 15px;
}
@keyframes blink {
- 0% {
- background: #ffffff00;
- }
+ 0% {
+ background: #ffffff00;
+ }
- 50% {
- background: white;
- }
+ 50% {
+ background: white;
+ }
- 100% {
- background: #ffffff00;
- }
+ 100% {
+ background: #ffffff00;
+ }
}
@-webkit-keyframes blink {
- 0% {
- background: #ffffff00;
- }
+ 0% {
+ background: #ffffff00;
+ }
- 50% {
- background: white;
- }
+ 50% {
+ background: white;
+ }
- 100% {
- background: #ffffff00;
- }
+ 100% {
+ background: #ffffff00;
+ }
}
-
ol,
ul {
- padding-left: 20px;
+ padding-left: 20px;
}
-
@keyframes spinner {
- to {
- transform: rotate(360deg);
- }
+ to {
+ transform: rotate(360deg);
+ }
}
.spinner:before {
- content: '';
- box-sizing: border-box;
- position: absolute;
- top: 50%;
- left: 45%;
- width: 20px;
- height: 20px;
+ content: "";
+ box-sizing: border-box;
+ position: absolute;
+ top: 50%;
+ left: 45%;
+ width: 20px;
+ height: 20px;
- border-radius: 50%;
- border: 1px solid var(--conversations);
- border-top-color: white;
- animation: spinner .6s linear infinite;
+ border-radius: 50%;
+ border: 1px solid var(--conversations);
+ border-top-color: white;
+ animation: spinner 0.6s linear infinite;
}
.grecaptcha-badge {
- visibility: hidden;
+ visibility: hidden;
}
.mobile-sidebar {
- display: none !important;
- position: absolute;
- z-index: 100000;
- top: 0;
- left: 0;
- margin: 10px;
- font-size: 20px;
- cursor: pointer;
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- background-color: var(--blur-bg);
- border-radius: 10px;
- border: 1px solid var(--blur-border);
- width: 40px;
- height: 40px;
- justify-content: center;
- align-items: center;
- transition: 0.33s;
+ display: none !important;
+ position: absolute;
+ z-index: 100000;
+ top: 0;
+ left: 0;
+ margin: 10px;
+ font-size: 20px;
+ cursor: pointer;
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+ background-color: var(--blur-bg);
+ border-radius: 10px;
+ border: 1px solid var(--blur-border);
+ width: 40px;
+ height: 40px;
+ justify-content: center;
+ align-items: center;
+ transition: 0.33s;
}
.mobile-sidebar i {
- transition: 0.33s;
+ transition: 0.33s;
}
.rotated {
- transform: rotate(360deg);
+ transform: rotate(360deg);
}
@media screen and (max-width: 990px) {
- .conversations {
- display: none;
- width: 100%;
- max-width: none;
- }
+ .conversations {
+ display: none;
+ width: 100%;
+ max-width: none;
+ }
- .buttons {
+ .buttons {
flex-wrap: wrap;
gap: 5px;
padding-bottom: 10vh;
margin-bottom: 10vh;
-}
+ }
- .field {
+ .field {
min-height: 5%;
width: fit-content;
-}
+ }
- .mobile-sidebar {
- display: flex !important;
- }
+ .mobile-sidebar {
+ display: flex !important;
+ }
}
@media screen and (max-height: 640px) {
- body {
- height: 87vh
- }
+ body {
+ height: 87vh;
+ }
}
-
.shown {
- display: flex;
+ display: flex;
}
-
a:-webkit-any-link {
- color: var(--accent);
+ color: var(--accent);
}
.conversation .user-input textarea {
- font-size: 15px;
- width: 100%;
- height: 100%;
- padding: 12px 15px;
- background: none;
- border: none;
- outline: none;
- color: var(--colour-3);
+ font-size: 15px;
+ width: 100%;
+ /* give a static-ish size so layout doesn't jump */
+ height: 110px;
+ padding: 12px 15px;
+ background: none;
+ border: none;
+ outline: none;
+ color: var(--colour-3);
- resize: vertical;
- max-height: 150px;
- min-height: 80px;
+ resize: vertical;
+ max-height: 180px;
+ min-height: 80px;
}
/* style for hljs copy */
.hljs-copy-wrapper {
- position: relative;
- overflow: hidden
+ position: relative;
+ overflow: hidden;
}
.hljs-copy-wrapper:hover .hljs-copy-button,
.hljs-copy-button:focus {
- transform: translateX(0)
+ transform: translateX(0);
}
.hljs-copy-button {
- position: absolute;
- transform: translateX(calc(100% + 1.125em));
- top: 1em;
- right: 1em;
- width: 2rem;
- height: 2rem;
- text-indent: -9999px;
- color: #fff;
- border-radius: .25rem;
- border: 1px solid #ffffff22;
- background-color: #2d2b57;
- background-image: url('data:image/svg+xml;utf-8,');
- background-repeat: no-repeat;
- background-position: center;
- transition: background-color 200ms ease, transform 200ms ease-out
+ position: absolute;
+ transform: translateX(calc(100% + 1.125em));
+ top: 1em;
+ right: 1em;
+ width: 2rem;
+ height: 2rem;
+ text-indent: -9999px;
+ color: #fff;
+ border-radius: 0.25rem;
+ border: 1px solid #ffffff22;
+ background-color: #2d2b57;
+ background-image: url('data:image/svg+xml;utf-8,');
+ background-repeat: no-repeat;
+ background-position: center;
+ transition: background-color 200ms ease, transform 200ms ease-out;
}
.hljs-copy-button:hover {
- border-color: #ffffff44
+ border-color: #ffffff44;
}
.hljs-copy-button:active {
- border-color: #ffffff66
+ border-color: #ffffff66;
}
.hljs-copy-button[data-copied="true"] {
- text-indent: 0;
- width: auto;
- background-image: none
+ text-indent: 0;
+ width: auto;
+ background-image: none;
}
-@media(prefers-reduced-motion) {
- .hljs-copy-button {
- transition: none
- }
+@media (prefers-reduced-motion) {
+ .hljs-copy-button {
+ transition: none;
+ }
}
.hljs-copy-alert {
- clip: rect(0 0 0 0);
- clip-path: inset(50%);
- height: 1px;
- overflow: hidden;
- position: absolute;
- white-space: nowrap;
- width: 1px
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
}
.visually-hidden {
- clip: rect(0 0 0 0);
- clip-path: inset(50%);
- height: 1px;
- overflow: hidden;
- position: absolute;
- white-space: nowrap;
- width: 1px;
-}
-
-
-.color-picker>fieldset {
- border: 0;
- display: flex;
- width: fit-content;
- background: var(--colour-1);
- margin-inline: auto;
- border-radius: 8px;
- -webkit-backdrop-filter: blur(20px);
- backdrop-filter: blur(20px);
- cursor: pointer;
- background-color: var(--blur-bg);
- border: 1px solid var(--blur-border);
- color: var(--colour-3);
- display: block;
- position: relative;
- overflow: hidden;
- outline: none;
- padding: 6px 16px;
+ clip: rect(0 0 0 0);
+ clip-path: inset(50%);
+ height: 1px;
+ overflow: hidden;
+ position: absolute;
+ white-space: nowrap;
+ width: 1px;
+}
+
+.color-picker > fieldset {
+ border: 0;
+ display: flex;
+ width: fit-content;
+ background: var(--colour-1);
+ margin-inline: auto;
+ border-radius: 8px;
+ -webkit-backdrop-filter: blur(20px);
+ backdrop-filter: blur(20px);
+ cursor: pointer;
+ background-color: var(--blur-bg);
+ border: 1px solid var(--blur-border);
+ color: var(--colour-3);
+ display: block;
+ position: relative;
+ overflow: hidden;
+ outline: none;
+ padding: 6px 16px;
}
.color-picker input[type="radio"]:checked {
- background-color: var(--radio-color);
+ background-color: var(--radio-color);
}
.color-picker input[type="radio"]#light {
- --radio-color: gray;
+ --radio-color: gray;
}
.color-picker input[type="radio"]#pink {
- --radio-color: pink;
+ --radio-color: pink;
}
.color-picker input[type="radio"]#blue {
- --radio-color: blue;
+ --radio-color: blue;
}
.color-picker input[type="radio"]#green {
- --radio-color: green;
+ --radio-color: green;
}
.color-picker input[type="radio"]#dark {
- --radio-color: #232323;
+ --radio-color: #232323;
}
.pink {
- --colour-1: hsl(310 50% 90%);
- --clr-card-bg: hsl(310 50% 100%);
- --colour-3: hsl(310 50% 15%);
- --conversations: hsl(310 50% 25%);
+ --colour-1: hsl(310 50% 90%);
+ --clr-card-bg: hsl(310 50% 100%);
+ --colour-3: hsl(310 50% 15%);
+ --conversations: hsl(310 50% 25%);
}
.blue {
- --colour-1: hsl(209 50% 90%);
- --clr-card-bg: hsl(209 50% 100%);
- --colour-3: hsl(209 50% 15%);
- --conversations: hsl(209 50% 25%);
+ --colour-1: hsl(209 50% 90%);
+ --clr-card-bg: hsl(209 50% 100%);
+ --colour-3: hsl(209 50% 15%);
+ --conversations: hsl(209 50% 25%);
}
.green {
- --colour-1: hsl(109 50% 90%);
- --clr-card-bg: hsl(109 50% 100%);
- --colour-3: hsl(109 50% 15%);
- --conversations: hsl(109 50% 25%);
+ --colour-1: hsl(109 50% 90%);
+ --clr-card-bg: hsl(109 50% 100%);
+ --colour-3: hsl(109 50% 15%);
+ --conversations: hsl(109 50% 25%);
}
.dark {
- --colour-1: hsl(209 50% 10%);
- --clr-card-bg: hsl(209 50% 5%);
- --colour-3: hsl(209 50% 90%);
- --conversations: hsl(209 50% 80%);
+ --colour-1: hsl(209 50% 10%);
+ --clr-card-bg: hsl(209 50% 5%);
+ --colour-3: hsl(209 50% 90%);
+ --conversations: hsl(209 50% 80%);
}
:root:has(#pink:checked) {
- --colour-1: hsl(310 50% 90%);
- --clr-card-bg: hsl(310 50% 100%);
- --colour-3: hsl(310 50% 15%);
- --conversations: hsl(310 50% 25%);
+ --colour-1: hsl(310 50% 90%);
+ --clr-card-bg: hsl(310 50% 100%);
+ --colour-3: hsl(310 50% 15%);
+ --conversations: hsl(310 50% 25%);
}
:root:has(#blue:checked) {
- --colour-1: hsl(209 50% 90%);
- --clr-card-bg: hsl(209 50% 100%);
- --colour-3: hsl(209 50% 15%);
- --conversations: hsl(209 50% 25%);
+ --colour-1: hsl(209 50% 90%);
+ --clr-card-bg: hsl(209 50% 100%);
+ --colour-3: hsl(209 50% 15%);
+ --conversations: hsl(209 50% 25%);
}
:root:has(#green:checked) {
- --colour-1: hsl(109 50% 90%);
- --clr-card-bg: hsl(109 50% 100%);
- --colour-3: hsl(109 50% 15%);
- --conversations: hsl(109 50% 25%);
+ --colour-1: hsl(109 50% 90%);
+ --clr-card-bg: hsl(109 50% 100%);
+ --colour-3: hsl(109 50% 15%);
+ --conversations: hsl(109 50% 25%);
}
:root:has(#dark:checked) {
- --colour-1: hsl(209 50% 10%);
- --clr-card-bg: hsl(209 50% 5%);
- --colour-3: hsl(209 50% 90%);
- --conversations: hsl(209 50% 80%);
-}
\ No newline at end of file
+ --colour-1: hsl(209 50% 10%);
+ --clr-card-bg: hsl(209 50% 5%);
+ --colour-3: hsl(209 50% 90%);
+ --conversations: hsl(209 50% 80%);
+}
+
+/* Small layout fixes for input and new-convo visibility */
+.conversations .top {
+ /* ensure the top area can always show the new conversation button */
+ min-height: 84px;
+ padding-bottom: 8px;
+}
+
+.new_convo {
+ align-items: center;
+ display: flex;
+ gap: 8px;
+}
+
+.box.input-box {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ padding: 10px;
+}
+
+.input-box textarea#message-input {
+ width: 100%;
+ min-height: 80px;
+ max-height: 180px;
+ resize: vertical;
+ padding: 12px;
+ box-sizing: border-box;
+ background: none;
+ border: none;
+ outline: none;
+ color: var(--colour-3);
+}
+
+#send-button {
+ width: 52px;
+ height: 52px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ cursor: pointer;
+}
+
+/* ensure the input box stays above decorative gradients */
+.box.input-box {
+ z-index: 2;
+}
diff --git a/client/html/index.html b/client/html/index.html
index 201ac155..d0a3b234 100644
--- a/client/html/index.html
+++ b/client/html/index.html
@@ -1,160 +1,232 @@
-