add accueil page

This commit is contained in:
Ploush 2026-03-25 19:49:56 +01:00
parent 6a7b3be7f4
commit 50360a590b
17 changed files with 563 additions and 254 deletions

View file

@ -1,6 +1,7 @@
import 'bootstrap'; import 'bootstrap';
import 'bootstrap/dist/css/bootstrap.min.css'; import 'bootstrap/dist/css/bootstrap.min.css';
import './stimulus_bootstrap.js'; import './stimulus_bootstrap.js';
import './js/header.js';
/* /*
* Welcome to your app's main JavaScript file! * Welcome to your app's main JavaScript file!
* *
@ -9,4 +10,3 @@ import './stimulus_bootstrap.js';
*/ */
import './styles/app.css'; import './styles/app.css';
console.log('This log comes from assets/app.js - welcome to AssetMapper! 🎉');

View file

@ -0,0 +1,26 @@
import { Controller } from '@hotwired/stimulus';
export default class extends Controller {
static targets = ['link'];
copy(event) {
event.preventDefault();
const text = this.linkTarget.getAttribute('data-copy-text') || this.linkTarget.textContent;
navigator.clipboard.writeText(text).then(() => {
// Feedback visuel : changement temporaire du texte/couleur
const originalText = this.linkTarget.textContent;
this.linkTarget.textContent = '✓ Copié !';
this.linkTarget.classList.add('copied');
// Restaurer après 2 secondes
setTimeout(() => {
this.linkTarget.textContent = originalText;
this.linkTarget.classList.remove('copied');
}, 2000);
}).catch(err => {
console.error('Erreur lors de la copie:', err);
});
}
}

View file

@ -0,0 +1,57 @@
import { Controller } from '@hotwired/stimulus';
/**
* Contrôleur Stimulus pour le dropdown de connexion
* Attache les événements de manière robuste et compatible avec Turbo
*/
export default class extends Controller {
static targets = ['wrapper', 'toggle', 'dropdown'];
connect() {
this.setupListeners();
// Ouvrir le dropdown s'il y a une erreur de connexion
const loginError = this.element.querySelector('.login-error');
if (loginError && this.dropdownTarget) {
this.open();
}
}
setupListeners() {
// Clic sur le bouton toggle
this.toggleTarget.addEventListener('click', (e) => {
e.stopPropagation();
this.toggle();
});
// Clic en dehors du dropdown
document.addEventListener('click', (e) => {
if (!this.wrapperTarget.contains(e.target)) {
this.close();
}
});
// Touche Echap
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
this.close();
}
});
}
toggle() {
const isOpen = this.dropdownTarget.classList.contains('open');
isOpen ? this.close() : this.open();
}
open() {
this.dropdownTarget.classList.add('open');
this.toggleTarget.setAttribute('aria-expanded', 'true');
}
close() {
this.dropdownTarget.classList.remove('open');
this.toggleTarget.setAttribute('aria-expanded', 'false');
}
}

View file

@ -0,0 +1,44 @@
import { Controller } from '@hotwired/stimulus';
/**
* Contrôleur Stimulus pour le toggle du thème
* Compatible avec Turbo et les rechargements traditionnel
*/
export default class extends Controller {
static targets = ['button'];
connect() {
this.applyBackground();
this.setupListener();
}
setupListener() {
this.buttonTarget.addEventListener('click', (e) => {
e.preventDefault();
this.toggle();
});
}
toggle() {
const current = document.documentElement.getAttribute('data-theme');
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('mc-theme', next);
this.applyBackground();
}
applyBackground() {
const body = document.getElementById('page-body');
if (!body) return;
const theme = document.documentElement.getAttribute('data-theme');
const bg = theme === 'dark' ? body.dataset.bgDark : body.dataset.bgLight;
if (bg) {
body.style.backgroundImage = `url('${bg}')`;
}
}
}

BIN
assets/images/bg-dark.png Executable file → Normal file

Binary file not shown.

Before

Width:  |  Height:  |  Size: 284 KiB

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 47 KiB

After

Width:  |  Height:  |  Size: 4.7 MiB

View file

