summaryrefslogtreecommitdiff
path: root/rushs/eplace/src/pages
diff options
context:
space:
mode:
authorMartial Simon <msimon_fr@hotmail.com>2025-09-15 01:08:27 +0200
committerMartial Simon <msimon_fr@hotmail.com>2025-09-15 01:08:27 +0200
commitc9b6b9a5ca082fe7c1b6f58d7713f785a9eb6a5c (patch)
tree3e4f42f93c7ae89a364e4d51fff6e5cec4e55fa9 /rushs/eplace/src/pages
add: graphs et rushs
Diffstat (limited to 'rushs/eplace/src/pages')
-rw-r--r--rushs/eplace/src/pages/complete/epita/index.html12
-rw-r--r--rushs/eplace/src/pages/complete/epita/index.js53
-rw-r--r--rushs/eplace/src/pages/debug.js34
-rw-r--r--rushs/eplace/src/pages/index.css78
-rw-r--r--rushs/eplace/src/pages/index.html173
-rw-r--r--rushs/eplace/src/pages/index.js10
-rw-r--r--rushs/eplace/src/pages/styles.less905
-rw-r--r--rushs/eplace/src/pages/utils.js95
8 files changed, 1360 insertions, 0 deletions
diff --git a/rushs/eplace/src/pages/complete/epita/index.html b/rushs/eplace/src/pages/complete/epita/index.html
new file mode 100644
index 0000000..a6ebe75
--- /dev/null
+++ b/rushs/eplace/src/pages/complete/epita/index.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <title>E/PLACE</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="icon" href="/favicon.ico" />
+ <script type="module" src="index.js"></script>
+ </head>
+ <body></body>
+</html>
diff --git a/rushs/eplace/src/pages/complete/epita/index.js b/rushs/eplace/src/pages/complete/epita/index.js
new file mode 100644
index 0000000..ced46cf
--- /dev/null
+++ b/rushs/eplace/src/pages/complete/epita/index.js
@@ -0,0 +1,53 @@
+// FIXME: This file should handle the auth redirection
+
+// Get the code from the URL parameters and redirect to the relevant page
+const params = new URLSearchParams(window.location.search);
+
+const code = params.get("code");
+const form = new FormData();
+
+form.append("client_id", import.meta.env.VITE_CLIENT_ID);
+form.append("redirect_uri", `${import.meta.env.VITE_URL}/complete/epita/`);
+form.append("grant_type", "authorization_code");
+form.append("code", code);
+const config = {
+ method: "POST",
+ body: form,
+};
+
+export async function getToken() {
+ try {
+ const res = await fetch(
+ `${import.meta.env.VITE_URL}/auth-api/token`,
+ config,
+ );
+ const response = await res.json();
+
+ const token = response.id_token;
+
+ localStorage.setItem("token", token);
+ localStorage.setItem("refresh_token", response.refresh_token);
+ window.location.replace(import.meta.env.VITE_URL);
+ return true;
+ } catch (error) {
+ console.log(error);
+ return false;
+ }
+}
+
+if (!code) {
+ const authQueryParams = {
+ client_id: import.meta.env.VITE_CLIENT_ID,
+ scope: "epita profile picture",
+ redirect_uri: `${import.meta.env.VITE_URL}/complete/epita/`,
+ response_type: "code",
+ };
+ const url = new URL(
+ `?client_id=${authQueryParams.client_id}&scope=${authQueryParams.scope}&redirect_uri=${authQueryParams.redirect_uri}&response_type=${authQueryParams.response_type}`,
+ `${import.meta.env.VITE_AUTH_URL}/authorize`,
+ );
+
+ window.location.replace(url);
+} else {
+ getToken();
+}
diff --git a/rushs/eplace/src/pages/debug.js b/rushs/eplace/src/pages/debug.js
new file mode 100644
index 0000000..e2da5f4
--- /dev/null
+++ b/rushs/eplace/src/pages/debug.js
@@ -0,0 +1,34 @@
+import $ from "jquery";
+import debugHtml from "../components/debug.html";
+
+function refreshLocalStorage() {
+ $("#token").text(localStorage.getItem("token") ?? "N/A");
+ $("#refresh_token").text(localStorage.getItem("refresh_token") ?? "N/A");
+}
+
+if (import.meta.env.MODE === "debug") {
+ $.get(debugHtml, function (response) {
+ $("body").html(response);
+ refreshLocalStorage();
+ }).fail(function (xhr, status, error) {
+ console.error("Error fetching debug HTML:", error);
+ });
+
+ $(document).on("click", "#errorBtn", function () {
+ // Make a call to VITE_URL/tests/error using your own api call function
+ });
+
+ $(document).on("click", "#expiredTokenBtn", function () {
+ // Make a call to VITE_URL/tests/expired using your own api call function
+ });
+
+ $(document).on("click", "#deleteTokenBtn", function () {
+ localStorage.removeItem("token");
+ refreshLocalStorage();
+ });
+
+ $(document).on("click", "#deleteRefreshTokenBtn", function () {
+ localStorage.removeItem("refresh_token");
+ refreshLocalStorage();
+ });
+}
diff --git a/rushs/eplace/src/pages/index.css b/rushs/eplace/src/pages/index.css
new file mode 100644
index 0000000..6548524
--- /dev/null
+++ b/rushs/eplace/src/pages/index.css
@@ -0,0 +1,78 @@
+@import url(http://fonts.googleapis.com/css?family=Barlow:800);
+@import url(http://fonts.googleapis.com/css?family=Montserrat:400,700);
+
+:root {
+ font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif, Barlow;
+ line-height: 1.5;
+ font-weight: 400;
+
+ color-scheme: light dark;
+ color: rgba(255, 255, 255, 0.87);
+ background-color: #242424;
+
+ font-synthesis: none;
+ text-rendering: optimizeLegibility;
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ -webkit-text-size-adjust: 100%;
+
+ -webkit-box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+}
+
+a {
+ font-weight: 500;
+ color: #646cff;
+ text-decoration: inherit;
+}
+a:hover {
+ color: #535bf2;
+}
+
+body {
+ margin: 0;
+ display: flex;
+ place-items: center;
+ justify-content: center;
+ min-width: 320px;
+ min-height: 100vh;
+}
+
+h1 {
+ font-size: 3.2em;
+ line-height: 1.1;
+ margin: 0;
+}
+
+h2 {
+ margin: 0;
+}
+
+button {
+ border-radius: 8px;
+ border: 1px solid transparent;
+ padding: 0.6em 1.2em;
+ font-size: 1em;
+ font-weight: 500;
+ font-family: inherit;
+ background-color: #1a1a1a;
+ cursor: pointer;
+ transition: border-color 0.25s;
+}
+button:hover {
+ border-color: #646cff;
+}
+button:focus,
+button:focus-visible {
+ outline: 4px auto -webkit-focus-ring-color;
+}
+input {
+ border: none;
+}
+.token-text {
+ max-width: 10ch;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+}
diff --git a/rushs/eplace/src/pages/index.html b/rushs/eplace/src/pages/index.html
new file mode 100644
index 0000000..d52232b
--- /dev/null
+++ b/rushs/eplace/src/pages/index.html
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8" />
+ <meta http-equiv="X-UA-Compatible" content="IE=edge" />
+ <title>E/PLACE</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
+ <link rel="icon" href="/favicon.ico" />
+ <link rel="stylesheet" type="text/css" media="screen" href="index.css" />
+ <link rel="stylesheet/less" type="text/css" href="styles.less" />
+ <link
+ href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css"
+ rel="stylesheet"
+ />
+ <script src="https://cdn.jsdelivr.net/npm/less"></script>
+ <script type="module" src="index.js"></script>
+ </head>
+ <body>
+ <div class="App">
+ <div class="AlertContainer" id="alert-container"></div>
+ <div class="Container" id="container">
+ <div class="RoomList Hidden" id="left-container">
+ <div class="Header">
+ <h1 class="Title">E/PLACE</h1>
+ <button class="CloseButton" id="close-left">
+ <i class="fa-solid fa-backward"></i>
+ </button>
+ </div>
+ <div class="ListContainer">
+ <div class="ListSearchBar">
+ <div class="InputContainer">
+ <input
+ aria-placeholder="Search a room..."
+ placeholder="Search a room..."
+ class="Input"
+ id="search-input"
+ />
+ <div class="Divider"></div>
+ <button class="RoomButton" id="create-room">
+ <i class="fa-solid fa-square-plus"></i>
+ </button>
+ <button class="RoomButton" id="refresh-rooms">
+ <i class="fa-solid fa-rotate"></i>
+ </button>
+ </div>
+ <div class="FilterContainer">
+ <span class="FilterText">Filters:</span>
+ <div class="FilterHeader">
+ <button class="Filter" id="filter-name">
+ <span>Name</span>
+ <i class="fa-solid fa-sort-down"></i>
+ </button>
+ <button class="Filter" id="filter-owner">
+ <span>Owned by you</span>
+ <i class="fa-solid fa-plus"></i>
+ </button>
+ <button class="Filter" id="filter-public">
+ <span>Public</span>
+ <i class="fa-solid fa-minus"></i>
+ </button>
+ <button class="Filter" id="filter-private">
+ <span>Private</span>
+ <i class="fa-solid fa-minus"></i>
+ </button>
+ </div>
+ </div>
+ </div>
+ <div class="RoomsContainer" id="rooms-container"></div>
+ </div>
+ <div class="StudentProfile">
+ <img src="" class="Avatar" id="profile-info-avatar" />
+ <div class="TextContainer">
+ <span class="Login" id="profile-info-login"></span>
+ <span class="Quote" id="profile-info-quote"></span>
+ </div>
+ <button class="ModifyProfileButton" id="profile-update">
+ <i class="fa-solid fa-pencil"></i>
+ </button>
+ </div>
+ </div>
+ <div class="RoomCanvas">
+ <div class="Header">
+ <div class="TextContainer">
+ <h2 class="Title" id="room-name">{{room_name}}</h2>
+ <span class="Description" id="room-description"
+ >{{room_description}}</span
+ >
+ </div>
+ <div class="HeaderDivider"></div>
+ <div class="ButtonContainer">
+ <button class="RoomButton" id="edit-room">
+ <i class="fa-solid fa-pencil"></i>
+ </button>
+ <button class="RoomButton" id="delete-room">
+ <i class="fa-solid fa-trash-can"></i>
+ </button>
+ <button class="ReportButton">Report Abuse</button>
+ </div>
+ </div>
+ <div class="CanvasContainer" id="canvas-container">
+ <canvas class="Canvas" id="canvas"></canvas>
+ <img class="Selector" id="selector" src="/selector.svg" />
+ <div class="Tooltip" id="tooltip">
+ <div class="Header">
+ <div class="PlacedByInfo">
+ <img
+ src="{{student_avatar}}"
+ class="Avatar"
+ id="tooltip-info-avatar"
+ />
+ <div class="Profile">
+ <span class="Login" id="tooltip-info-login"
+ >{{student_login}}</span
+ >
+ <span class="Quote" id="tooltip-info-quote"
+ >{{student_quote}}</span
+ >
+ </div>
+ </div>
+ <div class="TextContainer">
+ <span id="tooltip-date">{{date_placed}}</span>
+ <span id="tooltip-time">{{time_placed}}</span>
+ </div>
+ </div>
+ <div class="ButtonContainer">
+ <button class="ColorPicker" id="color-picker">
+ <i class="fas fa-eye-dropper"></i>
+ </button>
+ <button class="PlaceButton" id="color-place-button">
+ PLACE
+ </button>
+ </div>
+ </div>
+ </div>
+ <div class="PositionTooltip" id="position-tooltip">
+ <span>X=0</span>
+ <span>Y=0</span>
+ </div>
+ <div class="ColorWheelContainer" id="color-wheel-container">
+ <div class="ColorWheel" id="color-wheel"></div>
+ </div>
+ </div>
+ <div class="RoomChat" id="right-container">
+ <div class="ChatContainer">
+ <div class="Header">
+ <button class="CloseButton" id="close-right">
+ <i class="fa-solid fa-forward"></i>
+ </button>
+ </div>
+ <div class="ChatMessageContainer" id="chat-message-container"></div>
+ <form
+ class="InputContainer"
+ id="chat-input-form"
+ autocomplete="off"
+ >
+ <input
+ aria-placeholder="Type your message here..."
+ placeholder="Type your message here..."
+ class="Input"
+ id="message-content"
+ name="message-content"
+ autocomplete="off"
+ />
+ <div class="ChatTimeout">
+ <i class="fa-solid fa-stopwatch"></i>
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+ </body>
+</html>
diff --git a/rushs/eplace/src/pages/index.js b/rushs/eplace/src/pages/index.js
new file mode 100644
index 0000000..59cdb91
--- /dev/null
+++ b/rushs/eplace/src/pages/index.js
@@ -0,0 +1,10 @@
+// FIXME: This is the entry point of the application, write your code here
+
+import { calculateLayout } from "./utils";
+import "./debug";
+import { initSocket } from "../utils/streams";
+
+// Initialize the layout
+calculateLayout();
+
+initSocket();
diff --git a/rushs/eplace/src/pages/styles.less b/rushs/eplace/src/pages/styles.less
new file mode 100644
index 0000000..dd8ae95
--- /dev/null
+++ b/rushs/eplace/src/pages/styles.less
@@ -0,0 +1,905 @@
+@base: rgba(16, 18, 60, 0.95);
+@dark: #05061a;
+@light: #f0f0f0;
+@accent: #ff4603;
+
+.App {
+ background-color: @dark;
+ min-height: 100vh;
+ margin: 0 auto;
+ overflow: hidden;
+ padding: 0rem;
+ position: relative;
+ text-align: center;
+ width: 100vw;
+}
+
+.AlertContainer {
+ position: absolute;
+ top: 0;
+ right: 0;
+ display: flex;
+ flex-direction: column-reverse;
+ align-items: flex-end;
+ justify-content: flex-end;
+ gap: 1rem;
+ padding: 2rem;
+ z-index: 3;
+ pointer-events: none;
+
+ .Alert {
+ position: relative;
+ background-color: #fff;
+ display: flex;
+ align-items: center;
+ gap: 1rem;
+ padding: 0.5rem;
+ width: 100%;
+ pointer-events: all;
+
+ box-shadow: rgba(149, 157, 165, 0.2) 0px 8px 24px;
+
+ .Icon {
+ font-size: 28px;
+ }
+
+ .AlertBody {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: center;
+ margin: 0.5rem 2rem 0.5rem 0;
+ text-align: left;
+ font-family: Barlow;
+ color: @dark;
+
+ .AlertTitle {
+ font-size: 1rem;
+ font-weight: 800;
+ text-transform: uppercase;
+ }
+ .AlertContent {
+ font-size: 0.75rem;
+ opacity: 50%;
+ }
+ }
+
+ .AlertClose {
+ position: absolute;
+ top: 0.5rem;
+ right: 0.5rem;
+ font-size: 1em;
+ color: @dark;
+ }
+ }
+
+ .AlertSuccess {
+ border-left: 5px solid #49d761;
+
+ .Icon {
+ color: #49d761;
+ }
+ }
+ .AlertWarning {
+ border-left: 5px solid #ff9f1c;
+
+ .Icon {
+ color: #ff9f1c;
+ }
+ }
+ .AlertError {
+ border-left: 5px solid #fd2020;
+
+ .Icon {
+ color: #fd2020;
+ }
+ }
+}
+
+.Container {
+ column-gap: 2em;
+ display: grid;
+ flex-direction: row;
+ grid-auto-flow: row dense;
+ // Fully openned sidebars layout
+ // -> grid-template-columns: 1fr 2.5fr 1fr;
+ // Closed sidebars layout
+ grid-template-columns: 0fr 2.5fr 0fr;
+ height: calc(100vh - 4rem);
+ overflow: hidden;
+ padding: 2rem;
+
+ transition: all 0.5s ease-in-out;
+}
+
+.RoomList {
+ background-color: @base;
+ border-radius: 0.5rem;
+ display: grid;
+ flex-grow: 0;
+ gap: 0.5em 0em;
+ grid-template-rows: 1fr 8.25fr 0.75fr;
+ overflow: hidden;
+ padding: 1.5rem;
+ z-index: 1;
+ // Fully openned sidebar
+ // -> opacity: 1;
+ // Closed sidebar
+ opacity: 0;
+
+ transition: opacity 0.5s ease-in-out;
+
+ .Header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ overflow: hidden;
+
+ .Title {
+ color: @light;
+ font-family: Barlow;
+ font-size: 3rem;
+ font-weight: 800;
+ grid-row-start: 1;
+ overflow: hidden;
+ text-align: start;
+ text-overflow: ellipsis;
+ text-transform: uppercase;
+ white-space: nowrap;
+ }
+ }
+
+ .ListContainer {
+ display: flex;
+ flex-direction: column;
+ gap: 1em;
+ margin: 1rem 0rem;
+ overflow: auto;
+ padding: 0rem;
+ }
+
+ .ListSearchBar {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5em 0em;
+
+ .FilterContainer {
+ background-color: rgba(black, 0.5);
+ align-items: center;
+ display: flex;
+ font-family: Barlow;
+ font-weight: 800;
+ font-size: 0.75rem;
+ gap: 0em 1em;
+ overflow: hidden;
+ text-align: start;
+ white-space: nowrap;
+ padding: 0.5rem;
+ border-radius: 0.5rem;
+
+ .FilterText {
+ margin-left: 0.5rem;
+ opacity: 0.5;
+ font-size: 0.8rem;
+ }
+
+ .FilterHeader {
+ display: flex;
+ font-family: Barlow;
+ font-weight: 800;
+ overflow: scroll;
+ scrollbar-width: none;
+ text-align: start;
+ gap: 0.5em;
+
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ .Filter {
+ background-color: rgba(@light, 0.25);
+ border-radius: 0.5rem;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5em;
+ padding: 0.25rem 0.5rem;
+ font-size: 0.65rem;
+ }
+ }
+ }
+ }
+
+ .RoomsContainer {
+ display: flex;
+ flex-direction: column;
+ gap: 0.5rem;
+ grid-row-start: 2;
+ overflow: scroll;
+
+ .Room {
+ background-color: rgba(black, 0.25);
+ border-radius: 0.5rem;
+ display: flex;
+ gap: 1em;
+ align-items: center;
+ padding: 0.5rem;
+
+ &:disabled {
+ border: @accent 0.15rem solid;
+ cursor: not-allowed;
+ }
+
+ .Avatar {
+ height: 4rem;
+ width: 4rem;
+ object-fit: cover;
+ aspect-ratio: 1/1;
+ }
+
+ .TextContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ gap: 0.5em;
+ color: @light;
+ overflow: hidden;
+
+ .Name {
+ font-family: Barlow;
+ font-size: 1rem;
+ font-weight: 800;
+ text-align: start;
+ text-transform: uppercase;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+
+ .RoomPrivacy {
+ margin-right: 0.25rem;
+ }
+ }
+
+ .RoomOwner {
+ font-family: Montserrat;
+ font-size: 0.65rem;
+ font-weight: 700;
+ text-align: start;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+ }
+
+ .ListRoomDivider {
+ background-color: rgba(@light, 0.25);
+ height: 0.1rem;
+ margin: 0.5rem 0rem;
+ }
+ }
+
+ .StudentProfile {
+ align-items: center;
+ background-color: @light;
+ border-radius: 5rem;
+ column-gap: 0.5em;
+ display: flex;
+ flex-direction: row;
+ grid-row-start: 3;
+ overflow: hidden;
+ padding: 0.5rem;
+
+ .Avatar {
+ border-radius: 100%;
+ max-height: 3rem;
+ max-width: 3rem;
+ object-fit: cover;
+ aspect-ratio: 1/1;
+ }
+
+ .TextContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ margin-right: 0.5rem;
+ overflow: hidden;
+ color: black;
+ text-align: start;
+ overflow: hidden;
+
+ .Login {
+ font-family: Barlow;
+ font-size: 1rem;
+ font-weight: 800;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .Quote {
+ font-family: Montserrat;
+ font-size: 0.7rem;
+ font-weight: 700;
+ opacity: 65%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+
+ .ModifyProfileButton {
+ background-color: transparent;
+ color: @dark;
+ padding: 0.25rem;
+ margin-left: auto;
+ margin-right: 0.5rem;
+ border-radius: 100%;
+ }
+ }
+}
+
+.RoomCanvas {
+ display: grid;
+ flex-direction: column;
+ flex-grow: 0;
+ grid-template-rows: 0.25fr 9.75fr;
+ min-width: 50vw;
+ row-gap: 0em;
+ overflow: hidden;
+
+ .Header {
+ background-color: @base;
+ border-radius: 0.5rem;
+ align-items: center;
+ justify-content: center;
+ display: flex;
+ flex-direction: row;
+ gap: 1.5rem;
+ grid-row-start: 1;
+ padding: 0.5rem;
+ z-index: 1;
+
+ .TextContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ text-align: end;
+
+ .Title {
+ color: @light;
+ font-family: Barlow;
+ font-size: 2rem;
+ font-weight: 800;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ text-transform: uppercase;
+ white-space: nowrap;
+ }
+
+ .Description {
+ // Start with the description hidden.
+ // It needs to be visible if the room has a description.
+ display: none;
+ position: relative;
+ top: -0.25rem;
+
+ color: @light;
+ font-family: Montserrat;
+ font-size: 0.5rem;
+ font-weight: 700;
+ opacity: 0.5;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+
+ .HeaderDivider {
+ background-color: rgba(@light, 0.25);
+ height: 100%;
+ margin: 0.5rem 0rem;
+ width: 0.1rem;
+ }
+
+ .ButtonContainer {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+
+ .ReportButton {
+ background-color: @accent;
+ border-radius: 0.5rem;
+ border: 0px solid transparent;
+ color: @light;
+ display: block;
+ font-family: Montserrat;
+ font-size: 0.75rem;
+ font-weight: 700;
+ height: auto;
+ padding: 0.35rem 0.75rem;
+ text-transform: uppercase;
+ }
+ }
+ }
+
+ .CanvasContainer {
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ grid-row-start: 2;
+ overflow: hidden;
+ z-index: 0;
+
+ .Canvas {
+ transform: translate(0, 0) scale(2.5);
+ }
+
+ .Selector {
+ position: absolute;
+ inset: 0;
+ width: 1px;
+ height: 1px;
+ margin: auto;
+ pointer-events: none;
+ }
+
+ .Tooltip {
+ // The tooltip is a flexbox
+ // -> display: flex;
+ // Start with the tooltip hidden
+ display: none;
+
+ position: absolute;
+ top: 50%;
+ left: 50%;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ transform: translate(-50%, -150%);
+ background-color: @base;
+ border-radius: 0.5rem;
+ border: @accent 0.15rem solid;
+ padding: 0.5rem;
+
+ .Header {
+ display: flex;
+ gap: 0.75rem;
+
+ .PlacedByInfo {
+ position: relative;
+ display: inline-block;
+ height: 3rem;
+ width: 3rem;
+
+ .Avatar {
+ border-radius: 100%;
+ width: 100%;
+ height: 100%;
+ object-fit: cover;
+ aspect-ratio: 1/1;
+ }
+
+ .Profile {
+ visibility: hidden;
+ background-color: @dark;
+ color: @light;
+ text-align: center;
+ padding: 0.5rem;
+ border-radius: 0.5rem;
+
+ bottom: 100%;
+ left: 50%;
+ transform: translate(-50%, 0);
+ position: absolute;
+ z-index: 1;
+
+ &::after {
+ content: " ";
+ position: absolute;
+ top: 100%;
+ left: 50%;
+ margin-left: -5px;
+ border-width: 5px;
+ border-style: solid;
+ border-color: @dark transparent transparent transparent;
+ }
+
+ .Login {
+ font-family: Barlow;
+ font-size: 0.8rem;
+ font-weight: 800;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .Quote {
+ font-family: Montserrat;
+ font-size: 0.7rem;
+ font-weight: 700;
+ opacity: 0.5;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+ }
+
+ &:hover .Profile {
+ visibility: visible;
+ }
+ }
+
+ .TextContainer {
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ font-family: Montserrat;
+ font-size: 0.75rem;
+ font-weight: 700;
+ text-align: left;
+ }
+ }
+
+ .ButtonContainer {
+ display: grid;
+ grid-template-columns: 1fr 1fr;
+ gap: 0.5rem;
+ width: 100%;
+ font-family: Montserrat;
+ font-size: 0.75rem;
+ font-weight: 700;
+
+ .ColorPicker {
+ padding: 0.2rem;
+ outline: none;
+ color: white;
+ border: white 0.1rem solid;
+ }
+
+ .PlaceButton {
+ padding: 0.2rem;
+ border: @accent 0.1rem solid;
+ border-radius: 0.5rem;
+ outline: none;
+ font-family: Barlow;
+ font-size: 0.75rem;
+ font-weight: 800;
+
+ &:active {
+ background-color: @accent;
+ color: @light;
+ }
+
+ &:disabled {
+ background-color: @dark;
+ color: @accent;
+ }
+ }
+ }
+ }
+ }
+
+ .PositionTooltip {
+ background-color: rgba(@dark, 0.25);
+ padding: 0.5rem;
+ margin: 0.5rem auto auto 0;
+ border-radius: 0.5rem;
+ font-family: Montserrat;
+ font-weight: 700;
+ z-index: 2;
+ }
+
+ .ColorWheelContainer {
+ // The color wheel is a flexbox
+ // -> display: block;
+ // Start with the color wheel hidden
+ display: none;
+
+ background-color: @base;
+ margin: auto 0 0 auto;
+ padding: 0.5rem;
+ max-height: calc(100% - 12rem);
+ overflow: scroll;
+ border-radius: 1rem;
+ border: @accent 0.15rem solid;
+ z-index: 1;
+
+ min-block-size: -webkit-fill-available;
+
+ -ms-overflow-style: none;
+ scrollbar-width: none;
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ transition: all 0.5s ease-in-out;
+
+ .ColorWheel {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 0.5rem;
+ width: 100%;
+ margin: 0.25rem 0;
+ }
+ }
+}
+
+.RoomChat {
+ display: grid;
+ flex-grow: 0;
+ grid-auto-flow: row;
+ grid-template-rows: 1fr 8fr;
+ overflow: hidden;
+ padding: 0rem;
+ z-index: 1;
+ // Fully openned sidebar
+ // -> opacity: 1;
+ // Closed sidebar
+ opacity: 0;
+
+ transition: opacity 0.5s ease-in-out;
+
+ .ChatContainer {
+ background-color: @base;
+ border-radius: 0.5rem;
+ display: grid;
+ gap: 1em;
+ grid-row-start: 2;
+ grid-template-rows: 0.5fr 6.75fr 0.75fr;
+ grid-template-columns: 1fr;
+ overflow: hidden;
+ padding: 0.8rem;
+
+ .Header {
+ display: flex;
+ flex-direction: row-reverse;
+ align-items: center;
+ }
+
+ .ChatMessageContainer {
+ display: flex;
+ flex-direction: column-reverse;
+ overflow: auto;
+ padding: 0rem;
+ row-gap: 1em;
+
+ .ChatMessage {
+ color: @light;
+ background-color: rgba(black, 0.25);
+ border-radius: 0.5rem;
+ display: flex;
+ flex-direction: column;
+ padding: 0.5rem;
+
+ .MessageHeader {
+ column-gap: 0.5em;
+ display: flex;
+ padding: 0.5rem;
+ overflow: hidden;
+
+ .Avatar {
+ border-radius: 100%;
+ max-height: 2rem;
+ max-width: 2rem;
+ object-fit: cover;
+ aspect-ratio: 1/1;
+ }
+
+ .Login {
+ font-family: Barlow;
+ font-weight: 800;
+ text-align: left;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .Time {
+ font-family: Barlow;
+ margin-left: auto;
+ text-align: end;
+ }
+ }
+ }
+
+ .ChatUserEvent {
+ background-color: rgba(black, 0.25);
+ border-radius: 0.5rem;
+ display: flex;
+ flex-direction: column;
+ padding: 0.25rem;
+ opacity: 0.5;
+ }
+
+ .ChatMessageMentionned {
+ background-color: rgba(@accent, 0.25);
+ }
+
+ .MessageContent {
+ font-family: Montserrat;
+ font-size: 0.75rem;
+ font-weight: 400;
+ overflow-wrap: break-word;
+ padding: 0.5rem;
+ text-align: start;
+ }
+ }
+ }
+}
+
+.Hidden {
+ width: 0;
+ height: 0;
+ margin: 0;
+ padding: 0;
+}
+
+.CloseButton {
+ background-color: @accent;
+ border-radius: 0.5rem;
+ border: 0px solid transparent;
+ color: @light;
+ display: block;
+ font-family: Montserrat;
+ font-size: 0.75rem;
+ font-weight: 700;
+ max-height: 2rem;
+ padding: 0.5rem 0.75rem;
+ text-transform: uppercase;
+}
+
+.InputContainer {
+ background-color: rgba(black, 0.5);
+ border-radius: 0.5rem;
+ display: flex;
+ align-items: center;
+ gap: 0.5rem;
+ padding: 0.5rem;
+ overflow: hidden;
+
+ .Divider {
+ background-color: @light;
+ height: 2rem;
+ width: 0.1rem;
+ opacity: 0.75;
+ }
+}
+
+.RoomButton {
+ background-color: rgba(@light, 0.25);
+ padding: 0.35rem 0.5rem;
+ font-size: 0.75rem;
+}
+
+.ChatTimeout {
+ --fill-percent: 0;
+
+ border-radius: 50%;
+ padding: 0.25rem 0.5rem;
+
+ background: radial-gradient(
+ closest-side,
+ @dark 80%,
+ transparent 0 99.9%,
+ @dark 0
+ ),
+ conic-gradient(@accent calc(var(--fill-percent) * 1%), @dark 0);
+}
+
+.Input {
+ background-color: rgba(black, 0);
+ color: @light;
+ flex-grow: 1;
+ font-family: Montserrat;
+ font-size: 0.75rem;
+ font-weight: 700;
+ opacity: 1;
+ padding: 0.5rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.FormOverlay {
+ position: absolute;
+ inset: 0;
+ background-color: rgba(@dark, 0.95);
+ z-index: 2;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ .StylisedForm {
+ background-color: @base;
+ padding: 2rem 1rem;
+ border: 0.25rem solid @accent;
+ border-radius: 0.5rem;
+ display: flex;
+ flex-direction: column;
+ gap: 1rem;
+ align-items: center;
+ justify-content: center;
+ font-family: Barlow;
+ min-width: 65vw;
+ max-width: 90vw;
+ color: @light;
+
+ .FormHeader {
+ display: flex;
+ margin-bottom: 2rem;
+
+ .FormTitle {
+ font-size: 1.5rem;
+ font-weight: 800;
+ text-align: center;
+ }
+ }
+
+ .FormItem {
+ display: grid;
+ grid-template-columns: 1fr 6fr;
+ width: 100%;
+ gap: 1rem;
+ align-items: center;
+ justify-content: space-between;
+
+ .FormLabel {
+ font-size: 1rem;
+ font-weight: 800;
+ text-align: end;
+ }
+
+ .FormInput {
+ color: @light;
+ grid-column: 2;
+ padding: 0.5rem;
+ border-radius: 0.25rem;
+ font-family: Montserrat;
+ background-color: @dark;
+ }
+
+ .FormInput[type="checkbox"] {
+ width: auto;
+ margin-right: auto;
+ }
+ }
+
+ .FormButtons {
+ display: flex;
+ margin-top: 1rem;
+ gap: 0.5rem;
+ justify-content: space-around;
+
+ box-sizing: border-box;
+ -moz-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+
+ [type="submit"] {
+ background-color: #ff4603;
+ }
+
+ button {
+ background-color: @dark;
+ color: @light;
+ border: 0.1rem solid #ff4603;
+
+ &:hover {
+ border: 0.1rem solid @light;
+ }
+ }
+ }
+ }
+}
diff --git a/rushs/eplace/src/pages/utils.js b/rushs/eplace/src/pages/utils.js
new file mode 100644
index 0000000..f8b1008
--- /dev/null
+++ b/rushs/eplace/src/pages/utils.js
@@ -0,0 +1,95 @@
+/**
+ * Global variables
+ */
+let [leftTimer, rightTimer] = [null, null];
+let [leftSize, rightSize] = [
+ localStorage.getItem("leftSize") ?? 0,
+ localStorage.getItem("rightSize") ?? 0,
+];
+
+const parentContainer = document.getElementById("container");
+
+const leftContainer = document.getElementById("left-container");
+const closeLeftButton = document.getElementById("close-left");
+const rightContainer = document.getElementById("right-container");
+const closeRightButton = document.getElementById("close-right");
+
+leftContainer.classList.toggle("Hidden", !leftSize);
+
+/**
+ * Calculate the layout of the home page
+ */
+export const calculateLayout = () => {
+ const parentContainerSize = `${4.5 - leftSize - rightSize}fr`;
+
+ // left and right are reversed because of the grid layout
+ parentContainer.style.gridTemplateColumns = `${leftSize}fr ${parentContainerSize} ${rightSize}fr`;
+ leftContainer.style.opacity = leftSize;
+ rightContainer.style.opacity = rightSize;
+};
+
+closeLeftButton.addEventListener("click", () => {
+ leftSize = 1 - leftSize;
+ localStorage.setItem("leftSize", leftSize);
+
+ calculateLayout();
+ setTimeout(
+ () => {
+ leftContainer.classList.toggle("Hidden", true);
+ },
+ leftSize ? 0 : 300,
+ );
+});
+
+closeRightButton.addEventListener("click", () => {
+ rightSize = 1 - rightSize;
+ localStorage.setItem("rightSize", rightSize);
+
+ calculateLayout();
+ setTimeout(
+ () => {
+ rightContainer.classList.toggle("Hidden", true);
+ },
+ rightSize ? 0 : 300,
+ );
+});
+
+// If the mouse holds on the left side of the screen, open the left container
+document.addEventListener("mousemove", (e) => {
+ if (e.clientX < 10) {
+ if (!leftTimer) {
+ leftTimer = setTimeout(() => {
+ leftSize = 1;
+ localStorage.setItem("leftSize", leftSize);
+
+ calculateLayout();
+ setTimeout(() => {
+ leftContainer.classList.toggle("Hidden", false);
+ }, 300);
+ }, 200);
+ }
+ } else {
+ clearTimeout(leftTimer);
+ leftTimer = null;
+ }
+});
+
+// If the mouse holds on the right side of the screen, open the right container
+document.addEventListener("mousemove", (e) => {
+ if (e.clientX > window.innerWidth - 10) {
+ if (!rightTimer) {
+ rightTimer = setTimeout(() => {
+ rightSize = 1;
+ localStorage.setItem("rightSize", rightSize);
+
+ calculateLayout();
+ setTimeout(() => {
+ rightContainer.classList.toggle("Hidden", false);
+ }, 300);
+ }, 200);
+ }
+ } else {
+ clearTimeout(rightTimer);
+ rightTimer = null;
+ }
+});