Accès sécurisé
+Inscription, connexion, verrouillage temporaire après 5 tentatives et réinitialisation via token.
+diff --git a/assets/css/custom.css b/assets/css/custom.css
index 789132e..54ca4c4 100644
--- a/assets/css/custom.css
+++ b/assets/css/custom.css
@@ -1,403 +1,362 @@
-body {
- background: linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab);
- background-size: 400% 400%;
- animation: gradient 15s ease infinite;
- color: #212529;
- font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
- font-size: 14px;
- margin: 0;
- min-height: 100vh;
+:root {
+ --bg: #f5f7fa;
+ --surface: #ffffff;
+ --surface-muted: #f8fafc;
+ --border: #dbe1ea;
+ --border-strong: #c5ced9;
+ --text: #111827;
+ --text-muted: #667085;
+ --primary: #111827;
+ --secondary: #4b5563;
+ --accent: #0f172a;
+ --success: #166534;
+ --warning: #9a6700;
+ --danger: #b42318;
+ --shadow-sm: 0 1px 2px rgba(16, 24, 40, 0.04);
+ --shadow-md: 0 12px 30px rgba(15, 23, 42, 0.06);
+ --radius-sm: 6px;
+ --radius-md: 10px;
+ --radius-lg: 14px;
+ --spacing: 1rem;
}
-.main-wrapper {
- display: flex;
+* {
+ box-sizing: border-box;
+}
+
+html {
+ scroll-behavior: smooth;
+}
+
+body {
+ margin: 0;
+ font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
+ background: var(--bg);
+ color: var(--text);
+ line-height: 1.5;
+}
+
+body.app-shell {
+ background: linear-gradient(to bottom, #f8fafc 0, #f8fafc 220px, #f5f7fa 220px, #f5f7fa 100%);
+}
+
+a {
+ color: inherit;
+}
+
+.app-navbar {
+ background: rgba(255, 255, 255, 0.94);
+ backdrop-filter: blur(10px);
+}
+
+.navbar-brand {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.75rem;
+ color: var(--primary);
+}
+
+.brand-mark {
+ width: 2rem;
+ height: 2rem;
+ border-radius: var(--radius-sm);
+ background: var(--primary);
+ color: #fff;
+ display: inline-flex;
align-items: center;
justify-content: center;
- min-height: 100vh;
- width: 100%;
- padding: 20px;
- box-sizing: border-box;
- position: relative;
- z-index: 1;
+ font-size: 0.75rem;
+ letter-spacing: 0.12em;
}
-@keyframes gradient {
- 0% {
- background-position: 0% 50%;
- }
- 50% {
- background-position: 100% 50%;
- }
- 100% {
- background-position: 0% 50%;
- }
+.nav-link {
+ color: var(--secondary);
+ font-weight: 500;
}
-.chat-container {
- width: 100%;
- max-width: 600px;
- background: rgba(255, 255, 255, 0.85);
- border: 1px solid rgba(255, 255, 255, 0.3);
- border-radius: 20px;
- display: flex;
- flex-direction: column;
- height: 85vh;
- box-shadow: 0 20px 40px rgba(0,0,0,0.2);
- backdrop-filter: blur(15px);
- -webkit-backdrop-filter: blur(15px);
- overflow: hidden;
+.nav-link:hover,
+.nav-link:focus,
+.link-dark:hover,
+.link-dark:focus {
+ color: #000 !important;
}
-.chat-header {
- padding: 1.5rem;
- border-bottom: 1px solid rgba(0, 0, 0, 0.05);
- background: rgba(255, 255, 255, 0.5);
- font-weight: 700;
- font-size: 1.1rem;
- display: flex;
- justify-content: space-between;
- align-items: center;
-}
-
-.chat-messages {
- flex: 1;
- overflow-y: auto;
- padding: 1.5rem;
- display: flex;
- flex-direction: column;
- gap: 1.25rem;
-}
-
-/* Custom Scrollbar */
-::-webkit-scrollbar {
- width: 6px;
-}
-
-::-webkit-scrollbar-track {
+.hero-section {
background: transparent;
}
-::-webkit-scrollbar-thumb {
- background: rgba(255, 255, 255, 0.3);
- border-radius: 10px;
+.eyebrow,
+.section-kicker {
+ display: inline-flex;
+ align-items: center;
+ gap: 0.5rem;
+ color: var(--text-muted);
+ font-size: 0.78rem;
+ font-weight: 700;
+ letter-spacing: 0.08em;
+ text-transform: uppercase;
}
-::-webkit-scrollbar-thumb:hover {
- background: rgba(255, 255, 255, 0.5);
+.display-title {
+ font-size: clamp(2.1rem, 4vw, 3.4rem);
+ line-height: 1.05;
+ letter-spacing: -0.03em;
+ max-width: 12ch;
}
-.message {
- max-width: 85%;
- padding: 0.85rem 1.1rem;
- border-radius: 16px;
- line-height: 1.5;
- font-size: 0.95rem;
- box-shadow: 0 4px 15px rgba(0,0,0,0.05);
- animation: fadeIn 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
+.hero-copy {
+ max-width: 62ch;
+ color: var(--text-muted);
+ font-size: 1rem;
}
-@keyframes fadeIn {
- from { opacity: 0; transform: translateY(20px) scale(0.95); }
- to { opacity: 1; transform: translateY(0) scale(1); }
+.panel-card,
+.empty-card {
+ background: var(--surface);
+ border: 1px solid var(--border);
+ border-radius: var(--radius-lg);
+ box-shadow: var(--shadow-sm);
}
-.message.visitor {
- align-self: flex-end;
- background: linear-gradient(135deg, #212529 0%, #343a40 100%);
- color: #fff;
- border-bottom-right-radius: 4px;
+.panel-card {
+ box-shadow: var(--shadow-md);
}
-.message.bot {
- align-self: flex-start;
- background: #ffffff;
- color: #212529;
- border-bottom-left-radius: 4px;
+.metric-card {
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: 1rem;
+ background: var(--surface-muted);
+ min-height: 100%;
}
-.chat-input-area {
- padding: 1.25rem;
- background: rgba(255, 255, 255, 0.5);
- border-top: 1px solid rgba(0, 0, 0, 0.05);
+.metric-card span {
+ display: block;
+ color: var(--text-muted);
+ font-size: 0.82rem;
+ margin-bottom: 0.35rem;
}
-.chat-input-area form {
- display: flex;
- gap: 0.75rem;
+.metric-card strong {
+ font-size: 1.6rem;
+ line-height: 1;
+ letter-spacing: -0.03em;
}
-.chat-input-area input {
- flex: 1;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- padding: 0.75rem 1rem;
- outline: none;
- background: rgba(255, 255, 255, 0.9);
- transition: all 0.3s ease;
-}
-
-.chat-input-area input:focus {
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.2);
-}
-
-.chat-input-area button {
- background: #212529;
- color: #fff;
- border: none;
- padding: 0.75rem 1.5rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- transition: all 0.3s ease;
-}
-
-.chat-input-area button:hover {
- background: #000;
- transform: translateY(-2px);
- box-shadow: 0 5px 15px rgba(0,0,0,0.2);
-}
-
-/* Background Animations */
-.bg-animations {
- position: fixed;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- z-index: 0;
- overflow: hidden;
- pointer-events: none;
-}
-
-.blob {
- position: absolute;
- width: 500px;
- height: 500px;
- background: rgba(255, 255, 255, 0.2);
- border-radius: 50%;
- filter: blur(80px);
- animation: move 20s infinite alternate cubic-bezier(0.45, 0, 0.55, 1);
-}
-
-.blob-1 {
- top: -10%;
- left: -10%;
- background: rgba(238, 119, 82, 0.4);
-}
-
-.blob-2 {
- bottom: -10%;
- right: -10%;
- background: rgba(35, 166, 213, 0.4);
- animation-delay: -7s;
- width: 600px;
- height: 600px;
-}
-
-.blob-3 {
- top: 40%;
- left: 30%;
- background: rgba(231, 60, 126, 0.3);
- animation-delay: -14s;
- width: 450px;
- height: 450px;
-}
-
-@keyframes move {
- 0% { transform: translate(0, 0) rotate(0deg) scale(1); }
- 33% { transform: translate(150px, 100px) rotate(120deg) scale(1.1); }
- 66% { transform: translate(-50px, 200px) rotate(240deg) scale(0.9); }
- 100% { transform: translate(0, 0) rotate(360deg) scale(1); }
-}
-
-.header-link {
- font-size: 14px;
- color: #fff;
- text-decoration: none;
- background: rgba(0, 0, 0, 0.2);
- padding: 0.5rem 1rem;
- border-radius: 8px;
- transition: all 0.3s ease;
-}
-
-.header-link:hover {
- background: rgba(0, 0, 0, 0.4);
- text-decoration: none;
-}
-
-/* Admin Styles */
-.admin-container {
- max-width: 900px;
- margin: 3rem auto;
- padding: 2.5rem;
- background: rgba(255, 255, 255, 0.85);
- backdrop-filter: blur(20px);
- -webkit-backdrop-filter: blur(20px);
- border-radius: 24px;
- box-shadow: 0 20px 50px rgba(0,0,0,0.15);
- border: 1px solid rgba(255, 255, 255, 0.4);
- position: relative;
- z-index: 1;
-}
-
-.admin-container h1 {
- margin-top: 0;
- color: #212529;
- font-weight: 800;
-}
-
-.table {
- width: 100%;
- border-collapse: separate;
- border-spacing: 0 8px;
+.workflow-grid {
+ display: grid;
+ grid-template-columns: repeat(3, minmax(0, 1fr));
+ gap: 1rem;
margin-top: 1.5rem;
}
-.table th {
- background: transparent;
- border: none;
+.workflow-grid article {
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
padding: 1rem;
- color: #6c757d;
- font-weight: 600;
- text-transform: uppercase;
- font-size: 0.75rem;
- letter-spacing: 1px;
+ background: var(--surface-muted);
}
-.table td {
- background: #fff;
- padding: 1rem;
- border: none;
+.workflow-grid span {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 2rem;
+ height: 2rem;
+ border-radius: 999px;
+ border: 1px solid var(--border-strong);
+ font-size: 0.82rem;
+ font-weight: 700;
+ margin-bottom: 0.75rem;
}
-.table tr td:first-child { border-radius: 12px 0 0 12px; }
-.table tr td:last-child { border-radius: 0 12px 12px 0; }
-
-.form-group {
- margin-bottom: 1.25rem;
-}
-
-.form-group label {
- display: block;
+.workflow-grid h3 {
+ font-size: 1rem;
margin-bottom: 0.5rem;
- font-weight: 600;
- font-size: 0.9rem;
}
-.form-control {
- width: 100%;
- padding: 0.75rem 1rem;
- border: 1px solid rgba(0, 0, 0, 0.1);
- border-radius: 12px;
- background: #fff;
- transition: all 0.3s ease;
- box-sizing: border-box;
+.workflow-grid p,
+.empty-card p,
+.quick-links .list-group-item,
+.stack-item p,
+.detail-note {
+ color: var(--text-muted);
}
-.form-control:focus {
- outline: none;
- border-color: #23a6d5;
- box-shadow: 0 0 0 3px rgba(35, 166, 213, 0.1);
+.quick-links .list-group-item {
+ padding-inline: 0;
+ border-color: var(--border);
+ background: transparent;
}
-.header-container {
+.stack-list {
+ display: grid;
+ gap: 0.9rem;
+}
+
+.stack-item {
display: flex;
justify-content: space-between;
align-items: center;
+ gap: 1rem;
+ border: 1px solid var(--border);
+ border-radius: var(--radius-md);
+ padding: 0.95rem 1rem;
+ background: var(--surface-muted);
}
-.header-links {
+.app-table {
+ --bs-table-bg: transparent;
+ --bs-table-striped-bg: #f8fafc;
+ margin-bottom: 0;
+}
+
+.app-table thead th {
+ background: #f8fafc;
+ color: var(--text-muted);
+ font-size: 0.8rem;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+ border-bottom-width: 1px;
+}
+
+.app-table td,
+.app-table th {
+ padding: 1rem;
+ border-color: var(--border);
+}
+
+.auth-wrap {
+ min-height: calc(100vh - 180px);
display: flex;
+ align-items: center;
+}
+
+.form-control,
+.form-select {
+ min-height: 44px;
+ border-color: var(--border-strong);
+ border-radius: var(--radius-sm);
+ padding-inline: 0.9rem;
+ background-color: #fff;
+}
+
+.form-control:focus,
+.form-select:focus,
+.btn:focus,
+.nav-link:focus {
+ box-shadow: 0 0 0 0.2rem rgba(17, 24, 39, 0.12);
+ border-color: var(--primary);
+}
+
+.btn {
+ border-radius: var(--radius-sm);
+ padding: 0.7rem 1rem;
+ font-weight: 600;
+}
+
+.btn-lg {
+ padding: 0.85rem 1.25rem;
+}
+
+.btn-dark {
+ background: var(--primary);
+ border-color: var(--primary);
+}
+
+.btn-outline-secondary {
+ color: var(--secondary);
+ border-color: var(--border-strong);
+}
+
+.detail-list {
+ display: grid;
gap: 1rem;
}
-.admin-card {
- background: rgba(255, 255, 255, 0.6);
- padding: 2rem;
- border-radius: 20px;
- border: 1px solid rgba(255, 255, 255, 0.5);
- margin-bottom: 2.5rem;
- box-shadow: 0 10px 30px rgba(0,0,0,0.05);
+.detail-list div {
+ display: grid;
+ gap: 0.25rem;
+ padding-bottom: 0.9rem;
+ border-bottom: 1px solid var(--border);
}
-.admin-card h3 {
- margin-top: 0;
- margin-bottom: 1.5rem;
+.detail-list div:last-child {
+ border-bottom: 0;
+ padding-bottom: 0;
+}
+
+.detail-list dt {
+ font-size: 0.78rem;
font-weight: 700;
+ text-transform: uppercase;
+ letter-spacing: 0.06em;
+ color: var(--text-muted);
}
-.btn-delete {
- background: #dc3545;
- color: white;
- border: none;
- padding: 0.25rem 0.5rem;
- border-radius: 4px;
- cursor: pointer;
+.detail-list dd {
+ margin: 0;
+ font-size: 1rem;
}
-.btn-add {
- background: #212529;
- color: white;
- border: none;
- padding: 0.5rem 1rem;
- border-radius: 4px;
- cursor: pointer;
- margin-top: 1rem;
+.toast {
+ border-radius: var(--radius-sm);
+ box-shadow: var(--shadow-md);
}
-.btn-save {
- background: #0088cc;
- color: white;
- border: none;
- padding: 0.8rem 1.5rem;
- border-radius: 12px;
- cursor: pointer;
- font-weight: 600;
- width: 100%;
- transition: all 0.3s ease;
+.text-bg-success {
+ background-color: var(--success) !important;
}
-.webhook-url {
- font-size: 0.85em;
- color: #555;
- margin-top: 0.5rem;
+.text-bg-warning {
+ background-color: var(--warning) !important;
+ color: #fff !important;
}
-.history-table-container {
- overflow-x: auto;
- background: rgba(255, 255, 255, 0.4);
- padding: 1rem;
- border-radius: 12px;
- border: 1px solid rgba(255, 255, 255, 0.3);
+.text-bg-danger {
+ background-color: var(--danger) !important;
}
-.history-table {
- width: 100%;
+.text-bg-info {
+ background-color: #344054 !important;
}
-.history-table-time {
- width: 15%;
- white-space: nowrap;
- font-size: 0.85em;
- color: #555;
+.badge.text-bg-light {
+ background: #f8fafc !important;
+ color: var(--secondary) !important;
}
-.history-table-user {
- width: 35%;
- background: rgba(255, 255, 255, 0.3);
- border-radius: 8px;
- padding: 8px;
+.sticky-form-card {
+ position: sticky;
+ top: 5.5rem;
}
-.history-table-ai {
- width: 50%;
- background: rgba(255, 255, 255, 0.5);
- border-radius: 8px;
- padding: 8px;
+.app-footer {
+ background: transparent;
}
-.no-messages {
- text-align: center;
- color: #777;
-}
\ No newline at end of file
+@media (max-width: 991.98px) {
+ .workflow-grid {
+ grid-template-columns: 1fr;
+ }
+
+ .sticky-form-card {
+ position: static;
+ }
+}
+
+@media (max-width: 575.98px) {
+ .display-title {
+ max-width: none;
+ }
+
+ .panel-card,
+ .empty-card {
+ border-radius: var(--radius-md);
+ }
+
+ .app-table td,
+ .app-table th {
+ padding: 0.8rem;
+ }
+}
diff --git a/assets/js/main.js b/assets/js/main.js
index d349598..e414550 100644
--- a/assets/js/main.js
+++ b/assets/js/main.js
@@ -1,39 +1,12 @@
document.addEventListener('DOMContentLoaded', () => {
- const chatForm = document.getElementById('chat-form');
- const chatInput = document.getElementById('chat-input');
- const chatMessages = document.getElementById('chat-messages');
+ document.querySelectorAll('.toast').forEach((toastEl) => {
+ const toast = new bootstrap.Toast(toastEl, { delay: 4500 });
+ toast.show();
+ });
- const appendMessage = (text, sender) => {
- const msgDiv = document.createElement('div');
- msgDiv.classList.add('message', sender);
- msgDiv.textContent = text;
- chatMessages.appendChild(msgDiv);
- chatMessages.scrollTop = chatMessages.scrollHeight;
- };
-
- chatForm.addEventListener('submit', async (e) => {
- e.preventDefault();
- const message = chatInput.value.trim();
- if (!message) return;
-
- appendMessage(message, 'visitor');
- chatInput.value = '';
-
- try {
- const response = await fetch('api/chat.php', {
- method: 'POST',
- headers: { 'Content-Type': 'application/json' },
- body: JSON.stringify({ message })
- });
- const data = await response.json();
-
- // Artificial delay for realism
- setTimeout(() => {
- appendMessage(data.reply, 'bot');
- }, 500);
- } catch (error) {
- console.error('Error:', error);
- appendMessage("Sorry, something went wrong. Please try again.", 'bot');
- }
+ document.querySelectorAll('input[data-uppercase="true"]').forEach((input) => {
+ input.addEventListener('input', () => {
+ input.value = input.value.toUpperCase();
});
+ });
});
diff --git a/athlete.php b/athlete.php
new file mode 100644
index 0000000..e74381e
--- /dev/null
+++ b/athlete.php
@@ -0,0 +1,83 @@
+prepare('SELECT * FROM athletes WHERE id = :id AND user_id = :user_id LIMIT 1');
+$stmt->execute(['id' => $id, 'user_id' => (int) $user['id']]);
+$athlete = $stmt->fetch();
+
+if (!$athlete) {
+ http_response_code(404);
+ render_header('Fiche introuvable', ['description' => 'La fiche sportif demandée est introuvable.']);
+ ?>
+ Le sportif demandé n’existe pas ou n’est pas accessible avec votre compte. Fiche détaillée = e((string) ($athlete['position_name'] ?: 'Poste non renseigné')) ?> • = e((string) $athlete['sport_name']) ?> Distinctions = e((string) ($athlete['awards'] ?: 'Aucune distinction renseignée.')) ?> Note de parcours = nl2br(e((string) ($athlete['career_note'] ?: 'Aucune note de parcours pour le moment.'))) ?> Nouveau profil Créez une fiche complète avec club actuel, indicateurs de performance et note de parcours. Registre principal Sécurité Réinitialisation Saisissez votre email. Pour ce MVP, le token apparaît en notification et dans les logs serveur.Fiche introuvable
+ = e($athlete['first_name'] . ' ' . $athlete['last_name']) ?>
+ Résumé
+
+
+ Performance actuelle
+ Parcours professionnel — instantané
+ Parcours et distinctions
+ Ajouter un sportif
+
+
+
+ Liste des sportifs
+
+
+
+
+
+
+
+
+ Sportif
+ Sport
+ Club actuel
+ Statut
+ Stats
+
+
+
+
+
+
+ = e($athlete['first_name'] . ' ' . $athlete['last_name']) ?>
+
+ = e((string) $athlete['sport_name']) ?>
+ = e((string) $athlete['club_name']) ?>
+ = e(ucfirst((string) $athlete['status'])) ?>
+ = e((string) $athlete['matches_played']) ?> MJ · = e((string) $athlete['goals_scored']) ?> PTS · = e((string) $athlete['assists_count']) ?> AST
+ Voir la fiche
+ Modifier le mot de passe
+
+
+
+
+ Mot de passe oublié
+
= ($_SERVER['HTTP_HOST'] ?? '') === 'appwizzy.com' ? 'AppWizzy' : 'Flatlogic' ?> AI is collecting your requirements and applying the first changes.
-This page will update automatically as the plan is implemented.
-Runtime: PHP = htmlspecialchars($phpVersion) ?> — UTC = htmlspecialchars($now) ?>
Ce premier MVP livre une base réellement exploitable : authentification sécurisée, verrouillage après 5 échecs, réinitialisation par token, création de fiches sportifs, tableau de bord, liste filtrable et fiche détaillée.
+ +Vue synthèse
+Workflow livré
+Inscription, connexion, verrouillage temporaire après 5 tentatives et réinitialisation via token.
+Ajoutez un sportif avec discipline, club, statut, statistiques clés et note de parcours.
+Filtrez le registre, consultez la fiche détaillée et surveillez la répartition par sport et par club.
+Navigation rapide
+Discipline suivie
+Ajoutez votre premier sportif pour faire apparaître les disciplines suivies.
+| Sportif | +Club | +Statut | +
|---|---|---|
| = e($athlete['first_name'] . ' ' . $athlete['last_name']) ?> | += e((string) $athlete['club_name']) ?> | += e(ucfirst((string) $athlete['status'])) ?> | +
Le tableau de bord s’enrichira dès la première fiche créée.
+Authentification
+5 tentatives maximum, puis pause automatique de 30 secondes.
+ +Créer un compte
+Commencez avec un compte sécurisé pour enregistrer vos premiers sportifs.
+ +Déjà inscrit ? Connectez-vous.
+Validation du token
+Copiez le token reçu puis confirmez un nouveau mot de passe sécurisé.
+ +