@ -1,53 +1,27 @@
/** /**
* public/js/header.js * assets/js/header.js
* Comportements interactifs du header : * ES Module gestion simple du thème
* - Bascule thème clair / sombre * Les contrôleurs Stimulus gèrent les interactions interactives
* - Ouverture / fermeture du dropdown de connexion
*/ */
document.addEventListener('DOMContentLoaded', function () { // Fonction pour appliquer le fond d'écran
function applyBackground() {
const body = document.getElementById('page-body');
if (!body) return;
// ── THÈME ─────────────────────────────────────────────────── const theme = document.documentElement.getAttribute('data-theme') || 'light';
const themeToggle = document.getElementById('themeToggle'); const bg = theme === 'dark' ? body.dataset.bgDark : body.dataset.bgLight;
themeToggle.addEventListener('click', function () { if (bg) {
const current = document.documentElement.getAttribute('data-theme'); body.style.backgroundImage = `url('${bg}')`;
const next = current === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', next);
localStorage.setItem('mc-theme', next);
});
// ── DROPDOWN CONNEXION ──────────────────────────────────────
const loginWrapper = document.getElementById('loginWrapper');
const loginToggle = document.getElementById('loginToggle');
const loginDropdown = document.getElementById('loginDropdown');
/** Ouvre ou ferme le dropdown */
function toggleDropdown(force) {
const willOpen = force !== undefined ? force : !loginDropdown.classList.contains('open');
loginDropdown.classList.toggle('open', willOpen);
loginToggle.setAttribute('aria-expanded', String(willOpen));
} }
}
// Clic sur le bouton // Appliquer le fond au chargement
loginToggle.addEventListener('click', function (e) { applyBackground();
e.stopPropagation();
toggleDropdown();
});
// Clic en dehors → fermeture
document.addEventListener('click', function (e) {
if (!loginWrapper.contains(e.target)) {
toggleDropdown(false);
}
});
// Touche Échap → fermeture
document.addEventListener('keydown', function (e) {
if (e.key === 'Escape') {
toggleDropdown(false);
}
});
// Réappliquer après navigation Turbo
document.addEventListener('turbo:load', () => {
applyBackground();
}); });

View file

@ -1 +1,117 @@
/* ================================================================
assets/styles/app.css
Variables CSS globales Accessibles partout sur le site
================================================================ */
/* ── VARIABLES GLOBALES ────────────────────────────────────────── */
:root {
/* Couleurs principales */
--violet: #7C3AED;
--violet-light: #A78BFA;
--violet-glow: rgba(124, 58, 237, 0.18);
/* Backgrounds de navigation */
--nav-bg-light: rgba(255, 255, 255, 0.5);
--nav-bg-dark: rgba(12, 10, 18, 0.4);
/* Couleurs de texte */
--text-light: #1a1625;
--text-dark: #ede9f6;
--muted-light: #6b7280;
--muted-dark: #9ca3af;
/* Couleurs de bordure */
--border-light: rgba(0, 0, 0, 0.08);
--border-dark: rgba(255, 255, 255, 0.08);
/* Backgrounds des éléments pilule */
--pill-bg-light: rgba(124, 58, 237, 0.08);
--pill-bg-dark: rgba(124, 58, 237, 0.20);
/* Effets de blur */
--blur-sm: 8px;
--blur-md: 20px;
/* Durées de transition */
--duration-fast: 0.15s; /* Transforms rapides */
--duration-normal: 0.2s; /* Animations standard */
--duration-medium: 0.25s; /* Animations moyennes */
--duration-slow: 0.4s; /* Changements de thème */
}
/* ── RESET MINIMAL ─────────────────────────────────────────────── */
*, *::before, *::after {
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* ── STYLES GLOBAUX ────────────────────────────────────────────── */
html, body {
min-height: 100vh;
font-family: 'DM Sans', sans-serif;
transition: background var(--duration-slow) ease;
}
body {
background-image: url('../images/bg-light.png');
background-size: cover;
background-position: center;
background-attachment: fixed;
padding-top: 72px;
}
[data-theme="dark"] body {
background-image: url('../images/bg-dark.png');
}
/* ── STYLES POUR LE LIEN COPIABLE ──────────────────────────────── */
.copy-link {
cursor: pointer;
font-weight: 500;
color: var(--violet);
padding: 0.25rem 0.5rem;
border-radius: 6px;
transition: background var(--duration-fast), color var(--duration-fast);
position: relative;
}
[data-theme="dark"] .copy-link {
color: var(--violet-light);
}
.copy-link:hover {
background: var(--pill-bg-light);
text-decoration: underline;
}
[data-theme="dark"] .copy-link:hover {
background: var(--pill-bg-dark);
}
.copy-link.copied {
color: #10b981;
animation: pulse-copied var(--duration-normal);
}
@keyframes pulse-copied {
0% { transform: scale(1); }
50% { transform: scale(1.05); }
100% { transform: scale(1); }
}
/* ── STYLES POUR LE LIEN ──────────────────────────────── */
a {
color: var(--violet);
text-decoration: none;
transition: color var(--duration-fast);
}
[data-theme="dark"] a {
color: var(--violet-light);
}
a:hover{
text-decoration: underline;
}

View file

@ -0,0 +1,50 @@
.bulle-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
padding: 1rem;
place-items: center;
height: 100%;
}
.bulle{
/*style graphique*/
background: var(--nav-bg-light);
backdrop-filter: blur(var(--blur-sm));
-webkit-backdrop-filter: blur(var(--blur-sm));
border: 1px solid var(--border-light);
border-radius: 14px;
padding: 1.25rem;
transition: background var(--duration-slow), border-color var(--duration-slow), color var(--duration-slow), box-shadow var(--duration-slow);
margin: 1.25rem;
}
[data-theme="dark"] .bulle {
background: var(--nav-bg-dark);
border-color: var(--border-dark);
box-shadow: 0 16px 40px rgba(0, 0, 0, 0.4);
color: var(--text-dark);
}
.bulle:hover{
box-shadow: 0 16px 40px rgba(124, 58, 237, 0.20);
}
.bulle-container>.bulle {
/**/
height: 100%;
width: 100%;
margin: 0;
}
.bulle-container>.bulle.bulle-all-col {
grid-column: 1 / -1;
}
.bulle-container>.bulle.bulle-2-col {
grid-column: span 2;
}
.bulle-container>.bulle.bulle-2-row {
grid-row: span 2;
}

