Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| b40dad8f57 | |||
| 23cca7a249 | |||
| e1e529b3ca | |||
| e96c16819d | |||
| aefb858247 | |||
| a9f81b4d65 | |||
| 4b3909c1a3 | |||
| 4fa835b62a |
@@ -0,0 +1,3 @@
|
|||||||
|
|
||||||
|
INTRA_CLIENT_ID=u-s4t2ud-c226cd35cd1ac08a4c6668deee1c64d7d67a13a766aee672acafd4a1522d483c
|
||||||
|
INTRA_CLIENT_SECRET=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
|
||||||
@@ -12,6 +12,7 @@ import playerStatsRouter from './routes/player_stats.js';
|
|||||||
import {waitForDb, createTables, runMigrations, ensureOauthClient} from './db.js';
|
import {waitForDb, createTables, runMigrations, ensureOauthClient} from './db.js';
|
||||||
import setupSocketIO from './services/socket.js';
|
import setupSocketIO from './services/socket.js';
|
||||||
import avatarService from './services/avatar.js';
|
import avatarService from './services/avatar.js';
|
||||||
|
import intraRouter from './routes/intra.js';
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const httpsOptions = {
|
const httpsOptions = {
|
||||||
@@ -53,6 +54,7 @@ async function startServer()
|
|||||||
app.use('/api/avatar', avatarRouter);
|
app.use('/api/avatar', avatarRouter);
|
||||||
app.use('/api/friends', friendsRouter);
|
app.use('/api/friends', friendsRouter);
|
||||||
app.use('/api/stats', playerStatsRouter);
|
app.use('/api/stats', playerStatsRouter);
|
||||||
|
app.use('/api/intra', intraRouter);
|
||||||
app.get('/api', (req, res) => res.send('Backend running'));
|
app.get('/api', (req, res) => res.send('Backend running'));
|
||||||
|
|
||||||
server.listen(3001, () =>
|
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;
|
||||||
@@ -140,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)
|
function setupSocketIO(io)
|
||||||
{
|
{
|
||||||
ioInstance = io;
|
ioInstance = io;
|
||||||
@@ -276,6 +317,7 @@ function setupSocketIO(io)
|
|||||||
username: socket.user.username,
|
username: socket.user.username,
|
||||||
userId: socket.user.userId
|
userId: socket.user.userId
|
||||||
});
|
});
|
||||||
|
handlePlayerDeparture(io, roomId, socket.user.username);
|
||||||
socket.leave(roomId);
|
socket.leave(roomId);
|
||||||
console.log(`${socket.user.username} left ${roomId}`);
|
console.log(`${socket.user.username} left ${roomId}`);
|
||||||
|
|
||||||
@@ -574,7 +616,7 @@ function setupSocketIO(io)
|
|||||||
|
|
||||||
// Points: 10 per letter found, -5 for wrong guess
|
// Points: 10 per letter found, -5 for wrong guess
|
||||||
if (success) {
|
if (success) {
|
||||||
points = lettersFound * 10;
|
points = lettersFound * 5;
|
||||||
gameState.scores[username] += points;
|
gameState.scores[username] += points;
|
||||||
} else {
|
} else {
|
||||||
points = -5;
|
points = -5;
|
||||||
@@ -673,42 +715,7 @@ function setupSocketIO(io)
|
|||||||
message: `${username} a quitté la partie`
|
message: `${username} a quitté la partie`
|
||||||
});
|
});
|
||||||
|
|
||||||
const gameState = gameRooms.get(roomId);
|
handlePlayerDeparture(io, roomId, username);
|
||||||
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)
|
|
||||||
{
|
|
||||||
stopRoomTimer(roomId);
|
|
||||||
// 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`
|
|
||||||
});
|
|
||||||
startRoomTimer(io, roomId, 60);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
|
await checkAndStopSinglePlayerGame(io, roomId, dbRoomId);
|
||||||
|
|
||||||
@@ -956,6 +963,7 @@ function setupSocketIO(io)
|
|||||||
username: socket.user.username,
|
username: socket.user.username,
|
||||||
userId: socket.user.userId
|
userId: socket.user.userId
|
||||||
});
|
});
|
||||||
|
handlePlayerDeparture(io, roomId, socket.user.username);
|
||||||
|
|
||||||
// Get updated player list and broadcast
|
// Get updated player list and broadcast
|
||||||
if (dbRoomId) {
|
if (dbRoomId) {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* Initializes windows and handles menu interactions
|
* Initializes windows and handles menu interactions
|
||||||
*/
|
*/
|
||||||
import { windowRegistry } from './core/windows.js';
|
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 { LoginWindow } from './windows/login.js';
|
||||||
import { LogoutWindow } from './windows/logout.js';
|
import { LogoutWindow } from './windows/logout.js';
|
||||||
import { GlobalChat } from './windows/global_chat.js';
|
import { GlobalChat } from './windows/global_chat.js';
|
||||||
@@ -17,6 +19,7 @@ import { StatsWindow } from './windows/stats.js';
|
|||||||
*/
|
*/
|
||||||
class App {
|
class App {
|
||||||
constructor() {
|
constructor() {
|
||||||
|
this.invalidateStaleToken();
|
||||||
this.initWindows();
|
this.initWindows();
|
||||||
this.initMenu();
|
this.initMenu();
|
||||||
this.initPage();
|
this.initPage();
|
||||||
@@ -24,6 +27,51 @@ class App {
|
|||||||
this.colorizeUI();
|
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
|
* Initializes all windows
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -82,6 +82,7 @@ html {
|
|||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
overflow: hidden;
|
||||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
line-height: 1.5;
|
line-height: 1.5;
|
||||||
|
|||||||
@@ -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 {
|
body {
|
||||||
--color-primary: #ffc75e;
|
margin: 0;
|
||||||
--color-primary-hover: #ffc75e;
|
height: 100vh;
|
||||||
--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;
|
|
||||||
|
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
justify-content: center;
|
||||||
gap: var(--spacing-lg);
|
align-items: center;
|
||||||
|
background: linear-gradient(135deg, #1f001f, #1f1f00);
|
||||||
z-index: var(--z-menu);
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game__item {
|
h1 {
|
||||||
background: var(--color-surface);
|
font-size: 4rem;
|
||||||
color: var(--color-text);
|
color: white;
|
||||||
border: 1px solid var(--color-surface-light);
|
animation: float 2s ease-in-out infinite alternate;
|
||||||
border-radius: var(--radius-lg);
|
border: 5px double #654050;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.game__item:hover {
|
@keyframes float {
|
||||||
|
from { transform: translateY(0); }
|
||||||
|
to { transform: translateY(-20px); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* /////////////////////////////////////////// */
|
||||||
|
.box:hover {
|
||||||
background: var(--color-surface-light);
|
background: var(--color-surface-light);
|
||||||
font-size: var(--font-size-lg);
|
font-size: var(--font-size-lg);
|
||||||
|
|
||||||
|
animation: bobble 0.4s ease-in-out infinite alternate;
|
||||||
}
|
}
|
||||||
|
|
||||||
.game__item--active {
|
@keyframes bobble {
|
||||||
background: var(--color-primary);
|
from {
|
||||||
border-color: var(--color-primary);
|
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>
|
<title>Wiskas</title>
|
||||||
<link rel="stylesheet" href="wiskas.css" />
|
<link rel="stylesheet" href="wiskas.css" />
|
||||||
<style>
|
<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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h1 id="helloText">METS TON CHAT ICI</h1>
|
<h1 id="helloText"></h1>
|
||||||
|
|
||||||
<nav class="game" aria-label="Game">
|
<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>
|
onclick="window.location.href='../index.html'">Home Page</button>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<script>
|
<nav class="login" aria-label="Game">
|
||||||
const colors = ["#ff4b5c", "#56cfe1", "#80ed99", "#ffd166"];
|
<button class="box multicolor" aria-label="Home Page" id="login-button">Login</button>
|
||||||
const text = document.getElementById("helloText");
|
</nav>
|
||||||
|
|
||||||
setInterval(() => {
|
<div id="login"></div>
|
||||||
const randomColor = colors[Math.floor(Math.random() * colors.length)];
|
|
||||||
text.style.color = randomColor;
|
<script type="module" src="./wiskas.js"></script>
|
||||||
}, 500);
|
|
||||||
</script>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</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() })
|
||||||
|
|
||||||
Reference in New Issue
Block a user