^^._, work in progress, small changes

This commit is contained in:
Kali Gallon
2026-04-02 14:55:07 +02:00
parent 4fa835b62a
commit e1e529b3ca
7 changed files with 387 additions and 99 deletions
+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;
@@ -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() })