Organisation
- Pilote tes tournois
-Création rapide, génération Americano, scoring en deux clics.
-Connexion
- - - - +
+
+
+
+
+
+
+
+
+ Identifiants dans backend/.env (ADMIN_EMAIL / ADMIN_PASSWORD).
Actions rapides
-
-
-
+
+
Créer un tournoi
- - - - - +
+
Créer un tournoi
+ + +
+
+
-
-
-
-
- Générer Americano
- - - - - -
-
- Score Match
- - - - +
+
+
+
Tournaments
- -Matches
- + +
+
+
+
+ Ajouter un joueur
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Scorer un match
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
+
-
-
+
+
-
\ No newline at end of file
+
diff --git a/frontend/public/assets/admin.ux.js b/frontend/public/assets/admin.ux.js
new file mode 100644
index 0000000..4bb6b33
--- /dev/null
+++ b/frontend/public/assets/admin.ux.js
@@ -0,0 +1,160 @@
+import {
+ bindLoginForm, isAuthenticated, logout,
+ createTournament, addParticipant, scoreMatch,
+ listTournaments, listMatches
+} from '/assets/api.js?v=10';
+
+const toast = document.getElementById('toast');
+const adminSection = document.getElementById('adminSection');
+const loginSection = document.getElementById('loginSection');
+
+function showToast(msg, ok=true){
+ toast.textContent = msg;
+ toast.style.borderColor = ok ? 'rgba(143,237,255,.45)' : 'rgba(255,120,120,.45)';
+ toast.classList.add('show');
+ setTimeout(()=>toast.classList.remove('show'), 2200);
+}
+
+function toggleAdminUI(){
+ const logged = isAuthenticated();
+ adminSection.classList.toggle('hide', !logged);
+ loginSection.classList.toggle('hide', logged);
+}
+
+function disable(btn, spin, yes){
+ btn.disabled = !!yes;
+ spin.classList.toggle('hide', !yes);
+}
+
+async function renderTournaments(){
+ const wrap = document.getElementById('listTournaments');
+ const p_tid = document.getElementById('p_tid');
+ const s_tid = document.getElementById('s_tid');
+ try{
+ const ts = await listTournaments();
+ wrap.innerHTML = ts.map(t => `Tournois (aperçu)
+
+
+
+
+ #${t.id} — ${t.name} (${[t.location||'—', t.start_date].filter(Boolean).join(' • ')})
`).join('') || 'Aucun tournoi.
';
+ const opts = [''].concat(
+ ts.map(t => ``)
+ ).join('');
+ p_tid.innerHTML = opts;
+ s_tid.innerHTML = opts;
+ }catch(e){
+ wrap.innerHTML = `Erreur chargement: ${e.message}
`;
+ }
+}
+
+async function renderMatchesForSelectedTournament(){
+ const tid = Number(document.getElementById('s_tid').value || '0');
+ const s_mid = document.getElementById('s_mid');
+ if(!tid){ s_mid.innerHTML = ''; return; }
+ try{
+ const ms = await listMatches(tid);
+ s_mid.innerHTML = [''].concat(
+ ms.map(m => ``)
+ ).join('');
+ }catch(e){
+ s_mid.innerHTML = '';
+ }
+}
+
+function bindAuth(){
+ bindLoginForm({});
+ const logoutBtn = document.getElementById('logoutBtn');
+ logoutBtn?.addEventListener('click', ()=>{
+ logout(); toggleAdminUI(); showToast('Déconnecté.');
+ });
+}
+
+function bindAdminActions(){
+ document.getElementById('createTournamentBtn')?.addEventListener('click', async (e)=>{
+ const btn = e.currentTarget;
+ const spin = document.getElementById('createTournamentSpin');
+ const err = document.getElementById('createTournamentErr');
+ err.textContent='';
+ const name = document.getElementById('t_name').value.trim();
+ if(!name){ err.textContent='Le nom est requis.'; return; }
+ const payload = {
+ name,
+ location: document.getElementById('t_location').value.trim(),
+ start_date: document.getElementById('t_start').value || null,
+ end_date: document.getElementById('t_end').value || null
+ };
+ try{
+ disable(btn, spin, true);
+ await createTournament(payload);
+ showToast('Tournoi créé ✔');
+ document.getElementById('t_name').value='';
+ document.getElementById('t_location').value='';
+ document.getElementById('t_start').value='';
+ document.getElementById('t_end').value='';
+ renderTournaments();
+ }catch(e){
+ err.textContent = e?.payload?.error || e.message;
+ showToast('Erreur création tournoi', false);
+ }finally{
+ disable(btn, spin, false);
+ }
+ });
+
+ document.getElementById('addParticipantBtn')?.addEventListener('click', async (e)=>{
+ const btn = e.currentTarget;
+ const spin = document.getElementById('addParticipantSpin');
+ const err = document.getElementById('addParticipantErr');
+ err.textContent='';
+ const tid = Number(document.getElementById('p_tid').value || '0');
+ const full_name = document.getElementById('p_fullname').value.trim();
+ if(!tid){ err.textContent='Choisis un tournoi.'; return; }
+ if(!full_name){ err.textContent='Nom du joueur requis.'; return; }
+ try{
+ disable(btn, spin, true);
+ await addParticipant(tid, { full_name });
+ showToast('Joueur ajouté ✔');
+ document.getElementById('p_fullname').value='';
+ }catch(e){
+ err.textContent = e?.payload?.error || e.message;
+ showToast('Erreur ajout joueur', false);
+ }finally{
+ disable(btn, spin, false);
+ }
+ });
+
+ document.getElementById('s_tid')?.addEventListener('change', renderMatchesForSelectedTournament);
+
+ document.getElementById('scoreBtn')?.addEventListener('click', async (e)=>{
+ const btn = e.currentTarget;
+ const spin = document.getElementById('scoreSpin');
+ const err = document.getElementById('scoreErr');
+ err.textContent='';
+ const mid = Number(document.getElementById('s_mid').value || '0');
+ if(!mid){ err.textContent='Choisis un match.'; return; }
+ const payload = {
+ score_a: Number(document.getElementById('m_a').value||'0'),
+ score_b: Number(document.getElementById('m_b').value||'0'),
+ done: document.getElementById('m_done').checked
+ };
+ try{
+ disable(btn, spin, true);
+ await scoreMatch(mid, payload);
+ showToast('Score enregistré ✔');
+ document.getElementById('m_a').value='';
+ document.getElementById('m_b').value='';
+ document.getElementById('m_done').checked=false;
+ }catch(e){
+ err.textContent = e?.payload?.error || e.message;
+ showToast('Erreur scoring', false);
+ }finally{
+ disable(btn, spin, false);
+ }
+ });
+
+ document.getElementById('refreshTournaments')?.addEventListener('click', renderTournaments);
+}
+
+function init(){
+ toggleAdminUI();
+ bindAuth();
+ bindAdminActions();
+ if(isAuthenticated()) renderTournaments();
+}
+
+document.addEventListener('DOMContentLoaded', init);
diff --git a/frontend/public/assets/api.js b/frontend/public/assets/api.js
index e7aa18a..fa4766d 100644
--- a/frontend/public/assets/api.js
+++ b/frontend/public/assets/api.js
@@ -1,12 +1,80 @@
-const api = {
- base: '',
- token: null,
- setToken(t){ this.token = t; localStorage.setItem('ss_token', t); },
- getToken(){ return this.token || localStorage.getItem('ss_token'); },
- headers(){ const h = { 'Content-Type':'application/json' }; const t=this.getToken(); if(t) h['Authorization']='Bearer '+t; return h; },
- async get(path){ const r = await fetch('/api'+path, { headers: this.headers() }); if(!r.ok) throw new Error(await r.text()); return r.json(); },
- async post(path, body){ const r = await fetch('/api'+path, { method:'POST', headers:this.headers(), body: JSON.stringify(body) }); if(!r.ok) throw new Error(await r.text()); return r.json(); },
- async put(path, body){ const r = await fetch('/api'+path, { method:'PUT', headers:this.headers(), body: JSON.stringify(body) }); if(!r.ok) throw new Error(await r.text()); return r.json(); },
- async del(path, body){ const r = await fetch('/api'+path, { method:'DELETE', headers:this.headers(), body: JSON.stringify(body) }); if(!r.ok) throw new Error(await r.text()); return r.json(); }
-};
-window.SSAPI = api;
\ No newline at end of file
+/* ============================================================================
+ Super Sunday — Frontend API client (ESM)
+ ============================================================================ */
+const BASE = '/api';
+const TOKEN_KEY = 'ss_token';
+
+function getToken() { return localStorage.getItem(TOKEN_KEY) || ''; }
+function setToken(t) { if (t) localStorage.setItem(TOKEN_KEY, t); else localStorage.removeItem(TOKEN_KEY); }
+
+async function fetchJSON(url, opts = {}) {
+ const res = await fetch(url, opts);
+ const ct = res.headers.get('content-type') || '';
+ const isJSON = ct.includes('application/json');
+ const data = isJSON ? await res.json().catch(() => ({})) : await res.text();
+ if (!res.ok) {
+ const err = new Error((data && data.error) || res.statusText || 'HTTP Error');
+ err.status = res.status; err.payload = data;
+ throw err;
+ }
+ return data;
+}
+
+async function authFetch(path, options = {}) {
+ const token = getToken();
+ const headers = new Headers(options.headers || {});
+ headers.set('Content-Type','application/json');
+ if(token) headers.set('Authorization',`Bearer ${token}`);
+ return fetchJSON(`${BASE}${path}`,{...options,headers});
+}
+
+/* Public endpoints */
+export async function listTournaments(){ return fetchJSON(`${BASE}/tournaments`); }
+export async function getTournament(id){ return fetchJSON(`${BASE}/tournaments/${id}`); }
+export async function listParticipants(id){ return fetchJSON(`${BASE}/tournaments/${id}/participants`); }
+export async function listMatches(id){ return fetchJSON(`${BASE}/tournaments/${id}/matches`); }
+
+/* Admin endpoints */
+export async function login(email,password){
+ const data = await fetchJSON(`${BASE}/auth/login`,{
+ method:'POST',headers:{'Content-Type':'application/json'},
+ body: JSON.stringify({email,password})
+ });
+ if(data && data.token) setToken(data.token);
+ return data;
+}
+export function logout(){ setToken(''); }
+export function isAuthenticated(){ return !!getToken(); }
+
+export async function createTournament(payload){
+ return authFetch(`/tournaments`,{method:'POST',body:JSON.stringify(payload)});
+}
+export async function addParticipant(tid,payload){
+ return authFetch(`/tournaments/${tid}/participants`,{method:'POST',body:JSON.stringify(payload)});
+}
+export async function generateAmericano(tid,payload){
+ return authFetch(`/tournaments/${tid}/generate-americano`,{method:'POST',body:JSON.stringify(payload)});
+}
+export async function scoreMatch(mid,payload){
+ return authFetch(`/matches/${mid}/score`,{method:'POST',body:JSON.stringify(payload)});
+}
+
+/* Helpers */
+export function bindLoginForm({formSel='#loginSection',statusSel='#loginStatus'}={}){
+ const form=document.querySelector(formSel);
+ const status=document.querySelector(statusSel);
+ if(!form) return;
+ const email=form.querySelector('#email');
+ const password=form.querySelector('#password');
+ const btn=form.querySelector('#loginBtn');
+ btn?.addEventListener('click',async ()=>{
+ status.textContent='Connexion…';
+ try{
+ await login(email.value.trim(),password.value);
+ status.textContent='Connecté ✔';
+ const admin=document.querySelector('#adminSection');
+ if(admin) admin.style.display='grid';
+ form.style.display='none';
+ }catch(e){ status.textContent=`Erreur: ${e.payload?.error||e.message}`; }
+ });
+}
diff --git a/frontend/public/assets/style.css b/frontend/public/assets/style.css
index 1379f7d..e8f096d 100644
--- a/frontend/public/assets/style.css
+++ b/frontend/public/assets/style.css
@@ -1,371 +1,247 @@
-/* === Super Sunday — Global sporty theme + hero background + subtle bubbles === */
+/* ==========================================================================
+ Super Sunday — Frontend Theme (Clean, Consolidated)
+ - Dark, Sirion-like base
+ - Hero gradient headings (n8n vibe)
+ - Background image + dark overlays + colored bubbles
+ - Glass-dark cards, flat rounded buttons, dark inputs
+ - Simple, deterministic z-index stack
+ -------------------------------------------------------------------------- */
-/* Palette */
+/* ========== 0) Design tokens ==========
+*/
:root {
- --bg: #f4f7fb;
- --surface: #ffffff;
- --surface-2: #f2f5fa;
- --ink: #101827;
- --muted: #6b7280;
- --border: #e5e9f2;
+ /* Colors */
+ --ink: #eaf1ff;
+ --ink-strong: #ffffff;
+ --muted: #9fb0c7;
+ --panel: rgba(12,16,28,.75);
+ --panel-border: rgba(255,255,255,.12);
+ --cta: #1890ff;
+ --cta-hover: #3aa0ff;
- --accent-start: #1ea7ff;
- --accent-end: #27d980;
- --accent: #1890ff;
+ /* Accents for gradients */
+ --grad-rose: #ff7ad9;
+ --grad-violet: #7c4dff;
+ --grad-cyan: #00e5ff;
+ /* Layout */
--radius: 16px;
- --shadow: 0 6px 18px rgba(0,0,0,.06);
- --shadow-hover: 0 10px 28px rgba(0,0,0,.12);
+ --radius-xl: 20px;
+ --shadow: 0 10px 30px rgba(0,0,0,.35);
+ --shadow-hover: 0 16px 36px rgba(0,0,0,.45);
--trans: 180ms ease;
}
-*{box-sizing:border-box}
-html,body{height:100%}/* === Super Sunday — Dark "Sirion-like" theme overrides === */
+* { box-sizing: border-box; }
+html, body { height: 100%; }
-/* 0) Couleurs globales + typo */
-:root{
- --ink: #e8eef6; /* texte global clair */
- --ink-strong: #ffffff; /* titres */
- --muted: #b7c3d6; /* textes secondaires */
- --border-dark: rgba(255,255,255,.08);
- --surface-dark: rgba(15,23,42,.60); /* slate-900 translucide */
- --surface-dark-2: rgba(15,23,42,.72);
-}
-
-/* 1) Fond sombre bleuté + image adoucie */
-body{
+/* ========== 1) Base & background ==========
+*/
+body {
+ margin: 0;
+ font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji","Segoe UI Emoji", sans-serif;
color: var(--ink);
+ line-height: 1.5;
}
-body::before{
- content:"";
- position:fixed; inset:0; z-index:-2;
+
+/* Background image + dark overlays */
+body::before {
+ content: "";
+ position: fixed; inset: 0;
+ z-index: -3; /* far background */
background:
- radial-gradient(120% 85% at 20% -10%, rgba(3,7,18,.9) 0%, rgba(3,7,18,.6) 40%, rgba(3,7,18,.3) 60%, rgba(3,7,18,.2) 100%),
- linear-gradient(180deg, rgba(9,14,30,.75), rgba(9,14,30,.55) 40%, rgba(9,14,30,.75)),
+ /* colored ambience blobs */
+ radial-gradient(40% 35% at 15% 10%, rgba(124,77,255,.23), rgba(124,77,255,0) 60%),
+ radial-gradient(45% 40% at 85% 20%, rgba(0,229,255,.20), rgba(0,229,255,0) 60%),
+ radial-gradient(55% 50% at 50% 85%, rgba(255,122,217,.16), rgba(255,122,217,0) 60%),
+ /* dark veil */
+ linear-gradient(180deg, rgba(0,0,0,.72), rgba(0,0,0,.58) 35%, rgba(0,0,0,.70)),
+ /* photo */
url("/assets/image/backgroundp24p.jpg") center top / cover no-repeat;
- filter: saturate(.9) contrast(.95) brightness(.9) blur(0.5px);
+ filter: saturate(.96) contrast(.98) brightness(.9) blur(.4px);
}
-body::after{
- /* vignette douce pour focus centre */
- content:"";
- position:fixed; inset:0; z-index:-1; pointer-events:none;
+
+body::after {
+ /* vignette for edge darkening */
+ content: "";
+ position: fixed; inset: 0;
+ z-index: -2; /* above background */
+ pointer-events: none;
background:
- radial-gradient(140% 100% at 50% 0%, rgba(0,0,0,0) 55%, rgba(0,0,0,.26) 100%),
- radial-gradient(120% 90% at 50% 100%, rgba(0,0,0,0) 60%, rgba(0,0,0,.22) 100%);
+ radial-gradient(145% 100% at 50% 0%, rgba(0,0,0,0) 52%, rgba(0,0,0,.30) 100%),
+ radial-gradient(125% 90% at 50% 100%, rgba(0,0,0,0) 58%, rgba(0,0,0,.24) 100%);
}
-/* 2) Titres & textes */
-h1,h2,h3{ color: var(--ink-strong); }
-.hero-title{
- color:#fff !important;
- text-shadow: 0 8px 30px rgba(30,167,255,.25), 0 2px 12px rgba(0,0,0,.35);
+/* ========== 2) Colored bubbles (between overlays and UI) ==========
+*/
+@keyframes float {
+ 0% { transform: translateY(0) scale(1); opacity: .75; }
+ 50% { transform: translateY(-36px) scale(1.06); opacity: .45; }
+ 100% { transform: translateY(0) scale(1); opacity: .75; }
}
-.section-title{
- color:#f1f5ff;
-}
-.section-title::before{
- background: linear-gradient(180deg, #8ee7ff, #9fffcf);
- box-shadow: 0 6px 16px rgba(143, 237, 255, .35);
-}
-.muted{ color: var(--muted); }
-/* 3) Header sombre translucide */
-header{
- background: rgba(10,14,26,.78);
- color:#fff;
- border-bottom: 1px solid var(--border-dark);
+.bg-bubble {
+ position: fixed;
+ z-index: -1; /* between overlays and UI */
+ pointer-events: none;
+ border-radius: 50%;
+ opacity: .9;
+ /* blue ↔ violet glow */
+ background:
+ radial-gradient(60% 60% at 30% 30%, rgba(124,77,255,.55), rgba(124,77,255,0) 70%),
+ radial-gradient(55% 55% at 70% 70%, rgba(0,229,255,.45), rgba(0,229,255,0) 75%);
+ animation: float 10s ease-in-out infinite;
+ filter: blur(.3px);
+}
+
+.bg-bubble.b1 { width:120px; height:120px; left:12%; top:68%; animation-duration: 9s; }
+.bg-bubble.b2 { width:90px; height:90px; left:70%; top:58%; animation-duration:12s; }
+.bg-bubble.b3 { width:150px; height:150px; left:46%; top:76%; animation-duration:14s; }
+.bg-bubble.b4 { width:80px; height:80px; left:84%; top:22%; animation-duration:11s; }
+.bg-bubble.b5 { width:110px; height:110px; left:6%; top:18%; animation-duration:10s; }
+
+@media (prefers-reduced-motion: reduce) { .bg-bubble { animation: none; } }
+
+/* Ensure content sits above bubbles */
+header, main, nav, .card { position: relative; z-index: 0; }
+
+/* ========== 3) Header & navigation ==========
+*/
+header {
+ position: sticky; top: 0; z-index: 40;
+ display: flex; align-items: center; justify-content: space-between;
+ padding: 14px 24px;
+ background: linear-gradient(180deg, rgba(5,8,14,.96), rgba(5,8,14,.88));
+ color: #fff;
+ border-bottom: 1px solid rgba(255,255,255,.10);
backdrop-filter: blur(10px) saturate(130%);
-webkit-backdrop-filter: blur(10px) saturate(130%);
}
-nav a{ color:#e9f2ff; }
-nav a:hover{ background: rgba(255,255,255,.06); }
+header h1 { margin: 0; font-size: 20px; font-weight: 700; color: #fff; }
-/* 4) Cartes dark glass */
-.card{
- background: var(--surface-dark);
- border: 1px solid var(--border-dark);
- backdrop-filter: blur(10px);
- -webkit-backdrop-filter: blur(10px);
- box-shadow: 0 10px 30px rgba(0,0,0,.35);
+nav a {
+ margin-left: 16px; padding: 8px 14px;
+ color: #f2f6ff; text-decoration: none;
+ border-radius: 12px; font-weight: 600;
+ transition: background var(--trans), transform var(--trans), border-color var(--trans);
}
-.card.accent{
- background:
- linear-gradient(135deg, rgba(30,167,255,.12), rgba(39,217,128,.12)),
- var(--surface-dark-2);
- border-color: rgba(255,255,255,.12);
+nav a:hover { background: rgba(255,255,255,.08); }
+nav a.active {
+ background: rgba(255,255,255,.10);
+ border: 1px solid rgba(255,255,255,.22);
+ box-shadow: inset 0 0 0 1px rgba(255,255,255,.08);
}
-/* 5) Inputs / selects en sombre lisible */
-input, select{
- color:#e8eef6;
- background: rgba(8,12,24,.6);
- border: 1px solid rgba(255,255,255,.10);
-}
-input::placeholder, select::placeholder{ color:#9fb0c7; }
-input:focus, select:focus{
- border-color: rgba(143, 237, 255, .55);
- box-shadow: 0 0 0 3px rgba(143, 237, 255, .18);
-}
+/* ========== 4) Layout & containers ==========
+*/
+.container { max-width: 1160px; margin: 24px auto; padding: 0 16px; }
+.grid { display: grid; gap: 20px; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); }
-/* 6) Boutons */
-.btn{
- box-shadow: 0 12px 28px rgba(30,167,255,.3);
-}
-.btn-outline{
- color:#eaf3ff;
- background: rgba(255,255,255,.06);
- border:1px solid rgba(255,255,255,.25);
-}
-.btn-outline:hover{
- background: rgba(255,255,255,.12);
- border-color: rgba(255,255,255,.45);
-}
-
-/* 7) État vide & préformatés */
-.empty{
- background: rgba(255,255,255,.06);
- border: 1px dashed rgba(255,255,255,.18);
- color: #c8d5e8;
-}
-pre{
- color:#e7efff;
- background: rgba(8,12,24,.55);
- border: 1px solid rgba(255,255,255,.08);
- padding: 12px; border-radius: 12px; overflow:auto;
-}
-
-/* 8) Légère retouche des bulles pour le mode sombre */
-.bg-bubble{
- background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.22), rgba(255,255,255,0));
- mix-blend-mode: screen;
- opacity:.7;
-}
-body{
- margin:0;
- font-family: Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji", sans-serif;
- color:var(--ink);
- line-height:1.5;
-
- /* Hero background image */
- background-image: url("/assets/image/backgroundp24p.jpg");
- background-size: cover;
- background-position: center top;
- background-attachment: fixed;
-}
-
-/* dark overlay to ensure readability */
-body::before{
- content:"";
- position:fixed; inset:0;
- background: linear-gradient(180deg, rgba(255,255,255,.30), rgba(255,255,255,.60));
- mix-blend-mode: normal;
- z-index:-1;
-}
-
-/* ===== Header / Nav ===== */
-header{
- position: sticky; top:0; z-index:40;
- display:flex; align-items:center; justify-content:space-between;
- padding: 14px 24px;
- background: rgba(255,255,255,0.86);
- border-bottom:1px solid var(--border);
- backdrop-filter: blur(8px) saturate(120%);
-}
-header h1{margin:0; font-size:20px; font-weight:700; color:var(--ink)}
-nav a{
- margin-left:16px; padding:8px 14px;
- color:var(--ink); text-decoration:none; border-radius:12px;
- transition: background var(--trans), transform var(--trans);
-}
-nav a:hover{ background: linear-gradient(90deg, rgba(30,167,255,.15), rgba(39,217,128,.15)); transform:translateY(-1px) }
-
-/* ===== Layout ===== */
-.container{ max-width:1100px; margin:24px auto; padding:0 16px }
-.grid{ display:grid; gap:20px; grid-template-columns: repeat(auto-fit,minmax(280px,1fr)) }
-
-/* ===== Cards ===== */
-.card{
- background: var(--surface);
- border:1px solid var(--border);
- border-radius: var(--radius);
+/* ========== 5) Cards ==========
+*/
+.card {
+ background: var(--panel);
+ border: 1px solid var(--panel-border);
+ border-radius: var(--radius-xl);
padding: 18px;
box-shadow: var(--shadow);
transition: transform var(--trans), box-shadow var(--trans), border-color var(--trans);
}
-.card:hover{ transform: translateY(-2px); box-shadow: var(--shadow-hover); border-color:#dbe5f2 }
-a.card{ display:block; color:inherit; text-decoration:none }
-.card.accent{
- background: linear-gradient(135deg, rgba(30,167,255,.08), rgba(39,217,128,.08)), var(--surface);
- border-color: rgba(30,167,255,.25);
+.card:hover { transform: translateY(-3px); box-shadow: var(--shadow-hover); border-color: rgba(143,237,255,.35); }
+.card.accent {
+ background:
+ linear-gradient(180deg, rgba(124,77,255,.10), rgba(0,229,255,.10)),
+ var(--panel);
+ border-color: rgba(255,255,255,.14);
+}
+#tournaments .card, #matches .card { margin-bottom: 12px; }
+
+/* ========== 6) Typography ==========
+*/
+h1, h2, h3 { color: var(--ink-strong); line-height: 1.2; }
+.muted { color: var(--muted); }
+
+/* Hero & section titles (n8n-like gradients) */
+.hero-title {
+ font-size: clamp(42px, 6.8vw, 78px);
+ font-weight: 900;
+ letter-spacing: -.02em;
+ line-height: 1.04;
+ background: linear-gradient(90deg, var(--grad-rose) 0%, var(--grad-violet) 45%, var(--grad-cyan) 95%);
+ -webkit-background-clip: text; background-clip: text; color: transparent;
+ text-shadow: 0 10px 30px rgba(124,77,255,.22), 0 2px 12px rgba(0,0,0,.35);
+ margin-bottom: .25rem;
+}
+.section-title {
+ font-size: clamp(26px, 3.8vw, 42px);
+ font-weight: 800; letter-spacing: -.01em;
+ background: linear-gradient(90deg, var(--grad-violet), var(--grad-cyan));
+ -webkit-background-clip: text; background-clip: text; color: transparent;
}
-/* Titles / text */
-h1,h2,h3{ line-height:1.2 }
-h2{ margin: 20px 0 12px; font-size:22px; font-weight:600 }
-.muted{ color: var(--muted) }
-.mt{ margin-top:24px }
-
-/* Buttons & inputs */
-.btn{
- display:inline-block; padding:10px 18px; border-radius:12px; border:none;
- background: linear-gradient(90deg, var(--accent-start), var(--accent-end));
- color:#fff; font-weight:600; text-decoration:none;
- box-shadow: 0 8px 20px rgba(24,144,255,.25);
- transition: transform var(--trans), box-shadow var(--trans);
-}
-.btn:hover{ transform: translateY(-1px); box-shadow: 0 12px 28px rgba(24,144,255,.35) }
-
-input,select{
- width:100%; padding:10px 12px; border-radius:12px; border:1px solid var(--border);
- background: var(--surface-2); color: var(--ink); outline:none;
+/* ========== 7) Forms & buttons ==========
+*/
+input, select, textarea {
+ width: 100%;
+ padding: 12px 14px;
+ min-height: 44px;
+ border-radius: 14px;
+ color: #eaf3ff;
+ background: #0f1b33;
+ border: 1px solid rgba(255,255,255,.15);
+ outline: none;
transition: border-color var(--trans), box-shadow var(--trans);
+ margin: 10px 0 16px;
}
-input:focus,select:focus{ border-color:#b8d8ff; box-shadow: 0 0 0 3px rgba(24,144,255,.20) }
-
-/* Empty state */
-.empty{
- padding: 20px; text-align:center; color:var(--muted);
- background: var(--surface-2); border:1px dashed var(--border);
- border-radius: var(--radius);
+input::placeholder, textarea::placeholder { color: #93a4c0; }
+input:focus, select:focus, textarea:focus {
+ border-color: rgba(143,237,255,.55);
+ box-shadow: 0 0 0 3px rgba(143, 237, 255, .18);
}
-/* Specific existing sections */
-#tournaments .card, #matches .card{ margin-bottom:12px }
-
-/* ===== Animated bubbles (tennis/padel ball ghosts) ===== */
-@keyframes float {
- 0% { transform: translateY(0) scale(1); opacity:.7; }
- 50% { transform: translateY(-36px) scale(1.05); opacity:.4; }
- 100% { transform: translateY(0) scale(1); opacity:.7; }
-}
-.bg-bubble{
- position: fixed; z-index: -1; pointer-events: none; border-radius: 50%;
- background: radial-gradient(circle at 30% 30%, rgba(255,255,255,.75), rgba(255,255,255,0));
- filter: blur(0.2px);
- animation: float 9s ease-in-out infinite;
- mix-blend-mode: screen;
-}
-.bg-bubble.b1{ width:90px; height:90px; left:15%; top:72%; animation-duration:8s }
-.bg-bubble.b2{ width:70px; height:70px; left:68%; top:64%; animation-duration:10s }
-.bg-bubble.b3{ width:115px; height:115px; left:48%; top:78%; animation-duration:12s }
-.bg-bubble.b4{ width:60px; height:60px; left:82%; top:20%; animation-duration:11s }
-.bg-bubble.b5{ width:80px; height:80px; left:6%; top:18%; animation-duration:9s }
-
-@media (prefers-reduced-motion: reduce){
- .bg-bubble{ animation: none; }
-}
-
-/* Footer (if needed) */
-footer{ margin:40px 0 0; padding:24px 16px; text-align:center; color:var(--muted);
- border-top:1px solid var(--border); background: rgba(255,255,255,.75); backdrop-filter: blur(6px) }
-
- /* === Retouches: overlay noir + spacing formulaires === */
-
-/* 1) Overlay plus noir, façon Sirion */
-body::before{
- /* on garde l'image + on fonce nettement avec un voile noir bleuté */
- background:
- linear-gradient(180deg, rgba(0,0,0,.70), rgba(0,0,0,.55) 35%, rgba(0,0,0,.68)),
- radial-gradient(120% 85% at 20% -10%, rgba(0,10,20,.75) 0%, rgba(0,10,20,.35) 60%, rgba(0,10,20,.20) 100%),
- url("/assets/image/backgroundp24p.jpg") center top / cover no-repeat !important;
- filter: saturate(.95) contrast(.98) brightness(.88) blur(0.6px);
-}
-
-/* vignette un peu plus marquée sur les bords */
-body::after{
- background:
- radial-gradient(145% 100% at 50% 0%, rgba(0,0,0,0) 52%, rgba(0,0,0,.30) 100%),
- radial-gradient(125% 90% at 50% 100%, rgba(0,0,0,0) 58%, rgba(0,0,0,.24) 100%) !important;
-}
-
-/* 2) Spacing des formulaires (inputs & dropdowns) */
-.card input,
-.card select,
-.card textarea {
- margin-top: 8px !important;
- margin-bottom: 14px !important; /* +respiration */
- min-height: 42px; /* dropdowns plus grands */
- padding: 10px 12px !important;
- border-radius: 12px !important;
-}
-
-/* espacement entre groupes de champs (si direct dans .card) */
-.card > div + div { margin-top: 16px; }
-
-/* si tu utilises des