^^._, work in progress, small changes
This commit is contained in:
@@ -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() })
|
||||
|
||||
Reference in New Issue
Block a user