View file

@ -3,70 +3,37 @@
Styles du header thème clair & sombre Styles du header thème clair & sombre
================================================================ */ ================================================================ */
/* ── RESET MINIMAL ─────────────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
/* ── VARIABLES ─────────────────────────────────────────────────── */
:root {
--violet: #7C3AED;
--violet-light: #A78BFA;
--violet-glow: rgba(124, 58, 237, 0.18);
--nav-bg-light: rgba(255, 255, 255, 0.78);
--nav-bg-dark: rgba(12, 10, 18, 0.82);
--text-light: #1a1625;
--text-dark: #ede9f6;
--muted-light: #6b7280;
--muted-dark: #9ca3af;
--border-light: rgba(0, 0, 0, 0.08);
--border-dark: rgba(255, 255, 255, 0.08);
--pill-bg-light: rgba(124, 58, 237, 0.08);
--pill-bg-dark: rgba(124, 58, 237, 0.20);
}
/* ── BODY / BACKGROUND ─────────────────────────────────────────── */
html, body {
min-height: 100vh;
font-family: 'DM Sans', sans-serif;
transition: background 0.4s ease;
}
body {
background-image: url('../images/bg-light.png'); /* assets/images/bg-light.png */
background-size: cover;
background-position: center;
background-attachment: fixed;
padding-top: 64px; /* compense le header fixe */
}
[data-theme="dark"] body {
background-image: url('../images/bg-dark.png'); /* assets/images/bg-dark.png */
}
/* ── HEADER ────────────────────────────────────────────────────── */ /* ── HEADER ────────────────────────────────────────────────────── */
header { header {
position: fixed; position: fixed;
top: 0; left: 0; right: 0; top: 0; left: 0; right: 0;
z-index: 100; z-index: 100;
height: 64px; height: 72px;
padding: 0 2rem; padding: 0 2rem !important;
display: flex; display: flex !important;
align-items: center; align-items: center !important;
justify-content: space-between; justify-content: space-between !important;
gap: 1.5rem; gap: 1.5rem;
background: var(--nav-bg-light); background: var(--nav-bg-light);
backdrop-filter: blur(20px) saturate(1.4); backdrop-filter: blur(var(--blur-sm)) saturate(1.4);
-webkit-backdrop-filter: blur(20px) saturate(1.4); -webkit-backdrop-filter: blur(var(--blur-sm)) saturate(1.4);
border-bottom: 1px solid var(--border-light); border-bottom: 1px solid var(--border-light);
transition: background 0.4s ease, border-color 0.4s ease; transition: background var(--duration-slow) ease, border-color var(--duration-slow) ease;
} }
[data-theme="dark"] header { [data-theme="dark"] header {
background: var(--nav-bg-dark); background: var(--nav-bg-dark);
border-bottom-color: var(--border-dark); border-bottom-color: var(--border-dark);
} }
/* ── GROUPE GAUCHE : logo + nav ────────────────────────────────── */
.header-left {
display: flex !important;
align-items: center !important;
gap: 1.25rem;
flex-shrink: 0;
}
/* ── LOGO ──────────────────────────────────────────────────────── */ /* ── LOGO ──────────────────────────────────────────────────────── */
.logo { .logo {
display: flex; display: flex;
@ -76,14 +43,14 @@ header {
flex-shrink: 0; flex-shrink: 0;
} }
.logo-mark { .logo-mark {
width: 30px; width: 32px;
height: 30px; height: 32px;
background: var(--violet); background: var(--violet);
border-radius: 8px; border-radius: 8px;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
transition: transform 0.25s ease, box-shadow 0.25s ease; transition: transform var(--duration-medium) ease, box-shadow var(--duration-medium) ease;
} }
.logo:hover .logo-mark { .logo:hover .logo-mark {
transform: rotate(-6deg) scale(1.08); transform: rotate(-6deg) scale(1.08);
@ -94,100 +61,99 @@ header {
font-size: 1.05rem; font-size: 1.05rem;
letter-spacing: -0.02em; letter-spacing: -0.02em;
color: var(--text-light); color: var(--text-light);
transition: color 0.4s ease; transition: color var(--duration-slow) ease;
} }
[data-theme="dark"] .logo-name { color: var(--text-dark); } [data-theme="dark"] .logo-name { color: var(--text-dark); }
/* ── NAVIGATION ────────────────────────────────────────────────── */ /* ── NAVIGATION ────────────────────────────────────────────────── */
nav { header nav {
display: flex; display: flex !important;
align-items: center; align-items: center !important;
gap: 0.5rem; gap: 0.4rem !important;
flex: 1; flex-wrap: nowrap;
justify-content: center;
} }
.nav-link { /* ── LIENS DE NAVIGATION — style calqué sur .login-btn ─────────── */
position: relative; /*
padding: 0.6rem 1.5rem; "all: unset" neutralise Bootstrap 5 entièrement sur .nav-link,
border-radius: 10px; puis on réapplique exactement ce qu'on veut.
font-family: 'Syne', sans-serif; Les !important sur padding/border/background garantissent
font-weight: 600; qu'aucune règle Bootstrap résiduelle ne peut l'écraser.
font-size: 0.9rem; */
letter-spacing: 0.04em; header nav .nav-link {
text-transform: uppercase; all: unset;
color: var(--muted-light);
text-decoration: none;
white-space: nowrap;
transition: color 0.2s ease, background 0.2s ease, transform 0.15s ease;
}
[data-theme="dark"] .nav-link { color: var(--muted-dark); }
.nav-link::after { display: inline-flex !important;
content: ''; align-items: center !important;
position: absolute;
bottom: 4px;
left: 50%;
transform: translateX(-50%) scaleX(0);
width: 16px;
height: 2px;
border-radius: 2px;
background: var(--violet);
transition: transform 0.25s ease;
}
.nav-link:hover {
color: var(--text-light);
background: rgba(0, 0, 0, 0.05);
transform: translateY(-1px);
}
[data-theme="dark"] .nav-link:hover {
color: var(--text-dark);
background: rgba(255, 255, 255, 0.07);
}
.nav-link:hover::after {
transform: translateX(-50%) scaleX(1);
}
/* Onglet actif */
.nav-link.active {
color: var(--violet);
background: var(--pill-bg-light);
font-weight: 700;
}
[data-theme="dark"] .nav-link.active {
color: var(--violet-light);
background: var(--pill-bg-dark);
}
.nav-link.active::after {
transform: translateX(-50%) scaleX(1);
}
[data-theme="dark"] .nav-link.active::after {
background: var(--violet-light);
}
/* Actualité */
.nav-link:has(.badge-dot) {
display: flex;
align-items: center;
gap: 0.45rem; gap: 0.45rem;
} white-space: nowrap;
.badge-dot { cursor: pointer;
width: 6px; text-decoration: none !important;
height: 6px;
border-radius: 50%;
background: var(--violet);
flex-shrink: 0;
animation: pulse-dot 2.5s ease-in-out infinite;
}
[data-theme="dark"] .badge-dot { background: var(--violet-light); }
@keyframes pulse-dot { /* Dimensions — identiques au .login-btn */
0%, 100% { opacity: 1; transform: scale(1); } padding: 0.55rem 1.1rem !important;
50% { opacity: 0.5; transform: scale(0.7); } border-radius: 10px !important;
border: 1px solid var(--border-light) !important;
background: transparent !important;
/* Typographie */
font-family: 'Syne', sans-serif !important;
font-weight: 600 !important;
font-size: 0.875rem !important;
letter-spacing: 0.03em !important;
text-transform: uppercase !important;
color: var(--muted-light) !important;
transition:
color var(--duration-normal) ease,
background var(--duration-normal) ease,
border-color var(--duration-normal) ease,
box-shadow var(--duration-normal) ease,
transform var(--duration-fast) ease !important;
} }
/* ── ACTIONS ───────────────────────────────────────────────────── */ [data-theme="dark"] header nav .nav-link {
border-color: var(--border-dark) !important;
color: var(--muted-dark) !important;
}
/* Hover */
header nav .nav-link:hover {
color: var(--violet) !important;
background: rgba(0, 0, 0, 0.05) !important;
border-color: rgba(0, 0, 0, 0.15) !important;
transform: translateY(-1px) !important;
box-shadow: none !important;
}
[data-theme="dark"] header nav .nav-link:hover {
color: var(--violet-light) !important;
background: rgba(255, 255, 255, 0.07) !important;
border-color: rgba(255, 255, 255, 0.15) !important;
}
/* Actif — fond violet léger + bordure violet */
header nav .nav-link.active {
color: var(--violet) !important;
background: var(--pill-bg-light) !important;
border-color: rgba(124, 58, 237, 0.30) !important;
font-weight: 700 !important;
}
[data-theme="dark"] header nav .nav-link.active {
color: var(--violet-light) !important;
background: var(--pill-bg-dark) !important;
border-color: rgba(167, 139, 250, 0.30) !important;
}
/* Actif + hover — glow subtil */
header nav .nav-link.active:hover {
box-shadow: 0 4px 16px rgba(124, 58, 237, 0.20) !important;
}
/* Pas de pseudo-élément soulignement */
header nav .nav-link::before,
header nav .nav-link::after { display: none !important; }
/* ── ACTIONS (droite) ──────────────────────────────────────────── */
.header-actions { .header-actions {
display: flex; display: flex;
align-items: center; align-items: center;
@ -197,8 +163,8 @@ nav {
/* ── BOUTON THÈME ──────────────────────────────────────────────── */ /* ── BOUTON THÈME ──────────────────────────────────────────────── */
.theme-btn { .theme-btn {
width: 36px; width: 40px;
height: 36px; height: 40px;
border-radius: 10px; border-radius: 10px;
border: 1px solid var(--border-light); border: 1px solid var(--border-light);
background: transparent; background: transparent;
@ -207,7 +173,7 @@ nav {
align-items: center; align-items: center;
justify-content: center; justify-content: center;
color: var(--muted-light); color: var(--muted-light);
transition: color 0.25s, background 0.25s, border-color 0.25s, transform 0.3s; transition: color var(--duration-medium) ease, background var(--duration-medium) ease, border-color var(--duration-medium) ease, transform var(--duration-slow) ease;
} }
[data-theme="dark"] .theme-btn { [data-theme="dark"] .theme-btn {
border-color: var(--border-dark); border-color: var(--border-dark);
@ -223,7 +189,6 @@ nav {
color: var(--violet-light); color: var(--violet-light);
} }
/* Icônes soleil / lune */
.icon-sun { display: block; } .icon-sun { display: block; }
.icon-moon { display: none; } .icon-moon { display: none; }
[data-theme="dark"] .icon-sun { display: none; } [data-theme="dark"] .icon-sun { display: none; }
@ -234,7 +199,7 @@ nav {
display: flex; display: flex;
align-items: center; align-items: center;
gap: 0.45rem; gap: 0.45rem;
padding: 0.4rem 1.1rem; padding: 0.55rem 1.1rem;
border-radius: 10px; border-radius: 10px;
border: 1px solid var(--border-light); border: 1px solid var(--border-light);
background: transparent; background: transparent;
@ -244,7 +209,7 @@ nav {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-light); color: var(--text-light);
text-decoration: none; text-decoration: none;
transition: background 0.2s, border-color 0.2s, color 0.2s, box-shadow 0.2s; transition: background var(--duration-normal) ease, border-color var(--duration-normal) ease, color var(--duration-normal) ease, box-shadow var(--duration-normal) ease;
} }
[data-theme="dark"] .login-btn { [data-theme="dark"] .login-btn {
border-color: var(--border-dark); border-color: var(--border-dark);
@ -266,8 +231,8 @@ nav {
right: 0; right: 0;
width: 260px; width: 260px;
background: var(--nav-bg-light); background: var(--nav-bg-light);
backdrop-filter: blur(20px); backdrop-filter: blur(var(--blur-md));
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(var(--blur-md));
border: 1px solid var(--border-light); border: 1px solid var(--border-light);
border-radius: 14px; border-radius: 14px;
padding: 1.25rem; padding: 1.25rem;
@ -275,7 +240,7 @@ nav {
display: none; display: none;
flex-direction: column; flex-direction: column;
gap: 0.75rem; gap: 0.75rem;
animation: dropdown-in 0.2s ease; animation: dropdown-in var(--duration-normal) ease;
} }
[data-theme="dark"] .login-dropdown { [data-theme="dark"] .login-dropdown {
background: var(--nav-bg-dark); background: var(--nav-bg-dark);
@ -313,7 +278,7 @@ nav {
[data-theme="dark"] .form-field label { color: var(--muted-dark); } [data-theme="dark"] .form-field label { color: var(--muted-dark); }
.form-field input { .form-field input {
height: 36px; height: 38px;
border-radius: 8px; border-radius: 8px;
border: 1px solid var(--border-light); border: 1px solid var(--border-light);
background: rgba(0, 0, 0, 0.03); background: rgba(0, 0, 0, 0.03);
@ -322,7 +287,7 @@ nav {
font-size: 0.875rem; font-size: 0.875rem;
color: var(--text-light); color: var(--text-light);
outline: none; outline: none;
transition: border-color 0.2s, box-shadow 0.2s; transition: border-color var(--duration-normal) ease, box-shadow var(--duration-normal) ease;
} }
[data-theme="dark"] .form-field input { [data-theme="dark"] .form-field input {
border-color: var(--border-dark); border-color: var(--border-dark);
@ -336,7 +301,7 @@ nav {
.submit-btn { .submit-btn {
width: 100%; width: 100%;
height: 36px; height: 38px;
border-radius: 8px; border-radius: 8px;
border: none; border: none;
background: var(--violet); background: var(--violet);
@ -345,7 +310,7 @@ nav {
font-weight: 600; font-weight: 600;
font-size: 0.875rem; font-size: 0.875rem;
cursor: pointer; cursor: pointer;
transition: background 0.2s, box-shadow 0.2s, transform 0.15s; transition: background var(--duration-normal) ease, box-shadow var(--duration-normal) ease, transform var(--duration-fast) ease;
} }
.submit-btn:hover { .submit-btn:hover {
background: #6d28d9; background: #6d28d9;
@ -355,6 +320,7 @@ nav {
/* ── MOBILE ────────────────────────────────────────────────────── */ /* ── MOBILE ────────────────────────────────────────────────────── */
@media (max-width: 640px) { @media (max-width: 640px) {
nav { display: none; } header nav { display: none !important; }
header { padding: 0 1rem; } header { padding: 0 1rem !important; }
.header-left { gap: 0; }
} }

View file

@ -0,0 +1,9 @@
body{
display: flex;
flex-direction: column;
align-items: center;
}
main {
width: 100%;
max-width: 1200px;
}

View file

@ -2,6 +2,8 @@
namespace App\Controller; namespace App\Controller;
use App\Entity\User;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Attribute\Route; use Symfony\Component\Routing\Attribute\Route;
@ -10,8 +12,26 @@ use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController class SecurityController extends AbstractController
{ {
#[Route(path: '/login', name: 'app_login')] #[Route(path: '/login', name: 'app_login')]
public function login(AuthenticationUtils $authenticationUtils): Response public function login(AuthenticationUtils $authenticationUtils,EntityManagerInterface $em): Response
{ {
if($this->getUser()){
return $this->redirectToRoute('app_accueil');
}
// Créer un utilisateur admin par défaut s'il n'existe pas
$userRepository = $em->getRepository(User::class);
$adminUser = $userRepository->findOneBy(['pseudo' => 'admin']);
if(!$adminUser){
$user = new User();
$user->setRoles(["ROLE_ADMIN","ROLE_USER"]);
$user->setPseudo("admin");
$user->setPassword('$2y$13$CVJ/Hm29HJ3ehPrqrtMl8Oya55d/ZXwlhfL6D2TsWI238bHAqtCrS');
$user->setThemeSombre(false);
$em->persist($user);
$em->flush();
}
// get the login error if there is one // get the login error if there is one
$error = $authenticationUtils->getLastAuthenticationError(); $error = $authenticationUtils->getLastAuthenticationError();

View file

@ -1,37 +1,41 @@
<!DOCTYPE html> <!DOCTYPE html>
<html data-theme="light"> <html data-theme="light" lang="fr">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<title>{% block title %}CraftWorld{% endblock %}</title> <title>{% block title %}CraftWorld{% endblock %}</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 128 128%22><text y=%221.2em%22 font-size=%2296%22>⚫️</text><text y=%221.3em%22 x=%220.2em%22 font-size=%2276%22 fill=%22%23fff%22>sf</text></svg>"> <link rel="icon" href="...">
{# Fonts #} {# Anti-flash - DOIT être avant tout le reste #}
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
{# Styles globaux + header #}
<link rel="stylesheet" href="{{ asset('styles/header.css') }}">
{% block stylesheets %}{% endblock %}
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
{# Anti-flash thème sombre : exécuté avant le premier paint #}
<script> <script>
(function () { (function () {
const saved = localStorage.getItem('mc-theme') || 'light'; const saved = localStorage.getItem('mc-theme') || 'light';
document.documentElement.setAttribute('data-theme', saved); document.documentElement.setAttribute('data-theme', saved);
})(); })();
</script> </script>
</head>
<body> <link rel="preconnect" href="https://fonts.googleapis.com">
{% include 'partials/_header.html.twig' %} <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;600;700;800&family=DM+Sans:wght@300;400;500&display=swap" rel="stylesheet">
{% block body %}{% endblock %} {# Variables CSS globales et styles de base #}
<link rel="stylesheet" href="{{ asset('styles/app.css') }}">
{# Styles du header #}
<link rel="stylesheet" href="{{ asset('styles/partials/header.css') }}">
<script src="{{ asset('js/header.js') }}"></script> {% block stylesheets %}{% endblock %}
</body>
{% block javascripts %}
{% block importmap %}{{ importmap('app') }}{% endblock %}
{% endblock %}
</head>
{# id="page-body" + data-bg pour le changement de fond #}
<body id="page-body"
data-bg-light="{{ asset('images/bg-light.png') }}"
data-bg-dark="{{ asset('images/bg-dark.png') }}">
{% include 'partials/_header.html.twig' %}
{% block body %}{% endblock %}
</body>
</html> </html>

View file

@ -7,16 +7,16 @@
<header> <header>
{# ── Logo ──────────────────────────────────────────────── #} {# ── Gauche : Logo + Navigation ───────────────────────────── #}
<a href="{{ path('app_accueil') }}" class="logo"> <div class="header-left">
<a href="{{ path('app_accueil') }}" class="logo">
<span class="logo-name">LOGO</span> <span class="logo-name">LOGO</span>
</a> </a>
{# ── Navigation ────────────────────────────────────────── #}
<nav> <nav>
<a href="{{ path('app_accueil') }}" <a href="{{ path('app_accueil') }}"
class="nav-link {{ current_route == 'app_home' ? 'active' }}"> class="nav-link {{ current_route == 'app_accueil' ? 'active' }}">
Accueil Accueil
</a> </a>
<a href="{{ path('app_accueil') }}" <a href="{{ path('app_accueil') }}"
@ -29,11 +29,13 @@
</a> </a>
<a href="{{ path('app_accueil') }}" <a href="{{ path('app_accueil') }}"
class="nav-link {{ current_route == 'app_home' ? 'active' }}"> class="nav-link {{ current_route == 'app_home' ? 'active' }}">
<span class="badge-dot"></span>Actualité Actualité
</a> </a>
</nav> </nav>
{# ── Actions (thème + connexion) ───────────────────────── #} </div>
{# ── Droite : Actions (thème + connexion) ─────────────────── #}
<div class="header-actions"> <div class="header-actions">
{% include 'partials/_theme_toggle.html.twig' %} {% include 'partials/_theme_toggle.html.twig' %}
{% include 'partials/_login_dropdown.html.twig' %} {% include 'partials/_login_dropdown.html.twig' %}

View file

@ -3,11 +3,14 @@
Bouton + dropdown formulaire de connexion Bouton + dropdown formulaire de connexion
══════════════════════════════════════════════════════════════ #} ══════════════════════════════════════════════════════════════ #}
<div class="login-wrapper" id="loginWrapper"> <div class="login-wrapper" id="loginWrapper"
data-controller="login-dropdown"
data-login-dropdown-target="wrapper">
{# Bouton déclencheur #} {# Bouton déclencheur #}
<button class="login-btn" id="loginToggle" <button class="login-btn" id="loginToggle"
aria-haspopup="true" aria-expanded="false"> aria-haspopup="true" aria-expanded="false"
data-login-dropdown-target="toggle">
<svg width="15" height="15" viewBox="0 0 24 24" <svg width="15" height="15" viewBox="0 0 24 24"
fill="none" stroke="currentColor" stroke-width="2" fill="none" stroke="currentColor" stroke-width="2"
stroke-linecap="round" stroke-linejoin="round"> stroke-linecap="round" stroke-linejoin="round">
@ -19,11 +22,12 @@
{# Dropdown #} {# Dropdown #}
<div class="login-dropdown" id="loginDropdown" <div class="login-dropdown" id="loginDropdown"
role="dialog" aria-label="Formulaire de connexion"> role="dialog" aria-label="Formulaire de connexion"
data-login-dropdown-target="dropdown">
<span class="dropdown-title">Connexion</span> <span class="dropdown-title">Connexion</span>
<form action="{{ path('app_login') }}" method="post"> <form action="{{ path('app_login') }}" method="post" >
<div class="form-field"> <div class="form-field">
<label for="username">Pseudo</label> <label for="username">Pseudo</label>
@ -50,6 +54,11 @@
name="_csrf_token" name="_csrf_token"
value="{{ csrf_token('authenticate') }}"> value="{{ csrf_token('authenticate') }}">
{# Retenir la page courante pour y revenir en cas d'erreur #}
<input type="hidden"
name="_failure_path"
value="{{ app.request.uri }}">
<button class="submit-btn" type="submit">Se connecter</button> <button class="submit-btn" type="submit">Se connecter</button>
</form> </form>

View file

@ -3,7 +3,8 @@
Bouton bascule thème clair / sombre Bouton bascule thème clair / sombre
══════════════════════════════════════════════════════════════ #} ══════════════════════════════════════════════════════════════ #}
<button class="theme-btn" id="themeToggle" aria-label="Changer le thème" title="Changer le thème"> <button class="theme-btn" id="themeToggle" aria-label="Changer le thème" title="Changer le thème"
data-controller="theme-toggle" data-theme-toggle-target="button">
{# Soleil — affiché en mode clair #} {# Soleil — affiché en mode clair #}
<svg class="icon-sun" width="17" height="17" viewBox="0 0 24 24" <svg class="icon-sun" width="17" height="17" viewBox="0 0 24 24"

View file

@ -2,5 +2,36 @@
{% block title %} Accueil {% endblock %} {% block title %} Accueil {% endblock %}
{% block body %} {% block stylesheets %}
{{ parent() }}
<link rel="stylesheet" href="{{ asset('styles/vitrine.css') }}">
<link rel="stylesheet" href="{{ asset('styles/globals/bulles.css') }}">
{% endblock %}
{% block body %}
<main class="bulle-container">
<section class="bulle bulle-all-col flex-column align-items-center">
<h1 class="text-center">Bienvenue sur le serveur de Ploush</h1>
<p class="text-center">Le serveur est accessible a l'adresse :
<span class="copy-link" data-controller="copy-link"
data-action="click->copy-link#copy" data-copy-link-target="link"
data-copy-text="ploush.top"
>ploush.top</span></p>
</section>
<section class="bulle bulle-2-col bulle-2-row">
<h4>Bienvenue sur le serveur de Ploush</h4>
<p>Celui-ci est accessible a l'adresse <span class="copy-link" data-controller="copy-link" data-action="click->copy-link#copy" data-copy-link-target="link" data-copy-text="ploush.top">ploush.top</span></p>
</section>
<section class="bulle bulle-2-col">
<h4>Conseil launcher</h4>
<p>Afin de vous faciliter l'installation des mods, je vous conseille vivement d'utiliser <a href="https://prismlauncher.org/" target="_blank" rel="noopener noreferrer">prism launcher</a> .</p>
</section>
<section class="bulle">
<h4>Bienvenue sur le serveur de Ploush</h4>
<p>Celui-ci est accessible a l'adresse <span class="copy-link" data-controller="copy-link" data-action="click->copy-link#copy" data-copy-link-target="link" data-copy-text="ploush.top">ploush.top</span></p>
</section>
</main>
{% endblock %} {% endblock %}