diff --git a/Project/Firebase/database.go b/Project/Firebase/database.go
index cee665ecc5a5cb7d41812dc9ab29fe14f8c5dcc8..ff4da4e9864239843620dc8d8aef4d68c41936c2 100644
--- a/Project/Firebase/database.go
+++ b/Project/Firebase/database.go
@@ -1,5 +1,12 @@
 package firebase
 
+import "cloud.google.com/go/firestore"
+
+type Sort struct {
+	Field string
+	Order firestore.Direction
+}
+
 type Document struct {
 	Id   string
 	Data map[string]interface{}
@@ -8,7 +15,7 @@ type Document struct {
 type Database interface {
 	//Close() error
 
-	PostDoc(path string, values map[string]interface{}) error
+	PostDoc(path string, values map[string]interface{}) (string, error)
 	PostDocId(path string, id string, values map[string]interface{}) error
 
 	GetDoc(path string) (Document, error)
@@ -22,7 +29,7 @@ type Database interface {
 
 	IncrementDocField(path string, fields map[string]interface{}) error
 
-	GetPaginatedDocs(path string, pageNum int, pageSize int) ([]Document, error)
+	GetPaginatedDocs(path string, pageNum int, pageSize int, offset int, sortBy *Sort) ([]Document, error)
 	SearchDocs(path string, searchTerm string, pageNum int, pageSize int) ([]Document, error)
 }
 
diff --git a/Project/Firebase/firestore.go b/Project/Firebase/firestore.go
index 0a6065e3cf4558d86072ad539d5f6d6169610cb2..a03c47daed627d219d7dd4793be499db60a231f8 100644
--- a/Project/Firebase/firestore.go
+++ b/Project/Firebase/firestore.go
@@ -85,10 +85,6 @@ func (fs Firestore) UpdateDoc(path string, newValues map[string]interface{}) err
 
 // Get documents with fields equal to specified values. With no conditions(nil), this function will return all documents
 func (fs Firestore) GetDocsWhere(path string, conditionValues map[string]interface{}) ([]Document, error) {
-	/*if len(conditionValues) == 0 {
-		return []Document{}, errors.New("no conditions")
-	}*/
-
 	collection, id, subCollection, _, err := SplitPath(path)
 	if err != nil {
 		return []Document{}, err
@@ -245,10 +241,10 @@ func (fs Firestore) UpdateDocsWhere(path string, newValues map[string]interface{
 }
 
 // Post document
-func (fs Firestore) PostDoc(path string, values map[string]interface{}) error {
+func (fs Firestore) PostDoc(path string, values map[string]interface{}) (string, error) {
 	collection, id, subCollection, _, err := SplitPath(path)
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	ref := fs.Client.Collection(collection).Doc(id)
@@ -260,10 +256,10 @@ func (fs Firestore) PostDoc(path string, values map[string]interface{}) error {
 	doc := ref.Parent.NewDoc()
 	_, err = doc.Set(Ctx, values)
 	if err != nil {
-		return err
+		return "", err
 	}
 
-	return nil
+	return doc.ID, nil
 }
 
 // Post a document with specified id
@@ -328,21 +324,27 @@ func (fs Firestore) IncrementDocField(path string, fields map[string]interface{}
 }
 
 // GetPaginatedDocs gets all documents from a specified collection based on pagination
-func (fs Firestore) GetPaginatedDocs(path string, pageNum int, pageSize int) ([]Document, error) {
+func (fs Firestore) GetPaginatedDocs(path string, pageNum int, pageSize int, offset int, sortBy *Sort) ([]Document, error) {
 	// Validate parameters
 	if pageNum < 0 || pageSize < 0 {
 		return nil, errors.New("invalid pagination values")
 	}
 
-	collection, _, _, _, err := SplitPath(path)
+	collection, id, subCollection, subId, err := SplitPath(path)
 	if err != nil {
-		return nil, err
+		return []Document{}, err
 	}
 
-	query := fs.Client.Collection(collection).OrderBy("name", firestore.Asc).
-		Limit(pageSize).
-		Offset(pageNum * pageSize)
+	ref := fs.Client.Collection(collection).Doc(id)
+	if subCollection != "" {
+		ref = ref.Collection(subCollection).Doc(subId)
+	}
 
+	query := ref.Parent.Query
+	if sortBy != nil {
+		query = query.OrderBy(sortBy.Field, sortBy.Order)
+	}
+	query = query.Limit(pageSize).Offset(offset + pageNum*pageSize)
 	iter := query.Documents(Ctx)
 	defer iter.Stop()
 
diff --git a/Project/Firebase/firestoreMock.go b/Project/Firebase/firestoreMock.go
index 8bd96bfae00dc0598d5528e9d480cd960473bf74..aa7f0e129afe2812e28d9dc67c80dd969ff40f1b 100644
--- a/Project/Firebase/firestoreMock.go
+++ b/Project/Firebase/firestoreMock.go
@@ -77,7 +77,7 @@ func InitializeMockEnvironment() {
 					"name":        "Gam3",
 					"ratingCount": int64(100),
 					"ratingSum":   int64(700),
-					"verticalImg": "vimg2.jpg",
+					"verticalImg": "vimg3.jpg",
 				},
 			},
 		},
@@ -333,10 +333,10 @@ func (db MockFS) UpdateDocsWhere(path string, newValues map[string]interface{},
 	return nil
 }
 
-func (db MockFS) PostDoc(path string, values map[string]interface{}) error {
+func (db MockFS) PostDoc(path string, values map[string]interface{}) (string, error) {
 	collection, _, err := db.checkForSubcollectionDoc(path)
 	if err != nil {
-		return err
+		return "", err
 	}
 
 	// Generate id
@@ -344,7 +344,7 @@ func (db MockFS) PostDoc(path string, values map[string]interface{}) error {
 
 	collection[id] = values
 
-	return nil
+	return id, nil
 }
 
 // Post a document with specified id
@@ -407,7 +407,7 @@ func (db MockFS) IncrementDocField(path string, fields map[string]interface{}) e
 }
 
 // GetPaginatedDocs retrieves a paginated list of documents from a collection.
-func (db MockFS) GetPaginatedDocs(path string, pageSize int, pageNumber int) ([]Document, error) {
+func (db MockFS) GetPaginatedDocs(path string, pageSize int, pageNumber int, offset int, sortBy *Sort) ([]Document, error) {
 	return nil, nil
 }
 
diff --git a/Project/Firebase/functions.go b/Project/Firebase/functions.go
index de5f07af2c55f91de05eb7701ba8ee9c6b4916b0..39e57d21bb516fcaab578a83740cd0417c82c2e4 100644
--- a/Project/Firebase/functions.go
+++ b/Project/Firebase/functions.go
@@ -72,3 +72,47 @@ func RemoveFromJournal(userId string, gameId string) error {
 
 	return nil
 }
+
+// Find chat between two users
+func GetChat(user1 string, user2 string) (*Document, error) {
+	if user1 == user2 {
+		return nil, errors.New("sender and receiver cannot be the same")
+	}
+
+	// Confirm the second user exists ( User 1 should already have been authenticated )
+	/*path := "users/" + user2
+	_, err := DB.GetDoc(path)
+	if err != nil {
+		return nil, errors.New("user does not exist")
+	}*/
+
+	// Check if there is an existing chat between the users
+	var chatDocs []Document
+	path := "chats"
+	conditionValues := map[string]interface{}{
+		"user1": user1,
+		"user2": user2,
+	}
+	chatDocs, err := DB.GetDocsWhere(path, conditionValues)
+	if err != nil {
+	}
+
+	// Check with switched users
+	if len(chatDocs) == 0 {
+		conditionValues = map[string]interface{}{
+			"user1": user2,
+			"user2": user1,
+		}
+		chatDocs, err = DB.GetDocsWhere(path, conditionValues)
+		if err != nil {
+			return nil, err
+		}
+
+		// Not found
+		if len(chatDocs) == 0 {
+			return nil, nil
+		}
+	}
+
+	return &chatDocs[0], nil
+}
diff --git a/Project/Handlers/gameList.go b/Project/Handlers/gameList.go
index 78598377f13c7adb7b1901c7b135eb5008aa7be2..4e930dc2978b2a45b6ad07db4f00cbe238b6e9f5 100644
--- a/Project/Handlers/gameList.go
+++ b/Project/Handlers/gameList.go
@@ -6,6 +6,8 @@ import (
 	"log"
 	"net/http"
 	"strconv"
+
+	"cloud.google.com/go/firestore"
 )
 
 // getGames gets all the games to be displayed on a page when browsing
@@ -17,7 +19,11 @@ func GetGames(w http.ResponseWriter, r *http.Request) {
 	pageNum, _ := strconv.Atoi(page)
 	size, _ := strconv.Atoi(pageSize)
 
-	gameDocs, err := firebase.DB.GetPaginatedDocs("games", pageNum, size)
+	sortBy := firebase.Sort{
+		Field: "name",
+		Order: firestore.Asc,
+	}
+	gameDocs, err := firebase.DB.GetPaginatedDocs("games", pageNum, size, 0, &sortBy)
 	if err != nil {
 		http.Error(w, err.Error(), http.StatusInternalServerError)
 		return
diff --git a/Project/Handlers/getMessages.go b/Project/Handlers/getMessages.go
new file mode 100644
index 0000000000000000000000000000000000000000..4be6ff9d611826b083eee3da7b65cd08d6baa942
--- /dev/null
+++ b/Project/Handlers/getMessages.go
@@ -0,0 +1,93 @@
+package handlers
+
+import (
+	"encoding/json"
+	firebase "go-firebase-auth/Firebase"
+	"net/http"
+	"strconv"
+	"time"
+
+	"cloud.google.com/go/firestore"
+)
+
+// Returns a messages from a chat between two users
+func GetMessages(w http.ResponseWriter, r *http.Request) {
+
+	// Get parameters
+	urlQuery := r.URL.Query()
+	token := urlQuery.Get("token")
+	secondUserId := urlQuery.Get("user")
+	page := r.URL.Query().Get("page")
+	pageSize := r.URL.Query().Get("pageSize")
+	offsetStr := r.URL.Query().Get("offset")
+
+	pageNum, _ := strconv.Atoi(page)
+	size, _ := strconv.Atoi(pageSize)
+	offset, _ := strconv.Atoi(offsetStr)
+
+	userId, err := firebase.GetUserId(token)
+	if err != nil {
+		http.Error(w, "Failed to authenticate user: "+err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Get the chat
+	chatDoc, err := firebase.GetChat(userId, secondUserId)
+	if err != nil {
+		http.Error(w, "Failed to get chat: "+err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Create a new chat if not found
+	if chatDoc == nil {
+		path := "chats"
+		docData := map[string]interface{}{
+			"user1":     userId,
+			"user2":     secondUserId,
+			"startDate": time.Now(),
+			"blockedBy": "",
+		}
+
+		chatId, err := firebase.DB.PostDoc(path, docData)
+		if err != nil {
+			http.Error(w, "Failed to initiate chat: "+err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		chatDoc = &firebase.Document{
+			Id:   chatId,
+			Data: docData,
+		}
+	}
+
+	chatId := chatDoc.Id
+
+	path := "chats/" + chatId + "/messages"
+	sortBy := firebase.Sort{
+		Field: "date",
+		Order: firestore.Desc,
+	}
+
+	messageDocs, err := firebase.DB.GetPaginatedDocs(path, pageNum, size, offset, &sortBy)
+	if err != nil {
+		http.Error(w, err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	var messages []Message
+	for _, doc := range messageDocs {
+		data := doc.Data
+		message := Message{
+			Sender:  data["sender"].(string),
+			Content: data["content"].(string),
+			Date:    data["date"].(time.Time),
+		}
+		messages = append(messages, message)
+	}
+
+	// Encode the response body and send it back to the client
+	err = json.NewEncoder(w).Encode(messages)
+	if err != nil {
+		http.Error(w, "Failed to encode body", http.StatusInternalServerError)
+	}
+}
diff --git a/Project/Handlers/getUser.go b/Project/Handlers/getUser.go
new file mode 100644
index 0000000000000000000000000000000000000000..deb7b40b385effb314bb556ed87772702aca543a
--- /dev/null
+++ b/Project/Handlers/getUser.go
@@ -0,0 +1,29 @@
+package handlers
+
+import (
+	"encoding/json"
+	firebase "go-firebase-auth/Firebase"
+	"net/http"
+)
+
+func GetUser(w http.ResponseWriter, r *http.Request) {
+	urlQuery := r.URL.Query()
+	userId := urlQuery.Get("user")
+
+	path := "users/" + userId
+	userDoc, err := firebase.DB.GetDoc(path)
+	if err != nil {
+		http.Error(w, "Could not find user: "+err.Error(), http.StatusNotFound)
+		return
+	}
+
+	response := User{
+		Username: userDoc.Data["username"].(string),
+	}
+
+	// Encode the response body and send it back to the client
+	err = json.NewEncoder(w).Encode(response)
+	if err != nil {
+		http.Error(w, "Failed to encode body", http.StatusInternalServerError)
+	}
+}
diff --git a/Project/Handlers/getUserGameData.go b/Project/Handlers/getUserGameData.go
index 24158c15a45a245b343cb3cd51ba35f39cb21951..12c5af8490456bd3551244fd22c56cc879f26adc 100644
--- a/Project/Handlers/getUserGameData.go
+++ b/Project/Handlers/getUserGameData.go
@@ -18,31 +18,37 @@ func GetUserGameData(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
+	var review string
+	var rating int
+	inJournal := true
+
 	path := "users/" + userId + "/journal/" + gameId
 	doc, err := firebase.DB.GetDoc(path)
 	if err != nil {
-		http.Error(w, "Game not in journal", http.StatusNotFound)
-		return
+		inJournal = false
 	}
-	rating := int(doc.Data["rating"].(int64))
 
-	path = "reviews"
-	conditionValues := map[string]interface{}{
-		"user": userId,
-		"game": gameId,
-	}
-
-	var review string
-	reviewDoc, _ := firebase.DB.GetDocsWhere(path, conditionValues)
-	if len(reviewDoc) > 0 {
-		review = reviewDoc[0].Data["content"].(string)
-	} else {
-		review = ""
+	if inJournal {
+		rating = int(doc.Data["rating"].(int64))
+
+		path = "reviews"
+		conditionValues := map[string]interface{}{
+			"user": userId,
+			"game": gameId,
+		}
+
+		reviewDoc, _ := firebase.DB.GetDocsWhere(path, conditionValues)
+		if len(reviewDoc) > 0 {
+			review = reviewDoc[0].Data["content"].(string)
+		} else {
+			review = ""
+		}
 	}
 
 	var userGameData = UserGameDataResponse{
-		Rating: rating,
-		Review: review,
+		InJournal: inJournal,
+		Rating:    rating,
+		Review:    review,
 	}
 
 	// Encode the response body and send it back to the client
diff --git a/Project/Handlers/sendMessage.go b/Project/Handlers/sendMessage.go
new file mode 100644
index 0000000000000000000000000000000000000000..5963eb206d8b9d5746adb32b6dcebfae2cadecc2
--- /dev/null
+++ b/Project/Handlers/sendMessage.go
@@ -0,0 +1,73 @@
+package handlers
+
+import (
+	"encoding/json"
+	firebase "go-firebase-auth/Firebase"
+	"go-firebase-auth/websocket"
+	"net/http"
+	"time"
+)
+
+// Sends a message from one user to another
+func SendMessage(w http.ResponseWriter, r *http.Request) {
+	// Decode request body
+	var body SendMessageBody
+	decoder := json.NewDecoder(r.Body)
+	err := decoder.Decode(&body)
+	if err != nil {
+		http.Error(w, "Failed to decode body: "+err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Authenticate user
+	userId, err := firebase.GetUserId(body.Token)
+	if err != nil {
+		http.Error(w, "Failed to authenticate user: "+err.Error(), http.StatusBadRequest)
+		return
+	}
+	secondUserId := body.SecondUser
+
+	chatDoc, err := firebase.GetChat(userId, secondUserId)
+	if err != nil {
+		http.Error(w, "Failed to get chat: "+err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Create a new chat if not found
+	if chatDoc == nil {
+		path := "chats"
+		docData := map[string]interface{}{
+			"user1":     userId,
+			"user2":     secondUserId,
+			"startDate": time.Now(),
+			"blockedBy": "",
+		}
+
+		chatId, err := firebase.DB.PostDoc(path, docData)
+		if err != nil {
+			http.Error(w, "Failed to initiate chat: "+err.Error(), http.StatusInternalServerError)
+			return
+		}
+
+		chatDoc = &firebase.Document{
+			Id:   chatId,
+			Data: docData,
+		}
+	}
+
+	// Store the message
+	chatId := chatDoc.Id
+	path := "chats/" + chatId + "/messages"
+	_, err = firebase.DB.PostDoc(path, map[string]interface{}{
+		"sender":  userId,
+		"date":    time.Now(),
+		"content": body.Content,
+		"read":    false,
+	})
+	if err != nil {
+		http.Error(w, "Failed to send message: "+err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	websocket.SendMessageToUser(userId, secondUserId, body.Content)
+}
diff --git a/Project/Handlers/structs.go b/Project/Handlers/structs.go
index b2539dd07bdc6d8c6b17f897b37e2f7391100c1c..0393f32c50220f2a5e2ce1b78dda426813685244 100644
--- a/Project/Handlers/structs.go
+++ b/Project/Handlers/structs.go
@@ -2,6 +2,7 @@ package handlers
 
 import (
 	"go-firebase-auth/apifunctions"
+	"time"
 )
 
 type JustToken struct {
@@ -15,8 +16,9 @@ type GamePageResponse struct {
 }
 
 type UserGameDataResponse struct {
-	Rating int    `json:"rating"`
-	Review string `json:"review"`
+	InJournal bool   `json:"inJournal"`
+	Rating    int    `json:"rating"`
+	Review    string `json:"review"`
 }
 
 type Review struct {
@@ -63,6 +65,22 @@ type UserRegisterBody struct {
 	Username string `json:"username"`
 }
 
+type SendMessageBody struct {
+	Token      string `json:"token"`
+	SecondUser string `json:"secondUser"`
+	Content    string `json:"content"`
+}
+
+type Message struct {
+	Sender  string    `json:"sender"`
+	Content string    `json:"content"`
+	Date    time.Time `json:"date"`
+}
+
+type User struct {
+	Username string `json:"username"`
+}
+
 type Comment struct {
 	Content string `json:"content"`
 	User    string `json:"user"`
diff --git a/Project/Handlers/submitUserGameData.go b/Project/Handlers/submitUserGameData.go
index 82b0b973be3b3a1b71c9425d91b376f578a9c5c5..e3126da024d3c2fb6804bfc39baae14be16c2b2e 100644
--- a/Project/Handlers/submitUserGameData.go
+++ b/Project/Handlers/submitUserGameData.go
@@ -94,7 +94,7 @@ func SubmitUserGameData(w http.ResponseWriter, r *http.Request) {
 
 	if len(reviews) == 0 { // No review found
 		// Post review
-		err = firebase.DB.PostDoc(path, map[string]interface{}{
+		_, err = firebase.DB.PostDoc(path, map[string]interface{}{
 			"content": body.Review,
 			"game":    body.GameId,
 			"rating":  body.Rating,
diff --git a/Project/go.mod b/Project/go.mod
index 59df33aa6c3c75e7ea2676581d07db72c6d9584e..d67370d786ac447c05cb17f4db15ba6df5af345d 100644
--- a/Project/go.mod
+++ b/Project/go.mod
@@ -26,6 +26,7 @@ require (
 	github.com/google/uuid v1.6.0 // indirect
 	github.com/googleapis/enterprise-certificate-proxy v0.3.3 // indirect
 	github.com/googleapis/gax-go/v2 v2.13.0 // indirect
+	github.com/gorilla/websocket v1.5.3 // indirect
 	github.com/jarcoal/httpmock v1.3.1 // indirect
 	github.com/pmezard/go-difflib v1.0.0 // indirect
 	github.com/rjansen/raizel v0.1.0 // indirect
diff --git a/Project/go.sum b/Project/go.sum
index a264a1ee106b3d8bf5acfc97bf2a836b237b74a9..26623df805f71ac2ce24055d2e8974e03856ca6a 100644
--- a/Project/go.sum
+++ b/Project/go.sum
@@ -107,6 +107,8 @@ github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
 github.com/googleapis/gax-go/v2 v2.13.0 h1:yitjD5f7jQHhyDsnhKEBU52NdvvdSeGzlAnDPT0hH1s=
 github.com/googleapis/gax-go/v2 v2.13.0/go.mod h1:Z/fvTZXF8/uw7Xu5GuslPw+bplx6SS338j1Is2S+B7A=
+github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg=
+github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
 github.com/hailocab/go-hostpool v0.0.0-20160125115350-e80d13ce29ed/go.mod h1:tMWxXQ9wFIaZeTI9F+hmhFiGpFmhOHzyShyFUhRm0H4=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
diff --git a/Project/main.go b/Project/main.go
index a904c49104c6b64f620d0fa16e33337ae29318d5..27b0469dcda6106d7646864517b210bffe012932 100644
--- a/Project/main.go
+++ b/Project/main.go
@@ -6,6 +6,7 @@ import (
 	firebase "go-firebase-auth/Firebase"
 	handlers "go-firebase-auth/Handlers"
 	"go-firebase-auth/apifunctions"
+	"go-firebase-auth/websocket"
 	"log"
 	"net/http"
 	"path/filepath"
@@ -35,6 +36,8 @@ func main() {
 		http.ServeFile(w, r, filepath.Join("./static", "games.html"))
 	})
 
+	http.HandleFunc("/ws", websocket.HandleWebsocket)
+
 	http.HandleFunc("/gamepage", handlers.GamePage)
 	http.HandleFunc("/getusergamedata", handlers.GetUserGameData)
 	http.HandleFunc("/gamelist", handlers.GetGames)
@@ -49,6 +52,9 @@ func main() {
 	http.HandleFunc("/connectedtosteam", handlers.ConnectedToSteam)
 	http.HandleFunc("/getuserjournal", handlers.GetUserJournal)
 	http.HandleFunc("/getreviewsforgame", handlers.GetReviewsForGame)
+	http.HandleFunc("/sendmessage", handlers.SendMessage)
+	http.HandleFunc("/getmessages", handlers.GetMessages)
+	http.HandleFunc("/getuser", handlers.GetUser)
 	http.HandleFunc("/submitusergamecomment", handlers.SubmitUserGameComment)
 	http.HandleFunc("/getusergamecomments", handlers.GetUserGameComments)
 
diff --git a/Project/static/chat.html b/Project/static/chat.html
new file mode 100644
index 0000000000000000000000000000000000000000..eb41a834ef9fbb3586a16a9feca2625090e4db18
--- /dev/null
+++ b/Project/static/chat.html
@@ -0,0 +1,48 @@
+<!-- static/chat.html -->
+<!DOCTYPE html>
+<html lang="en">
+    <head>
+        <meta charset="UTF-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1.0">
+        <link rel="stylesheet" href="/style/style_chat.css">
+        <title>Chat</title>
+        <link rel="icon" href="img/LogoNew.png" type="image/x-icon"/>
+
+    </head>
+    <body>
+        <header>
+            <div id="divHeader">
+                <a href="index.html"><img src="img/LogoNew.png" alt="Joystick Journal Logo" id="imgLogo"></a>
+                <h1>Joystick Journal</h1>
+            </div>
+        </header>
+
+        <nav>
+            <ul>
+                <li><a id="userPageButton" href="userpage/index.html">My Profile</a></li>
+                <li><a id="signOutButton" href="#">Sign out</a></li>
+                <li><a id="loginButton" href="login.html">Log in</a></li>
+                <li><a id="registerButton" href="register.html">Registrer deg</a></li>
+            </ul>
+        </nav>
+
+        <div id="divContent">
+            <p id="name"></p>
+            <div id = "chat"></div>
+            <form id="messageForm">
+                <input id="content" type="text">
+                <button id="sendMessage" type="button">Send</button>
+
+            </form>
+        </div>
+
+        
+        <footer>
+            <div>
+                <p>&copy; 2024 Joystick Journal. All rights not reserved yet.</p>
+            </div>
+        </footer>
+
+        <script type="module" src="js/chat.js"></script>
+    </body>
+</html>
diff --git a/Project/static/game.html b/Project/static/game.html
index ff7c0342b3e79a088df3e9c25150bf575b869ba9..322ace47f97966418d2952c3dd0f9450226333c5 100644
--- a/Project/static/game.html
+++ b/Project/static/game.html
@@ -48,18 +48,6 @@
                     <button id = "addDelete">Add to journal</button>
 
                     <form id="reviewForm">
-                        <label>Your rating: </label>
-                        <select id="rating">
-                            <option value="0">Select rating</option>
-                        </select>
-                        
-
-                        <label>Write your review: </label>
-                        <br>
-                        <textarea id="review"></textarea>
-                        <br>
-                        <button id="submitRatingReview" type="button">Update</button>
-                        <button id="deleteReview" type="button">Delete review</button>
                     </form>
    
                 </section>
diff --git a/Project/static/js/chat.js b/Project/static/js/chat.js
new file mode 100644
index 0000000000000000000000000000000000000000..3f34c42ad688a31729b9b8abe95da5a2a733bf76
--- /dev/null
+++ b/Project/static/js/chat.js
@@ -0,0 +1,192 @@
+import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-app.js";
+import { getAuth, onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-auth.js";
+
+import { initFirebase, setupAuthStateListener, setupButtonHandlers, sendGETRequest, sendRequest } from "./utils.js";
+
+const auth = initFirebase();
+
+let socket
+
+const eMessageForm = document.getElementById("messageForm")
+const eMessageContent = document.getElementById("content")
+const eSendButton = document.getElementById("sendMessage")
+
+const eChat = document.getElementById("chat");
+const eName = document.getElementById("name");
+
+// Get second user's id from url
+const queryString = window.location.search;
+const urlParams = new URLSearchParams(queryString);
+const secondUserId = urlParams.get('user');
+let secondUserName = "";
+
+
+let currentUser;
+let messages = [];
+let loadedMessageCount = 0; // Messages sent or received through the websocket
+
+
+setupAuthStateListener(auth, {
+    loginButton,
+    userPageButton,
+    signOutButton,
+    registerButton
+});
+
+setupButtonHandlers(auth, {
+    loginButton,
+    userPageButton,
+    signOutButton,
+    registerButton
+});
+
+
+onAuthStateChanged(auth, async (user) => {
+    if (user) {
+        const idToken = await user.getIdToken();
+        socket = new WebSocket(`ws://${window.location.hostname}:${window.location.port}/ws?token=${idToken}&user=${secondUserId}`);
+
+        socket.addEventListener("message", (event) => receiveMessage(event));
+
+        currentUser = user;
+
+        secondUserName = await getUsername();
+        eName.innerHTML = "Chatting with " + secondUserName;    
+
+        if(secondUserName == "") {
+            document.body.innerHTML = "404 - user not found";
+        } else {
+            loadMessages(0, 10, 0, true);
+        }
+    } else {
+        window.location.href = '/';
+    }
+});
+
+
+async function getUsername() {
+
+    let name = localStorage.getItem(secondUserId)
+
+    if(name == null) {
+
+        try {
+            const request = "/getuser?user=" + secondUserId;
+
+            let response = await fetch(request);
+            let data = await response.json();
+
+            localStorage.setItem(secondUserId, data.username);
+
+            return data.username;
+        } catch (error) {
+            console.log("Error fetching second user: ", error);
+            return "";
+        }
+    } else {
+        return name;
+    }
+}
+
+
+function receiveMessage(event) {
+    const content = event.data
+    let message = { sender: secondUserId, content: content, date: Date.now() };
+
+    // Determine if user has scrolled down to the last message
+    let scrollDown = (eChat.scrollTop == (eChat.scrollHeight - eChat.offsetHeight));
+
+    messages.push(message);
+    displayMessages(messages);
+
+    loadedMessageCount++;
+
+    if(scrollDown) {
+        eChat.scrollTop = eChat.scrollHeight; // Scroll to the bottom
+    }
+}
+
+async function loadMessages(page, pageSize, offset, first) {
+    if(secondUserName  != "") {
+        const idToken = await currentUser.getIdToken();
+        const request = `/getmessages?token=${idToken}&user=${secondUserId}&page=${page}&pageSize=${pageSize}&offset=${offset}`;
+
+        fetch(request)
+        .then(response => response.json())
+        .then(data => {
+            messages = data.reverse().concat(messages);
+            displayMessages(messages);
+
+            loadedMessageCount += data.length;
+
+            if(first) {
+                eChat.scrollTop = eChat.scrollHeight; // Scroll down to the latest message
+            }
+        })
+        .catch(error => console.error("Error fetching messages: ", error));
+    } else {
+        document.body.innerHTML = "404 - not found";
+    }
+}
+
+function displayMessages(messages) {
+    eChat.innerHTML = "";
+    //messages = messages.reverse();
+    messages.forEach(m => {
+        // Create a message element
+        let message = document.createElement("div");
+        message.className = "message " + (m.sender == currentUser.uid ? "you" : "other");
+        let content = document.createElement("p");
+        content.innerHTML = m.content;
+        message.appendChild(content);
+
+        eChat.appendChild(message);
+    });
+}
+
+
+
+eSendButton.addEventListener('click', async function (event) {
+    const content = eMessageContent.value
+    if(content != "") {
+        let idToken = await currentUser.getIdToken();
+
+        const request = "/sendmessage"
+        fetch(request, {
+            method: "POST",
+            headers: {
+                "Content-Type": "application/json",
+            },
+            body: JSON.stringify({
+                "token": idToken,
+                "secondUser": secondUserId,
+                "content": content
+            })
+        }).then(response => {
+            if(response.ok) {
+                let message = { sender: currentUser.uid, content: eMessageContent.value, date: Date.now() };
+
+                messages.push(message);
+                displayMessages(messages);
+
+                loadedMessageCount++;
+
+                // Scroll down to the sent message
+                eChat.scrollTop = eChat.scrollHeight;
+
+                eMessageContent.value = "";
+            }
+        }).catch((error) => {
+            console.error("Error sending message:", error);
+        })
+    } else {
+        alert("You cannot send an empty message");
+    }
+});
+
+
+eChat.addEventListener("scroll", function(e) {
+    if(eChat.scrollTop == 0) {
+        loadMessages(0, 5, loadedMessageCount, false)
+    }
+});
\ No newline at end of file
diff --git a/Project/static/js/firebase-config.js b/Project/static/js/firebase-config.js
index 8a13b6fa09ffcebff28a36d0550fb2ee7b37d918..9c2b145ebb3ff2784aede2a4642de1377433d616 100644
--- a/Project/static/js/firebase-config.js
+++ b/Project/static/js/firebase-config.js
@@ -5,4 +5,4 @@ export const firebaseConfig = {
   storageBucket: "joystick-journal.appspot.com",
   messagingSenderId: "808652272382",
   appId: "1:808652272382:web:0f7f71ea572316bb952baf"
-};
\ No newline at end of file
+};
diff --git a/Project/static/js/game.js b/Project/static/js/game.js
index eb7ca97f1ba710de833705dc80073446a1cdc8df..37263ec66581bcd8915c3723cb19174b488ff7f4 100644
--- a/Project/static/js/game.js
+++ b/Project/static/js/game.js
@@ -1,22 +1,22 @@
 import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-app.js";
 import { getAuth, onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-auth.js";
-import { initFirebase, setupAuthStateListener, setupButtonHandlers } from "./utils.js";
-//import { json } from "sjcl";
+import { initFirebase, setupAuthStateListener, setupButtonHandlers, sendGETRequest, sendRequest } from "./utils.js";
 
 const auth = initFirebase();
 
 const eTitle = document.getElementById('title');
 const eAvgRating = document.getElementById('avgRating');
 const eDescription = document.getElementById('description');
-const eRating = document.getElementById('rating');
-const eReviewForm = document.getElementById('reviewForm');
-const eReview = document.getElementById('review');
-const eFormButton = document.getElementById('submitRatingReview');
-const eDeleteReviewButton = document.getElementById('deleteReview');
 const addDeleteButton = document.getElementById('addDelete');
 const eSubmitCommentButton = document.getElementById('submitComment');
 const eComment = document.getElementById('comment');
 
+const eReviewForm = document.getElementById('reviewForm');
+let eReview = document.getElementById('review');
+let eFormButton = document.getElementById('submitRatingReview');
+let eDeleteReviewButton = document.getElementById('deleteReview');
+let eRating = document.getElementById('rating');
+
 const loginButton = document.getElementById('loginButton');
 const userPageButton = document.getElementById('userPageButton');
 const signOutButton = document.getElementById('signOutButton');
@@ -45,7 +45,15 @@ setupButtonHandlers(auth, {
 });
 
 
+function userRequests() {
+    return [
+        "/getusergamedata?token=" + currentUser.uid + "&id=" + gameId,
+        "/getuserjournal?token=" + currentUser.uid
+    ];
+}
+
 getGameData();
+showReviews();
 
 
 onAuthStateChanged(auth, async (user) => {
@@ -63,146 +71,149 @@ onAuthStateChanged(auth, async (user) => {
 
 
 // Get data for the game
-function getGameData() {
+async function getGameData() {
+    const request = "/gamepage?id=" + gameId;
+    let gameData = await sendGETRequest(request, true, request);
+    if (gameData != null) {
+        insertGameData(gameData);
+    }
+}
 
-    let request = "/gamepage?id=" + gameId;
+// Insert game data on the game page
+function insertGameData(data) {
+    const game = data.game;
 
-    fetch(request).then(
-        response => response.json()
-    ).then(data => {
-        const game = data.game;
+    const ratingCount = data.ratingCount;
+    const ratingSum = data.ratingSum;
 
-        const ratingCount = data.ratingCount;
-        const ratingSum = data.ratingSum;
+    eTitle.innerHTML = game.name;
+    eDescription.innerHTML = game.detailed_description;
 
-        eTitle.innerHTML = game.name;
-        eDescription.innerHTML = game.detailed_description;
+    if(ratingCount == 0) {
+        eAvgRating.innerHTML = "No ratings have been given";
+    } else {
+        eAvgRating.innerHTML = "Average rating: " + ratingSum/ratingCount;
+    }
 
-        if(ratingCount == 0) {
-            eAvgRating.innerHTML = "No ratings have been given";
-        } else {
-            eAvgRating.innerHTML = "Average rating: " + ratingSum/ratingCount;
-        }
+    document.title = game.name
+}
 
-        document.title = game.name
+
+// Get the user's rating and review for the game
+async function getUserGameData(token) {
+    const request = "/getusergamedata?token=" + encodeURIComponent(token) + "&id=" + gameId;
+    const cacheRequest = "/getusergamedata?token=" + currentUser.uid + "&id=" + gameId;
+
+    let userGameData = await sendGETRequest(request, true, cacheRequest);
+    if (userGameData != null && userGameData.inJournal) {
+            eReviewForm.innerHTML = `
+            <label>Your rating: </label>
+            <select id="rating">
+                <option value="0">Select rating</option>
+            </select>
+                        
+
+            <label>Write your review: </label>
+            <br>
+            <textarea id="review"></textarea>
+            <br>
+            <button id="submitRatingReview" type="button">Update</button>
+            <button id="deleteReview" type="button">Delete review</button>
+        `
+
+        eReview = document.getElementById('review');
+        eFormButton = document.getElementById('submitRatingReview');
+        eDeleteReviewButton = document.getElementById('deleteReview');
+        eRating = document.getElementById('rating');
 
         for(let i = 1; i < 11; i++) {
             eRating.innerHTML += `<option value=\"${i}\">${i}</option>`;
         }
-    
-    }).catch((error) => {
-        //eTitle.innerHTML = "This game is not available"
-        console.error("Error fetching game data: ", error);
 
-        document.getElementsByTagName('body')[0].innerHTML = "<h2> 404 - not found </h2>";
-    });
-}
-
-
-// Get the user's rating and review for the game
-function getUserGameData(token) {
-    const request = "getusergamedata?token=" + encodeURIComponent(token) + "&id=" + gameId;
-
-    fetch(request, {
-        method: "GET",
-        headers: {
-            "Content-Type": "application/json"
-        },
-    }).then(
-        response => response.json()
-    ).then(data => {
-        eRating.value = (data.rating).toString();
-        eReview.value = data.review;
+        activateFormButtons();
 
+        eRating.value = (userGameData.rating).toString();
+        eReview.value = userGameData.review;
+    
         addDeleteButton.innerHTML = "Remove from journal";
         addDeleteButton.addEventListener('click', removeFromJournal);
-
-        if(data.review == "") {
+    
+        if(userGameData.review == "") {
             eDeleteReviewButton.remove();
         }
-    }).catch(error => {
+    } else {
         eReviewForm.innerHTML = "";
     
         addDeleteButton.innerHTML = "Add to journal";
         addDeleteButton.addEventListener('click', addToJournal);
-    });
+    }
 }
+
 //executing comments and reviews
 showReviews();
 showComments();
 
 // Submit review and rating
-eFormButton.addEventListener('click', async (e) => {
-    e.preventDefault();
+function activateFormButtons() {
+    // Submit button
+    eFormButton.addEventListener('click', async (e) => {
+        e.preventDefault();
+    
+        if(currentUser) {
+            const rating = parseInt(eRating.value)
+            const review = eReview.value
+    
+            if(rating == 0 && review != "") {
+                alert("Please rate the game before posting a review.");
+                return;
+            }
+    
+            const request = "/submitusergamedata";
+    
+            let idToken = await currentUser.getIdToken();
+    
+            const body = JSON.stringify({ 
+                "token": idToken, 
+                "gameId": gameId,
+                "rating": rating,
+                "review": review
+            });
+    
+            let response = await sendRequest(request, "POST", body, true, userRequests());
+    
+            if (response.ok) {
+                alert("Succcessfully updated entry");
+                location.reload();
+            }
+        }
+    });
 
-    if(currentUser) {
+
+    // Delete review button
+    eDeleteReviewButton.addEventListener('click', async (e) => {
+        e.preventDefault();
+    
         // Fetch game id from url
         const queryString = window.location.search;
         const urlParams = new URLSearchParams(queryString);
         const gameId = urlParams.get('g');
-
-        const rating = parseInt(eRating.value)
-        const review = eReview.value
-
-        if(rating == 0 && review != "") {
-            alert("Please rate the game before posting a review.");
-            return;
-        }
-
-        const request = "/submitusergamedata";
-
+    
         let idToken = await currentUser.getIdToken();
-
-        fetch(request, {
-            method: "POST",
-            headers: {
-                "Content-Type": "application/json"
-            },
-            body: JSON.stringify({ 
-                "token": idToken, 
-                "gameId": gameId,
-                "rating": rating,
-                "review": review
-            })
-        }).then( (response) => {
-            alert("Succcessfully updated entry");
-            location.reload();
-        }).catch( (error) => {
-            console.error("Error updating entry: ", error);
-            alert("Failed to update entry");
+    
+        const request = "/deletereview"
+        const body = JSON.stringify({ 
+            "token": idToken, 
+            "gameId": gameId
         });
-    }
-});
-
-
-eDeleteReviewButton.addEventListener('click', async (e) => {
-    e.preventDefault();
-
-    // Fetch game id from url
-    const queryString = window.location.search;
-    const urlParams = new URLSearchParams(queryString);
-    const gameId = urlParams.get('g');
 
-    let idToken = await currentUser.getIdToken();
+        let response = await sendRequest(request, "DELETE", body, true, userRequests());
 
-    const request = "/deletereview"
-    fetch(request, {
-        method: "DELETE",
-        headers: {
-            "Content-Type": "application/json"
-        },
-        body: JSON.stringify({ 
-            "token": idToken, 
-            "gameId": gameId
-        })
-    }).then(response => {
-        switch(response.status) {
-            case 404: alert("You have no review posted for this game."); break
-            case 200: alert("Successfully deleted review."); location.reload(); break
-            default: alert("Failed to delete review. Try again.");
+        if (response.ok) {
+            alert("Succcessfully deleted review.");
+            location.reload();
         }
     });
-});
+}
 
 eSubmitCommentButton.addEventListener('click', async (e) => {
     e.preventDefault();
@@ -246,57 +257,40 @@ async function addToJournal() {
     let idToken = await currentUser.getIdToken();
 
     const request = "/addtojournal";
-    fetch(request, {
-        method: "POST",
-        headers: {
-            "Content-Type": "application/json"
-        },
-        body: JSON.stringify({ 
-            "token": idToken, 
-            "gameId": gameId
-        })
-    }).then(response => {
-        switch(response.status) {
-            case 200: alert("Successfully added to journal."); location.reload(); break;
-            case 403: alert("Could not verify ownership."); break;
-            default: alert("Failed to add to journal.");
-        }
+
+    const body = JSON.stringify({ 
+        "token": idToken, 
+        "gameId": gameId
     });
+
+    let response = await sendRequest(request, "POST", body, true, userRequests());
+    switch(response.status) {
+        case 200: alert("Successfully added to journal."); location.reload(); break;
+        case 403: alert("Could not verify ownership."); break;
+        default: alert("Failed to add to journal.");
+    }
 }
 
 async function removeFromJournal() {
-    // Fetch game id from url
-    const queryString = window.location.search;
-    const urlParams = new URLSearchParams(queryString);
-    const gameId = urlParams.get('g');
-
     let idToken = await currentUser.getIdToken();
 
     const request = "/removefromjournal";
-    fetch(request, {
-        method: "DELETE",
-        headers: {
-            "Content-Type": "application/json"
-        },
-        body: JSON.stringify({ 
-            "token": idToken, 
-            "gameId": gameId
-        })
-    }).then(response => {
-        if(response.status == 200) {
-            alert("Successfully removed from journal.");
-            location.reload();
-        } else {
-            alert("Failed to remove from journal.");
-        }
+
+    const body = JSON.stringify({ 
+        "token": idToken, 
+        "gameId": gameId
     });
+
+    let response = await sendRequest(request, "POST", body, true, userRequests());
+    if(response.ok) {
+        alert("Successfully removed from journal.");
+        location.reload();
+    } else {
+        alert("Failed to remove from journal.");
+    }
 }
 
 function showReviews() {
-    const queryString = window.location.search;
-    const urlParams = new URLSearchParams(queryString);
-    const gameId = urlParams.get('g');
-
     let request = "/getreviewsforgame?id=" + gameId;
     fetch(request, {
         method: 'GET',
diff --git a/Project/static/js/userpage.js b/Project/static/js/userpage.js
index b38bebf655543f58f691ec35416c5eb953d4e39b..3c84de6f7046d542c4f370d80c1fb243cc32323d 100644
--- a/Project/static/js/userpage.js
+++ b/Project/static/js/userpage.js
@@ -1,7 +1,7 @@
 // static/js/userpage.js
 import { initializeApp } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-app.js";
 import { getAuth, onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/10.12.2/firebase-auth.js";
-import { initFirebase, setupAuthStateListener, setupButtonHandlers } from "./utils.js";
+import { initFirebase, setupAuthStateListener, setupButtonHandlers, sendGETRequest, sendRequest } from "./utils.js";
 
 const auth = initFirebase();
 
@@ -103,15 +103,10 @@ async function DisconnectFromSteam() {
 async function showUserJournal() {    
     const idToken = await currentUser.getIdToken();
     const request = "/getuserjournal?token=" + idToken;
+    const cacheRequest = "/getuserjournal?token=" + currentUser.uid;
 
-    fetch(request, {
-        method: 'GET',
-        headers: {
-            'Content-Type': 'application/json'
-        }
-    })
-    .then(response => response.json())
-    .then(games => {
+    let games = await sendGETRequest(request, true, cacheRequest);
+    if (games != null) {
         const journalContainer = document.getElementById('journalList');
         journalContainer.innerHTML = ''; // Clear previous journal entries
     
@@ -133,6 +128,5 @@ async function showUserJournal() {
             `;
             journalContainer.appendChild(gameElement);
         });
-    })
-    .catch(error => console.error('Error fetching journal:', error));
+    }
 }
diff --git a/Project/static/js/utils.js b/Project/static/js/utils.js
index b08ff3cd1526393e39b0a31d2b5a879f3cd64ca4..c254f4b78aca73962514482f4a081d2212ea1082 100644
--- a/Project/static/js/utils.js
+++ b/Project/static/js/utils.js
@@ -55,4 +55,55 @@ export const setupButtonHandlers = (auth, elements) => {
             console.error("Error signing out: ", error);
         });
     });
-}    
\ No newline at end of file
+}    
+
+
+
+// Send a GET request
+export async function sendGETRequest(request, cache, cacheRequest) {
+    let cachedData = sessionStorage.getItem(cacheRequest)
+    if(!cache || cachedData==null) {
+        try {
+            console.log("Sending request...");
+            let response = await fetch(request);
+            let data = await response.json();
+
+            // Cache the data if requested
+            if (cache) {
+                sessionStorage.setItem(cacheRequest, JSON.stringify(data));
+            }
+            return data;
+        } catch (error) {
+            console.log("Failed to retrieve data: " + error);
+            return null;
+        }
+    } else {
+        console.log("Getting cached data...");
+        let parsedData = JSON.parse(cachedData);
+        return parsedData;
+    }
+}
+
+// Send a non-GET request
+export async function sendRequest(request, method, body, cache, getRequests) {
+    try {
+        let response = await fetch(request, {
+            method: method,
+            headers: {
+                "Content-Type": "application/json",
+            },
+            body: body
+        });
+
+        // Remove get request data from cache
+        if (cache) {
+            for(let r of getRequests) {
+                sessionStorage.removeItem(r);
+            }
+        }
+        return response;
+    } catch (error) {
+        console.error("Error: ", error);
+        return null;
+    }
+}
diff --git a/Project/static/style/style_chat.css b/Project/static/style/style_chat.css
new file mode 100644
index 0000000000000000000000000000000000000000..b2d58741bb778d1f957b417811f2041a5b594398
--- /dev/null
+++ b/Project/static/style/style_chat.css
@@ -0,0 +1,259 @@
+/* Reset default margins on the body and html */
+html, body {
+    margin: 0;
+    padding: 0;
+    width: 100%;
+    background-color: rgb(34, 40, 49);
+    overflow-x: hidden; /* To prevent horizontal scrolling */
+    overflow-y:auto;
+    font-family: Arial, Helvetica, sans-serif;
+}
+h1, h2, h4, p{ /*text for some reason has a invisible margin, removing this*/
+    margin: 0;
+    padding: 0px;
+}
+h1 {
+    padding-top: 6px;
+}
+
+header {
+    display:flex;
+    justify-content: space-between;
+    top: 0;
+    width: 100%; 
+    height: auto;
+    
+    color: white;
+    background-color: rgb(0, 146, 202);
+    
+}
+
+#searchForm {
+    display: flex;
+    align-items: center;
+}
+#imgLogo {
+    position: relative;
+    top: 0;
+    left: 0;
+    height: 50px; 
+    width: 50px;
+    background-color: rgb(0, 146, 202);
+    padding: 0px;
+}
+
+
+
+ul { /*list is sticky*/
+    position: sticky;
+    display: flex;
+    list-style-type: none;
+    top: 0;
+    margin: 0;
+    padding: 0;
+    background-color: rgb(57, 62, 70); 
+}
+
+#divHeader {
+    width: 56%;
+    display : flex;
+    justify-content: space-between;
+}
+
+main ul li a {
+    text-decoration: none;
+    background-color: rgb(0, 100, 148);
+    color: white;
+    display: block; /* Ensure the <a> is block-level */
+    padding: 10px;
+    padding-left: 50px;
+    padding-right: 50px;
+    text-align: center;
+    
+    
+}
+main ul {
+    border-top-left-radius: 5px;
+    border-top-right-radius: 5px;
+}
+
+ul li a {
+    text-decoration: none;
+    background-color: rgb(0, 100, 148);
+    color: white;
+    display: block; /* Ensure the <a> is block-level */
+    padding: 18px;
+    padding-left: 50px;
+    padding-right: 50px;
+    text-align: center;
+}
+
+ul li a:hover {/* Change background color on hover */
+    background-color: rgb(34, 40, 49); 
+    cursor: pointer;
+}
+
+
+
+
+#divContent {
+    /*display: flex;
+    justify-content: center;*/
+    height: 100vh;
+}
+main {
+    display: flex;
+    flex-direction: column; /* Organize list and section vertically */
+    background-color: rgb(34, 40, 49);
+    width: 70%;
+    height:auto; 
+    padding: 10px;
+    margin: 10px;
+    border-radius: 5px;
+    overflow: hidden; /* Prevent content from overflowing main */
+}
+
+.sectionMain {
+    background-color: rgb(57, 62, 70);
+    color: white;
+    flex-grow: 1; /* Allow it to grow to fill the remaining space */
+    padding: 30px;
+    overflow-y: auto; 
+    scrollbar-color: rgb(5, 25, 35) rgb(0, 100, 148) ;
+    scrollbar-width:auto;
+    scroll-behavior: smooth;
+    
+    border-bottom-left-radius: 5px;
+    border-bottom-right-radius: 5px;
+   
+}
+
+.sectionAside {
+    color:white;
+    background-color: rgb(57, 62, 70);
+    
+}
+
+aside {
+    overflow-y: auto; 
+    background-color: rgb(34, 40, 49);
+    width: 30%;
+    height: auto;
+    padding: 10px;
+    margin: 10px;
+    border-radius: 5px;
+    
+}
+
+article {
+    color: white;
+    width: auto;
+    
+    padding: 30px;
+    background-color: rgb(57, 62, 70);
+    border-radius: 5px;
+}
+
+footer {
+    text-align: center;
+    padding: 10px;
+    background-color: rgb(0, 146, 202);
+    position: relative; /* Keeps the footer fixed at the bottom */
+    bottom: 0; /* Sticks to the bottom */
+    width: 100%; 
+}
+
+@media screen and (max-width: 600px) {
+    #divContent {
+      display: flex;
+      flex-direction: column;
+      flex-wrap: wrap;
+    }
+
+    main {
+        width: 95%;
+        
+    }
+    aside {
+        width: 95%;
+    }
+  }
+
+
+
+#name {
+    text-align: center;
+    color: white;
+    margin: 10px;
+}
+
+#chat {
+    margin: 20px;
+    margin-left: auto;
+    margin-right: auto;
+    border: 3px solid rgb(0, 100, 148);
+    flex-grow: 1; /* Allow chat to grow and fill available space */
+    height: 60vh; /* Set a height for the chat box */
+    width: 70vh;
+    background-color: rgb(230, 230, 230);
+    padding: 10px;
+    overflow-y: auto; /* Scroll if content exceeds the height */
+    display: flex;
+    flex-direction: column;
+    gap: 10px;
+    scrollbar-color: rgb(5, 25, 35) rgb(0, 100, 148);
+}
+
+
+#messageForm {
+    display: flex;
+    justify-content: center;
+    padding: 10px;
+    background-color: rgb(34, 40, 49);
+}
+
+
+#messageForm input {
+    width: 40vh;
+    padding: 10px;
+    border-radius: 5px;
+    border: none;
+    margin-right: 5px;
+}
+
+#messageForm button {
+    padding: 10px;
+    border-radius: 5px;
+    background-color: rgb(0, 146, 202);
+    color: white;
+    border: none;
+    cursor: pointer;
+    white-space: nowrap;
+}
+
+#messageForm button:hover {
+    background-color: rgb(0, 100, 148);
+}
+
+/* Message styles */
+.message {
+    display: flex;
+    align-items: center;
+    max-width: 60%; /* Limit message width */
+    padding: 10px;
+    border-radius: 5px;
+    color: white;
+    word-wrap: break-word; /* Ensure long words don't break the layout */
+    min-height: 30px;
+}
+
+
+.you {
+    align-self: flex-end; /* Align messages from 'You' to the right */
+    background-color: rgb(0, 146, 202);
+}
+
+.other {
+    align-self: flex-start; /* Align messages from 'Other' to the left */
+    background-color: rgb(100, 100, 100);
+}
\ No newline at end of file
diff --git a/Project/websocket/websocket.go b/Project/websocket/websocket.go
new file mode 100644
index 0000000000000000000000000000000000000000..3df4d742bfc091b35b66f1ed1496eeed720c90e5
--- /dev/null
+++ b/Project/websocket/websocket.go
@@ -0,0 +1,70 @@
+package websocket
+
+import (
+	firebase "go-firebase-auth/Firebase"
+	"net/http"
+	"sync"
+
+	"github.com/gorilla/websocket"
+)
+
+var clients = make(map[string]*websocket.Conn)
+var mutex sync.Mutex
+var upgrader = websocket.Upgrader{
+	ReadBufferSize:  1024,
+	WriteBufferSize: 1024,
+}
+
+// WConnect clients when they enter a chat page
+func HandleWebsocket(w http.ResponseWriter, r *http.Request) {
+	urlQuery := r.URL.Query()
+	token := urlQuery.Get("token")
+	toId := urlQuery.Get("user")
+
+	fromId, err := firebase.GetUserId(token)
+	if err != nil {
+		http.Error(w, "Failed to authenticate user: "+err.Error(), http.StatusBadRequest)
+		return
+	}
+
+	// Upgrade to websocket
+	ws, err := upgrader.Upgrade(w, r, nil)
+	if err != nil {
+		http.Error(w, "Failed to establish websocket connection: "+err.Error(), http.StatusInternalServerError)
+		return
+	}
+
+	socketId := SocketId(toId, fromId)
+	mutex.Lock()
+	clients[socketId] = ws
+	mutex.Unlock()
+
+	// Create a go routine that runs until the client disconnects
+	go func() {
+		for {
+			_, _, err = ws.ReadMessage()
+			if err != nil {
+				delete(clients, fromId)
+				ws.Close()
+
+				break
+			}
+		}
+	}()
+}
+
+// Send a message to a user client
+func SendMessageToUser(to string, from string, msg string) {
+	// Check if the user is connected
+	socketId := SocketId(to, from)
+	conn, ok := clients[socketId]
+	if ok {
+		// Send the message to the client
+		conn.WriteMessage(websocket.TextMessage, []byte(msg))
+	}
+}
+
+// Create an id for a receiver and a sender
+func SocketId(to string, from string) string {
+	return from + " -> " + to
+}