8 Commits

Author SHA1 Message Date
Kali Gallon b40dad8f57 ^^._, work in progress, small changes 2026-04-02 15:00:36 +02:00
Kali Gallon 23cca7a249 Merge branch 'Rendu' of github.com:OlaketalAmigo/Transcendence into Rendu 2026-04-02 14:56:50 +02:00
Kali Gallon e1e529b3ca ^^._, work in progress, small changes 2026-04-02 14:55:07 +02:00
Thomas Fauve-Piot e96c16819d Merge branch 'Rendu' of github.com:OlaketalAmigo/Transcendence into Rendu 2026-04-02 12:59:19 +02:00
Thomas Fauve-Piot aefb858247 Overflow game.css 2026-04-02 12:55:56 +02:00
Yannis Antoine a9f81b4d65 5 point par lettre bonne 2026-04-02 12:53:46 +02:00
Georges-Leonard Prunet 4b3909c1a3 auto log out + clean leave 2026-04-02 12:52:06 +02:00
Georges-Leonard Prunet 4fa835b62a clean 2026-04-02 11:58:46 +02:00
12 changed files with 576 additions and 138 deletions
+3
View File
@@ -0,0 +1,3 @@
INTRA_CLIENT_ID=u-s4t2ud-c226cd35cd1ac08a4c6668deee1c64d7d67a13a766aee672acafd4a1522d483c
INTRA_CLIENT_SECRET=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
+2
View File
@@ -12,6 +12,7 @@ import playerStatsRouter from './routes/player_stats.js';
import {waitForDb, createTables, runMigrations, ensureOauthClient} from './db.js';
import setupSocketIO from './services/socket.js';
import avatarService from './services/avatar.js';
import intraRouter from './routes/intra.js';
const app = express();
const httpsOptions = {
@@ -53,6 +54,7 @@ async function startServer()
app.use('/api/avatar', avatarRouter);
app.use('/api/friends', friendsRouter);
app.use('/api/stats', playerStatsRouter);
app.use('/api/intra', intraRouter);
app.get('/api', (req, res) => res.send('Backend running'));
server.listen(3001, () =>
@@ -0,0 +1,53 @@
// routes/intra.js
import express from 'express';
const router = express.Router();
let token;
async function set_token() {
try {
const response = await fetch("https://api.intra.42.fr/oauth/token", {
method: "POST",
body: new URLSearchParams({
grant_type: "client_credentials",
client_id: process.env.INTRA_CLIENT_ID,
client_secret: process.env.INTRA_CLIENT_SECRET
}),
headers: {
"Content-Type": "application/x-www-form-urlencoded"
}
});
token = await response.json();
setTimeout(set_token, (token.expires_in - 60) * 1000);
} catch (e) {
console.error("Token error:", e);
}
}
set_token();
router.get('/profile/:login', async (req, res) => {
try {
const response = await fetch(
`https://api.intra.42.fr/v2/users/${req.params.login}`,
{
headers: {
Authorization: `Bearer ${token.access_token}`
}
}
);
if (!response.ok) {
return res.status(response.status).json({ error: 'User not found' });
}
res.json(await response.json());
} catch (e) {
res.status(500).json({ error: 'Failed to fetch profile' });
}
});
export default router;
+112 -37
View File
@@ -30,6 +30,63 @@ async function broadcastRoomsList(io) {
}
}
function startRoomTimer(io, roomId, seconds)
{
const gameState = gameRooms.get(roomId);
if (!gameState) return;
if (gameState.timerInterval)
clearInterval(gameState.timerInterval);
gameState.timerSeconds = seconds;
gameState.timerInterval = setInterval(() => {
gameState.timerSeconds--;
if (gameState.timerSeconds < 0)
gameState.timerSeconds = 0;
if (gameState.timerSeconds <= 0)
{
io.to(roomId).emit('game-timer-sync', {
remaining: 0
});
clearInterval(gameState.timerInterval);
gameState.timerInterval = null;
io.to(roomId).emit('game-timer-ended', { message: 'Temps écoulé !' });
gameState.currentPlayerIndex = (gameState.currentPlayerIndex + 1) % gameState.players.length;
const nextDrawer = gameState.players[gameState.currentPlayerIndex];
gameState.drawer = nextDrawer;
gameState.currentWord = '';
gameState.revealedLetters = [];
gameState.revealedWord = [];
gameState.guessedLetters = [];
gameState.wrongGuesses = 0;
io.to(roomId).emit('game-new-round', {
drawer: nextDrawer
});
}
else
{
io.to(roomId).emit('game-timer-sync', {
remaining: gameState.timerSeconds
});
}
}, 1000);
}
function stopRoomTimer(roomId)
{
const gameState = gameRooms.get(roomId);
if (!gameState || !gameState.timerInterval) return;
clearInterval(gameState.timerInterval);
gameState.timerInterval = null;
}
// Check if a playing game has only 1 player left and auto-stop it
async function checkAndStopSinglePlayerGame(io, roomId, dbRoomId) {
if (!dbRoomId) return;
@@ -43,6 +100,7 @@ async function checkAndStopSinglePlayerGame(io, roomId, dbRoomId) {
const players = await gameRoomService.getRoomPlayers(dbRoomId);
if (players.length <= 1) {
console.log(`Room ${dbRoomId} has only ${players.length} player(s) left, ending game`);
stopRoomTimer(roomId);
// Update room status to 'ended'
await gameRoomService.updateRoomStatus(dbRoomId, 'waiting');
@@ -82,6 +140,47 @@ async function saveRoundPoints(currentScores, roundStartScores) {
}
}
function handlePlayerDeparture(io, roomId, username) {
const gameState = gameRooms.get(roomId);
if (!gameState || !gameState.isPlaying) return;
if (!Array.isArray(gameState.players)) return;
const leavingIndex = gameState.players.indexOf(username);
if (leavingIndex === -1) return;
const wasDrawer = gameState.drawer === username;
gameState.players = gameState.players.filter(p => p !== username);
if (gameState.scores) {
delete gameState.scores[username];
}
if (gameState.currentPlayerIndex >= leavingIndex) {
gameState.currentPlayerIndex = Math.max(0, gameState.currentPlayerIndex - 1);
}
if (gameState.currentPlayerIndex >= gameState.players.length) {
gameState.currentPlayerIndex = 0;
}
if (wasDrawer && gameState.players.length > 0) {
stopRoomTimer(roomId);
const newDrawer = gameState.players[gameState.currentPlayerIndex];
gameState.drawer = newDrawer;
gameState.currentWord = '';
gameState.revealedLetters = [];
gameState.revealedWord = [];
gameState.guessedLetters = [];
gameState.wrongGuesses = 0;
io.to(roomId).emit('game-drawer-changed', {
newDrawer: newDrawer,
reason: 'drawer_left',
message: `${username} (dessinateur) a quitte, ${newDrawer} devient le nouveau dessinateur`
});
startRoomTimer(io, roomId, 60);
}
}
function setupSocketIO(io)
{
ioInstance = io;
@@ -192,7 +291,9 @@ function setupSocketIO(io)
revealedLetters: gameState.revealedLetters,
revealedWord: gameState.revealedWord || [],
guessedLetters: gameState.guessedLetters,
players: gameState.players
players: gameState.players,
scores: gameState.scores || {},
timer: gameState.timerSeconds || 0
});
}
});
@@ -216,6 +317,7 @@ function setupSocketIO(io)
username: socket.user.username,
userId: socket.user.userId
});
handlePlayerDeparture(io, roomId, socket.user.username);
socket.leave(roomId);
console.log(`${socket.user.username} left ${roomId}`);
@@ -277,7 +379,8 @@ function setupSocketIO(io)
revealedWord: gameState.revealedWord || [],
guessedLetters: gameState.guessedLetters,
players: gameState.players,
scores: gameState.scores || {}
scores: gameState.scores || {},
timer: gameState.timerSeconds || 0
});
}
} catch (err) {
@@ -399,6 +502,7 @@ function setupSocketIO(io)
const gameState = gameRooms.get(roomId);
if (!gameState) return;
startRoomTimer(io, roomId, 60);
gameState.currentWord = data.word.toLowerCase();
gameState.revealedLetters = new Array(data.word.length).fill(false);
gameState.revealedWord = new Array(data.word.length).fill('_');
@@ -512,7 +616,7 @@ function setupSocketIO(io)
// Points: 10 per letter found, -5 for wrong guess
if (success) {
points = lettersFound * 10;
points = lettersFound * 5;
gameState.scores[username] += points;
} else {
points = -5;
@@ -561,6 +665,8 @@ function setupSocketIO(io)
// Update round start scores for next round
gameState.roundStartScores = { ...gameState.scores };
stopRoomTimer(roomId);
io.to(roomId).emit('game-word-found', {
word: gameState.currentWord,
winner: username,
@@ -609,40 +715,7 @@ function setupSocketIO(io)
message: `${username} a quitté la partie`
});
const gameState = gameRooms.get(roomId);
if (gameState)
{
const wasDrawer = gameState.drawer === username;
gameState.players = gameState.players.filter(p => p !== username);
delete gameState.scores[username];
io.to(roomId).emit('scores-updated', gameState.scores);
// If the drawer left and there are still enough players, choose a new drawer
if (wasDrawer && gameState.players.length >= 1)
{
// Pick the next player as the new drawer
gameState.currentPlayerIndex = gameState.currentPlayerIndex % gameState.players.length;
const newDrawer = gameState.players[gameState.currentPlayerIndex];
gameState.drawer = newDrawer;
// Reset the word state for the new round
gameState.currentWord = '';
gameState.revealedLetters = [];
gameState.revealedWord = [];
gameState.guessedLetters = [];
gameState.wrongGuesses = 0;
console.log(`Drawer ${username} left, new drawer is ${newDrawer}`);
io.to(roomId).emit('game-drawer-changed', {
newDrawer: newDrawer,
reason: 'drawer_left',
message: `${username} (dessinateur) a quitté, ${newDrawer} devient le nouveau dessinateur`
});
}
}
handlePlayerDeparture(io, roomId, username);
await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
@@ -661,6 +734,7 @@ function setupSocketIO(io)
socket.on('game-end', async () => {
const roomId = socket.gameRoomId;
if (!roomId) return;
stopRoomTimer(roomId);
// Update room status to 'waiting' in database
const dbRoomId = socket.gameRoomDbId;
@@ -889,6 +963,7 @@ function setupSocketIO(io)
username: socket.user.username,
userId: socket.user.userId
});
handlePlayerDeparture(io, roomId, socket.user.username);
// Get updated player list and broadcast
if (dbRoomId) {
+48
View File
@@ -3,6 +3,8 @@
* Initializes windows and handles menu interactions
*/
import { windowRegistry } from './core/windows.js';
import { API, STORAGE_KEYS } from './core/config.js';
import { eventBus, Events } from './core/events.js';
import { LoginWindow } from './windows/login.js';
import { LogoutWindow } from './windows/logout.js';
import { GlobalChat } from './windows/global_chat.js';
@@ -17,6 +19,7 @@ import { StatsWindow } from './windows/stats.js';
*/
class App {
constructor() {
this.invalidateStaleToken();
this.initWindows();
this.initMenu();
this.initPage();
@@ -24,6 +27,51 @@ class App {
this.colorizeUI();
}
async invalidateStaleToken() {
const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
if (!token) return;
if (this.isJwtExpired(token)) {
localStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
eventBus.emit(Events.USER_LOGGED_OUT);
return;
}
try {
const response = await fetch(API.STATS.ME, {
headers: { 'Authorization': `Bearer ${token}` }
});
if (response.status === 401) {
localStorage.removeItem(STORAGE_KEYS.AUTH_TOKEN);
eventBus.emit(Events.USER_LOGGED_OUT);
setTimeout(() => window.location.reload(), 500);
}
} catch (error) {
console.warn('Token validation skipped:', error);
}
}
isJwtExpired(token) {
try {
const payload = this.decodeJwtPayload(token);
if (!payload || !payload.exp) return false;
const now = Math.floor(Date.now() / 1000);
return payload.exp <= now;
} catch (error) {
return false;
}
}
decodeJwtPayload(token) {
const parts = token.split('.');
if (parts.length < 2) return null;
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
return JSON.parse(atob(padded));
}
/**
* Initializes all windows
*/
@@ -82,6 +82,7 @@ html {
body {
margin: 0;
overflow: hidden;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
color: var(--color-text);
line-height: 1.5;
@@ -34,6 +34,7 @@ export class GameRoomWindow extends Window {
});
this.updateTabsAccess();
this.loadCurrentTab();
// Verifier si l'utilisateur est deja dans un salon au chargement
const token = localStorage.getItem(STORAGE_KEYS.AUTH_TOKEN);
@@ -119,7 +120,8 @@ export class GameRoomWindow extends Window {
this.gameInfo = this.createElement('div', 'gameroom__game-info');
this.currentDrawerInfo = this.createElement('div', 'gameroom__drawer-info', { text: '' });
this.scoresDisplay = this.createElement('div', 'gameroom__scores-display');
this.gameInfo.append(this.currentDrawerInfo, this.scoresDisplay);
this.timerDisplay = this.createElement('div', 'gameroom__timer-display');
this.gameInfo.append(this.currentDrawerInfo, this.scoresDisplay, this.timerDisplay);
// Affichage du mot caché
this.wordDisplay = this.createElement('div', 'gameroom__word-display');
@@ -196,7 +198,8 @@ export class GameRoomWindow extends Window {
guessedLetters: [],
scores: {},
counter: 0,
counterRound: 0
counterRound: 0,
timer: 0
};
this.initDrawing();
@@ -521,6 +524,8 @@ export class GameRoomWindow extends Window {
this.gameState.revealedWord = data.revealedWord || new Array(data.wordLength).fill('_');
this.gameState.players = data.players;
this.gameState.scores = data.scores || {};
this.gameState.timer = data.timer || 0;
this.updateTimerUI();
this.showGameUI();
this.updateWordDisplay();
@@ -610,6 +615,15 @@ export class GameRoomWindow extends Window {
// Setup UI for new round with new drawer
this.setupRound();
});
this.socket.on('game-timer-sync', (data) => {
this.gameState.timer = data.remaining;
this.updateTimerUI();
});
this.socket.on('game-timer-ended', (data) => {
this.showMessage(data.message || 'Temps écoulé !', 'info');
})
}
disconnectGameSocket() {
@@ -1244,6 +1258,12 @@ export class GameRoomWindow extends Window {
}, 5000);
}
updateTimerUI()
{
if (this.timerDisplay)
this.timerDisplay.textContent = `Temps restant : ${this.gameState.timer}s`;
}
// ============================================
// LOGIQUE DU JEU
// ============================================
@@ -1298,6 +1318,9 @@ export class GameRoomWindow extends Window {
this.isSpectating = false;
this.gameState.scores = {};
this.gameState.counter = 0;
this.gameState.counterRound = 0;
this.gameState.timer = 0;
this.gameState.players = [];
this.gameState.currentPlayerIndex = 0;
this.gameState.guessedLetters = [];
@@ -0,0 +1,53 @@
import { colorizeText, updateElement } from "./tools.js";
/* /////////////////////////////////////////// */
export class Popup {
constructor(msg, parent = document.body) {
this.msg = msg;
this.parent = parent;
this.obj = updateElement({
parent: parent,
classList: ['popup'],
additionalStyles: {
opacity: '0'
}
});
this.run();
}
async create() {
this.parent.appendChild(this.obj);
this.obj.style.transition = "opacity 0.5s ease";
requestAnimationFrame(() => {
this.obj.style.opacity = "1";
});
await new Promise(r => setTimeout(r, 500));
}
async write(speed = 30) {
for (let i = 0; i < this.msg.length; i++) {
this.obj.textContent += this.msg[i];
// colorizeText();
await new Promise(r => setTimeout(r, speed));
}
}
async remove() {
await new Promise(r => setTimeout(r, 2000));
this.obj.style.transition = "opacity 0.3s ease";
this.obj.style.opacity = "0";
await new Promise(r => setTimeout(r, 300));
if (this.obj.parentNode) {
this.obj.parentNode.removeChild(this.obj);
}
}
async run() {
await this.create();
await this.write();
await this.remove();
}
}
@@ -0,0 +1,61 @@
/* /////////////////////////////////////////// */
// render in color the text of all .multicolor
export function colorizeText() {
const elements = document.querySelectorAll(".multicolor");
const colorizeText = (el) => {
const text = el.textContent;
el.innerHTML = "";
const baseHue = Math.random() * 360;
// 🎲 random step = makes rainbow "scrambled"
const step = (Math.random() * 60) + 10; // 10 → 70
// 🎲 random direction (left or right rainbow)
const direction = Math.random() < 0.5 ? 1 : -1;
[...text].forEach((char, i) => {
const span = document.createElement("span");
span.textContent = char;
const hue = baseHue + (i * step * direction);
span.style.color = `hsl(${hue}, 90%, 60%)`;
span.style.textShadow = `1px 1px 0 rgba(0,0,0,0.3)`;
el.appendChild(span);
});
};
elements.forEach(colorizeText);
}
export function updateElement({
el, // existing element or null to create new
parent = document.body,
id = null,
classList = [], // object like { css - classes to add }
textContent = "",
additionalStyles = {} // object like { color: 'red', display: 'flex' }
} = {}) {
// If no element passed, create a div by default
if (!el) {
el = document.createElement('div');
parent.appendChild(el);
}
// Set ID if provided
if (id) el.id = id;
// Manage classes
classList.forEach(cls => el.classList.add(cls));
// Set text content
if (textContent !== undefined) el.textContent = textContent;
Object.assign(el.style, additionalStyles);
return el;
}
@@ -1,79 +1,106 @@
:root {
--color-primary: #ffc75e;
--color-primary-hover: #ffc75e;
--color-success: #00c71b;
--color-success-dark: #ffc75e;
--color-error: #ff4d4d;
--color-warning: #ffc75e;
--color-github: #ffc75e;
--color-bg: #ffe5b5;
--color-surface: #ffcc00;
--color-surface-light: #feffa6;
--color-text: #000000;
--color-text-muted: #353535;
--font-size-base: 10px;
--font-size-sm: 1.2rem;
--font-size-md: 1.4rem;
--font-size-lg: 1.6rem;
--font-size-xl: 3rem;
--spacing-xs: 4px;
--spacing-sm: 8px;
--spacing-md: 12px;
--spacing-lg: 16px;
--spacing-xl: 24px;
--radius-sm: 4px;
--radius-md: 6px;
--radius-lg: 12px;
--radius-full: 50%;
--shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 8px rgba(0, 0, 0, 0.5);
--shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.5);
--transition-fast: 150ms ease;
--transition-normal: 250ms ease;
--z-menu: 2;
--z-window: 100;
--z-modal: 200;
}
.game {
position: fixed;
top: var(--spacing-lg);
right: 50px;
body {
margin: 0;
height: 100vh;
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
z-index: var(--z-menu);
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #1f001f, #1f1f00);
overflow: hidden;
}
.game__item {
background: var(--color-surface);
color: var(--color-text);
border: 1px solid var(--color-surface-light);
border-radius: var(--radius-lg);
border-color: #fda725;
padding: var(--spacing-sm) var(--spacing-md);
font-size: var(--font-size-md);
cursor: pointer;
transition: all var(--transition-fast);
text-align: center;
h1 {
font-size: 4rem;
color: white;
animation: float 2s ease-in-out infinite alternate;
border: 5px double #654050;
}
.game__item:hover {
@keyframes float {
from { transform: translateY(0); }
to { transform: translateY(-20px); }
}
/* /////////////////////////////////////////// */
.box:hover {
background: var(--color-surface-light);
font-size: var(--font-size-lg);
animation: bobble 0.4s ease-in-out infinite alternate;
}
.game__item--active {
background: var(--color-primary);
border-color: var(--color-primary);
@keyframes bobble {
from {
transform: translateX(-5px);
}
to {
transform: translateX(5px);
}
}
.box {
background: #142d4a;
height: 100px;
margin: 15px;
aspect-ratio: 1/1;
border-radius: 25px;
position: fixed;
top: 0;
right: 0;
display: flex;
justify-content: center;
align-items: center;
font-family: "Roboto";
font-size: 15px;
z-index: 10;
}
@property --deg {
syntax: '<angle>';
inherits: true;
initial-value: 0deg;
}
.box::before,
.box::after {
content: "";
position: absolute;
height: 100%;
width: 100%;
background: conic-gradient(
from var(--deg) at center,
#00c3ff,
#4d0199,
#6300c6,
#009dcd
);
border-radius: inherit;
z-index: -2;
padding: 2px;
animation: autoRotate 2s linear infinite;
}
.box::after {
filter: blur(10px);
}
@keyframes autoRotate {
to{ --deg: 360deg; }
}
/* /////////////////////////////////////////// */
.popup {
max-width: 300px;
background-color: #41030c;
margin: 30px;
margin-right: -50px;
padding: 1em;
font-weight: 700;
color: #ffffff;
font-size: 20px;
text-align: center;
border: 10px solid var(--clr-accent);
border-radius: 50px;
position: fixed;
right: 50%;
top: 50%;
z-index: 999;
}
@@ -5,45 +5,24 @@
<title>Wiskas</title>
<link rel="stylesheet" href="wiskas.css" />
<style>
body {
margin: 0;
height: 100vh;
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #6a11cb, #2575fc);
font-family: Arial, sans-serif;
overflow: hidden;
}
h1 {
font-size: 4rem;
color: white;
animation: float 2s ease-in-out infinite alternate;
}
@keyframes float {
from { transform: translateY(0); }
to { transform: translateY(-20px); }
}
</style>
</head>
<body>
<h1 id="helloText">METS TON CHAT ICI</h1>
<h1 id="helloText"></h1>
<nav class="game" aria-label="Game">
<button class="game__item" data-action="Home page" aria-label="Home Page"
<button class="box multicolor" data-action="Home page" aria-label="Home Page"
onclick="window.location.href='../index.html'">Home Page</button>
</nav>
</nav>
<script>
const colors = ["#ff4b5c", "#56cfe1", "#80ed99", "#ffd166"];
const text = document.getElementById("helloText");
<nav class="login" aria-label="Game">
<button class="box multicolor" aria-label="Home Page" id="login-button">Login</button>
</nav>
setInterval(() => {
const randomColor = colors[Math.floor(Math.random() * colors.length)];
text.style.color = randomColor;
}, 500);
</script>
<div id="login"></div>
<script type="module" src="./wiskas.js"></script>
</body>
</html>
@@ -0,0 +1,113 @@
import { Popup } from "./popup.js";
import { colorizeText, updateElement } from "./tools.js";
/* /////////////////////////////////////////// */
/* /////////////////////////////////////////// */
// 2️⃣ Add click handler
async function tryLogin() {
const login = prompt("Enter your 42 login:"); // Ask for a login
if (!login) return;
try {
// Call your backend route
const res = await fetch(`/api/intra/profile/${login}`);
if (!res.ok) {
new Popup('Please, who do you think we are?\nWe already know all about you.\nNow enter your correct login and nobody gets hurt');
const errorData = await res.json();
return null;
}
const data = await res.json();
console.log("Profile data:", data);
return data;
} catch (err) {
console.error("Fetch failed:", err);
return null;
}
}
/* /////////////////////////////////////////// */
export class Wiskas {
constructor(parent = document.body) {
this.parent = parent;
this.obj = updateElement({
parent: parent,
classList: ['wiskas']
})
this.json_login = '';
this.index_chaberu = 0;
this.iniChat();
}
chaberu() {
let num = Math.min(this.index_chaberu, this.discussions.length - 1);
let text = this.discussions[num];
new Popup(text, this.obj);
this.index_chaberu++;
}
iniChat() {
this.discussions = ['Well hi there...',
'Please refrain from touching\n the yellow button without\n beeing logged in',
'We are going to take actions\n if you continue..',
'Actions already taken\n you are only making it worse'];
}
async login() {
let answer = await tryLogin();
if (!answer) return;
this.json_login = answer;
let dataUser = {
firstName: this.json_login.usual_first_name ?? this.json_login.first_name,
lastName: this.json_login.last_name,
photo: this.json_login.image.link,
month: this.json_login.pool_month,
year: this.json_login.pool_year,
projects: this.json_login.projects_users.filter(project => project.status === "in_progress").map(project => project.project.name),
perfect: this.json_login.projects_users.filter(project => project.final_mark === 125).map(project => project.project.name),
};
this.discussions = [
`Welcome ${dataUser.firstName} ${dataUser.lastName}.`,
`We heard quite a lot about the piscine of ${dataUser.month} ${dataUser.year}...\nIt's suprising to see you here`,
`How is your ${dataUser.projects[Math.floor(Math.random() * dataUser.projects.length)]} coming along?`,
`Perfect score for ${dataUser.perfect[Math.floor(Math.random() * dataUser.perfect.length)]}, impressive.. Should you really spend so much time in front of a screen?`,
`Shouldn't you be working on your ${dataUser.projects[Math.floor(Math.random() * dataUser.projects.length)]}?`,
`Quite an ugly human...\n but then again, you arent a cat`
];
}
}
/* /////////////////////////////////////////// */
let el = document.getElementById('helloText');
let img = document.createElement('img');
img.src = '../assets/wiskas-the-third.jpg';
el.append(img);
/* /////////////////////////////////////////// */
colorizeText();
/* /////////////////////////////////////////// */
// 1️⃣ Create a button dynamically
const app = document.getElementById('login');
let cat = new Wiskas;
let buttonLogin = document.getElementById('login-button');
Object.assign(buttonLogin.style, {
position: 'fixed', // make sure it's fixed
top: '0',
left: '0',
right: 'auto' // remove the right: 0 if it comes from CSS
});
buttonLogin.addEventListener('click', () => cat.login());
img.addEventListener('click', () => { cat.chaberu() })